8 Commits 27da55357d ... 3b61d2cd83

Author SHA1 Message Date
  liyangbin 3b61d2cd83 Merge branch 'feature/v3.0' into develop 4 months ago
  liyangbin 822c58d670 feat:三轮修复 4 months ago
  liyangbin 7a90f91adb feat:3.0调整 4 months ago
  liyangbin da0176aea1 fix:三轮修复 4 months ago
  liyangbin 8923bb25d5 fix:二轮修复 5 months ago
  liyangbin 1054900b0c feat:获取管理员列表接口调整 5 months ago
  liyangbin cd9a0bae20 fix:一轮修复 5 months ago
  liyangbin efbbeecfd0 feat:后台加购买 5 months ago

+ 11 - 1
.env

@@ -1 +1,11 @@
-VITE_BASE_URL = http://139.196.160.101:8032
+VITE_BASE_URL = http://139.196.160.101:8032
+VITE_STORE_DOMIN = http://test-store.kdan.cn:3018
+VITE_PRO_WEB = http://test-pdf-pro.kdan.cn:3021
+VITE_APP = PDFReaderPro_
+VITE_MAC_APP_VERSION = v4.0.3
+VITE_WINDOWS_APP_VERSION = v4.3.0
+VITE_EDITOR = LynxPDF_Editor_
+VITE_MAC_EDITOR_VERSION = v1.1.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.1
+VITE_KEY_DATA = AS9-y4jfmKr6YrlOGQmAy42kkLAEZ1eaH2Y1-TUht0o0S0Yze6w_ZVDulAVzClbCI6bnlR1zXBCBhyPW
+VITE_VENDOR_DATA = 2760

+ 11 - 1
.env.preparing

@@ -1 +1,11 @@
-VITE_BASE_URL = http://139.196.160.101:8032
+VITE_BASE_URL = http://139.196.160.101:8032
+VITE_STORE_DOMIN = http://test-store.kdan.cn:3018
+VITE_PRO_WEB = http://test-pdf-pro.kdan.cn:3021
+VITE_APP = PDFReaderPro_
+VITE_MAC_APP_VERSION = v4.0.3
+VITE_WINDOWS_APP_VERSION = v4.3.0
+VITE_EDITOR = LynxPDF_Editor_
+VITE_MAC_EDITOR_VERSION = v1.1.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.1
+VITE_KEY_DATA = AS9-y4jfmKr6YrlOGQmAy42kkLAEZ1eaH2Y1-TUht0o0S0Yze6w_ZVDulAVzClbCI6bnlR1zXBCBhyPW
+VITE_VENDOR_DATA = 2760

+ 11 - 1
.env.production

@@ -1 +1,11 @@
-VITE_BASE_URL = https://console.pdfreaderpro.com
+VITE_BASE_URL = https://console.pdfreaderpro.com
+VITE_STORE_DOMIN = https://store.pdfreaderpro.com
+VITE_PRO_WEB = https://www.pdfreaderpro.com
+VITE_APP = PDFReaderPro_
+VITE_MAC_APP_VERSION = v4.0.3
+VITE_WINDOWS_APP_VERSION = v4.3.0
+VITE_EDITOR = LynxPDF_Editor_
+VITE_MAC_EDITOR_VERSION = v1.1.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.1
+VITE_KEY_DATA = AZlF7BTjlelPeQJ7sNlvhHpsUdYc12_C3EsLG9Nkr0hn6gtco_BrpUNLNUoAOkGF0aNcPNphEKbgTi8b
+VITE_VENDOR_DATA = 134050

+ 18 - 13
index.html

@@ -1,16 +1,21 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <meta data-n-head="1" data-hid="description" name="description" content="">
-    <meta data-n-head="1" data-hid="keywords" name="keywords" content="">
-    <link rel="stylesheet" async preload href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,600,700,900">
-    <title>Admin Console</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.js"></script>
-  </body>
+
+<head>
+  <meta charset="UTF-8" />
+  <link rel="icon" href="/favicon.ico" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta data-n-head="1" data-hid="description" name="description" content="">
+  <meta data-n-head="1" data-hid="keywords" name="keywords" content="">
+  <link rel="stylesheet" async preload href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,600,700,900">
+  <script src="https://cdn.paddle.com/paddle/paddle.js" defer hid='Paddle'></script>
+  <!-- <script src="https://www.paypalobjects.com/api/checkout.js" defer></script> -->
+  <title>Admin Console</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.js"></script>
+</body>
+
 </html>

BIN
src/assets/images/cross.png


BIN
src/assets/images/logo.png


File diff suppressed because it is too large
+ 32 - 0
src/assets/images/store/alipay.svg


File diff suppressed because it is too large
+ 18 - 0
src/assets/images/store/card.svg


BIN
src/assets/images/store/ic_close.png


BIN
src/assets/images/store/loading.gif


File diff suppressed because it is too large
+ 3 - 0
src/assets/images/store/mac.svg


BIN
src/assets/images/store/order_box.png


File diff suppressed because it is too large
+ 1 - 0
src/assets/images/store/paypal_icon.svg


File diff suppressed because it is too large
+ 25 - 0
src/assets/images/store/wechatpay.svg


+ 6 - 0
src/assets/images/store/windows.svg

@@ -0,0 +1,6 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4 4H14.8V14.8H4V4Z" fill="#232A40"/>
+<path d="M4 17.2H14.8V28H4V17.2Z" fill="#232A40"/>
+<path d="M17.2002 4H28.0002V14.8H17.2002V4Z" fill="#232A40"/>
+<path d="M17.2002 17.2H28.0002V28H17.2002V17.2Z" fill="#232A40"/>
+</svg>

File diff suppressed because it is too large
+ 7 - 0
src/components/icon/bulb.vue


File diff suppressed because it is too large
+ 7 - 0
src/components/icon/info.vue


+ 7 - 0
src/components/icon/left_arrow.vue

@@ -0,0 +1,7 @@
+<template>
+  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12" fill="none">
+    <path fill-rule="evenodd" clip-rule="evenodd"
+      d="M6.7559 0.410734C7.08134 0.736171 7.08134 1.26381 6.7559 1.58925L3.17849 5.16666H12.8333C13.2936 5.16666 13.6666 5.53975 13.6666 5.99999C13.6666 6.46023 13.2936 6.83332 12.8333 6.83332H3.17849L6.7559 10.4107C7.08134 10.7362 7.08134 11.2638 6.7559 11.5892C6.43047 11.9147 5.90283 11.9147 5.57739 11.5892L0.577391 6.58925C0.251954 6.26381 0.251954 5.73617 0.577391 5.41073L5.57739 0.410734C5.90283 0.0852972 6.43047 0.0852972 6.7559 0.410734Z"
+      fill="currentColor" />
+  </svg>
+</template>

File diff suppressed because it is too large
+ 7 - 0
src/components/icon/menu_download.vue


File diff suppressed because it is too large
+ 7 - 0
src/components/icon/menu_order.vue


+ 20 - 0
src/components/icon/shopping.vue

@@ -0,0 +1,20 @@
+<template>
+  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
+    <g clip-path="url(#clip0_3078_15001)">
+      <path
+        d="M6.25 14.5C6.66421 14.5 7 14.1642 7 13.75C7 13.3358 6.66421 13 6.25 13C5.83579 13 5.5 13.3358 5.5 13.75C5.5 14.1642 5.83579 14.5 6.25 14.5Z"
+        stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
+      <path
+        d="M12.75 14.5C13.1642 14.5 13.5 14.1642 13.5 13.75C13.5 13.3358 13.1642 13 12.75 13C12.3358 13 12 13.3358 12 13.75C12 14.1642 12.3358 14.5 12.75 14.5Z"
+        stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
+      <path
+        d="M1 1H3.33335L5.12 8.92667C5.18096 9.2336 5.34794 9.50931 5.5917 9.70553C5.83546 9.90176 6.14047 10.006 6.45333 10H12.6C12.9129 10.006 13.2179 9.90176 13.4616 9.70553C13.7054 9.50931 13.8724 9.2336 13.9333 8.92667L15 3.99999H4"
+        stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
+    </g>
+    <defs>
+      <clipPath id="clip0_3078_15001">
+        <rect width="16" height="16" fill="white" />
+      </clipPath>
+    </defs>
+  </svg>
+</template>

+ 79 - 47
src/components/sideMenu.vue

@@ -8,12 +8,14 @@ import License from '@/components/icon/menu_license.vue'
 import Device from '@/components/icon/menu_device.vue'
 import Settings from '@/components/icon/menu_setting.vue'
 import Support from '@/components/icon/menu_support.vue'
+import Order from '@/components/icon/menu_order.vue'
 import Logout from '@/components/icon/logout.vue'
