瀏覽代碼

feat:后台加购买

liyangbin 8 月之前
父節點
當前提交
efbbeecfd0

+ 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.2.0
+VITE_EDITOR = PDFTech_Editor_
+VITE_MAC_EDITOR_VERSION = v1.0.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.0
+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.2.0
+VITE_EDITOR = PDFTech_Editor_
+VITE_MAC_EDITOR_VERSION = v1.0.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.0
+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.2.0
+VITE_EDITOR = PDFTech_Editor_
+VITE_MAC_EDITOR_VERSION = v1.0.1
+VITE_WINDOWS_EDITOR_VERSION = v1.1.0
+VITE_KEY_DATA = AZlF7BTjlelPeQJ7sNlvhHpsUdYc12_C3EsLG9Nkr0hn6gtco_BrpUNLNUoAOkGF0aNcPNphEKbgTi8b
+VITE_VENDOR_DATA = 134050

+ 19 - 14
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>PDF Tech</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.js"></script>
-  </body>
-</html>
+
+<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>PDF Tech</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.js"></script>
+</body>
+
+</html>

二進制
src/assets/images/cross.png


二進制
src/assets/images/logo.png


文件差異過大導致無法顯示
+ 32 - 0
src/assets/images/store/alipay.svg


文件差異過大導致無法顯示
+ 18 - 0
src/assets/images/store/card.svg


二進制
src/assets/images/store/ic_close.png


二進制
src/assets/images/store/loading.gif


二進制
src/assets/images/store/order_box.png


文件差異過大導致無法顯示
+ 1 - 0
src/assets/images/store/paypal_icon.svg


文件差異過大導致無法顯示
+ 25 - 0
src/assets/images/store/wechatpay.svg


文件差異過大導致無法顯示
+ 7 - 0
src/components/icon/bulb.vue


文件差異過大導致無法顯示
+ 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>

文件差異過大導致無法顯示
+ 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>

+ 71 - 45
src/components/sideMenu.vue