+import Download from '@/components/icon/menu_download.vue'
 import { userStore } from '@/store/userInfo'
 
 export default {
-  name:'sideMenu',
-  components:{
+  name: 'sideMenu',
+  components: {
     Dashboard,
     Product,
     Team,
@@ -21,21 +23,27 @@ export default {
     Device,
     Settings,
     Support,
+    Order,
+    Download,
     Logout
   },
   data() {
     return {
-      userName:'',
-      email:'',
-      role:''
+      userName: '',
+      email: '',
+      role: '',
+      isNoAdmin: false
     }
   },
-  mounted(){
+  mounted() {
     this.role = userStore().user.role
+    if (this.$route.query && this.$route.query.type === 'isNoAdmin') {
+      this.isNoAdmin = true
+    }
   },
-  methods:{
-    logout(){
-      get('/pdf-tech/logout').then((res)=>{
+  methods: {
+    logout() {
+      get('/pdf-tech/logout').then((res) => {
         if (res.data.code === 200 && res.data.msg == 'success') {
           this.$router.push('/login')
         }
@@ -43,7 +51,7 @@ export default {
       this.$cookies.remove('accessToken')
       userStore().clearUserInfo()
     },
-    updateUserInfo(){
+    updateUserInfo() {
       get(
         '/pdf-tech/vppMember/getMemberInfo'
       ).then((res) => {
@@ -53,43 +61,43 @@ export default {
       })
     }
   },
-  filters:{
-    avatar(){
+  filters: {
+    avatar() {
       if (userStore().user.userName) {
         return userStore().user.userName.trim().substr(0, 1).toUpperCase()
       }
     },
-    name(){
+    name() {
       if (userStore().user.userName) {
         return userStore().user.userName
       }
     },
-    email(){
+    email() {
       if (userStore().user.userName) {
         return userStore().user.email
       }
     }
   },
-  computed:{
-    path () {
+  computed: {
+    path() {
       if (this.$route.path === '/manage-team/create-team' || this.$route.path === '/manage-team/edit-team') {
         return '/manage-team'
-      } else if (this.$route.path === '/manage-member/add-member' || 
-                this.$route.path.indexOf('/manage-member/edit-member') !== -1) {
+      } else if (this.$route.path === '/manage-member/add-member' ||
+        this.$route.path.indexOf('/manage-member/edit-member') !== -1) {
         return '/manage-member'
       } else if (this.$route.path === '/manage-admin/add-admin' ||
-                this.$route.path.indexOf('/manage-admin/edit-admin') !== -1) {
+        this.$route.path.indexOf('/manage-admin/edit-admin') !== -1) {
         return '/manage-admin'
-      }  else {
+      } else {
         return this.$route.path
       }
     },
-    roles(){
+    roles() {
       if (this.role?.indexOf("1") !== -1) {
         return 'Super Admin'
-      } else if (this.role?.indexOf("2") !== -1){
+      } else if (this.role?.indexOf("2") !== -1) {
         return 'Team Admin'
-      } else if (this.role?.indexOf("4") !== -1){
+      } else if (this.role?.indexOf("4") !== -1) {
         return 'Reseller'
       } else {
         return 'Team member'
@@ -103,7 +111,9 @@ export default {
   <div class=" flex flex-col justify-between h-100vh">
     <div class="aside ">
       <div class="user-info">
-        <div class="head-photo"><p>{{ userName | avatar() }}</p></div>
+        <div class="head-photo">
+          <p>{{ userName | avatar() }}</p>
+        </div>
         <div class="info">
           <p>{{ userName | name() }}</p>
           <p>{{ email | email() }}</p>
@@ -111,63 +121,76 @@ export default {
         <div class="type">{{ roles }}</div>
       </div>
       <el-menu :default-active="path" active-text-color="#1460F3" text-color="#232A40" router>
-        <div v-if="role?.indexOf('2') !== -1 || role?.indexOf('1') !== -1">
-          <el-menu-item index="/dashboard">
+        <!-- <div v-if="role?.indexOf('2') !== -1 || role?.indexOf('1') !== -1"> -->
+        <div>
+          <el-menu-item index="/dashboard" :disabled="isNoAdmin">
             <Dashboard />
             <span>Home</span>
           </el-menu-item>
-          <el-menu-item index="/product">
+          <el-menu-item :index="isNoAdmin ? '/product?type=isNoAdmin' : '/product'">
             <Product />
-            <span>Product Management</span>
+            <span>Products</span>
+          </el-menu-item>
+          <el-menu-item index="/order" :disabled="isNoAdmin">
+            <Order />
+            <span>Order</span>
           </el-menu-item>
-          <el-submenu index="3">
+          <el-submenu index="3" :disabled="isNoAdmin">
             <template slot="title">
               <Team />
               <span>Team Management</span>
             </template>
-            <el-menu-item index="/manage-team">Manage Team</el-menu-item>
-            <el-menu-item index="/manage-member">Manage Member</el-menu-item>
-            <el-menu-item index="/manage-admin">Manage Admin</el-menu-item>
+            <el-menu-item index="/manage-team" :disabled="isNoAdmin">Manage Team</el-menu-item>
+            <el-menu-item index="/manage-member" :disabled="isNoAdmin">Manage Member</el-menu-item>
+            <el-menu-item index="/manage-admin" :disabled="isNoAdmin">Manage Admin</el-menu-item>
           </el-submenu>
-          <el-submenu index="4">
-            <template slot="title"> 
+          <el-submenu index="4" :disabled="isNoAdmin">
+            <template slot="title">
               <License />
               <span>Manage License</span>
             </template>
-            <el-menu-item index="/license-management">Manage License</el-menu-item>
-            <el-menu-item index="/assign-license">Assign License</el-menu-item>
-            <el-menu-item index="/batch-cancel-license">Batch Remove</el-menu-item>
+            <el-menu-item index="/license-management" :disabled="isNoAdmin">Manage License</el-menu-item>
+            <el-menu-item index="/assign-license" :disabled="isNoAdmin">Assign License</el-menu-item>
+            <el-menu-item index="/batch-cancel-license" :disabled="isNoAdmin">Batch Remove</el-menu-item>
           </el-submenu>
-          <el-menu-item index="/manage-device">
+          <el-menu-item index="/manage-device" :disabled="isNoAdmin">
             <Device />
             <span>Unbind Device</span>
           </el-menu-item>
         </div>
         <div v-if="role?.indexOf('4') !== -1">
-          <el-menu-item index="/reseller-manage-product">
+          <el-menu-item index="/reseller-manage-product" :disabled="isNoAdmin">
             <Product />
             <span>Product Management</span>
           </el-menu-item>
-          <el-menu-item index="/reseller-manage-license">
+          <el-menu-item index="/reseller-manage-license" :disabled="isNoAdmin">
             <License />
             <span>Manage License</span>
           </el-menu-item>
         </div>
-        <el-submenu index="6">
-          <template slot="title"> 
+        <el-menu-item :index="isNoAdmin ? '/download?type=isNoAdmin' : '/download'">
+          <Download />
+          <span>Download</span>
+        </el-menu-item>
+        <el-submenu index="6" :disabled="isNoAdmin">
+          <template slot="title">
             <Settings />
             <span>Settings</span>
           </template>
-          <el-menu-item index="/samlsetting" v-if="role?.indexOf('1') !== -1">Directory Settings</el-menu-item>
-          <el-menu-item index="/settings">Personal Settings</el-menu-item>
+          <el-menu-item index="/samlsetting" v-if="role?.indexOf('1') !== -1" :disabled="isNoAdmin">Directory
+            Settings</el-menu-item>
+          <el-menu-item index="/settings" :disabled="isNoAdmin">Personal Settings</el-menu-item>
         </el-submenu>
-        <el-menu-item index="/support">
+        <el-menu-item index="/support" :disabled="isNoAdmin">
           <Support />
-          <span>Support</span>
+          <span>Contact Us</span>
         </el-menu-item>
       </el-menu>
     </div>
-    <div @click="logout()" class="logout py-10px w-full max-w-260px bg-[#F6F7F9] flex items-center cursor-pointer sticky bottom-0"><Logout class="ml-27px" /><span class="ml-11px text-14px text-[#232A40] font-bold">Log out</span></div>
+    <div @click="logout()"
+      class="logout py-10px w-full max-w-260px bg-[#F6F7F9] flex items-center cursor-pointer sticky bottom-0">
+      <Logout class="ml-27px" /><span class="ml-11px text-14px text-[#232A40] font-bold">Log out</span>
+    </div>
   </div>
 </template>
 
@@ -175,18 +198,22 @@ export default {
 .aside {
   padding: 48px 20px 0;
   background-color: #fff;
+
   ::v-deep .el-submenu.is-active,
   ::v-deep .el-submenu.is-active .el-submenu__title {
     color: #1460F3 !important;
   }
+
   ::v-deep .el-submenu__title {
     color: #232A40 !important;
   }
+
   .user-info {
     display: flex;
     flex-direction: column;
     align-items: center;
     margin-bottom: 40px;
+
     .head-photo {
       display: flex;
       justify-content: center;
@@ -195,6 +222,7 @@ export default {
       height: 56px;
       border-radius: 50%;
       background-color: #8D9CB9;
+
       p {
         font-weight: 700;
         font-size: 30px;
@@ -202,23 +230,27 @@ export default {
         color: #FFF;
       }
     }
+
     .info {
       margin-top: 8px;
       text-align: center;
       word-wrap: break-word;
       word-break: break-all;
+
       & p:first-child {
         font-weight: 700;
         font-size: 18px;
         line-height: 24px;
         color: #232A40;
       }
+
       & p:last-child {
         font-size: 14px;
         line-height: 20px;
         color: #505258;
       }
     }
+
     .type {
       margin-top: 8px;
       padding: 4px 12px;

+ 42 - 1
src/router/index.js

@@ -75,6 +75,30 @@ const router = new VueRouter({
         title: 'Forget Password | Admin Account'
       }
     },
+    {
+      path: "/product/custom",
+      name: "productCustom",
+      component: () => import("../views/Product/Custom.vue"),
+      meta: {
+        title: 'Product | Admin Console'
+      }
+    },
+    {
+      path: "/product/pay",
+      name: "productPay",
+      component: () => import("../views/Product/Pay.vue"),
+      meta: {
+        title: 'Product | Admin Console'
+      }
+    },
+    {
+      path: "/product/:id",
+      name: "productCode",
+      component: () => import("../views/Product/ID.vue"),
+      meta: {
+        title: 'Product | Admin Console'
+      }
+    },
     {
       path: '',
       name: 'container',
@@ -90,10 +114,18 @@ const router = new VueRouter({
         },
         {
           path: "/product",
+          name: "product",
+          component: () => import("../views/Product/Index.vue"),
+          meta: {
+            title: 'Product | Admin Console'
+          }
+        },
+        {
+          path: "/order",
           name: "productManagement",
           component: () => import("../views/ProductManagement.vue"),
           meta: {
-            title: 'Product | Admin Console'
+            title: 'Order | Admin Console'
           }
         },
         {
@@ -200,6 +232,14 @@ const router = new VueRouter({
             title: 'Manage Device | Admin Console'
           }
         },
+        {
+          path: "/download",
+          name: "download",
+          component: () => import("../views/Download.vue"),
+          meta: {
+            title: 'Admin Console'
+          }
+        },
         {
           path: "/samlsetting",
           name: "samlsetting",
@@ -277,6 +317,7 @@ router.beforeEach((to, from, next) => {
       }
     } else {
       next('/non-admin-user')
+      // next('/product?type=isNoAdmin')
     }
   } else {
     next()

+ 4 - 1
src/store/userInfo.js

@@ -15,6 +15,7 @@ export const userStore = defineStore('user', {
                     userName: user.fullName,
                     email: user.email,
                     role: user.role,
+                    companyId: user.companyId,
                     area: user.area
                 }
                 localStorage.setItem('userInfo', JSON.stringify(this.user))
@@ -22,7 +23,9 @@ export const userStore = defineStore('user', {
                 this.user = {
                     userName: '',
                     email: '',
-                    role: ''
+                    role: '',
+                    companyId: '',
+                    area: ''
                 }
             }
         },

+ 15 - 5
src/views/Dashboard.vue

@@ -340,12 +340,22 @@ export default {
     },
     // 获取所有团队
     getTeamList() {
-      get('/pdf-tech/vppTeam/getManageTeamList', {
-        teamId: '',
-        keyword: ''
-      }).then((res) => {
+      // get('/pdf-tech/vppTeam/getManageTeamList', {
+      //   teamId: '',
+      //   keyword: ''
+      // }).then((res) => {
+      //   if (res.data.code === 200) {
+      //     res.data.result.list.forEach(item => {
+      //       this.teamList.push({
+      //           label: item.name,
+      //           value: item.id
+      //       })
+      //     })
+      //   }
+      // })
+      get('/pdf-tech/vppTeam/listWithAdmin').then((res) => {
         if (res.data.code === 200) {
-          res.data.result.list.forEach(item => {
+          res.data.result.forEach(item => {
             this.teamList.push({
                 label: item.name,
                 value: item.id

+ 101 - 123
src/views/Device.vue

@@ -46,24 +46,24 @@ export default {
     pagingQuery() {
       get(
         '/pdf-tech/vppDevice/page?page=' +
-          this.currentPage +
-          '&' +
-          'pageSize=' +
-          this.size +
-          '&' +
-          'productId=' +
-          this.productId +
-          '&' +
-          'teamId=' +
-          this.teamId +
-          '&' +
-          'status=' +
-          this.status +
-          '&' +
-          'queryString=' +
-          this.queryString
+        this.currentPage +
+        '&' +
+        'pageSize=' +
+        this.size +
+        '&' +
+        'productId=' +
+        this.productId +
+        '&' +
+        'teamId=' +
+        this.teamId +
+        '&' +
+        'status=' +
+        this.status +
+        '&' +
+        'queryString=' +
+        this.queryString
       ).then((res) => {
-        if(res.data.code === 200){
+        if (res.data.code === 200) {
           const data = res.data.result?.list
           for (let i = 0; i < data.length; i++) {
             if (data[i].status === 0) {
@@ -84,12 +84,17 @@ export default {
     },
     // 获取团队管理列表
     getTeamList() {
-      get('/pdf-tech/vppTeam/getManageTeamList', {
-        teamId: '',
-        keyword: ''
-      }).then((res) => {
+      // get('/pdf-tech/vppTeam/getManageTeamList', {
+      //   teamId: '',
+      //   keyword: ''
+      // }).then((res) => {
+      //   if (res.data.code === 200) {
+      //     this.teamList = res.data.result.list
+      //   }
+      // })
+      get('/pdf-tech/vppTeam/listWithAdmin').then((res) => {
         if (res.data.code === 200) {
-          this.teamList = res.data.result.list
+          this.teamList = res.data.result
         }
       })
     },
@@ -103,43 +108,43 @@ export default {
     },
     //取消设备授权
     cancelAuthorization() {
-        if (this.downloadLoading) return
-        this.downloadLoading = true
-        setTimeout(() => {
-          this.downloadLoading = false
-        }, 2000)
-        var urlencoded = new URLSearchParams()
-        urlencoded.append('deviceId', this.deviceId)
-        post(
-          '/pdf-tech/vppDevice/cancel',
-          urlencoded
-        ).then((res) => {
-          if (res.data.code === 200 && res.data.msg == ' Canceled!') {
-            this.dialogVisible = false
-            this.$message({
-              duration: 5000,
-              message: 'Canceled Successfully',
-              type: 'success'
-            })
-            this.pagingQuery()
-          } else {
-            this.$message({
-              duration: 5000,
-              message: 'Canceled Failed',
-              type: 'error'
-            })
-          }
-        })
-      
+      if (this.downloadLoading) return
+      this.downloadLoading = true
+      setTimeout(() => {
+        this.downloadLoading = false
+      }, 2000)
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('deviceId', this.deviceId)
+      post(
+        '/pdf-tech/vppDevice/cancel',
+        urlencoded
+      ).then((res) => {
+        if (res.data.code === 200 && res.data.msg == ' Canceled!') {
+          this.dialogVisible = false
+          this.$message({
+            duration: 5000,
+            message: 'Canceled Successfully',
+            type: 'success'
+          })
+          this.pagingQuery()
+        } else {
+          this.$message({
+            duration: 5000,
+            message: 'Canceled Failed',
+            type: 'error'
+          })
+        }
+      })
+
     },
     //打开对话框
     handleClick(val) {
       // if (this.deviceStatus === 'Activated'){
-        this.dialogVisible = true
-        this.cancelEmail = val.email
-        this.cancelUniqueSn = val.uniqueSn
-        this.deviceId = val.id
-        this.deviceStatus = val.status
+      this.dialogVisible = true
+      this.cancelEmail = val.email
+      this.cancelUniqueSn = val.uniqueSn
+      this.deviceId = val.id
+      this.deviceStatus = val.status
       // }
     },
     handleSizeChange(value) {
@@ -156,7 +161,7 @@ export default {
       this.pagingQuery()
     },
     // 下载设备文件
-    downLoad(){
+    downLoad() {
       if (this.downloadLoading) return
       this.downloadLoading = true
       var urlencoded = new URLSearchParams()
@@ -164,12 +169,12 @@ export default {
       urlencoded.append("productId", this.productId)
       urlencoded.append("teamId", this.teamId)
       urlencoded.append("status", this.status)
-      downLoad("/pdf-tech/vppDevice/download", urlencoded).then((res)=>{
+      downLoad("/pdf-tech/vppDevice/download", urlencoded).then((res) => {
         setTimeout(() => {
           this.downloadLoading = false
         }, 2000)
         let url = window.URL.createObjectURL(new Blob([res.data], { type: '.xlsx' }))
-        let a= document.createElement('a')
+        let a = document.createElement('a')
         a.style.display = 'none'
         a.href = url
         // 自定义文件名
@@ -179,8 +184,8 @@ export default {
         a.click()
         // 释放内存
         url = window.URL.revokeObjectURL(url)
-        document.body.removeChild(a) 
-  })
+        document.body.removeChild(a)
+      })
     }
   }
 }
@@ -190,8 +195,7 @@ export default {
   <div class="flex flex-col items-center">
     <div class="w-full">
       <h1 class="leading-40px">Device Management</h1>
-      <div
-        class="
+      <div class="
           mt-36px
           mb-16px
           leading-20px
@@ -199,17 +203,10 @@ export default {
           font-bold
           flex
           justify-between
-        "
-      >
+        ">
         <span>Content</span>
-        <el-tooltip
-          class="item"
-          effect="dark"
-          content="Export Data (.xlsx)"
-          placement="bottom"
-        >
-        <span
-          class="
+        <el-tooltip class="item" effect="dark" content="Export Data (.xlsx)" placement="bottom">
+          <span class="
             flex
             justify-center
             items-center
@@ -217,21 +214,15 @@ export default {
             min-w-28px min-h-28px
             bg-[#fff]
             cursor-pointer
-          "
-          @click="downLoad"
-          >
-          <Download/>
-        </span>
+          " @click="downLoad">
+            <Download />
+          </span>
         </el-tooltip>
       </div>
       <div class="flex bg-[#fff] pt-32px px-24px rounded-t-8px w-full">
         <select v-model="productId" class="w-140px" :class="{ '!text-[#232A40]': productId !== '' }">
           <option value="" selected>Product</option>
-          <option
-            v-for="item in productList"
-            :key="item.value"
-            :value="item.id"
-          >
+          <option v-for="item in productList" :key="item.value" :value="item.id">
             {{ item.name }}
           </option>
         </select>
@@ -243,25 +234,20 @@ export default {
           <option value="4" selected>Expired</option>
         </select>
         <select v-model="teamId" class="w-140px ml-16px" :class="{ '!text-[#232A40]': teamId !== '' }">
-          <option value="" selected >Team</option>
+          <option value="" selected>Team</option>
           <option v-for="item in teamList" :key="item.value" :value="item.id">
             {{ item.name }}
           </option>
         </select>
         <div class="relative">
-          <el-input
-            v-model="queryString"
-            size="mini"
-            class="!w-316px ml-16px input-with-select"
-            placeholder="Search User Name / Email / UUID"
-          >
+          <el-input v-model="queryString" size="mini" class="!w-316px ml-16px input-with-select"
+            placeholder="Search User Name / Email / UUID">
           </el-input>
-          <button class="absolute top-8px right-8px" @click="searchInfo()"><Search /></button>
+          <button class="absolute top-8px right-8px" @click="searchInfo()">
+            <Search />
+          </button>
         </div>
-        <button
-          type="button"
-          @click="searchInfo()"
-          class="
+        <button type="button" @click="searchInfo()" class="
             w-70px
             h-28px
             border-1px border-[#1460F3]
@@ -269,40 +255,34 @@ export default {
             ml-16px
             text-[#1460F3]
             hover:opacity-80
-          "
-        >
+          ">
           Confirm
         </button>
       </div>
       <el-table :data="tableData" class="px-24px rounded-b-8px !w-full">
         <el-table-column prop="productName" label="Product"> </el-table-column>
-        <el-table-column prop="memberName" label="User Name"  label-class-name="wordBreak"> </el-table-column>
+        <el-table-column prop="memberName" label="User Name" label-class-name="wordBreak"> </el-table-column>
         <el-table-column prop="email" label="Email">
         </el-table-column>
         <el-table-column prop="teamName" label="Team"> </el-table-column>
         <el-table-column prop="uniqueSn" label="UUID"> </el-table-column>
         <el-table-column prop="status" label="Status"> </el-table-column>
-        <el-table-column prop="activeDate" label="Activated Date"  label-class-name="wordBreak">
+        <el-table-column prop="activeDate" label="Activated Date" label-class-name="wordBreak">
         </el-table-column>
-        <el-table-column prop="expireDate" label="Expiration Data"  label-class-name="wordBreak">
+        <el-table-column prop="expireDate" label="Expiration Data" label-class-name="wordBreak">
         </el-table-column>
-        <el-table-column prop="canceledDate" label="Canceled Date"  label-class-name="wordBreak">
+        <el-table-column prop="canceledDate" label="Canceled Date" label-class-name="wordBreak">
         </el-table-column>
         <el-table-column prop="operate" label="Action" align="left" min-width="110">
           <template slot-scope="scope">
-            <button
-              v-show="scope.row.status === 'Activated'"
-              @click="handleClick(scope.row)"
-              type="text"
-              class="
+            <button v-show="scope.row.status === 'Activated'" @click="handleClick(scope.row)" type="text" class="
                 w-60px
                 h-20px
                 rounded-4px
                 border-1px border-[#1460F3]
                 text-[#1460F3]
                 leading-12px
-              "
-            >
+              ">
               Unbind
             </button>
             <el-dialog :visible.sync="dialogVisible" width="376px" top="30vh" center :show-close="false">
@@ -322,17 +302,10 @@ export default {
         </el-table-column>
         <p slot="empty">No Data Available</p>
       </el-table>
-      <el-pagination
-        @size-change="handleSizeChange"
-        @current-change="handleCurrentChange"
-        :current-page.sync="currentPage"
-        :page-sizes="[5, 10, 20]"
-        :page-size="size"
-        :background="true"
-        layout="prev, pager, next, sizes, jumper"
-        :total="total"
-        class="px-24px !rounded-0 rounded-b-8px mt-20px flex justify-end"
-      >
+      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+        :current-page.sync="currentPage" :page-sizes="[5, 10, 20]" :page-size="size" :background="true"
+        layout="prev, pager, next, sizes, jumper" :total="total"
+        class="px-24px !rounded-0 rounded-b-8px mt-20px flex justify-end">
       </el-pagination>
     </div>
   </div>
@@ -340,8 +313,9 @@ export default {
 
 <style lang="scss" scoped>
 select option:checked {
-    color: #000 !important;
+  color: #000 !important;
 }
+
 .el-table__header-wrapper::v-deep .el-table__header {
   display: flex;
   width: 1100px !important;
@@ -352,7 +326,7 @@ select option:checked {
   width: 1100px !important;
 }
 
-.el-table::v-deep .el-table__cell{
+.el-table::v-deep .el-table__cell {
   padding-right: 10px !important;
   padding-left: 10px !important;
 }
@@ -360,21 +334,25 @@ select option:checked {
 .el-table::v-deep thead {
   color: #000 !important;
 }
+
 ::v-deep .input-with-select .el-input__inner {
   padding: 0 20px 0 8px;
   border-color: #d9d9d9;
   font-size: 14px;
   color: #232A40;
 }
+
 ::v-deep .input-with-select .el-input__inner::placeholder {
   font-size: 14px;
   font-weight: 400;
   color: #808185;
 }
-.el-table::v-deep .el-table__cell .wordBreak{
-  word-break: normal!important;
+
+.el-table::v-deep .el-table__cell .wordBreak {
+  word-break: normal !important;
 }
-.el-table::v-deep .cell{
+
+.el-table::v-deep .cell {
   word-break: break-word;
 }
 </style>

+ 95 - 0
src/views/Download.vue

@@ -0,0 +1,95 @@
+<script setup>
+import { onMounted, getCurrentInstance } from 'vue'
+import { userStore } from '@/store/userInfo'
+import { get } from '../../utils/request'
+import Download from '@/components/icon/menu_download.vue'
+
+const { proxy } = getCurrentInstance()
+
+onMounted(() => {
+  updataUserInfo()
+})
+// 获取用户信息
+const updataUserInfo = () => {
+  get(
+    '/pdf-tech/vppMember/getMemberInfo'
+  ).then((res) => {
+    if (res.data.code === 200 && res.data.msg == 'success') {
+      userStore().setUserInfo(res.data.result)
+      if (res.data.result.validFlag === '0') {
+        proxy.$router.push('/login')
+      }
+    }
+  })
+}
+
+// 下载链接
+let macPro = import.meta.env.VITE_PRO_WEB + '/downloads/' + import.meta.env.VITE_APP + import.meta.env.VITE_MAC_APP_VERSION + '.dmg'
+let windowsPro = import.meta.env.VITE_PRO_WEB + '/downloads/' + import.meta.env.VITE_APP + import.meta.env.VITE_WINDOWS_APP_VERSION + '.exe'
+let macEditor = import.meta.env.VITE_PRO_WEB + '/downloads/' + import.meta.env.VITE_EDITOR + import.meta.env.VITE_MAC_EDITOR_VERSION + '.dmg'
+let windowsEditor = import.meta.env.VITE_PRO_WEB + '/downloads/' + import.meta.env.VITE_EDITOR + import.meta.env.VITE_WINDOWS_EDITOR_VERSION + '.exe'
+</script>
+
+<template>
+  <div>
+    <h1 class="leading-40px text-[#232A40]">Download Center</h1>
+    <div class="mt-32px flex items-center text-20px leading-24px font-700 text-[#232A40]">
+      <img class="w-32px mr-12px" src="@/assets/images/logo.png" alt="logo">
+      LynxPDF Editor (Enterprise)
+    </div>
+    <div class="mt-20px flex">
+      <div class="card w-320px bg-[#FFFFFF] rounded-8px p-32px">
+        <div class="flex items-center text-16px leading-20px font-700 text-[#232A40]">
+          <img src="@/assets/images/store/mac.svg" alt="mac" class="mr-8px">
+          macOS
+        </div>
+        <div class="mt-20px text-14px leading-20px text-[#505258]">Support macOS 11 or later</div>
+        <a :href="macEditor"
+          class="rounded-8px bg-[#1460F3] mt-24px flex leading-40px text-[#fff] justify-center items-center font-700 text-16px hover:opacity-80">
+          <Download class="mr-8px" /> Download</a>
+      </div>
+      <div class="card w-320px ml-24px bg-[#FFFFFF] rounded-8px p-32px">
+        <div class="flex items-center text-16px leading-20px font-700 text-[#232A40]">
+          <img src="@/assets/images/store/windows.svg" alt="mac" class="mr-8px">
+          Windows
+        </div>
+        <div class="mt-20px text-14px leading-20px text-[#505258]">Support Windows 7 or later</div>
+        <a :href="windowsEditor"
+          class="rounded-8px bg-[#1460F3] mt-24px flex leading-40px text-[#fff] justify-center items-center font-700 text-16px hover:opacity-80">
+          <Download class="mr-8px" /> Download</a>
+      </div>
+    </div>
+    <div class="mt-48px flex items-center text-20px leading-24px font-700 text-[#232A40]">
+      <img class="w-32px mr-12px" src="@/assets/images/logo.png" alt="logo">
+      PDF Reader Pro
+    </div>
+    <div class="mt-20px flex">
+      <div class="card w-320px bg-[#FFFFFF] rounded-8px p-32px">
+        <div class="flex items-center text-16px leading-20px font-700 text-[#232A40]">
+          <img src="@/assets/images/store/mac.svg" alt="mac" class="mr-8px">
+          macOS
+        </div>
+        <div class="mt-20px text-14px leading-20px text-[#505258]">Support macOS 11 or later</div>
+        <a :href="macPro"
+          class="rounded-8px bg-[#1460F3] mt-24px flex leading-40px text-[#fff] justify-center items-center font-700 text-16px hover:opacity-80">
+          <Download class="mr-8px" /> Download</a>
+      </div>
+      <div class="card w-320px ml-24px bg-[#FFFFFF] rounded-8px p-32px">
+        <div class="flex items-center text-16px leading-20px font-700 text-[#232A40]">
+          <img src="@/assets/images/store/windows.svg" alt="mac" class="mr-8px">
+          Windows
+        </div>
+        <div class="mt-20px text-14px leading-20px text-[#505258]">Support Windows 7 or later</div>
+        <a :href="windowsPro"
+          class="rounded-8px bg-[#1460F3] mt-24px flex leading-40px text-[#fff] justify-center items-center font-700 text-16px hover:opacity-80">
+          <Download class="mr-8px" /> Download</a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.card {
+  box-shadow: 0px 0px 12px 0px #2361A90D;
+}
+</style>

+ 11 - 5
src/views/LicenseManage.vue

@@ -154,12 +154,18 @@ const selectAllField = () => {
 }
 // 获取团队管理列表
 const getTeamList = (val) => {
-  get('/pdf-tech/vppTeam/getManageTeamList', {
-    teamId: '',
-    keyword: ''
-  }).then((res) => {
+  // get('/pdf-tech/vppTeam/getManageTeamList', {
+  //   teamId: '',
+  //   keyword: ''
+  // }).then((res) => {
+  //   if (res.data.code === 200) {
+  //     teamList.value = res.data.result.list
+  //   }
+  // })
+  //获取团队列表
+  get('/pdf-tech/vppTeam/listWithAdmin').then((res) => {
     if (res.data.code === 200) {
-      teamList.value = res.data.result.list
+      teamList.value = res.data.result
     }
   })
 }

+ 8 - 7
src/views/Login.vue

@@ -68,11 +68,6 @@ export default {
           post('/pdf-tech/login', formData).then(
             (res) => {
               if (res.data.code === 200 && res.data.msg === '获取授权码成功') {
-                this.$message({
-                  type: 'success',
-                  duration: 5000,
-                  message: 'Sign in Success'
-                })
                 get(
                   '/pdf-tech/auth/getToken?code=' +
                   res.data.result
@@ -106,7 +101,6 @@ export default {
                 } else {
                   localStorage.removeItem('isRemember')
                 }
-
               } else if (
                 res.data.code === 306 &&
                 res.data.msg === '账号或者密码错误'
@@ -156,6 +150,11 @@ export default {
     },
     getMemberInfo() {
       get('/pdf-tech/vppMember/getMemberInfo').then((res) => {
+        this.$message({
+          type: 'success',
+          duration: 5000,
+          message: 'Sign in Success'
+        })
         if (res.data.code === 200 && res.data.msg == 'success') {
           userStore().setUserInfo(res.data.result)
           // 判断是否是分销商
@@ -164,7 +163,8 @@ export default {
               '/pdf-tech/vppLicenseCode/checkCompanyLicense?companyId=' + res.data.result.companyId
             ).then((res) => {
               if (!res.data.result) {
-                this.$router.push('/non-admin-user')
+                // this.$router.push('/non-admin-user')
+                this.$router.push('/product?type=isNoAdmin')
                 return
               }
             })
@@ -179,6 +179,7 @@ export default {
           this.$router.push('/dashboard')
         } else {
           this.$router.push('/non-admin-user')
+          // this.$router.push('/product?type=isNoAdmin')
         }
       })
     },

+ 11 - 5
src/views/ManageAdmin.vue

@@ -82,12 +82,18 @@ const searchInfo = () => {
 }
 // 获取团队管理列表
 const getTeamList = (val) => {
-  get('/pdf-tech/vppTeam/getManageTeamList', {
-    teamId: '',
-    keyword: ''
-  }).then((res) => {
+  // get('/pdf-tech/vppTeam/getManageTeamList', {
+  //   teamId: '',
+  //   keyword: ''
+  // }).then((res) => {
+  //   if (res.data.code === 200) {
+  //     teamList.value = res.data.result.list
+  //   }
+  // })
+  //获取团队列表
+  get('/pdf-tech/vppTeam/listWithAdmin').then((res) => {
     if (res.data.code === 200) {
-      teamList.value = res.data.result.list
+      teamList.value = res.data.result
     }
   })
 }

File diff suppressed because it is too large
+ 1092 - 0
src/views/Product/Custom.vue


+ 828 - 0
src/views/Product/ID.vue

@@ -0,0 +1,828 @@
+<script setup>
+import { onMounted, ref, getCurrentInstance, nextTick, onUnmounted, computed, watch } from 'vue'
+import { country } from '../../../utils/country.js'
+import Arrow from '@/components/icon/left_arrow.vue'
+import Info from '@/components/icon/info.vue'
+import { get, postWithHeader } from '../../../utils/request'
+import { userStore } from '@/store/userInfo'
+const { proxy } = getCurrentInstance()
+
+const CountryOption = country()
+const baseUrl = import.meta.env.VITE_STORE_DOMIN
+const id = proxy.$route.params.id
+const codeMap = {
+  '0': {
+    title: "LynxPDF Editor (Enterprise)",
+    desc: "Cost-Effective PDF Solution Tailored to Your Business",
+    code: "com.brother.pdftecheditor"
+  },
+  '1': {
+    title: "PDF Reader Pro Cross-Platform",
+    desc: "Mac + Windows",
+    code: "com.brother.pdfreaderpro.cross.platform.product_3"
+  },
+  '2': {
+    title: "PDF Reader Pro Permanent for Mac",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.mac.product_3"
+  },
+  '3': {
+    title: "PDF Reader Pro Premium for Mac",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.mac.product_1"
+  },
+  '4': {
+    title: "PDF Reader Pro Permanent for Windows",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.windows.product_3"
+  },
+  '5': {
+    title: "PDF Reader Pro Premium for Windows",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.windows.product_1"
+  }
+}
+const product = codeMap[id]
+const productNum = ref(1)
+
+const payMethod = ref('paypal')
+const countryCode = ref('US')
+const postCode = ref('')
+const paypalLoading = ref(true)
+const paddleShow = ref(true)
+const paymentID = ref(null)
+
+// 回到上一级
+const goBack = () => {
+  if (!document.referrer) {
+    window.location.href = '/product'
+    return
+  }
+  window.history.back()
+}
+// 获取用户信息
+const useInfo = ref({})
+const updataUserInfo = () => {
+  get(
+    '/pdf-tech/vppMember/getMemberInfo'
+  ).then((res) => {
+    if (res.data.code === 200 && res.data.msg == 'success') {
+      useInfo.value = res.data.result
+      if (useInfo.value.role !== '1') {
+        window.location.href = '/non-admin-user'
+      }
+      getCommodityInfo()
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+
+// 获取订单信息
+const commodityInfo = ref({})
+const getCommodityInfo = () => {
+  get(
+    baseUrl + '/api/shopping-cart/accumulated-discount?vpp_member_id=' + useInfo.value.id + '&product_code=' + product.code + '&number=' + productNum.value
+  ).then((res) => {
+    if (res.data.code === 200) {
+      commodityInfo.value = res.data.data
+      allNum.value = commodityInfo.value.purchase_quantity
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+
+const allNum = ref(1)
+let useTeamDiscount = computed(() => {
+  if (allNum.value >= 2 && allNum.value <= 4) {
+    return '15%'
+  } else if (allNum.value >= 5 && allNum.value <= 9) {
+    return '25%'
+  } else if (allNum.value >= 10 && allNum.value <= 29) {
+    return '30%'
+  } else if (allNum.value >= 30 && allNum.value <= 99) {
+    return '35%'
+  } else if (allNum.value >= 100) {
+    return '45%'
+  } else {
+    return '100%'
+  }
+})
+
+watch(productNum, () => {
+  if (productNum.value > 100 && paddleShow.value) {
+    paddleShow.value = false
+    if (payMethod.value === 'paddle') {
+      payMethod.value = 'paypal'
+    }
+  }
+  if (productNum.value <= 100 && !paddleShow.value) {
+    paddleShow.value = true
+  }
+})
+
+// 查询支付状态
+const getState = (trade_no, payMethod) => {
+  get(
+    baseUrl + '/api/subscriptions/payed_callback?out_trade_no=' + trade_no
+  )
+    .then(function (res) {
+      if ((Object.keys(res.data.data).length !== 0) && res.data.data.subscription.status === 'actived') {
+        const url = '/product/pay??out_trade_no=' + trade_no
+        window.location.href = url
+      } else if (payMethod && payMethod === 'paddle') {
+        paddleTimer.value = setTimeout(getState, 1000, trade_no, 'paddle')
+      } else {
+        timer.value = setTimeout(getState, 4000, trade_no, payMethod)
+      }
+    })
+    .catch(function (error) {
+      window.location.href = '/product/pay'
+      console.error('server error', error, payMethod, trade_no)
+    })
+}
+// 获取支付data
+const getRequestData = (payMethod) => {
+  let payment = null
+  switch (payMethod) {
+    case 'paypal':
+      payment = 0
+      break
+    case 'alipay':
+      payment = 1
+      break
+    case 'wxpay':
+      payment = 2
+      break
+    case 'paddle':
+      payment = 3
+      break
+  }
+  let data = {
+    vpp_member_id: useInfo.value.id,
+    type: 1,
+    product_code: product.code,
+    number: productNum.value,
+    customer_country: countryCode.value,
+    customer_postcode: postCode.value,
+    payment: payment
+  }
+  return { data }
+}
+// 绑定paypal按钮
+const bindPaypalBtn = async () => {
+  if (document.getElementById('paypal-button') !== undefined) {
+    //@ts-ignore
+    await paypal.Button.render({
+      locale: 'en_US',
+      env: import.meta.env.MODE === 'production' ? 'production' : 'sandbox',
+      commit: false, // Show a 'Pay Now' button
+      client: {
+        sandbox: import.meta.env.VITE_KEY_DATA,
+        production: import.meta.env.VITE_KEY_DATA
+      },
+
+      style: {
+        layout: 'horizontal',
+        label: 'buynow',
+        fundingicons: false, // optional
+        branding: true, // optional
+        size: 'responsive', // small | medium | large | responsive
+        shape: 'rect', // pill | rect
+        color: 'blue', // gold | blue | silve | black
+        tagline: false,
+        height: 46
+      },
+
+      // payment() is called when the button is clicked
+      async payment() {
+        const { data } = getRequestData('paypal')
+        return postWithHeader(baseUrl + '/api/order/create', data)
+          .then(function (res) {
+            console.log(res)
+            paymentID.value = res.data.data.third_order_no
+            return res.data.data.third_order_no
+          })
+          .catch(function (err) {
+            console.log('err', err)
+            window.location.href = '/product/pay'
+          })
+      },
+      onAuthorize(data, actions) {
+        const EXECUTE_URL = baseUrl + '/api/subscriptions/payed_callback?payment_id=' + data.paymentID
+        console.info('approved', data, actions)
+
+        return actions.payment.execute().then(function (payment) {
+          // The payment is complete!
+          // You can now show a confirmation message to the customer
+          //@ts-ignore
+          paypal.request.get(EXECUTE_URL)
+            .then(function (res) {
+              if (res.data && res.data.state === 'pending') {
+                pendingShow.value = true
+                return
+              }
+              if (res.data && res.data.subscription) {
+                const url = '/product/pay?payment_id=' + data.paymentID
+                window.location.href = url
+              } else {
+                alert('Your purchase is failed!\nPlease let us known if you have payed.')
+              }
+            })
+            .catch(function (err) {
+              console.log(err)
+              window.location.href = '/product/pay'
+            })
+        })
+      },
+      onCancel(data, actions) {
+        /*
+        * Buyer cancelled the payment
+        */
+        console.info('cancelled', data, actions)
+      },
+      onError(err) {
+        console.info('err', err)
+        window.location.href = '/product/pay'
+      }
+    }, '#paypal-button')
+    paypalLoading.value = false
+  }
+}
+// 动态加载paypal的js文件
+const initTimer = ref(null)
+const createPaypal = () => {
+  const scriptElement = document.createElement('script')
+  scriptElement.src = `https://www.paypalobjects.com/api/checkout.js`
+  scriptElement.defer = true
+  document.head.appendChild(scriptElement)
+
+  scriptElement.onload = () => {
+    initTimer.value = setInterval(() => {
+      if (paypal && paypal.Button) {
+        clearInterval(initTimer.value)
+        bindPaypalBtn()
+      }
+    }, 0)
+  }
+
+}
+
+// 初始化paddle
+const paddleTimer = ref(null)
+const timer = ref(null)
+const initPaddle = () => {
+  if (import.meta.env.MODE !== 'production') {
+    //@ts-ignore
+    Paddle.Environment.set('sandbox')
+  }
+  //@ts-ignore
+  Paddle.Setup({
+    vendor: Number(import.meta.env.VITE_VENDOR_DATA),
+    eventCallback(data) {
+      // The data.event will specify the event type
+      if (data.event === 'Checkout.Complete') {
+        proxy.$route.query.debug && console.log(data.eventData) // Data specifics on the event
+        // Get the state of the transaction
+        paddleTimer.value = setTimeout(getState, 1000, data.eventData.checkout.passthrough, 'paddle')
+      } else if (data.event === 'Checkout.Close') {
+        console.log('Cancel') // Data specifics on the event
+      }
+    }
+  })
+}
+const isLoading = ref(false)
+const loading = ref(true)
+const qrcode = ref('')
+const wxpayShow = ref(false)
+
+// 购买
+const payNow = () => {
+  if (isLoading.value) { return }
+  isLoading.value = true
+  if (payMethod.value === 'wxpay' || payMethod.value === 'alipay') {
+    wxpayShow.value = true
+    loading.value = true
+  }
+  const { data } = getRequestData(payMethod.value)
+  postWithHeader(baseUrl + '/api/order/create', data)
+    .then((res) => {
+      isLoading.value = false
+      if (timer.value) {
+        clearTimeout(timer.value)
+      }
+      if (payMethod.value === 'wxpay') {
+        console.log(res.data.data.page_pay_url)
+        loading.value = false
+        qrcode.value = res.data.data.page_pay_url
+        let trade_no = res.data.data.page_pay_url.split('/')
+        trade_no = trade_no[trade_no.length - 1].split('.')[0]
+        timer.value = setTimeout(getState, 4000, trade_no, payMethod.value)
+        return
+      }
+      if (payMethod.value === 'alipay' && /.png$/.test(res.data.data.page_pay_url)) {
+        console.log('createPayment: ', res.data.data.page_pay_url)
+        loading.value = false
+        qrcode.value = res.data.data.page_pay_url
+        timer.value = setTimeout(getState, 4000, res.data.data.trade_no, payMethod.value)
+        return
+      }
+      if (payMethod.value === 'paddle') {
+        Paddle.Checkout.open({
+          override: res.data.data.page_pay_url
+        })
+        return
+      }
+      console.log('createPayment: ', res.data.subscription.alipay_order.page_pay_url)
+      window.location.href = res.data.subscription.alipay_order.page_pay_url
+    })
+    .catch((err) => {
+      window.location.href = '/product/pay'
+      isLoading.value = false
+      console.log(err)
+    })
+}
+// 支付弹窗
+const closeWXpayShow = () => {
+  wxpayShow.value = false
+  clearTimeout(timer.value)
+}
+
+updataUserInfo()
+onMounted(() => {
+  nextTick(() => {
+    createPaypal()
+  })
+  initPaddle()
+})
+onUnmounted(() => {
+  if (initTimer.value) {
+    clearInterval(initTimer.value)
+  }
+  if (paddleTimer.value) {
+    clearInterval(paddleTimer.value)
+  }
+  if (timer.value) {
+    clearInterval(paddleTimer.value)
+  }
+})
+</script>
+
+<template>
+  <div class="min-h-100vh bg-[#E7EAEE] py-48px px-120px">
+    <div class="font-700 text-28px leading-40px text-[#232A40]">Add Licenses</div>
+    <div @click="goBack"
+      class="mt-16px inline-flex items-center text-[#1460F3] cursor-pointer pb-16px border-b-1px border-[#0000001A]">
+      <Arrow class="mr-6px" />
+      Back
+    </div>
+    <div class="pt-32px flex justify-between border-t-1px border-[#0000001A]">
+      <div>
+        <div class="font-700 text-[#232A40] text-16px leading-20px">Review Your Order</div>
+        <table class="mt-24px">
+          <tr>
+            <td class="text-14px leading-20px font-700 text-[#232A40] pr-40px">Super Admin Email</td>
+            <td class="text-14px leading-20px text-[#232A40]">{{ useInfo.email }}</td>
+          </tr>
+          <tr>
+            <td class="text-14px leading-20px font-700 text-[#232A40] pt-16px pr-40px">Organization Name</td>
+            <td class="text-14px leading-20px text-[#232A40] pt-17px">{{ useInfo.company }}</td>
+          </tr>
+          <tr>
+            <td class="text-14px leading-20px font-700 text-[#232A40] pt-16px pr-40px">User Name</td>
+            <td class="text-14px leading-20px text-[#232A40] pt-17px">{{ useInfo.fullName }}</td>
+          </tr>
+        </table>
+      </div>
+      <div>
+        <div class="font-700 text-16px leading-20px text-[#232A40]">You’re Adding</div>
+        <div class="card w-500px rounded-8px p-40px bg-[#fff] mt-12px">
+          <div class="flex">
+            <img v-if="id === '1'" src="@/assets/images/cross.png" alt="logo" class="h-48px">
+            <img v-else src="@/assets/images/logo.png" alt="logo" class="h-48px">
+            <div class="ml-20px">
+              <div class="font-700 leading-20px text-16px text-[#232A40]">{{ product.title }}</div>
+              <div class="leading-20px text-14px text-[#505258] mt-4px">{{ product.desc }}</div>
+            </div>
+          </div>
+          <div class="mt-32px font-700 text-14px leading-20px text-[#232A40]">
+            Authorized Devices
+          </div>
+          <div class="recommend-icon text-14px leading-20px text-[#808185] cursor-pointer">
+            <span v-show="commodityInfo.next_number">
+              Authorize another <span class="text-[#232A40]">{{ commodityInfo.next_number }}
+                <span v-if="commodityInfo.next_number !== 1">devices</span>
+                <span v-else>device</span></span> to upgrade to
+              <span class="text-[#232A40]">{{ commodityInfo.next_discount }} OFF</span>
+            </span>
+            <span v-show="!commodityInfo.next_number">
+              <a href="mailto:support@pdfreaderpro.com?subject=Feedback from Admin Console"
+                class="text-[#1460F3] hover:underline">Contact us</a> for a higher discount.
+            </span>
+            <Info class="ml-8px inline-block mb-4px" />
+            <div class="tip">
+              You have currently authorized {{ commodityInfo.purchase_quantity }} devices
+              <div>Authorize 2 devices cumulatively: 15% OFF</div>
+              <div>Authorize 5 devices cumulatively: 25% OFF</div>
+              <div>Authorize 10 devices cumulatively: 30% OFF</div>
+              <div>Authorize 30 devices cumulatively: 35% OFF</div>
+              <div>Authorize 100 devices cumulatively:: 45% OFF</div>
+            </div>
+          </div>
+          <div class="mt-16px pb-32px border-[#0000001A] border-b-1px">
+            <el-input-number v-model="productNum" :precision="0" @change="getCommodityInfo" controls-position="right"
+              :min="1"></el-input-number>
+          </div>
+          <div class="flex justify-between mt-32px">
+            <div class="text-14px font-700 leading-20px text-[#232A40]">Discount</div>
+            <div class="text-16px leading-20px font-700 text-[#FF5054]"><span
+                v-show="useTeamDiscount !== commodityInfo.discount">Upgrade to</span> {{ commodityInfo.discount }} OFF
+            </div>
+          </div>
+          <div class="flex justify-between mt-12px items-center">
+            <div class="text-14px font-700 leading-20px text-[#232A40]">Total Price</div>
+            <!-- 数据没回来之前不展示 -->
+            <template v-if="commodityInfo.discount_price">
+              <div v-show="payMethod === 'paypal' || payMethod === 'paddle'"
+                class="text-28px leading-40px font-700 text-[#232A40]">
+                <span class="text-16px font-400 text-[#808185] line-through">${{ commodityInfo.price }}</span>
+                ${{ commodityInfo.discount_price }}
+              </div>
+              <div v-show="payMethod === 'wxpay' || payMethod === 'alipay'"
+                class="text-28px leading-40px font-700 text-[#232A40]">
+                <span class="text-16px font-400 text-[#808185] line-through">¥{{ commodityInfo.cny_price }}</span>
+                ¥{{ commodityInfo.discount_cny_price }}
+              </div>
+            </template>
+            <div v-else class="h-40px"></div>
+          </div>
+          <div class="method-container">
+            <div class="paypal-btn pay-btn" :class="{ active: payMethod === 'paypal' }" @click="payMethod = 'paypal'">
+              <img loading="lazy" src="@/assets/images/store/order_box.png" alt="select pic">
+            </div>
+            <div v-if="paddleShow" class="card-btn pay-btn" :class="{ active: payMethod === 'paddle' }"
+              @click="payMethod = 'paddle'">
+              <img loading="lazy" src="@/assets/images/store/order_box.png" alt="select pic">
+            </div>
+            <div class="wxpay-btn pay-btn pay-btn-cny" :class="{ active: payMethod === 'wxpay' }"
+              @click="payMethod = 'wxpay'">
+              <img loading="lazy" src="@/assets/images/store/order_box.png" alt="select pic">
+            </div>
+            <div class="ali-btn pay-btn pay-btn-cny" :class="{ active: payMethod === 'alipay' }"
+              @click="payMethod = 'alipay'">
+              <img loading="lazy" src="@/assets/images/store/order_box.png" alt="select pic">
+            </div>
+          </div>
+          <div v-if="payMethod === 'paddle'" class="info-message flex justify-center items-center mt-16px">
+            <select v-model="countryCode" name="countryCode"
+              class="w-[60%] border-1 border-[#E2E3E5] leading-40px h-40px">
+              <option disabled value>Select a country</option>
+              <option v-for="(item, index) in CountryOption" :key="index" :value="item.code">{{ item.name }}
+              </option>
+            </select>
+            <input v-model.trim="postCode" type="text" name="Postcode" placeholder="Postcode"
+              class="border-1 border-[#E2E3E5] ml-12px rounded-4px leading-40px px-8px text-[#888C94]">
+          </div>
+          <div v-if="payMethod !== 'paypal'"
+            class="mt-24px cursor-pointer leading-48px text-center bg-[#0097ff] rounded-8px text-16px font-700 text-[#fff] hover:bg-[#1460F3]"
+            @click="payNow">Continue to Checkout</div>
+          <button v-show="!paypalLoading && payMethod === 'paypal'" id="paypal-button" class="w-[100%] mt-24px"></button>
+          <div v-if="paypalLoading && payMethod === 'paypal'" class="load-container">
+            <svg class="load" x="0px" y="0px" viewBox="0 0 150 150">
+              <circle id="loading-inner" cx="75" cy="75" r="50" />
+            </svg>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 支付弹窗 -->
+    <div v-show="wxpayShow" class="wxpay-container">
+      <div class="wxpay-content">
+        <span class="wx-close" @click="closeWXpayShow"></span>
+        <div class="title">{{ payMethod === 'wxpay' ? 'Wechat Pay' : 'Alipay' }}</div>
+        <span class="img-container">
+          <template v-if="loading">
+            <img loading="lazy" class="loading" src="@/assets/images/store/loading.gif" alt="loading icon">
+            <div class="loading">Loading ...</div>
+          </template>
+          <img v-else loading="lazy" class="qrcode" :src="qrcode" alt="QR code">
+        </span>
+        <p class="note">{{ payMethod === 'wxpay' ? 'Please scan QR Code with Wechat' : 'Please scan QR Code with Alipay'
+        }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.card {
+  box-shadow: 0px 0px 12px 0px #2361A90D;
+}
+
+.recommend-icon {
+  position: relative;
+  display: inline-block;
+
+  .tip {
+    display: none;
+    box-shadow: 0px 4px 16px 0px #8195C82E;
+    padding: 8px;
+    z-index: 101;
+    top: 0px;
+    right: 0px;
+    transform: translate(50%, -100%);
+    width: 320px;
+    position: absolute;
+    font-size: 14px;
+    font-weight: 700;
+    line-height: 20px;
+    color: #FFFFFF;
+    text-align: left;
+
+    &::before {
+      content: '';
+      border-radius: 4px;
+      position: absolute;
+      top: 0;
+      left: 0;
+      display: block;
+      background: #373A47;
+      width: 100%;
+      height: calc(100% - 6px);
+      z-index: -1;
+    }
+
+    div {
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 20px;
+    }
+  }
+
+  &::before {
+    content: '';
+    top: -7px;
+    right: 0%;
+    transform: translate(50%, 0);
+    position: absolute;
+    z-index: 2;
+    border: 6px solid transparent;
+    border-top-color: #373A47;
+    display: none;
+  }
+
+  &:hover {
+
+    &::before,
+    .tip {
+      display: block;
+    }
+  }
+}
+
+::v-deep .el-input-number {
+  width: 180px;
+  line-height: 40px;
+
+  .el-input-number__increase,
+  .el-input-number__decrease {
+    width: 20px;
+    height: 14px;
+    right: 4px;
+    border: none;
+
+    i {
+      font-weight: 700;
+    }
+  }
+
+  .el-input-number__increase {
+    top: 7px;
+    border-radius: 4px 4px 0 0;
+
+    i {
+      border-bottom: 1px solid #E2E3E5;
+    }
+  }
+
+  .el-input-number__decrease {
+    bottom: 7px;
+    border-radius: 0 0 4px 4px;
+
+    i {
+      border-top: 1px solid #E2E3E5;
+      vertical-align: top;
+    }
+  }
+
+  .el-input {
+    input {
+      text-align: left;
+    }
+  }
+}
+
+.method-container {
+  font-size: 0;
+  margin-top: 32px;
+
+  input {
+    display: none;
+  }
+
+  .pay-btn {
+    position: relative;
+    display: inline-block;
+    width: 20.5882%;
+    height: 30px;
+    border-radius: 3px;
+    border: 1px solid #cacaca;
+    cursor: pointer;
+
+    img {
+      display: none;
+      position: absolute;
+      bottom: -1px;
+      right: -1px;
+      width: 12px;
+    }
+
+    &.active {
+      border-color: #0097ff;
+
+      img {
+        display: inline;
+      }
+    }
+  }
+
+  .card-btn {
+    width: 29.4117%;
+    margin-right: 2.9412%;
+    background: url('@/assets/images/store/card.svg') center no-repeat;
+    background-size: 100px auto;
+  }
+
+  .paypal-btn {
+    width: 20.5882%;
+    margin-right: 2.9412%;
+    background: url('@/assets/images/store/paypal_icon.svg') center no-repeat;
+    background-size: 71.4286% auto;
+  }
+
+  .pay-btn-cny {
+    width: 20.5882%;
+  }
+
+  .wxpay-btn {
+    margin-right: 2.9412%;
+    background: url('@/assets/images/store/wechatpay.svg') center no-repeat;
+    background-size: 48px auto;
+  }
+
+  .ali-btn {
+    background: url('@/assets/images/store/alipay.svg') center no-repeat;
+    background-size: 48px auto;
+  }
+}
+
+.info-message {
+  select {
+    height: 40px;
+    line-height: 40px;
+    color: #101828;
+
+    option {
+      width: 60%;
+    }
+  }
+}
+
+@keyframes loading-circle {
+  0% {
+    stroke-dashoffset: 0;
+  }
+
+  100% {
+    stroke-dashoffset: -600;
+  }
+}
+
+.load-container {
+  margin-top: 24px;
+  display: flex;
+  justify-content: center;
+
+  .load {
+    width: 46px;
+    -webkit-animation: loading 3s linear infinite;
+    animation: loading 3s linear infinite;
+
+    #loading-inner {
+      stroke-dashoffset: 0;
+      stroke-dasharray: 300;
+      stroke-width: 6;
+      stroke-miterlimit: 10;
+      stroke-linecap: round;
+      -webkit-animation: loading-circle 2s linear infinite;
+      animation: loading-circle 2s linear infinite;
+      stroke: #0097ff;
+      fill: transparent;
+    }
+  }
+}
+
+// wechatpay
+.wxpay-container {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 1800;
+  background: rgba(0, 0, 0, 0.5);
+
+  .wxpay-content {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    padding-bottom: 50px;
+    background-color: #fff;
+    width: 334px;
+    border-radius: 7px;
+    text-align: center;
+
+    .title {
+      padding-top: 42px;
+      font-size: 18px;
+      color: rgba(51, 51, 51, 1);
+      line-height: 25px;
+    }
+
+    .img-container {
+      display: inline-block;
+      width: 244px;
+      height: 244px;
+
+      img.loading {
+        margin: 0 auto;
+        margin-top: 110px;
+        margin-bottom: 10px;
+      }
+
+      div.loading {
+        font-size: 14px;
+        color: rgba(51, 51, 51, 1);
+        line-height: 20px;
+      }
+    }
+
+    .qrcode {
+      width: 244px;
+      height: 244px;
+      border: none;
+    }
+
+    .note {
+      margin-bottom: 5px;
+      font-size: 16px;
+      color: rgba(51, 51, 51, 1);
+      line-height: 22px;
+    }
+
+    .mess {
+      font-size: 16px;
+      color: rgba(153, 153, 153, 1);
+      line-height: 22px;
+
+      a {
+        color: #3285E3;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+
+    .wx-close {
+      position: absolute;
+      top: 10px;
+      right: 10px;
+      display: inline-block;
+      width: 14px;
+      height: 14px;
+      background: url('@/assets/images/store/ic_close.png') no-repeat;
+      background-size: 14px 14px;
+      opacity: 0.5;
+      cursor: pointer;
+
+      &:hover {
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>
+

+ 276 - 0
src/views/Product/Index.vue

@@ -0,0 +1,276 @@
+<script setup>
+import { onMounted, ref, getCurrentInstance, computed, watch } from 'vue'
+import Shopping from '@/components/icon/shopping.vue'
+import { get, postWithHeader } from '../../../utils/request'
+import { userStore } from '@/store/userInfo'
+const { proxy } = getCurrentInstance()
+
+const memberRole = ref('')
+const companyId = ref('')
+const baseUrl = import.meta.env.VITE_STORE_DOMIN
+
+onMounted(() => {
+  updataUserInfo()
+  memberRole.value = userStore().user.role
+  companyId.value = userStore().user.companyId
+  isOwnTry()
+  const userAgent = navigator.userAgent
+  const isWindows = /Windows/.test(userAgent)
+  if (isWindows) {
+    techPlatform.value = 'Windows'
+    proPlatform.value = 'Windows'
+  }
+  getOverview()
+})
+
+// 获取用户信息
+const updataUserInfo = () => {
+  get(
+    '/pdf-tech/vppMember/getMemberInfo'
+  ).then((res) => {
+    if (res.data.code === 200 && res.data.msg == 'success') {
+      userStore().setUserInfo(res.data.result)
+    }
+  })
+}
+
+// 判断是否有试用权限
+const showTry = ref(false)
+const dialogVisible = ref(false)
+const isOwnTry = () => {
+  get(
+    baseUrl + '/api/member/already-tried?company_id=' + companyId.value
+  ).then((res) => {
+    if (res.data.code === 200) {
+      showTry.value = !res.data.tried
+    }
+  })
+}
+const freeTry = () => {
+  const data = JSON.stringify({
+    company_id: companyId.value
+  })
+  postWithHeader(baseUrl + '/api/vpp_members/trialByCompany', data).then(res => {
+    dialogVisible.value = false
+    if (res.data.code !== 200) {
+      proxy.$message.error({
+        duration: 5000,
+        message: res.data.message
+      })
+    } else {
+      proxy.$message({
+        duration: 5000,
+        message: 'Trial Successfully',
+        type: 'success'
+      })
+      window.location.href = '/product'
+      isOwnTry()
+    }
+  })
+}
+
+const techPlatform = ref('Mac')
+const proPlatform = ref('Mac')
+const proProduct = ref('Permanent License')
+
+// 购买链接
+const proBuyUrl = computed(() => {
+  if (proPlatform.value === 'Windows') {
+    if (proProduct.value === 'Permanent License') {
+      return '/product/4'
+    } else {
+      return "/product/5"
+    }
+  } else {
+    if (proProduct.value === 'Permanent License') {
+      return '/product/2'
+    } else {
+      return "/product/3"
+    }
+  }
+})
+
+// 产品
+const tableData = ref([])
+const isHaveProduct = ref('or')
+const codeMap = [
+  {
+    title: "LynxPDF Editor (Enterprise)",
+    code: "com.brother.pdftecheditor",
+    url: "/product/0"
+  },
+  {
+    title: "PDF Reader Pro Cross-Platform",
+    code: "com.brother.pdfreaderpro.cross.platform.product_3",
+    url: "/product/1"
+  },
+  {
+    title: "PDF Reader Pro Permanent for Mac",
+    code: "com.brother.pdfreaderpro.mac.product_3",
+    url: "/product/2"
+  },
+  {
+    title: "PDF Reader Pro Premium for Mac",
+    code: "com.brother.pdfreaderpro.mac.product_1",
+    url: "/product/3"
+  },
+  {
+    title: "PDF Reader Pro Permanent for Windows",
+    code: "com.brother.pdfreaderpro.windows.product_3",
+    url: "/product/4"
+  },
+  {
+    title: "PDF Reader Pro Premium for Windows",
+    code: "com.brother.pdfreaderpro.windows.product_1",
+    url: "/product/5"
+  }
+]
+// 计算商品信息
+const computeList = (productList) => {
+  tableData.value = productList.map(product => {
+    const match = codeMap.find(item => item.code === product.code)
+    if (match) {
+      return {
+        ...match,
+        id: product.id,
+        assignedCodeNumber: product.assignedCodeNumber,
+        codeNumber: product.codeNumber,
+        name: product.name,
+      }
+    }
+    return null
+  }).filter(item => item !== null)
+}
+const getOverview = () => {
+  if (proxy.$route.query && proxy.$route.query.type === 'isNoAdmin') {
+    isHaveProduct.value = 'no'
+    return
+  }
+  get('/pdf-tech/product/purchasedProductList').then((res) => {
+    if (res.data.result.length === 0) {
+      isHaveProduct.value = 'no'
+      return
+    }
+    computeList(res.data.result)
+    isHaveProduct.value = 'yes'
+  })
+}
+</script>
+
+<template>
+  <div class="flex flex-col items-center">
+    <div class="w-full">
+      <div class="
+      mb-32px
+      leading-20px
+      text-16px
+      font-bold
+      flex
+      justify-between
+      ">
+        <h1 class="leading-40px">Product</h1>
+        <a v-if="memberRole === '1'" href="/product/custom"
+          class="flex items-center h-28px px-10px py-4px bg-[#1460F3] rounded-4px text-14px leading-20px font-400 text-[#fff] cursor-pointer hover:opacity-80">
+          <Shopping class="mr-8px" />Buy More Products
+        </a>
+      </div>
+      <div v-if="isHaveProduct === 'or'"></div>
+      <div v-else-if="isHaveProduct === 'yes'">
+        <el-table :data="tableData" class="px-24px pb-40px rounded-b-8px">
+          <el-table-column prop="title" label="Product">
+            <template slot-scope="scope">
+              <div class="flex items-center text-[#232A40]">
+                <img v-if="scope.row.code === 'com.brother.pdfreaderpro.cross.platform.product_3'"
+                  src="@/assets/images/cross.png" alt="logo" class="h-32px mr-12px">
+                <img v-else src="@/assets/images/logo.png" alt="logo" class="h-32px mr-12px">
+                {{ scope.row.title }}
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="Quantity" label="Quantity" width="360">
+            <template slot-scope="scope">
+              {{ scope.row.assignedCodeNumber }}/{{ scope.row.codeNumber }}
+              <span v-if="scope.row.codeNumber !== 1">Licenses</span>
+              <span v-else>License</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="Purchase" label="Purchase" width="160">
+            <template slot-scope="scope">
+              <div v-if="memberRole?.indexOf('1') !== -1">
+                <a :href="scope.row.url"
+                  class="w-89px inline-block text-center h-28px mt-8px rounded-4px border-1px border-[#1460F3] text-[#1460F3] leading-28px hover:bg-[#1460F333]">
+                  Buy Now
+                </a>
+              </div>
+            </template>
+          </el-table-column>
+          <p slot="empty">No Data Available</p>
+        </el-table>
+      </div>
+      <!-- 购买卡片 -->
+      <div v-else class="flex mt-24px">
+        <div class="card max-w-400px w-[32%]">
+          <div class="bg-[#fff] rounded-t-8px p-32px text-center">
+            <img class="w-64px m-auto" src="@/assets/images/logo.png" alt="logo">
+            <div class="font-bold text-20px leading-24px mt-16px text-[#232A40]">LynxPDF Editor (Enterprise)</div>
+            <div class="mt-16px text-14px leading-20px text-[#505258]">Cost-Effective PDF Solution Tailored to Your
+              Business</div>
+          </div>
+          <div class="bg-[#F6F7F9] p-32px rounded-b-8px">
+            <div v-if="memberRole === '1'">
+              <a href="/product/0"
+                class="rounded-8px bg-[#1460F3] flex leading-40px text-[#fff] justify-center font-700 text-16px hover:opacity-80">
+                Buy Now</a>
+              <a v-if="showTry" @click="dialogVisible = true"
+                class="mt-16px cursor-pointer rounded-8px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80">
+                Apply for Trial</a>
+            </div>
+            <div v-else class="text-center text-14px leading-20px text-[#808185]">Please contact the super admin to purchase license or apply for trial.</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 试用弹窗 -->
+    <el-dialog title="" :visible.sync="dialogVisible" width="376px" top="30vh" center :show-close="false">
+      <!-- <Warning /> -->
+      <p class="mt-16px">Are you sure to apply for the 14-day free trial of LynxPDF Editor (Enterprise)?</p>
+      <p>The License code will be sent to the Admin Console after applying successfully.</p>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="freeTry">
+          Apply
+        </el-button>
+        <el-button @click="dialogVisible = false">Cancel</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+::v-deep .el-select {
+  width: 100%;
+  line-height: 40px;
+  margin-top: 20px;
+
+  .el-input {
+    input {
+      border: 1px solid #E2E3E5;
+      font-size: 16px;
+      font-weight: 400;
+      line-height: 24px;
+      text-align: left;
+      color: #101828;
+    }
+
+    .el-select__caret {
+      color: #404653;
+    }
+  }
+}
+::v-deep .el-table {
+  border-radius: 8px;
+  &::before {
+    display: none;
+  }
+}
+</style>
+

+ 84 - 0
src/views/Product/Pay.vue

@@ -0,0 +1,84 @@
+
+<script setup>
+import { onMounted, ref, getCurrentInstance, computed } from 'vue'
+import { get } from '../../../utils/request'
+import Bulb from '@/components/icon/bulb.vue'
+import Arrow from '@/components/icon/left_arrow.vue'
+const { proxy } = getCurrentInstance()
+const baseUrl = import.meta.env.VITE_STORE_DOMIN
+
+const serErr = ref(false)
+let waitRes = ref(false)
+
+// 回到上一级
+const goBack = () => {
+  if (!document.referrer) {
+    window.location.href = '/product'
+    return
+  }
+  window.history.back()
+}
+const route = proxy.$route
+const tradeParam = route.query.payment_id ? 'payment_id=' + route.query.payment_id : 'out_trade_no=' + route.query.out_trade_no
+const reqUrl = baseUrl + '/api/subscriptions/payed_callback?' + tradeParam
+get(
+  reqUrl
+).then(function (res) {
+  waitRes.value = true
+  if (!(Object.keys(res.data).length === 0) && res.data.data?.subscription?.status === 'actived') {
+    serErr.value = false
+  } else {
+    console.log(res)
+    serErr.value = true
+  }
+})
+</script>
+
+<template>
+  <div class="min-h-100vh bg-[#E7EAEE] py-48px px-120px flex justify-center items-center">
+    <template v-if="waitRes">
+      <div v-if="serErr">
+        <img src="@/assets/images/fail.svg" alt="fail" class="w-64px m-auto">
+        <div class="text-28px leading-40px mt-32px font-700 text-center">Payment Failure!</div>
+        <div class="flex mt-26px font-700 text-16px leading-20px text-[#232A40] items-center">
+          <Bulb class="mr-4px" />
+          Solutions as below:
+        </div>
+        <ul class="mt-16px max-w-800px">
+          <li class="text-[#505258] text-16px leading-20px"><span
+              class="inline-block flex-shrink-0 mx-8px w-4px h-4px rounded-[50%] bg-[#505258] align-middle"></span>Change
+            the network and have a try again.</li>
+          <li class="text-[#505258] text-16px leading-20px mt-20px"><span
+              class="inline-block flex-shrink-0 mx-8px w-4px h-4px rounded-[50%] bg-[#505258] align-middle"></span>Please
+            try Google Chrome, Safari or other different browser, or you can clear browsing data and try again.</li>
+          <li class="text-[#505258] text-16px leading-20px mt-20px"><span
+              class="inline-block flex-shrink-0 mx-8px w-4px h-4px rounded-[50%] bg-[#505258] align-middle"></span>Try
+            other
+            payment methods. If you want to use other payment methods, please <a class="text-[#1460F3] hover:underline"
+              href="mailto:support@pdfreaderpro.com">contact us</a> directly.</li>
+          <li class="text-[#505258] text-16px leading-20px mt-20px flex"><span
+              class="inline-block flex-shrink-0 mx-8px w-4px h-4px rounded-[50%] bg-[#505258] align-middle mt-8px"></span>
+            <div>If you
+              still have purchase problems, you can send your error message or screenshots to us via <a
+                class="text-[#1460F3] hover:underline" href="mailto:support@pdfreaderpro.com">support@pdfreaderpro.com</a>
+            </div>
+          </li>
+        </ul>
+        <a href="mailto:support@pdfreaderpro.com"
+          class="bg-[#1460F3] mt-32px font-700 flex rounded-8px m-auto justify-center w-220px leading-40px text-16px text-[#fff] hover:opacity-80">Get
+          Help</a>
+        <span class="mt-16px flex items-center text-[#1460F3] cursor-pointer pb-16px flex justify-center" @click="goBack">
+          <Arrow class="mr-6px" />
+          Back
+        </span>
+      </div>
+      <div v-else>
+        <img src="@/assets/images/success.svg" alt="success" class="w-64px m-auto">
+        <div class="text-28px leading-40px mt-32px font-700">Payment Success!</div>
+        <a href="/dashboard"
+          class="bg-[#1460F3] mt-32px font-700 flex rounded-8px m-auto justify-center w-220px leading-40px text-16px text-[#fff] hover:opacity-80">Back
+          Home</a>
+      </div>
+    </template>
+  </div>
+</template>

+ 5 - 5
src/views/ProductManagement.vue

@@ -29,8 +29,8 @@ onMounted(() => {
   const userAgent = navigator.userAgent
   const isWindows = /Windows/.test(userAgent)
   const baseurl = import.meta.env.MODE === 'production' ? 'https://www.pdfreaderpro.com' : 'http://test-pdf-pro.kdan.cn:3021'
-  const base = isWindows ? baseurl + '/windows/store?mode=team' : baseurl + '/store?mode=team'
-  url.value = base + '&email=' + userStore().user.email
+  const base = baseurl + '/store/pdftecheditor'
+  url.value = base + '?email=' + userStore().user.email
   ProURL.value = baseurl
   pagingQuery()
   getproductList()
@@ -99,7 +99,7 @@ const handleCurrentChange = (value) => {
 <template>
   <div class="flex flex-col items-center">
     <div class="w-full">
-      <h1 class="leading-40px">Product Management</h1>
+      <h1 class="leading-40px">Order</h1>
       <div class="
           mt-36px
           mb-16px
@@ -110,7 +110,7 @@ const handleCurrentChange = (value) => {
           justify-between
         ">
         <span>Content</span>
-        <a v-if="role === '1'" target="_blank" :href="url"
+        <a v-if="role === '1'" href="/product/custom"
           class="h-28px px-10px py-4px bg-[#1460F3] rounded-4px text-14px leading-20px font-400 text-[#fff] cursor-pointer hover:opacity-80">
           Buy More Product</a>
       </div>
@@ -140,7 +140,7 @@ const handleCurrentChange = (value) => {
         <el-table-column prop="expirationAt" label="Expiration Date" min-width="110"></el-table-column>
         <el-table-column prop="detailType" label="Actions" min-width="100">
           <template slot-scope="scope">
-            <div v-if="scope.row.detailType !== 2 && memberRole?.indexOf('1') !== -1">
+            <div v-if="scope.row.detailType === 1 && memberRole?.indexOf('1') !== -1">
               <a :href="`${ProURL}/store/purchase-pdftech-editor/${scope.row.id}`" target="_blank" class="w-68px inline-block text-center h-20px mt-8px rounded-4px border-1px border-[#1460F3] text-[#1460F3] leading-18px hover:bg-[#1460F333]">
                 Purchase
               </a>

+ 1 - 1
src/views/Support.vue

@@ -46,7 +46,7 @@ const updataUserInfo = () => {
         <div class="ml-32px mr-24px"><Email /></div>
         <div>
             <p class="text-16px font-bold leading-20px">Email</p>
-            <a href="mailto:support@pdfreaderpro.com?subject=Cooperate with PDF Reader Pro " class="flex leading-20px mt-12px text-[#1460F3] underline">support@pdfreaderpro.com</a>
+            <a href="mailto:support@pdfreaderpro.com?subject=Cooperate with PDF Technologies, Inc. " class="flex leading-20px mt-12px text-[#1460F3] underline">support@pdfreaderpro.com</a>
         </div>
       </div>
       <div class="w-[49%] mt-24px h-116px bg-[#fff] rounded-8px flex items-center">

+ 118 - 96
src/views/TeamManagement/ManageMember.vue

@@ -26,7 +26,7 @@ const product = ref('')
 const size = ref(5)
 const memberId = ref('')
 const tag = ref('invited')
-const tagSecond  = ref('all')
+const tagSecond = ref('all')
 const userList = ref([])
 const directoryEmail = ref('')
 const directoryMemberId = ref('')
@@ -86,14 +86,14 @@ const getDirectoryUser = async () => {
 // 获取删除目录用户列表
 const getDeleteDirectoryUser = async () => {
   const { data } = await get('/pdf-tech/vppRTeamMemberRole/pageForSSOMember?page=' +
-      currentPage.value +
-      '&pageSize=' +
-      size.value +
-      '&status=0' +
-      '&queryString=' +
-      queryString.value +
-      '&domain=' +
-      domain.value)
+    currentPage.value +
+    '&pageSize=' +
+    size.value +
+    '&status=0' +
+    '&queryString=' +
+    queryString.value +
+    '&domain=' +
+    domain.value)
   loading.value = false
   deleteDirectoryData.value = data.result.list
   deleteDirectoryTotal.value = data.result.total
@@ -149,12 +149,18 @@ const handleCurrentChange = (val) => {
 }
 // 获取团队管理列表
 const getTeamList = () => {
-  get('/pdf-tech/vppTeam/getManageTeamList', {
-    teamId: '',
-    keyword: ''
-  }).then((res) => {
+  // get('/pdf-tech/vppTeam/getManageTeamList', {
+  //   teamId: '',
+  //   keyword: ''
+  // }).then((res) => {
+  //   if (res.data.code === 200) {
+  //     teamList.value = res.data.result.list
+  //   }
+  // })
+  //获取团队列表
+  get('/pdf-tech/vppTeam/listWithAdmin').then((res) => {
     if (res.data.code === 200) {
-      teamList.value = res.data.result.list
+      teamList.value = res.data.result
     }
   })
 }
@@ -169,34 +175,34 @@ const getManageMemberList = () => {
   // 初始化表格
   tableData.value = []
   get('/pdf-tech/vppRTeamMemberRole/pageForMember?page=' +
-      currentPage.value +
-      '&pageSize=' +
-      size.value +
-      '&teamId=' +
-      teamId.value +
-      '&status=' +
-      status.value +
-      '&queryString=' +
-      queryString.value +
-      '&productCode=' +
-      product.value).then((res) => {
-    let data = res.data.result.list
-    data = productListNameMapping(data)
-    for (let i = 0; i < data.length; i++) {
-      if (data[i].validFlag === '0') {
-        data[i].validFlag = 'Deleted'
-      } else if (data[i].validFlag === '1') {
-        data[i].validFlag = 'Joined'
-      } else {
-        data[i].validFlag = 'Invited'
+    currentPage.value +
+    '&pageSize=' +
+    size.value +
+    '&teamId=' +
+    teamId.value +
+    '&status=' +
+    status.value +
+    '&queryString=' +
+    queryString.value +
+    '&productCode=' +
+    product.value).then((res) => {
+      let data = res.data.result.list
+      data = productListNameMapping(data)
+      for (let i = 0; i < data.length; i++) {
+        if (data[i].validFlag === '0') {
+          data[i].validFlag = 'Deleted'
+        } else if (data[i].validFlag === '1') {
+          data[i].validFlag = 'Joined'
+        } else {
+          data[i].validFlag = 'Invited'
+        }
+        data[i].productNames = res.data.result.list[i].productNames
+          ?.replace(/,/g, ',|')
+          .split('|')
       }
-      data[i].productNames = res.data.result.list[i].productNames
-        ?.replace(/,/g, ',|')
-        .split('|')
-    }
-    tableData.value = data
-    total.value = res.data.result.total
-  })
+      tableData.value = data
+      total.value = res.data.result.total
+    })
 }
 // 团队成员列表导出
 const exportManageMemberList = () => {
@@ -447,8 +453,7 @@ const handleDownload = () => {
   <div>
     <h1 class="mb-16px">Manage Member</h1>
     <div class="flex">
-      <span
-        class="flex justify-center bg-[#C6C9CC] rounded-t-8px items-center font-bold w-200px h-32px cursor-pointer"
+      <span class="flex justify-center bg-[#C6C9CC] rounded-t-8px items-center font-bold w-200px h-32px cursor-pointer"
         @click="changeTag('directory')" :class="tag === 'directory' && 'active'">Directory Users</span>
       <span
         class="flex justify-center bg-[#C6C9CC] rounded-t-8px items-center font-bold w-200px h-32px cursor-pointer ml-8px"
@@ -461,10 +466,13 @@ const handleDownload = () => {
           <a class="w-28px h-28px border-1 border-[#D9D9D9] rounded-4px bg-[#F9FAFB] flex justify-center items-center mr-12px cursor-pointer relative"
             @click="exportManageMemberList">
             <img src="@/assets/images/download.svg" alt="download" />
-            <div class="hidden absolute w-110px bg-[#373A47] rounded-4px px-8px py-2px text-14px leading-20px text-white top-33px z-3 left-[-40px]">Download data</div>
+            <div
+              class="hidden absolute w-110px bg-[#373A47] rounded-4px px-8px py-2px text-14px leading-20px text-white top-33px z-3 left-[-40px]">
+              Download data</div>
           </a>
           <router-link :to="{ name: 'AddManageMember' }">
-            <div class="h-28px px-10px py-4px bg-[#1460F3] rounded-4px text-14px leading-20px font-400 text-[#fff] cursor-pointer hover:opacity-80">
+            <div
+              class="h-28px px-10px py-4px bg-[#1460F3] rounded-4px text-14px leading-20px font-400 text-[#fff] cursor-pointer hover:opacity-80">
               Add Member
             </div>
           </router-link>
@@ -478,19 +486,22 @@ const handleDownload = () => {
               {{ item.name }}
             </option>
           </select>
-          <select name="status" class="min-w-100px mr-16px" v-model="status" :class="{ '!text-[#232A40]': status !== '' }">
+          <select name="status" class="min-w-100px mr-16px" v-model="status"
+            :class="{ '!text-[#232A40]': status !== '' }">
             <option value="" selected>Status</option>
             <option value="1">Joined</option>
             <option value="0">Deleted</option>
           </select>
-          <select name="Assigned Product" class="min-w-100px mr-16px" v-model="product" :class="{ '!text-[#232A40]': product !== '' }">
+          <select name="Assigned Product" class="min-w-100px mr-16px" v-model="product"
+            :class="{ '!text-[#232A40]': product !== '' }">
             <option value="" selected>Assigned Product</option>
             <option v-for="item in productList" :key="item.value" :value="item.code">
               {{ item.name }}
             </option>
           </select>
           <div class="relative mr-16px">
-            <el-input v-model="queryString" size="mini" clearable class="!w-316px input-with-select" placeholder="Search Member Name/Email">
+            <el-input v-model="queryString" size="mini" clearable class="!w-316px input-with-select"
+              placeholder="Search Member Name/Email">
             </el-input>
             <button class="absolute top-8px right-8px" @click="searchInfo()">
               <Search />
@@ -513,19 +524,22 @@ const handleDownload = () => {
           <el-table-column label="Assigned Product" min-width="150px">
             <template slot-scope="scope">
               <span v-for="(item, index) in scope.row.productNames" :key="index">
-                {{ item }}<br/>
+                {{ item }}<br />
               </span>
             </template>
           </el-table-column>
           <el-table-column label="Action" min-width="100px">
             <template slot-scope="scope">
               <div>
-                <router-link :to="{ name: 'EditManageMember', query: { MemberName: scope.row.fullName, Email: scope.row.email, Team: scope.row.teamName, Id: scope.row.id } }">
-                  <div class="w-61px h-20px border-1 border-[#1460F3] rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer mb-8px">
+                <router-link
+                  :to="{ name: 'EditManageMember', query: { MemberName: scope.row.fullName, Email: scope.row.email, Team: scope.row.teamName, Id: scope.row.id } }">
+                  <div
+                    class="w-61px h-20px border-1 border-[#1460F3] rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer mb-8px">
                     Edit
                   </div>
                 </router-link>
-                <div class="w-61px h-20px border-1 border-[#1460F3] rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer"
+                <div
+                  class="w-61px h-20px border-1 border-[#1460F3] rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer"
                   @click="handleClick(scope.row)">
                   Delete
                 </div>
@@ -542,26 +556,33 @@ const handleDownload = () => {
     <div v-show="tag === 'directory'" class="block directory p-40px">
       <h2 class="!text-28px !leading-40px mb-12px">Content</h2>
       <div class="border-b-2px border-[#F1F1F2] text-[#404653] flex mb-12px">
-        <div class="py-12px tet-14px leading-20px mb-[-2px] cursor-pointer mr-20px" :class="tagSecond === 'all' && 'border-b-2px border-[#1460F3] text-[#065CBC]'" @click="changeSecondTag('all')">All Users</div>
-        <div class="py-12px tet-14px leading-20px mb-[-2px] cursor-pointer" :class="tagSecond === 'delete' && 'border-b-2px border-[#1460F3] text-[#065CBC]'" @click="changeSecondTag('delete')">Removed Users</div>
+        <div class="py-12px tet-14px leading-20px mb-[-2px] cursor-pointer mr-20px"
+          :class="tagSecond === 'all' && 'border-b-2px border-[#1460F3] text-[#065CBC]'" @click="changeSecondTag('all')">
+          All Users</div>
+        <div class="py-12px tet-14px leading-20px mb-[-2px] cursor-pointer"
+          :class="tagSecond === 'delete' && 'border-b-2px border-[#1460F3] text-[#065CBC]'"
+          @click="changeSecondTag('delete')">Removed Users</div>
       </div>
       <!-- 筛选模块 -->
       <div class="flex justify-between mb-12px pt-24px">
         <div class="flex">
           <div class="relative mr-16px">
-            <el-input v-model="queryString" size="mini" clearable class="!w-316px input-with-select" placeholder="Search user name or email">
+            <el-input v-model="queryString" size="mini" clearable class="!w-316px input-with-select"
+              placeholder="Search user name or email">
             </el-input>
             <button class="absolute top-8px right-8px" @click="searchInfo()">
               <Search />
             </button>
           </div>
-          <select name="Status" v-show="tagSecond === 'all'" class="min-w-50px mr-16px" v-model="directoryStatus" :class="{ '!text-[#232A40]': directoryStatus !== '' }">
+          <select name="Status" v-show="tagSecond === 'all'" class="min-w-50px mr-16px" v-model="directoryStatus"
+            :class="{ '!text-[#232A40]': directoryStatus !== '' }">
             <option value="" selected>Status</option>
             <option value="">All</option>
             <option value="1">Available access</option>
             <option value="3">Revoked</option>
           </select>
-          <select name="Domains" v-show="tagSecond === 'all'" class="min-w-100px mr-16px" v-model="domain" :class="{ '!text-[#232A40]': domain !== '' }">
+          <select name="Domains" v-show="tagSecond === 'all'" class="min-w-100px mr-16px" v-model="domain"
+            :class="{ '!text-[#232A40]': domain !== '' }">
             <option value="" selected>Domains</option>
             <option v-for="(item, index) in domainList" :key="index" :value="item.domain">{{ item.domain }}</option>
           </select>
@@ -574,7 +595,9 @@ const handleDownload = () => {
           <a class="w-28px h-28px border-1 border-[#D9D9D9] rounded-4px bg-[#F9FAFB] flex justify-center items-center mx-12px cursor-pointer relative"
             @click="handleDownload">
             <img src="@/assets/images/download.svg" alt="download" />
-            <div class="hidden absolute bg-[#373A47] rounded-4px px-8px py-2px text-14px leading-20px text-white top-33px z-3 left-[-16px]">Export</div>
+            <div
+              class="hidden absolute bg-[#373A47] rounded-4px px-8px py-2px text-14px leading-20px text-white top-33px z-3 left-[-16px]">
+              Export</div>
           </a>
           <button v-show="tagSecond === 'all'" :disabled="!userList.length" @click="handleClickSecond"
             class="h-28px px-10px py-4px bg-[#1460F3] rounded-4px text-14px leading-20px font-400 text-[#fff] cursor-pointer hover:opacity-80"
@@ -585,21 +608,17 @@ const handleDownload = () => {
         </div>
       </div>
       <!-- 所有用户 -->
-      <el-table
-        v-show="tagSecond === 'all'"
-        ref="multipleTable"
-        v-loading="loading"
-        :data="directoryData"
-        tooltip-effect="dark"
-        @selection-change="handleSelectionChange"
-      >
+      <el-table v-show="tagSecond === 'all'" ref="multipleTable" v-loading="loading" :data="directoryData"
+        tooltip-effect="dark" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55"></el-table-column>
         <el-table-column prop="fullName" label="User Name" min-width="100px"></el-table-column>
         <el-table-column prop="email" label="Email" min-width="100px"></el-table-column>
         <el-table-column label="Status" min-width="100px">
           <template slot-scope="scope">
-            <div class="text-14px leading-20px flex items-center" :class="Number(scope.row.validFlag) === 1 ? 'text-[#41CC00]' : 'text-[#808185]'">
-              <span class="flex w-6px h-6px rounded-[50%] mr-8px" :class="Number(scope.row.validFlag) === 1 ? 'bg-[#41CC00]' : 'bg-[#808185]'"></span>
+            <div class="text-14px leading-20px flex items-center"
+              :class="Number(scope.row.validFlag) === 1 ? 'text-[#41CC00]' : 'text-[#808185]'">
+              <span class="flex w-6px h-6px rounded-[50%] mr-8px"
+                :class="Number(scope.row.validFlag) === 1 ? 'bg-[#41CC00]' : 'bg-[#808185]'"></span>
               <template v-if="Number(scope.row.validFlag) === 1">
                 Available access
               </template>
@@ -619,11 +638,13 @@ const handleDownload = () => {
             <div class="flex">
               <div class="h-20px rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer"
                 @click="directoryEmail = scope.row.email, directoryMemberId = scope.row.id">
-                <span @click="unRevoke" v-if="Number(scope.row.validFlag) === 3" class="hover:text-[#0C3A92]">Access</span>
+                <span @click="unRevoke" v-if="Number(scope.row.validFlag) === 3"
+                  class="hover:text-[#0C3A92]">Access</span>
                 <span @click="revokeDialogVisible = true" v-else class="hover:text-[#0C3A92]">Revoke</span>
               </div>
               <span class="text-[#D9D9D9]">丨</span>
-              <div class="h-20px rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer hover:text-[#0C3A92]"
+              <div
+                class="h-20px rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer hover:text-[#0C3A92]"
                 @click="openDeleteDialog(), directoryEmail = scope.row.email, directoryMemberId = scope.row.id, handleMultiple = false">
                 Remove
               </div>
@@ -637,13 +658,17 @@ const handleDownload = () => {
       </el-table>
       <!-- 吊销对话框 -->
       <el-dialog :visible.sync="revokeDialogVisible" width="556px" top="30vh" center :show-close="false">
-        <h5 class="w-full text-24px leading-32px font-bold text-[#232A40] flex justify-between items-center"><p>Revoke</p><CloseBig class="cursor-pointer" @click.native="revokeDialogVisible = false" /></h5>
+        <h5 class="w-full text-24px leading-32px font-bold text-[#232A40] flex justify-between items-center">
+          <p>Revoke</p>
+          <CloseBig class="cursor-pointer" @click.native="revokeDialogVisible = false" />
+        </h5>
         <p class="text-left mt-12px text-14px leading-20px text-[#808185]">
           You are about to revoke user access for:
-          <ul class="py-12px">
-            <li class="flex items-center pl-12px"><span class="flex w-4px h-4px rounded-[50%] bg-[#808185] mr-8px"></span>{{ directoryEmail }}</li>
-          </ul>
-          This user won't be able to use your site. You can give them access again at any time.
+        <ul class="py-12px">
+          <li class="flex items-center pl-12px"><span class="flex w-4px h-4px rounded-[50%] bg-[#808185] mr-8px"></span>{{
+            directoryEmail }}</li>
+        </ul>
+        This user won't be able to use your site. You can give them access again at any time.
         </p>
         <div slot="footer" class="dialog-footer flex justify-end">
           <el-button @click="revokeDialogVisible = false" class="!font-700">Cancel</el-button>
@@ -652,9 +677,13 @@ const handleDownload = () => {
       </el-dialog>
       <!-- 删除目录用户对话框 -->
       <el-dialog :visible.sync="deleteDialogVisible" width="556px" top="30vh" center :show-close="false">
-        <h5 class="w-full text-24px leading-32px font-bold text-[#232A40] flex justify-between items-center"><p>Remove</p><CloseBig class="cursor-pointer" @click.native="deleteDialogVisible = false" /></h5>
+        <h5 class="w-full text-24px leading-32px font-bold text-[#232A40] flex justify-between items-center">
+          <p>Remove</p>
+          <CloseBig class="cursor-pointer" @click.native="deleteDialogVisible = false" />
+        </h5>
         <p class="text-left mt-12px text-14px leading-20px text-[#808185]">
-          The licenses of these users will be removed. They will no longer have access to this site and won't be able to collaborate with your team. You can restore these users in removed users list.
+          The licenses of these users will be removed. They will no longer have access to this site and won't be able to
+          collaborate with your team. You can restore these users in removed users list.
         </p>
         <div slot="footer" class="dialog-footer flex justify-end">
           <el-button @click="deleteDialogVisible = false" class="!font-700">Cancel</el-button>
@@ -662,21 +691,16 @@ const handleDownload = () => {
         </div>
       </el-dialog>
       <!-- 已删除用户 -->
-      <el-table
-        v-show="tagSecond === 'delete'"
-        ref="multipleTable"
-        v-loading="loading"
-        :data="deleteDirectoryData"
-        tooltip-effect="dark"
-        @selection-change="handleSelectionChange"
-      >
+      <el-table v-show="tagSecond === 'delete'" ref="multipleTable" v-loading="loading" :data="deleteDirectoryData"
+        tooltip-effect="dark" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55"> </el-table-column>
         <el-table-column prop="fullName" label="User Name" min-width="100px"></el-table-column>
         <el-table-column prop="email" label="Email" min-width="275px"></el-table-column>
         <el-table-column prop="action" label="Actions" min-width="100px">
           <template slot-scope="scope">
             <div class="flex">
-              <div class="h-20px rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer hover:text-[#0C3A92]"
+              <div
+                class="h-20px rounded-4px text-center text-14px leading-20px text-[#1460F3] cursor-pointer hover:text-[#0C3A92]"
                 @click="restore(scope.row), handleMultiple = false">
                 Restore
               </div>
@@ -701,17 +725,10 @@ const handleDownload = () => {
         <el-button type="primary" @click="deleteMember()">Delete</el-button>
       </span>
     </el-dialog>
-    <el-pagination
-      @size-change="handleSizeChange"
-      @current-change="handleCurrentChange"
-      :current-page.sync="currentPage"
-      :page-sizes="[5, 10, 20]"
-      :page-size="1"
-      :background="true"
-      layout="prev, pager, next, sizes, jumper"
+    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="currentPage"
+      :page-sizes="[5, 10, 20]" :page-size="1" :background="true" layout="prev, pager, next, sizes, jumper"
       :total="tag === 'invited' ? total : tagSecond === 'all' ? directoryTotal : deleteDirectoryTotal"
-      class="px-24px !rounded-0 rounded-b-8px flex justify-end"
-    >
+      class="px-24px !rounded-0 rounded-b-8px flex justify-end">
     </el-pagination>
   </div>
 </template>
@@ -720,15 +737,18 @@ const handleDownload = () => {
 .block {
   border-top-left-radius: 0px;
 }
+
 .active {
   color: #1460f3;
   background-color: #fff;
 }
+
 .relative:hover {
   .hidden {
     display: block;
   }
 }
+
 .el-table::v-deep thead {
   color: #000 !important;
 }
@@ -761,9 +781,11 @@ const handleDownload = () => {
   font-weight: 400;
   color: #808185;
 }
+
 .el-table::v-deep .cell {
   word-break: break-word;
 }
+
 .dialog-footer {
   ::v-deep .el-button {
     height: 40px;

+ 1 - 1
utils/country.js

@@ -246,5 +246,5 @@ export function country() {
             { name: "Zambia", code: "ZM" },
             { name: "Zimbabwe", code: "ZW" }
         ]
-        return country
+    return country
 } 

+ 7 - 6
utils/request.js

@@ -5,7 +5,7 @@ import { Message } from 'element-ui'
 
 //关闭断网时toast
 let msg = {
-  close(){}
+  close() { }
 }
 
 // 创建请求实例
@@ -28,7 +28,7 @@ instance.interceptors.request.use(
      * }
      */
     msg.close()
-    if(!(config.url.indexOf("pdf-tech/vppMember/create") !== -1 || config.url.indexOf("/pdf-tech/login") !== -1)){
+    if (!(config.url.indexOf("pdf-tech/vppMember/create") !== -1 || config.url.indexOf("/pdf-tech/login") !== -1)) {
       const token = cookies.get('accessToken')
       config.headers.Authorization = 'Bearer ' + token
     }
@@ -46,10 +46,11 @@ instance.interceptors.response.use(
      * 根据你的项目实际情况来对 response 和 error 做处理
      * 这里对 response 和 error 不做任何处理,直接返回
      */
-    if(response.data.msg === "当前用户不具备操作权限" || response.data.msg === "Insufficient permissions"){
+    if (response.data.msg === "当前用户不具备操作权限" || response.data.msg === "Insufficient permissions") {
       cookies.remove('accessToken')
       userStore().clearUserInfo()
       window.location.href = '/non-admin-user'
+      // window.location.href = '/product?type=isNoAdmin'
     }
     return response;
   },
@@ -58,7 +59,7 @@ instance.interceptors.response.use(
     msg.close()
     if (response && response.data) {
       //没有登录或者登录过期跳到登录页面
-      if(response.data.code === 310 && response.data.msg === "无效的token或者token已过期"){
+      if (response.data.code === 310 && response.data.msg === "无效的token或者token已过期") {
         const token = cookies.get('accessToken')
         if (token) {
           cookies.set('accessToken', 'expired')
@@ -70,7 +71,7 @@ instance.interceptors.response.use(
     }
     const { message } = error;
     //断网情况下,弹出框
-    if(error.request){
+    if (error.request) {
       msg = Message({
         duration: 5000,
         showClose: true,
@@ -115,7 +116,7 @@ export function downLoad(url, params = {}) {
     method: 'get',
     url,
     params,
-    responseType:"arraybuffer"
+    responseType: "arraybuffer"
   });
 }
 

+ 1 - 1
vite.config.js

@@ -35,7 +35,7 @@ export default defineConfig({
     // 是否开启 https
     https: false,
     // 端口号
-    port: 3000,
+    port: 3042,
     // 监听所有地址
     host: "0.0.0.0",
     // 服务启动时是否自动打开浏览器