@@ -8,12 +8,13 @@ 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 { userStore } from '@/store/userInfo'
 
 export default {
-  name:'sideMenu',
-  components:{
+  name: 'sideMenu',
+  components: {
     Dashboard,
     Product,
     Team,
@@ -21,21 +22,26 @@ export default {
     Device,
     Settings,
     Support,
+    Order,
     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 +49,7 @@ export default {
       this.$cookies.remove('accessToken')
       userStore().clearUserInfo()
     },
-    updateUserInfo(){
+    updateUserInfo() {
       get(
         '/pdf-tech/vppMember/getMemberInfo'
       ).then((res) => {
@@ -53,43 +59,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 +109,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 +119,72 @@ 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">
             <Product />
-            <span>Product Management</span>
+            <span>Products</span>
           </el-menu-item>
-          <el-submenu index="3">
+          <el-menu-item index="/order" :disabled="isNoAdmin">
+            <Order />
+            <span>Order</span>
+          </el-menu-item>
+          <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-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>
         </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 +192,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 +216,7 @@ export default {
       height: 56px;
       border-radius: 50%;
       background-color: #8D9CB9;
+
       p {
         font-weight: 700;
         font-size: 30px;
@@ -202,23 +224,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;

+ 34 - 1
src/router/index.js

@@ -75,6 +75,30 @@ const router = new VueRouter({
         title: 'Forget Password | PDF Tech Account'
       }
     },
+    {
+      path: "/product/custom",
+      name: "productCustom",
+      component: () => import("../views/Product/Custom.vue"),
+      meta: {
+        title: 'Product | PDF Tech Console'
+      }
+    },
+    {
+      path: "/product/pay",
+      name: "productPay",
+      component: () => import("../views/Product/Pay.vue"),
+      meta: {
+        title: 'Product | PDF Tech Console'
+      }
+    },
+    {
+      path: "/product/:id",
+      name: "productCode",
+      component: () => import("../views/Product/ID.vue"),
+      meta: {
+        title: 'Product | PDF Tech 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 | PDF Tech Console'
+          }
+        },
+        {
+          path: "/order",
           name: "productManagement",
           component: () => import("../views/ProductManagement.vue"),
           meta: {
-            title: 'Product | PDF Tech Console'
+            title: 'Order | PDF Tech Console'
           }
         },
         {
@@ -277,6 +309,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: ''
                 }
             }
         },

+ 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')
         }
       })
     },

+ 879 - 0
src/views/Product/Custom.vue

@@ -0,0 +1,879 @@
+
+<script setup>
+import { onMounted, ref, getCurrentInstance, computed, onUnmounted, nextTick } from 'vue'
+import { country } from '../../../utils/country.js'
+import Arrow from '@/components/icon/left_arrow.vue'
+import Close from '@/components/icon/close.vue'
+import Info from '@/components/icon/info.vue'
+import { get, postWithHeader } from '../../../utils/request'
+const { proxy } = getCurrentInstance()
+
+const CountryOption = country()
+const baseUrl = import.meta.env.VITE_STORE_DOMIN
+const proPlatform = ref('Windows')
+const proProduct = ref('Permanent License')
+
+const codeMap = [
+  {
+    title: "PDFTech Editor (Enterprise)",
+    desc: "Cost-Effective PDF Solution Tailored to Your Business",
+    code: "com.brother.pdftecheditor"
+  },
+  {
+    title: "PDF Reader Pro Cross-Platform",
+    desc: "Mac + Windows",
+    code: "com.brother.pdfreaderpro.cross.platform.product_3"
+  },
+  {
+    title: "PDF Reader Pro Permanent for Mac",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.mac.product_3"
+  },
+  {
+    title: "PDF Reader Pro Premium for Mac",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.mac.product_1"
+  },
+  {
+    title: "PDF Reader Pro Permanent for Windows",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.windows.product_3"
+  },
+  {
+    title: "PDF Reader Pro Premium for Windows",
+    desc: "All-in-One & Affordable PDF Solution",
+    code: "com.brother.pdfreaderpro.windows.product_1"
+  }
+]
+const combinedList = ref([])
+const computeList = (productList) => {
+  combinedList.value = productList.map(product => {
+    const match = codeMap.find(item => item.code === product.code)
+    if (match) {
+      return {
+        ...match,
+        number: product.number,
+        id: product.id,
+        product_id: product.product_id,
+      }
+    }
+    return null
+  }).filter(item => item !== 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 = '/product?type=isNoAdmin'
+      }
+      getShopping()
+    }
+  })
+}
+
+// 获取购物车
+const commodityInfo = ref({})
+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%'
+  }
+})
+let allShoppingNum = computed(() => {
+  let addNum = 0
+  combinedList.value.forEach(item => {
+    addNum = addNum + item.number
+  })
+  return addNum
+})
+const getShopping = () => {
+  get(
+    baseUrl + '/api/shopping-cart?vpp_member_id=' + useInfo.value.id
+  ).then((res) => {
+    if (res.data.code === 200) {
+      commodityInfo.value = res.data.data
+      allNum.value = commodityInfo.value.purchase_quantity
+      computeList(commodityInfo.value.products)
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+// 加入购物车
+const addShopping = (code) => {
+  let productCode = ""
+  switch (code) {
+    case 'com.brother.pdfreaderpro.cross.platform.product_3':
+      productCode = 'com.brother.pdfreaderpro.cross.platform.product_3'
+      break
+    case 'com.brother.pdftecheditor':
+      productCode = 'com.brother.pdftecheditor'
+      break
+    case 'pro':
+      if (proPlatform.value === 'Windows') {
+        if (proProduct.value === 'Permanent License') {
+          productCode = 'com.brother.pdfreaderpro.windows.product_3'
+        } else {
+          productCode = 'com.brother.pdfreaderpro.windows.product_1'
+        }
+      } else {
+        if (proProduct.value === 'Permanent License') {
+          productCode = 'com.brother.pdfreaderpro.mac.product_3'
+        } else {
+          productCode = 'com.brother.pdfreaderpro.mac.product_1'
+        }
+      }
+      break
+  }
+  const data = {
+    "vpp_member_id": useInfo.value.id,
+    "product_code": productCode,
+    "number": 1
+  }
+  postWithHeader(baseUrl + '/api/shopping-cart/add', data).then((res) => {
+    if (res.data.code === 200) {
+      commodityInfo.value = res.data.data
+      allNum.value = commodityInfo.value.purchase_quantity
+      computeList(commodityInfo.value.products)
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+
+// 更新购物车
+const changeNum = (item) => {
+  const data = {
+    "vpp_member_id": useInfo.value.id,
+    "id": item.id,
+    "number": item.number
+  }
+  postWithHeader(baseUrl + '/api/shopping-cart/update', data).then((res) => {
+    if (res.data.code === 200) {
+      commodityInfo.value = res.data.data
+      allNum.value = commodityInfo.value.purchase_quantity
+      computeList(commodityInfo.value.products)
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+
+// 删除购物车商品
+const deleteShopping = (item) => {
+  get(
+    baseUrl + '/api/shopping-cart/del?vpp_member_id=' + useInfo.value.id + '&id=' + item.id
+  ).then((res) => {
+    if (res.data.code === 200) {
+      commodityInfo.value = res.data.data
+      allNum.value = commodityInfo.value.purchase_quantity
+      computeList(commodityInfo.value.products)
+    } else {
+      window.location.href = '/product/pay'
+    }
+  })
+}
+const payMethod = ref('paypal')
+const countryCode = ref('US')
+const postCode = ref('')
+const paypalLoading = ref(true)
+const paddleShow = ref(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: 2,
+    customer_country: countryCode.value,
+    customer_postcode: postCode.value,
+    payment: payment
+  }
+  return { data }
+}
+// 绑定paypal按钮
+const paymentID = ref(null)
+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 Products</div>
+    <div @click="goBack"
+      class="mt-16px flex items-center text-[#1460F3] cursor-pointer pb-16px border-b-1px border-[#0000001A]">
+      <Arrow class="mr-6px" />
+      Back
+    </div>
+    <div class="mt-32px flex justify-between">
+      <div class="w-[55%] max-w-840px">
+        <div class="text-[16px] leading-20px text-[#232A40] font-700">Your Current Products</div>
+        <div class="text-[16px] leading-20px text-[#232A40] mt-8px">Add more licenses for products that you already own.
+        </div>
+        <div
+          class="mt-24px rounded-8px p-28px bg-[#FFFFFF] border-1px border-[#D9D9D9] flex justify-between items-center">
+          <div class="flex items-center">
+            <img src="@/assets/images/cross.png" alt="logo" class="h-48px">
+            <div class="ml-20px">
+              <div class="text-[16px] leading-20px text-[#232A40] font-700">PDF Reader Pro Cross-Platform</div>
+              <div class="text-[14px] leading-20px text-[#232A40] mt-4px">Mac + Windows</div>
+            </div>
+          </div>
+          <div
+            class="rounded-8px cursor-pointer w-96px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80"
+            @click="addShopping('com.brother.pdfreaderpro.cross.platform.product_3')">
+            Add</div>
+        </div>
+        <div class="mt-32px text-[16px] leading-20px text-[#232A40] font-700">More Products and Services</div>
+        <div class="text-[16px] leading-20px text-[#232A40] mt-8px">See other ways to enhance your team’s productivity
+        </div>
+        <div
+          class="mt-24px rounded-8px p-28px bg-[#FFFFFF] border-1px border-[#D9D9D9] flex justify-between items-center">
+          <div class="flex items-center">
+            <img src="@/assets/images/logo.png" alt="logo" class="h-48px">
+            <div class="ml-20px">
+              <div class="text-[16px] leading-20px text-[#232A40] font-700">PDFTech Editor (Enterprise)</div>
+              <div class="text-[14px] leading-20px text-[#232A40] mt-4px">Cost-Effective PDF Solution Tailored to Your
+                Business</div>
+            </div>
+          </div>
+          <div
+            class="rounded-8px cursor-pointer w-96px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80"
+            @click="addShopping('com.brother.pdftecheditor')">
+            Add</div>
+        </div>
+        <div class="mt-24px rounded-8px p-28px bg-[#FFFFFF] border-1px border-[#D9D9D9]">
+          <div class=" flex justify-between items-center border-b-1px border-[#0000001A] pb-16px">
+            <div class="flex items-center">
+              <img src="@/assets/images/logo.png" alt="logo" class="h-48px">
+              <div class="ml-20px">
+                <div class="text-[16px] leading-20px text-[#232A40] font-700">PDF Reader Pro</div>
+                <div class="text-[14px] leading-20px text-[#232A40] mt-4px">All-in-One & Affordable PDF Solution</div>
+              </div>
+            </div>
+            <div
+              class="rounded-8px cursor-pointer w-96px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80"
+              @click="addShopping('pro')">
+              Add</div>
+          </div>
+          <div class="flex mt-16px">
+            <el-select v-model="proPlatform">
+              <el-option label="Windows" value="Windows"></el-option>
+              <el-option label="Mac" value="Mac"></el-option>
+            </el-select>
+            <el-select v-model="proProduct">
+              <el-option label="Permanent License" value="Permanent License"></el-option>
+              <el-option label="Premium License" value="Premium License"></el-option>
+            </el-select>
+          </div>
+        </div>
+      </div>
+      <div class="w-[40%] max-w-500px">
+        <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 justify-between">
+            <div>
+              <div class="font-700 text-14px leading-20px text-[#232A40]">
+                Total Licenses
+              </div>
+              <div v-show="commodityInfo.next_number"
+                class="recommend-icon text-14px leading-20px text-[#808185] cursor-pointer">
+                Purchase <span class="text-[#232A40]">{{ commodityInfo.next_number }} Licenses</span> to upgrade to
+                <span class="text-[#232A40]">{{ commodityInfo.next_discount }} OFF</span>
+                <Info class="ml-8px inline-block mb-4px" />
+                <div class="tip">
+                  Your company has currently purchased {{ commodityInfo.purchase_quantity }} licenses and enjoys a {{
+                    useTeamDiscount }} discount.
+                  <div>All discount gradients are as follows:</div>
+                  <div>Cumulative purchase 2 licenses: 15% OFF</div>
+                  <div>Cumulative purchase 5 licenses: 25% OFF</div>
+                  <div>Cumulative purchase 10 licenses: 30% OFF</div>
+                  <div>Cumulative purchase 30 licenses: 35% OFF</div>
+                  <div>Cumulative purchase 100 licenses: 45% OFF</div>
+                </div>
+              </div>
+            </div>
+            <div class="font-700 text-16px leading-20px text-[#232A40]">
+              {{ allShoppingNum }}
+            </div>
+          </div>
+          <div class="flex justify-between mt-12px">
+            <div class="text-14px font-700 leading-20px text-[#232A40]">Add-on 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="combinedList.length === 0">
+              <div v-show="payMethod === 'paypal' || payMethod === 'paddle'"
+                class="text-28px leading-40px font-700 text-[#232A40]">$0</div>
+              <div v-show="payMethod === 'wxpay' || payMethod === 'alipay'"
+                class="text-28px leading-40px font-700 text-[#232A40]">¥0</div>
+            </template>
+            <template v-else-if="commodityInfo.discount_price">
+              <div v-show="payMethod === 'paypal' || payMethod === 'paddle'"
+                class="text-28px leading-40px font-700 text-[#232A40]">${{ commodityInfo.discount_price }}</div>
+              <div v-show="payMethod === 'wxpay' || payMethod === 'alipay'"
+                class="text-28px leading-40px font-700 text-[#232A40]">¥{{ 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-16px 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 class="card w-500px rounded-8px bg-[#fff] mt-12px">
+          <div v-for="item in combinedList" :key="item.id" class="list p-40px relative">
+            <Close class="absolute right-14px top-14px w-14px h-14px cursor-pointer z-2"
+              @click.native="deleteShopping(item)" />
+            <div class="flex">
+              <img v-if="item.code === 'com.brother.pdfreaderpro.cross.platform.product_3'"
+                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]">{{ item.title }}</div>
+                <div class="leading-20px text-14px text-[#505258] mt-4px">{{ item.desc }}</div>
+              </div>
+            </div>
+            <div class="mt-24px text-14px leading-20px font-700 text-[#232A40]">Licenses</div>
+            <div class="mt-16px">
+              <el-input-number v-model="item.number" :precision="0" controls-position="right" :min="1"
+                @change="changeNum(item)"></el-input-number>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+::v-deep .el-select {
+  width: 200px;
+  line-height: 40px;
+  margin-right: 8px;
+
+  .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-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;
+    }
+  }
+}
+
+.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: 10;
+    bottom: -6px;
+    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: '';
+    bottom: -7px;
+    right: 0%;
+    transform: translate(50%, 0);
+    position: absolute;
+    z-index: 2;
+    border: 6px solid transparent;
+    border-bottom-color: #373A47;
+    display: none;
+  }
+
+  &:hover {
+
+    &::before,
+    .tip {
+      display: block;
+    }
+  }
+}
+
+.method-container {
+  font-size: 0;
+  margin-top: 24px;
+
+  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: 16px;
+  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;
+    }
+  }
+}
+
+.list+.list {
+  border-top: 1px solid #0000001A;
+}
+</style>
+

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

@@ -0,0 +1,806 @@
+<script setup>
+import { onMounted, ref, getCurrentInstance, nextTick, onUnmounted, computed } 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'
+import { number } from 'echarts'
+const { proxy } = getCurrentInstance()
+
+const CountryOption = country()
+const baseUrl = import.meta.env.VITE_STORE_DOMIN
+const id = proxy.$route.params.id
+const codeMap = {
+  '0': {
+    title: "PDFTech 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 = '/product?type=isNoAdmin'
+      }
+      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%'
+  }
+})
+
+// 查询支付状态
+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 flex items-center text-[#1460F3] cursor-pointer pb-16px border-b-1px border-[#0000001A]">
+      <Arrow class="mr-6px" />
+      Back
+    </div>
+    <div class="flex justify-between mt-32px">
+      <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]">
+            Licenses
+          </div>
+          <div v-show="commodityInfo.next_number"
+            class="recommend-icon text-14px leading-20px text-[#808185] cursor-pointer">
+            Purchase <span class="text-[#232A40]">{{ commodityInfo.next_number }} Licenses</span> to upgrade to
+            <span class="text-[#232A40]">{{ commodityInfo.next_discount }} OFF</span>
+            <Info class="ml-8px inline-block mb-4px" />
+            <div class="tip">
+              Your company has currently purchased {{ commodityInfo.purchase_quantity }} licenses and enjoys a {{
+                useTeamDiscount }} discount.
+              <div>All discount gradients are as follows:</div>
+              <div>Cumulative purchase 2 licenses: 15% OFF</div>
+              <div>Cumulative purchase 5 licenses: 25% OFF</div>
+              <div>Cumulative purchase 10 licenses: 30% OFF</div>
+              <div>Cumulative purchase 30 licenses: 35% OFF</div>
+              <div>Cumulative purchase 100 licenses: 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]">Add-on 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]">${{ commodityInfo.discount_price }}</div>
+              <div v-show="payMethod === 'wxpay' || payMethod === 'alipay'"
+                class="text-28px leading-40px font-700 text-[#232A40]">¥{{ 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: 10;
+    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>
+

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

@@ -0,0 +1,225 @@
+<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 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'
+      })
+      isOwnTry()
+    }
+  })
+}
+
+const techPlatform = ref('Windows')
+const proPlatform = ref('Windows')
+const proProduct = ref('Permanent License')
+
+// 下载链接
+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'
+
+const editorDownUrl = ref(windowsEditor)
+const proDownUrl = ref(windowsPro)
+watch(techPlatform, (val) => {
+  if (val === 'Windows') {
+    editorDownUrl.value = windowsEditor
+  } else {
+    editorDownUrl.value = macEditor
+  }
+})
+watch(proPlatform, (val) => {
+  if (val === 'Windows') {
+    proDownUrl.value = windowsPro
+  } else {
+    proDownUrl.value = macPro
+  }
+})
+
+// 购买链接
+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"
+    }
+  }
+})
+</script>
+
+<template>
+  <div class="flex flex-col items-center">
+    <div class="w-full">
+      <h1 class="leading-40px">Product</h1>
+      <div class="
+          mt-36px
+          mb-16px
+          leading-20px
+          text-16px
+          font-bold
+          flex
+          justify-between
+        ">
+        <span>All Products</span>
+        <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 with Cart
+        </a>
+      </div>
+      <div class="flex justify-between 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]">PDFTech Editor (Enterprise)</div>
+            <div class="mt-16px text-14px leading-20px text-[#505258]">Cost-Effective PDF Solution Tailored to Your
+              Business</div>
+            <el-select v-model="techPlatform">
+              <el-option label="Windows" value="Windows"></el-option>
+              <el-option label="Mac" value="Mac"></el-option>
+            </el-select>
+          </div>
+          <div class="bg-[#F6F7F9] p-32px rounded-b-8px">
+            <a v-if="memberRole === '1'" 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 && memberRole === '1'" @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>
+            <a :href="editorDownUrl"
+              class="mt-16px rounded-8px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80">
+              Download</a>
+          </div>
+        </div>
+        <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]">PDF Reader Pro</div>
+            <div class="mt-16px text-14px leading-20px text-[#505258]">All-in-One & Affordable PDF Solution</div>
+            <el-select v-model="proPlatform">
+              <el-option label="Windows" value="Windows"></el-option>
+              <el-option label="Mac" value="Mac"></el-option>
+            </el-select>
+            <el-select v-model="proProduct">
+              <el-option label="Permanent License" value="Permanent License"></el-option>
+              <el-option label="Premium License" value="Premium License"></el-option>
+            </el-select>
+          </div>
+          <div class="bg-[#F6F7F9] p-32px rounded-b-8px">
+            <a v-if="memberRole === '1'" :href="proBuyUrl"
+              class="rounded-8px bg-[#1460F3] flex leading-40px text-[#fff] justify-center font-700 text-16px hover:opacity-80">
+              Buy Now</a>
+            <a :href="proDownUrl"
+              class="mt-16px rounded-8px border-1px border-[#1460F3] flex leading-40px text-[#1460F3] justify-center font-700 text-16px hover:opacity-80">
+              Download</a>
+          </div>
+        </div>
+        <div class="card max-w-400px w-[32%]">
+          <div class="bg-[#fff] rounded-t-8px p-32px text-center">
+            <img class="w-208px m-auto" src="@/assets/images/cross.png" alt="logo">
+            <div class="font-bold text-20px leading-24px mt-16px text-[#232A40]">PDF Reader Pro Cross-Platform</div>
+            <div class="mt-16px text-14px leading-20px text-[#505258]">Mac + Windows</div>
+          </div>
+          <div class="bg-[#F6F7F9] p-32px rounded-b-8px">
+            <a v-if="memberRole === '1'" href="/product/1"
+              class="rounded-8px bg-[#1460F3] flex leading-40px text-[#fff] justify-center font-700 text-16px hover:opacity-80">
+              Buy Now</a>
+          </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 7-day free trial of PDFTech 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;
+    }
+  }
+}
+</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>

+ 1 - 1
src/views/ProductManagement.vue

@@ -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

+ 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"
   });
 }