root 3 жил өмнө
parent
commit
bffc0953f1

+ 2 - 0
.env.production

@@ -3,5 +3,7 @@ ENV = 'production'
 
 # base api
 VUE_APP_BASE_API = '/prod-api'
+VUE_APP_CDN_API = '/'
+VUE_APP_PAY_STACK_PUBLIC_KEY = 'pk_live_fae524f9d073d877beeb661fd825a37a9bc91f0a'
 ACCESS_TOKEN_PREFIX = 'Bearer '
 

+ 5 - 2
.env.staging

@@ -4,5 +4,8 @@ NODE_ENV = production
 ENV = 'staging'
 
 # base api
-VUE_APP_BASE_API = '/stage-api'
-
+# VUE_APP_BASE_API = '/stage-api'
+VUE_APP_BASE_API = 'http://16.163.228.151:8026'
+VUE_APP_CDN_API = 'http://16.163.228.151:8029'
+VUE_APP_PAY_STACK_PUBLIC_KEY = 'pk_test_2eed10135c4a958c5073795b22854ded9d1a6c55'
+VUE_APP_ACCESS_TOKEN_PREFIX = 'Bearer '

+ 2 - 0
.gitignore

@@ -21,3 +21,5 @@ selenium-debug.log
 
 package-lock.json
 yarn.lock
+
+.env.development

+ 2 - 1
package.json

@@ -36,9 +36,10 @@
     "script-loader": "0.7.2",
     "sortablejs": "1.8.4",
     "tui-editor": "1.3.3",
-    "vue": "2.6.10",
+    "vue": "^2.6.10",
     "vue-count-to": "1.0.13",
     "vue-i18n": "7.3.2",
+    "vue-paystack": "^2.0.4",
     "vue-router": "3.0.2",
     "vue-splitpane": "1.0.4",
     "vuedraggable": "2.20.0",

+ 62 - 0
src/api/shop.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 商品列表
+export function fetchProductList(query) {
+  return request({
+    url: '/v1/shop/index',
+    method: 'get',
+    params: query
+  })
+}
+
+// 商品详情
+export function fetchProduct(id) {
+  return request({
+    url: '/v1/shop/detail',
+    method: 'get',
+    params: { id }
+  })
+}
+
+// 报单列表
+export function fetchDecOrderList(query) {
+  return request({
+    url: '/v1/shop/dec-order-list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 订单列表
+export function fetchOrderList(query) {
+  return request({
+    url: '/v1/shop/order-list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 下载订单PDF
+export function downloadOrder(orderSn) {
+  return request({
+    url: `/v1/shop/order-export/${orderSn}`,
+    method: 'get',
+    params: { }
+  })
+}
+
+export function createArticle(data) {
+  return request({
+    url: '/v1/article/create',
+    method: 'post',
+    data
+  })
+}
+
+export function updateArticle(data) {
+  return request({
+    url: '/v1/article/update',
+    method: 'post',
+    data
+  })
+}

+ 18 - 0
src/api/user.js

@@ -73,3 +73,21 @@ export function getInfo(token) {
 //     method: 'post'
 //   })
 // }
+
+// 修改用户
+export function updateInfo(data) {
+  return request({
+    url: '/v1/user/edit',
+    method: 'post',
+    data
+  })
+}
+
+// 修改登录密码
+export function updateLoginPassword(data) {
+  return request({
+    url: '/v1/user/password',
+    method: 'post',
+    data
+  })
+}

+ 6 - 6
src/components/LangSelect/index.vue

@@ -10,12 +10,12 @@
       <el-dropdown-item :disabled="language==='en'" command="en">
         English
       </el-dropdown-item>
-      <el-dropdown-item :disabled="language==='es'" command="es">
-        Español
-      </el-dropdown-item>
-      <el-dropdown-item :disabled="language==='ja'" command="ja">
-        日本語
-      </el-dropdown-item>
+<!--      <el-dropdown-item :disabled="language==='es'" command="es">-->
+<!--        Español-->
+<!--      </el-dropdown-item>-->
+<!--      <el-dropdown-item :disabled="language==='ja'" command="ja">-->
+<!--        日本語-->
+<!--      </el-dropdown-item>-->
     </el-dropdown-menu>
   </el-dropdown>
 </template>

+ 10 - 1
src/components/Pagination/index.vue

@@ -1,11 +1,12 @@
 <template>
-  <div :class="{'hidden':hidden}" class="pagination-container">
+  <div :class="{'hidden':hidden}" :style="{'float':float}" class="pagination-container">
     <el-pagination
       :background="background"
       :current-page.sync="currentPage"
       :page-size.sync="pageSize"
       :layout="layout"
       :page-sizes="pageSizes"
+      :hide-on-single-page="hideOnSinglePage"
       :total="total"
       v-bind="$attrs"
       @size-change="handleSizeChange"
@@ -53,6 +54,14 @@ export default {
     hidden: {
       type: Boolean,
       default: false
+    },
+    float: {
+      type: String,
+      default: 'right'
+    },
+    hideOnSinglePage: {
+      type: Boolean,
+      default: true
     }
   },
   computed: {

+ 55 - 1
src/lang/en.js

@@ -3,7 +3,7 @@ export default {
     dashboard: 'Dashboard',
     shop: 'Mall management',
     indexShop: 'Products list',
-    shopReconsume:'Reconsume List',
+    shopReconsume: 'Reconsume List',
     memberManagement: 'Member management',
     personalInfo: 'Personal Info',
     documentation: 'Documentation',
@@ -203,5 +203,59 @@ export default {
     activeDeadline: 'Active Deadline Date',
     more: 'more',
     articleNotification: 'System Notification'
+  },
+  currency: {
+    unit: 'Naira',
+    sign: '₦'
+  },
+
+  common: {
+    save: 'Save',
+    modify: 'Modify',
+    submit: 'Submit'
+  },
+
+  shop: {
+    productCode: 'Product Code',
+    productName: 'Product Name',
+    productPicture: 'Product Picture',
+    productPrice: 'Product Price',
+    productBV: 'Product BV',
+    qty: 'Qty',
+    taxRate: 'Tax Rate',
+    taxAmount: 'Tax',
+    totalPrice: 'Total Price',
+    orderCode: 'Order Code',
+    orderType: 'Order Type',
+    memberCode: 'Member Code',
+    memberName: 'Member Name',
+    recipientName: 'Recipient Name',
+    phoneNumber: 'Phone Number',
+    shippingAddress: 'Shipping Address',
+    payment: 'Payment',
+    createdTime: 'Created Time',
+    payTime: 'Pay Time',
+    payStatus: 'Pay Status',
+    action: 'Action',
+    download: 'Download',
+    sponsorCode: 'Sponsor Code',
+    inventory: 'Inventory',
+    checkOut: 'Check Out',
+    unPaid: '待支付',
+    paid: '已支付'
+  },
+
+  user: {
+    email: 'Email'
+  },
+
+  profile: {
+    personalInformation: 'Personal Information',
+    account: 'Account',
+    loginPassword: 'Login Password',
+    paymentPassword: 'Payment Password',
+    originalPassword: 'Original Password',
+    newPassword: 'New Password',
+    confirmPassword: 'Confirm Password'
   }
 }

+ 56 - 1
src/lang/zh.js

@@ -203,5 +203,60 @@ export default {
     activeDeadline: '活跃日期截止',
     more: '更多',
     articleNotification: '系统通知'
-  }
+  },
+
+  currency: {
+    unit: '元',
+    sign: '¥'
+  },
+
+  common: {
+    save: '保存',
+    modify: '修改',
+    submit: '提交'
+  },
+
+  shop: {
+    productCode: '商品编号',
+    productName: '商品名称',
+    productPrice: '商品价格',
+    productPicture: '商品图片',
+    productBV: '商品BV',
+    qty: '数量',
+    taxRate: '税率',
+    taxAmount: '税额',
+    totalPrice: '商品总价',
+    orderCode: '订单编号',
+    orderType: '订单类型',
+    memberCode: '会员编号',
+    memberName: '会员姓名',
+    recipientName: '收货人',
+    phoneNumber: '收货电话',
+    shippingAddress: '收货地址',
+    payment: '支付方式',
+    createdTime: '下单时间',
+    payTime: '支付时间',
+    payStatus: '支付状态',
+    action: '操作',
+    download: '下载',
+    sponsorCode: '接点人编号',
+    inventory: '库存',
+    checkOut: '结算',
+    unPaid: '待支付',
+    paid: '已支付'
+  },
+
+  user: {
+    email: '电子邮箱'
+  },
+
+  profile: {
+    personalInformation: '个人资料',
+    account: '账户',
+    loginPassword: '登录密码',
+    paymentPassword: '支付密码',
+    originalPassword: '原密码',
+    newPassword: '新密码',
+    confirmPassword: '重复新密码'
+  },
 }

+ 1 - 1
src/layout/components/Sidebar/Logo.vue

@@ -24,7 +24,7 @@ export default {
   },
   data() {
     return {
-      title: 'Vue Element Admin',
+      title: 'Member Management System',
       logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
     }
   }

+ 169 - 202
src/router/index.js

@@ -11,6 +11,8 @@ import componentsRouter from './modules/components'
 import chartsRouter from './modules/charts'
 import tableRouter from './modules/table'
 import nestedRouter from './modules/nested'
+import shopRouter from '@/router/modules/shop'
+import profileRouter from '@/router/modules/profileRouter'
 
 /**
  * Note: sub-menu only appear when route children.length >= 1
@@ -88,7 +90,7 @@ export const constantRoutes = [
         meta: { title: 'dashboard', icon: 'dashboard', affix: true }
       }
     ]
-  },
+  }
   // {
   //   path: '/dashboard/index',
   //   component: Layout,
@@ -127,20 +129,6 @@ export const constantRoutes = [
   //     }
   //   ]
   // },
-  {
-    path: '/profile',
-    component: Layout,
-    redirect: '/profile/index',
-    hidden: true,
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/profile/index'),
-        name: 'Profile',
-        meta: { title: 'profile', icon: 'user', noCache: true }
-      }
-    ]
-  }
 ]
 
 /**
@@ -148,29 +136,6 @@ export const constantRoutes = [
  * the routes that need to be dynamically loaded based on user roles
  */
 export const asyncRoutes = [
-  {
-    path: '/shop',
-    component: Layout,
-    redirect: '/shop/index',
-    // alwaysShow: true,
-    name: 'Shop',
-    meta: {
-      title: 'shop',
-      icon: 'edit',
-      // roles: ['admin', 'editor']
-    },
-    children: [
-      {
-        path: '/shop/index',
-        component: () => import('@/views/shop/index'),
-        name: 'IndexShop',
-        meta: {
-          title: 'indexShop',
-          roles: ['admin', 'editor'] // or you can only set roles in sub nav
-        }
-      }
-    ]
-  },
   {
     path: '/permission',
     component: Layout,
@@ -231,51 +196,53 @@ export const asyncRoutes = [
   chartsRouter,
   nestedRouter,
   tableRouter,
+  shopRouter,
+  profileRouter,
 
-  {
-    path: '/example',
-    component: Layout,
-    redirect: '/example/list',
-    name: 'Example',
-    meta: {
-      title: 'example',
-      icon: 'el-icon-s-help'
-    },
-    children: [
-      {
-        path: 'create',
-        component: () => import('@/views/example/create'),
-        name: 'CreateArticle',
-        meta: { title: 'createArticle', icon: 'edit' }
-      },
-      {
-        path: 'edit/:id(\\d+)',
-        component: () => import('@/views/example/edit'),
-        name: 'EditArticle',
-        meta: { title: 'editArticle', noCache: true, activeMenu: '/example/list' },
-        hidden: true
-      },
-      {
-        path: 'list',
-        component: () => import('@/views/example/list'),
-        name: 'ArticleList',
-        meta: { title: 'articleList', icon: 'list' }
-      }
-    ]
-  },
+  // {
+  //   path: '/example',
+  //   component: Layout,
+  //   redirect: '/example/list',
+  //   name: 'Example',
+  //   meta: {
+  //     title: 'example',
+  //     icon: 'el-icon-s-help'
+  //   },
+  //   children: [
+  //     {
+  //       path: 'create',
+  //       component: () => import('@/views/example/create'),
+  //       name: 'CreateArticle',
+  //       meta: { title: 'createArticle', icon: 'edit' }
+  //     },
+  //     {
+  //       path: 'edit/:id(\\d+)',
+  //       component: () => import('@/views/example/edit'),
+  //       name: 'EditArticle',
+  //       meta: { title: 'editArticle', noCache: true, activeMenu: '/example/list' },
+  //       hidden: true
+  //     },
+  //     {
+  //       path: 'list',
+  //       component: () => import('@/views/example/list'),
+  //       name: 'ArticleList',
+  //       meta: { title: 'articleList', icon: 'list' }
+  //     }
+  //   ]
+  // },
 
-  {
-    path: '/tab',
-    component: Layout,
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/tab/index'),
-        name: 'Tab',
-        meta: { title: 'tab', icon: 'tab' }
-      }
-    ]
-  },
+  // {
+  //   path: '/tab',
+  //   component: Layout,
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/tab/index'),
+  //       name: 'Tab',
+  //       meta: { title: 'tab', icon: 'tab' }
+  //     }
+  //   ]
+  // },
 
   {
     path: '/error',
@@ -315,128 +282,128 @@ export const asyncRoutes = [
     ]
   },
 
-  {
-    path: '/excel',
-    component: Layout,
-    redirect: '/excel/export-excel',
-    name: 'Excel',
-    meta: {
-      title: 'excel',
-      icon: 'excel'
-    },
-    children: [
-      {
-        path: 'export-excel',
-        component: () => import('@/views/excel/export-excel'),
-        name: 'ExportExcel',
-        meta: { title: 'exportExcel' }
-      },
-      {
-        path: 'export-selected-excel',
-        component: () => import('@/views/excel/select-excel'),
-        name: 'SelectExcel',
-        meta: { title: 'selectExcel' }
-      },
-      {
-        path: 'export-merge-header',
-        component: () => import('@/views/excel/merge-header'),
-        name: 'MergeHeader',
-        meta: { title: 'mergeHeader' }
-      },
-      {
-        path: 'upload-excel',
-        component: () => import('@/views/excel/upload-excel'),
-        name: 'UploadExcel',
-        meta: { title: 'uploadExcel' }
-      }
-    ]
-  },
-
-  {
-    path: '/zip',
-    component: Layout,
-    redirect: '/zip/download',
-    alwaysShow: true,
-    name: 'Zip',
-    meta: { title: 'zip', icon: 'zip' },
-    children: [
-      {
-        path: 'download',
-        component: () => import('@/views/zip/index'),
-        name: 'ExportZip',
-        meta: { title: 'exportZip' }
-      }
-    ]
-  },
-
-  {
-    path: '/pdf',
-    component: Layout,
-    redirect: '/pdf/index',
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/pdf/index'),
-        name: 'PDF',
-        meta: { title: 'pdf', icon: 'pdf' }
-      }
-    ]
-  },
-  {
-    path: '/pdf/download',
-    component: () => import('@/views/pdf/download'),
-    hidden: true
-  },
-
-  {
-    path: '/theme',
-    component: Layout,
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/theme/index'),
-        name: 'Theme',
-        meta: { title: 'theme', icon: 'theme' }
-      }
-    ]
-  },
-
-  {
-    path: '/clipboard',
-    component: Layout,
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/clipboard/index'),
-        name: 'ClipboardDemo',
-        meta: { title: 'clipboardDemo', icon: 'clipboard' }
-      }
-    ]
-  },
-
-  {
-    path: '/i18n',
-    component: Layout,
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/i18n-demo/index'),
-        name: 'I18n',
-        meta: { title: 'i18n', icon: 'international' }
-      }
-    ]
-  },
+  // {
+  //   path: '/excel',
+  //   component: Layout,
+  //   redirect: '/excel/export-excel',
+  //   name: 'Excel',
+  //   meta: {
+  //     title: 'excel',
+  //     icon: 'excel'
+  //   },
+  //   children: [
+  //     {
+  //       path: 'export-excel',
+  //       component: () => import('@/views/excel/export-excel'),
+  //       name: 'ExportExcel',
+  //       meta: { title: 'exportExcel' }
+  //     },
+  //     {
+  //       path: 'export-selected-excel',
+  //       component: () => import('@/views/excel/select-excel'),
+  //       name: 'SelectExcel',
+  //       meta: { title: 'selectExcel' }
+  //     },
+  //     {
+  //       path: 'export-merge-header',
+  //       component: () => import('@/views/excel/merge-header'),
+  //       name: 'MergeHeader',
+  //       meta: { title: 'mergeHeader' }
+  //     },
+  //     {
+  //       path: 'upload-excel',
+  //       component: () => import('@/views/excel/upload-excel'),
+  //       name: 'UploadExcel',
+  //       meta: { title: 'uploadExcel' }
+  //     }
+  //   ]
+  // },
+  //
+  // {
+  //   path: '/zip',
+  //   component: Layout,
+  //   redirect: '/zip/download',
+  //   alwaysShow: true,
+  //   name: 'Zip',
+  //   meta: { title: 'zip', icon: 'zip' },
+  //   children: [
+  //     {
+  //       path: 'download',
+  //       component: () => import('@/views/zip/index'),
+  //       name: 'ExportZip',
+  //       meta: { title: 'exportZip' }
+  //     }
+  //   ]
+  // },
+  //
+  // {
+  //   path: '/pdf',
+  //   component: Layout,
+  //   redirect: '/pdf/index',
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/pdf/index'),
+  //       name: 'PDF',
+  //       meta: { title: 'pdf', icon: 'pdf' }
+  //     }
+  //   ]
+  // },
+  // {
+  //   path: '/pdf/download',
+  //   component: () => import('@/views/pdf/download'),
+  //   hidden: true
+  // },
+  //
+  // {
+  //   path: '/theme',
+  //   component: Layout,
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/theme/index'),
+  //       name: 'Theme',
+  //       meta: { title: 'theme', icon: 'theme' }
+  //     }
+  //   ]
+  // },
+  //
+  // {
+  //   path: '/clipboard',
+  //   component: Layout,
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/clipboard/index'),
+  //       name: 'ClipboardDemo',
+  //       meta: { title: 'clipboardDemo', icon: 'clipboard' }
+  //     }
+  //   ]
+  // },
 
-  {
-    path: 'external-link',
-    component: Layout,
-    children: [
-      {
-        path: 'https://github.com/PanJiaChen/vue-element-admin',
-        meta: { title: 'externalLink', icon: 'link' }
-      }
-    ]
-  },
+  // {
+  //   path: '/i18n',
+  //   component: Layout,
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/i18n-demo/index'),
+  //       name: 'I18n',
+  //       meta: { title: 'i18n', icon: 'international' }
+  //     }
+  //   ]
+  // },
+  //
+  // {
+  //   path: 'external-link',
+  //   component: Layout,
+  //   children: [
+  //     {
+  //       path: 'https://github.com/PanJiaChen/vue-element-admin',
+  //       meta: { title: 'externalLink', icon: 'link' }
+  //     }
+  //   ]
+  // },
 
   // 404 page must be placed at the end !!!
   { path: '*', redirect: '/404', hidden: true }

+ 17 - 0
src/router/modules/profileRouter.js

@@ -0,0 +1,17 @@
+import Layout from '@/layout'
+
+const profileRouter = {
+  path: '/profile',
+  component: Layout,
+  redirect: '/profile/index',
+  hidden: true,
+  children: [
+    {
+      path: 'index',  // 用户中心
+      component: () => import('@/views/profile/index'),
+      name: 'Profile',
+      meta: { title: 'profile', icon: 'user', noCache: true }
+    },
+  ]
+}
+export default profileRouter

+ 52 - 0
src/router/modules/shop.js

@@ -0,0 +1,52 @@
+import Layout from '@/layout'
+
+const shopRouter = {
+  path: '/shop',
+  component: Layout,
+  redirect: '/shop/standard-products',
+  name: 'Shopping Mall',
+  meta: {
+    title: 'shop',
+    icon: 'el-icon-s-goods'
+  },
+  children: [
+    {
+      path: 'standard-products', // 普通商品列表
+      component: () => import('@/views/shop/standard-products'),
+      name: 'StandardProducts',
+      meta: { title: 'standardProducts', icon: 'el-icon-goods' }
+    },
+    {
+      path: 'car-fund-products',  // 车奖商品列表
+      component: () => import('@/views/shop/car-fund-products'),
+      name: 'CarFundProducts',
+      meta: { title: 'carFundProducts', icon: 'el-icon-goods' }
+    },
+    {
+      path: 'villa-fund-products',  // 房奖商品列表
+      component: () => import('@/views/shop/villa-fund-products'),
+      name: 'VillaFundProducts',
+      meta: { title: 'villaFundProducts', icon: 'el-icon-goods' }
+    },
+    {
+      path: 'shopping-cart',  // 购物车
+      component: () => import('@/views/shop/shopping-cart'),
+      name: 'ShoppingCart',
+      meta: { title: 'shoppingCart', icon: 'el-icon-shopping-cart-full' },
+      hidden: true
+    },
+    {
+      path: 'dec-order-list', // 报单列表
+      component: () => import('@/views/shop/dec-order-list'),
+      name: 'DecOrderList',
+      meta: { title: 'DecOrderList', icon: 'el-icon-goods' }
+    },
+    {
+      path: 'order-list', // 订单列表
+      component: () => import('@/views/shop/order-list'),
+      name: 'OrderList',
+      meta: { title: 'orderList', icon: 'el-icon-goods' }
+    }
+  ]
+}
+export default shopRouter

+ 9 - 1
src/store/getters.js

@@ -11,6 +11,14 @@ const getters = {
   introduction: state => state.user.introduction,
   roles: state => state.user.roles,
   permission_routes: state => state.permission.routes,
-  errorLogs: state => state.errorLog.logs
+  errorLogs: state => state.errorLog.logs,
+  email: state => state.user.email,
+  mobile: state => state.user.mobile,
+  realName: state => state.user.realName,
+  decLv: state => state.user.decLv,
+  empLv: state => state.user.empLv,
+  crownLv: state => state.user.crownLv,
+  remainBV: state => state.user.remainBV,
+  joinAt: state => state.user.joinAt
 }
 export default getters

+ 10 - 7
src/store/modules/user.js

@@ -1,4 +1,4 @@
-import { login, logout, getInfo, isLoginVerify, getUserInfo, getBaseInfo, getMessageData, backendQuickLogin } from '@/api/user'
+import { login, isLoginVerify, getUserInfo, getBaseInfo, getMessageData, backendQuickLogin } from '@/api/user'
 import { getToken, setToken, removeToken } from '@/utils/auth'
 import router, { resetRouter } from '@/router'
 import usersInfo from '@/utils/usersInfo'
@@ -82,7 +82,7 @@ const actions = {
   },
   isLoginVerify( { commit }, data ) {
     return new Promise((resolve, reject) => {
-      isLoginVerify({userName: data.userName}).then(response => {
+      isLoginVerify({ userName: data.userName }).then(response => {
         resolve(response.data)
       }).catch(error => {
         reject(error)
@@ -95,14 +95,21 @@ const actions = {
         let ret = response.data
         usersInfo.userId(ret.ID)
         usersInfo.userName(ret.USER_NAME)
+        usersInfo.userEmail(ret.EMAIL)
         usersInfo.baseData({
           AVATAR: ret.AVATAR,
           VERIFIED: ret.VERIFIED,
           DEC_LV: ret.DEC_LV,
           EMP_LV: ret.EMP_LV,
+          CROWN_LV: ret.CROWN_LV,
           PROVINCE: ret.PROVINCE,
           CITY: ret.CITY,
-          COUNTY: ret.COUNTY
+          COUNTY: ret.COUNTY,
+          EMAIL: ret.EMAIL,
+          BANK_NO: ret.BANK_NO,
+          MOBILE: ret.MOBILE,
+          CREATED_AT: ret.CREATED_AT,
+          REAL_NAME: ret.REAL_NAME
         })
         resolve(response)
       }).catch(error => {
@@ -144,8 +151,6 @@ const actions = {
       usersInfo.baseData()
       removeToken()
       resetRouter()
-      // reset visited views and cached views
-      // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
       dispatch('tagsView/delAllViews', null, { root: true })
       resolve()
     })
@@ -179,8 +184,6 @@ const actions = {
   //   })
   // },
 
-  
-
   // // user logout
   // logout({ commit, state, dispatch }) {
   //   return new Promise((resolve, reject) => {

+ 1 - 1
src/utils/get-page-title.js

@@ -1,7 +1,7 @@
 import defaultSettings from '@/settings'
 import i18n from '@/lang'
 
-const title = defaultSettings.title || 'Vue Element Admin'
+const title = defaultSettings.title || 'Member Management System'
 
 export default function getPageTitle(key) {
   const hasKey = i18n.te(`route.${key}`)

+ 16 - 16
src/utils/request.js

@@ -9,7 +9,7 @@ const service = axios.create({
   baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
 
   // withCredentials: true, // send cookies when cross-domain requests
-  timeout: 5000 // request timeout
+  timeout: 12000 // request timeout
 })
 // request interceptor
 service.interceptors.request.use(
@@ -77,27 +77,27 @@ service.interceptors.request.use(
 // response interceptor
 service.interceptors.response.use(
   response => {
-  const responseData = response.data
-  const data = responseData.data
-  if (responseData.success === false) {
+    const responseData = response.data
+    const data = responseData.data
+    if (responseData.success === false) {
     // const err = new Error(data.message)
     // err.data = data
     // err.message = data.message
     // err.response = response
     // throw err
-    Message({
-      message: data.message || 'Error',
-      type: 'error',
-      duration: 5 * 1000
-    })
-    return
-  } else {
-    if (!data) {
-      return {data: responseData}
+      Message({
+        message: data.message || 'Error',
+        type: 'error',
+        duration: 5 * 1000
+      })
+      return
+    } else {
+      if (!data) {
+        return { data: responseData }
+      }
+      return data
     }
-    return data
-  }
-},
+  },
   err => {
     if (err && err.response && err.response.data && err.response.data.message) {
       err.message = err.response.data.message

+ 57 - 50
src/utils/tool.js

@@ -2,8 +2,9 @@
 // import baseInfo from './baseInfo'
 // import {PRICE_IS_ROUND,SERVER_API_HTTP_TYPE,SERVER_API_DOMAIN,CDN_BASE_URL} from './config'
 import usersInfo from '@/utils/usersInfo'
+import errorCode from "core-js/internals/internal-state";
 
-let tool = {
+const tool = {
   /**
    * 设置JS存在客户端的Storage值
    * @param key
@@ -42,19 +43,19 @@ let tool = {
     return Math.round(dateObj.getTime() / 1000 + (days * 86400))
   },
   formatDate(timestamp, withTime = true) {
-    let newDate = new Date()
+    const newDate = new Date()
     timestamp = parseInt(timestamp)
     if (timestamp) {
       newDate.setTime(timestamp * 1000)
     } else {
       return ''
     }
-    let Y = newDate.getFullYear() + '-'
-    let M = (newDate.getMonth() + 1 < 10 ? '0' + (newDate.getMonth() + 1) : newDate.getMonth() + 1) + '-'
-    let D = (newDate.getDate() < 10 ? '0' + (newDate.getDate()) : newDate.getDate()) + ' '
-    let h = (newDate.getHours() < 10 ? '0' + newDate.getHours() : newDate.getHours()) + ':'
-    let m = (newDate.getMinutes() < 10 ? '0' + newDate.getMinutes() : newDate.getMinutes()) + ':'
-    let s = (newDate.getSeconds() < 10 ? '0' + newDate.getSeconds() : newDate.getSeconds())
+    const Y = newDate.getFullYear() + '-'
+    const M = (newDate.getMonth() + 1 < 10 ? '0' + (newDate.getMonth() + 1) : newDate.getMonth() + 1) + '-'
+    const D = (newDate.getDate() < 10 ? '0' + (newDate.getDate()) : newDate.getDate()) + ' '
+    const h = (newDate.getHours() < 10 ? '0' + newDate.getHours() : newDate.getHours()) + ':'
+    const m = (newDate.getMinutes() < 10 ? '0' + newDate.getMinutes() : newDate.getMinutes()) + ':'
+    const s = (newDate.getSeconds() < 10 ? '0' + newDate.getSeconds() : newDate.getSeconds())
     if (withTime) return Y + M + D + h + m + s
     return Y + M + D
   },
@@ -68,7 +69,7 @@ let tool = {
     let status = 0
     let todo
     if (errorCode.has(error)) {
-      let errorResult = errorCode.get(error)
+      const errorResult = errorCode.get(error)
       message = errorResult.message
       status = errorResult.status ? errorResult.status : 0
       todo = errorResult.todo
@@ -85,7 +86,7 @@ let tool = {
       message = todo?'长时间未进行操作,请重新登录':(message==='Connection not operated for too long' ?'长时间未进行操作,请重新登录' :message)
       usersInfo.logout()
     }
-    return {message, todo, status}
+    return { message, todo, status }
   },
   /**
    * 解析URL地址
@@ -104,7 +105,7 @@ let tool = {
    * @returns {{source: *, protocol: string, host: string, port: string, query: string, params, file: string | *, hash: string, path: string, relative: string | *, segments: string[]}}
    */
   parseURL(url) {
-    let a = document.createElement('a')
+    const a = document.createElement('a')
     a.href = url
     return {
       source: url,
@@ -112,13 +113,13 @@ let tool = {
       host: a.hostname,
       port: a.port,
       query: a.search,
-      params: (function () {
-        let waitUrl = a.hash.replace(/^#\/[\-_a-zA-Z0-9]+\?/, '\?') || a.search
-        let ret = {},
-          seg = waitUrl.replace(/^\?/, '').split('&'),
-          len = seg.length,
-          i = 0,
-          s
+      params: (function() {
+        const waitUrl = a.hash.replace(/^#\/[\-_a-zA-Z0-9]+\?/, '\?') || a.search
+        const ret = {}
+        const seg = waitUrl.replace(/^\?/, '').split('&')
+        const len = seg.length
+        let i = 0
+        let s
         for (; i < len; i++) {
           if (!seg[i]) {
             continue
@@ -144,7 +145,7 @@ let tool = {
     }
   },
   isInArray(arr, value) {
-    for (let i in arr) {
+    for (const i in arr) {
       if (value === arr[i]) {
         return true
       }
@@ -161,12 +162,7 @@ let tool = {
    */
   formatPrice(val) {
     val = Number.parseFloat(val)
-    if (PRICE_IS_ROUND) {
-      return val.toFixed(2)
-    } else {
-      val = val.toFixed(3)
-      return val.substring(0, val.lastIndexOf('.') + 3)
-    }
+    return val.toFixed(2)
   },
   /**
    * 根据状态返回颜色
@@ -177,57 +173,68 @@ let tool = {
     switch (val) {
       case '0':
         return 'info'
-        break
       case '1':
         return ''
-        break
       case '2':
         return 'danger'
-        break
       case '3':
         return 'warning'
-        break
       case '4':
         return 'success'
-        break
       default:
         return 'info'
     }
   },
-  sum(arr) {//求数组总和
-    var s = 0;
-    for (var i=arr.length-1; i>=0; i--) {
-        s += arr[i];
+  sum(arr) { // 求数组总和
+    var s = 0
+    for (var i = arr.length - 1; i >= 0; i--) {
+      s += arr[i]
     }
-    return s;
+    return s
   },
-    /**
+  /**
      * 获取table显示高度
      * @param hasStatusBar
      * @returns {number}
      */
-    getTableHeight(hasStatusBar = false) {
-        if (hasStatusBar) return window.innerHeight - 320
-        return window.innerHeight - 260
-    },
+  getTableHeight(hasStatusBar = false) {
+    if (hasStatusBar) return window.innerHeight - 320
+    return window.innerHeight - 260
+  },
 
-  /**
-   * 拼装图片地址.
-   * @param imageUrl
-   * @param path
-   * @returns {string}
-   */
-  getLocaleLink(imageUrl, path = '') {
-      return imageUrl.indexOf('http') > -1 ? imageUrl : `${CDN_BASE_URL}${path}${imageUrl}`;
+  // /**
+  //  * 拼装图片地址.
+  //  * @param imageUrl
+  //  * @param path
+  //  * @returns {string}
+  //  */
+  // getLocaleLink(imageUrl, path = '') {
+  //   return imageUrl.indexOf('http') > -1 ? imageUrl : `${process.env.VUE_APP_CDN_API}${path}${imageUrl}`
+  // },
+
+  // 计算商品税额
+  calculateTax(amount, taxRate, count = 1) {
+    const taxAmount = (amount - amount / (1 + taxRate / 100)) * count
+    return Math.round(taxAmount * 100) / 100
+  },
+
+  // 计算商品BV
+  calculateBV(amount, count = 1) {
+    return Math.round(amount * count)
   },
+
   /**
    * 拼装图片
    * @param imageUrl 图片
    * @param path 路径
    * @returns {string}
    */
-   getArImage(imageUrl, path) {
-    return `${process.env.VUE_APP_BASE_CDN}${path}${imageUrl}`;
+  getArImage(imageUrl, path) {
+    return process.env.VUE_APP_BASE_CDN + `/${path}${imageUrl}`
+  },
+
+  getEmpLv(id) {
+    // return id.length > 0 ? getBaseInfo().empLevels : 'No Rank';
   }
 }
 

+ 10 - 0
src/utils/usersInfo.js

@@ -139,6 +139,16 @@ let usersInfo = {
         }
         localStorage.setItem('userName', arg[0])
       },
+      userEmail (...arg) {
+        if (arg.length === 0) {
+          return localStorage.getItem('userEmail')
+        }
+        if (arg[0] === '') {
+          localStorage.removeItem('userEmail')
+          return
+        }
+        localStorage.setItem('userEmail', arg[0])
+      },
       updateLoginAllInfo (response) {
         // 更新本地accessToken
         this.accessToken(response.accessToken)

+ 49 - 12
src/views/profile/components/Account.vue

@@ -1,18 +1,27 @@
 <template>
-  <el-form>
-    <el-form-item label="Name">
-      <el-input v-model.trim="user.name" />
+  <el-form wi v-loading="loading">
+    <el-form-item :label="$t('shop.memberCode')">
+      <el-input size="medium" v-model.trim="user.name" prefix-icon="el-icon-user-solid" readonly />
     </el-form-item>
-    <el-form-item label="Email">
-      <el-input v-model.trim="user.email" />
+		<el-form-item :label="$t('shop.memberName')">
+			<el-input size="medium" v-model.trim="user.realName" prefix-icon="el-icon-user" readonly />
+		</el-form-item>
+		<el-form-item :label="$t('shop.phoneNumber')">
+			<el-input size="medium" v-model.trim="user.mobile" prefix-icon="el-icon-phone-outline" readonly />
+		</el-form-item>
+    <el-form-item :label="$t('user.email')">
+      <el-input size="medium" v-model.trim="user.email" prefix-icon="el-icon-s-comment" />
     </el-form-item>
     <el-form-item>
-      <el-button type="primary" @click="submit">Update</el-button>
+      <el-button type="primary" size="small" @click="submit">{{ $t('common.save') }}</el-button>
     </el-form-item>
   </el-form>
 </template>
 
 <script>
+
+import { updateInfo } from '@/api/user'
+import usersInfo from '@/utils/usersInfo'
 export default {
   props: {
     user: {
@@ -20,18 +29,46 @@ export default {
       default: () => {
         return {
           name: '',
-          email: ''
+          email: '',
+					mobile: '',
+					realName: '',
         }
       }
     }
   },
+	data() {
+		return {
+			loading: false,
+		}
+	},
   methods: {
     submit() {
-      this.$message({
-        message: 'User information has been updated successfully',
-        type: 'success',
-        duration: 5 * 1000
-      })
+			const data = {
+				email: this.user.email,
+			}
+
+			this.loading = true
+			updateInfo(data).then(() => {
+				setTimeout(() => {
+					this.loading = false
+				}, 1.5 * 1000)
+				this.$message({
+					message: 'User information has been updated successfully.',
+					type: 'success',
+					duration: 5 * 1000
+				})
+				// 覆盖localStorage
+				usersInfo.userEmail(this.user.email)
+				// 页面重载
+				location.reload()
+			}).catch((err) => {
+				this.$message({
+					message: err,
+					type: 'error',
+					duration: 5 * 1000
+				})
+				this.loading = false
+			})
     }
   }
 }

+ 65 - 0
src/views/profile/components/LoginPassword.vue

@@ -0,0 +1,65 @@
+<template>
+  <el-form wi v-loading="loading">
+    <el-form-item :label="$t('shop.originalPassword')">
+      <el-input size="medium" type="password" v-model.trim="password.oldPassword" prefix-icon="el-icon-user-solid" readonly />
+    </el-form-item>
+		<el-form-item :label="$t('shop.memberName')">
+			<el-input size="medium" type="password" v-model.trim="password.password" prefix-icon="el-icon-user" readonly />
+		</el-form-item>
+		<el-form-item :label="$t('shop.phoneNumber')">
+			<el-input size="medium" type="password" v-model.trim="password.verifyPassword" prefix-icon="el-icon-phone-outline" readonly />
+		</el-form-item>
+    <el-form-item>
+      <el-button type="primary" size="small" @click="submit">{{ $t('common.save') }}</el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+import { updateInfo } from '@/api/user'
+import usersInfo from '@/utils/usersInfo'
+export default {
+  props: {
+
+  },
+	data() {
+		return {
+			loading: false,
+			password: {
+				type: Object,
+				default: () => {
+					return {
+						oldPassword: '',
+						password: '',
+						verifyPassword: '',
+					}
+				}
+			}
+		}
+	},
+  methods: {
+    submit() {
+			this.loading = true
+			updateInfo(this.password).then(response => {
+				setTimeout(() => {
+					this.loading = false
+				}, 1.5 * 1000)
+				this.$message({
+					message: 'User login password has been updated successfully.',
+					type: 'success',
+					duration: 5 * 1000
+				})
+				// 强制重新登录
+				usersInfo.logout()
+			}).catch((err) => {
+				this.$message({
+					message: err,
+					type: 'error',
+					duration: 5 * 1000
+				})
+				this.loading = false
+			})
+    }
+  }
+}
+</script>

+ 67 - 0
src/views/profile/components/PaymentPassword.vue

@@ -0,0 +1,67 @@
+<template>
+  <el-form wi v-loading="loading">
+    <el-form-item :label="$t('shop.memberCode')">
+      <el-input size="medium" v-model.trim="user.name" prefix-icon="el-icon-user-solid" readonly />
+    </el-form-item>
+		<el-form-item :label="$t('shop.memberName')">
+			<el-input size="medium" v-model.trim="user.realName" prefix-icon="el-icon-user" readonly />
+		</el-form-item>
+		<el-form-item :label="$t('shop.phoneNumber')">
+			<el-input size="medium" v-model.trim="user.mobile" prefix-icon="el-icon-phone-outline" readonly />
+		</el-form-item>
+    <el-form-item>
+      <el-button type="primary" size="small" @click="submit">{{ $t('common.save') }}</el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+import { updateInfo } from '@/api/user'
+import usersInfo from '@/utils/usersInfo'
+export default {
+  props: {
+    user: {
+      type: Object,
+      default: () => {
+        return {
+          name: '',
+          email: '',
+					mobile: '',
+					realName: '',
+        }
+      }
+    }
+  },
+	data() {
+		return {
+			loading: false,
+		}
+	},
+  methods: {
+    submit() {
+			const data = {
+				email: this.user.email,
+			}
+
+			this.loading = true
+			updateInfo(data).then(() => {
+				setTimeout(() => {
+					this.loading = false
+				}, 1.5 * 1000)
+				this.$message({
+					message: 'User information has been updated successfully.',
+					type: 'success',
+					duration: 5 * 1000
+				})
+			}).catch((err) => {
+				this.$message({
+					message: err,
+					type: 'error',
+					duration: 5 * 1000
+				})
+				this.loading = false
+			})
+    }
+  }
+}
+</script>

+ 151 - 38
src/views/profile/components/UserCard.vue

@@ -1,59 +1,163 @@
 <template>
-  <el-card style="margin-bottom:20px;">
+  <el-card>
     <div slot="header" class="clearfix">
-      <span>About me</span>
+      <span>{{ $t('profile.personalInformation') }}</span>
     </div>
 
     <div class="user-profile">
       <div class="box-center">
         <pan-thumb :image="user.avatar" :height="'100px'" :width="'100px'" :hoverable="false">
           <div>Hello</div>
-          {{ user.role }}
         </pan-thumb>
       </div>
       <div class="box-center">
-        <div class="user-name text-center">{{ user.name }}</div>
-        <div class="user-role text-center text-muted">{{ user.role | uppercaseFirst }}</div>
+        <div class="user-name text-center"><svg-icon icon-class="user" /> {{ user.name }}</div>
+<!--        <div class="user-role text-center text-muted">{{ user.role | uppercaseFirst }}</div>-->
       </div>
     </div>
 
     <div class="user-bio">
-      <div class="user-education user-bio-section">
-        <div class="user-bio-section-header"><svg-icon icon-class="education" /><span>Education</span></div>
-        <div class="user-bio-section-body">
-          <div class="text-muted">
-            JS in Computer Science from the University of Technology
-          </div>
-        </div>
-      </div>
-
-      <div class="user-skills user-bio-section">
-        <div class="user-bio-section-header"><svg-icon icon-class="skill" /><span>Skills</span></div>
-        <div class="user-bio-section-body">
-          <div class="progress-item">
-            <span>Vue</span>
-            <el-progress :percentage="70" />
-          </div>
-          <div class="progress-item">
-            <span>JavaScript</span>
-            <el-progress :percentage="18" />
-          </div>
-          <div class="progress-item">
-            <span>Css</span>
-            <el-progress :percentage="12" />
-          </div>
-          <div class="progress-item">
-            <span>ESLint</span>
-            <el-progress :percentage="100" status="success" />
-          </div>
-        </div>
-      </div>
+			<div class="user-skills user-bio-section">
+				<div class="user-bio-section-header"></div>
+				<div class="user-bio-section-body">
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-time"></i>
+									<span>Current System Time</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+	<!--						<span>{{ tool.getTimestamp() }}</span>-->
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-time"></i>
+									<span>Member Join Time</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>{{ user.joinAt | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-star-on"></i>
+									<span>Member Level</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-s-data"></i>
+									<span>Remain BV</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-medal"></i>
+									<span>Highest Director</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-medal-1"></i>
+									<span>Highest Crown</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-picture-outline-round"></i>
+									<span>Current Pay Cycle</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+
+					<div class="progress-item" style="margin-top: 15px;">
+						<el-row>
+							<el-col :span="12">
+								<div class="grid-content bg-purple">
+									<i class="el-icon-time"></i>
+									<span>Active Deadline Date</span>
+								</div>
+							</el-col>
+							<el-col :span="12">
+								<div class="grid-content bg-purple-light">
+									<span>VIP</span>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+				</div>
+			</div>
     </div>
   </el-card>
 </template>
 
 <script>
 import PanThumb from '@/components/PanThumb'
+import tool from '@/utils/tool'
+import { parseTime } from '@/utils'
+import usersInfo from "@/utils/usersInfo"
 
 export default {
   components: { PanThumb },
@@ -65,11 +169,20 @@ export default {
           name: '',
           email: '',
           avatar: '',
-          role: ''
+          role: '',
+					decLv: '',
+					empLv: '',
+					crownLv: '',
+					joinAt: 0,
         }
       }
-    }
-  }
+    },
+  },
+	data() {
+		return {
+			tool: tool,
+		}
+	},
 }
 </script>
 

+ 32 - 15
src/views/profile/index.vue

@@ -3,21 +3,21 @@
     <div v-if="user">
       <el-row :gutter="20">
 
-        <el-col :span="6" :xs="24">
+        <el-col :span="8" :xs="24">
           <user-card :user="user" />
         </el-col>
 
-        <el-col :span="18" :xs="24">
+        <el-col :span="8" :xs="24">
           <el-card>
             <el-tabs v-model="activeTab">
-              <el-tab-pane label="Activity" name="activity">
-                <activity />
+              <el-tab-pane :label="$t('profile.account')" name="account">
+                <account :user="user" />
               </el-tab-pane>
-              <el-tab-pane label="Timeline" name="timeline">
-                <timeline />
+              <el-tab-pane :label="$t('profile.loginPassword')" name="loginPassword">
+                <login-password />
               </el-tab-pane>
-              <el-tab-pane label="Account" name="account">
-                <account :user="user" />
+              <el-tab-pane :label="$t('profile.paymentPassword')" name="paymentPassword">
+                <activity />
               </el-tab-pane>
             </el-tabs>
           </el-card>
@@ -34,22 +34,33 @@ import UserCard from './components/UserCard'
 import Activity from './components/Activity'
 import Timeline from './components/Timeline'
 import Account from './components/Account'
+import usersInfo from '@/utils/usersInfo'
+import { parseTime } from '@/utils'
+import LoginPassword from '@/views/profile/components/LoginPassword'
 
 export default {
   name: 'Profile',
-  components: { UserCard, Activity, Timeline, Account },
+  components: { LoginPassword, UserCard, Activity, Timeline, Account },
   data() {
     return {
       user: {},
-      activeTab: 'activity'
+      activeTab: 'account'
     }
   },
   computed: {
     ...mapGetters([
       'name',
       'avatar',
-      'roles'
+      'roles',
+      'email',
+      'mobile',
+      'realName',
+      'decLv',
+      'crownLv',
+      'remainBV',
+      'joinAt'
     ])
+
   },
   created() {
     this.getUser()
@@ -57,10 +68,16 @@ export default {
   methods: {
     getUser() {
       this.user = {
-        name: this.name,
-        role: this.roles.join(' | '),
-        email: 'admin@test.com',
-        avatar: this.avatar
+				role: this.roles.join(' | '),
+        name: usersInfo.userName(),
+        email: usersInfo.baseData().EMAIL,
+        mobile: usersInfo.baseData().MOBILE,
+        realName: usersInfo.baseData().REAL_NAME,
+        avatar: usersInfo.baseData().AVATAR ? usersInfo.baseData().AVATAR : 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+        decLv: usersInfo.baseData().DEC_LV,
+        empLv: usersInfo.baseData().EMP_LV,
+        crownLv: usersInfo.baseData().CROWN_LV,
+        joinAt: usersInfo.baseData().CREATED_AT
       }
     }
   }

+ 229 - 0
src/views/shop/car-fund-products.vue

@@ -0,0 +1,229 @@
+<template>
+	<div class="app-container">
+		<el-table
+			:key="tableKey"
+			v-loading="listLoading"
+			:data="tableData"
+			border
+			fit
+			highlight-current-row
+			style="width: 100%;"
+			ref="multipleTable"
+			@selection-change="handleSelectionChange"
+		>
+			<el-table-column type="selection" width="70" align="center"></el-table-column>
+			<el-table-column :label="$t('shop.productName')" align="center" prop="GOODS_NAME">
+				<template slot-scope="{row}">
+					<span>{{ row.GOODS_NAME }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productPicture')" align="center" min-width="70px" prop="GOODS_NAME">
+				<template slot-scope="{row}">
+					<el-image
+						style="width: 70px; height: 70px"
+						:src="tool.getArImage(row.COVER, '/files/')"
+						:preview-src-list="[tool.getArImage(row.COVER, '/files/')]"
+					>
+					</el-image>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productPrice')" align="center" prop="SELL_PRICE">
+				<template slot-scope="{row}">
+					<span>{{ row.SELL_PRICE }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productBV')" align="center" prop="PRICE_PV">
+				<template slot-scope="{row}">
+					<span>{{ row.PRICE_PV }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.taxRate')" align="center" prop="TAX_RATE">
+				<template slot-scope="{row}">
+					<span>{{ row.TAX_RATE / 100  }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.taxAmount')" align="center">
+				<template slot-scope="{row}">
+					<span>{{ row | taxAmountFilter }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.inventory')" align="center" prop="STORE_NUMS">
+				<template slot-scope="{row}">
+					<span>{{ row.STORE_NUMS }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.inventory')" min-width="90" align="center">
+				<template slot-scope="scope">
+					<el-input-number size="mini" v-model="storeNums[scope.$index]" :min="0" :max="Number(scope.row.STORE_NUMS)" @change="(val)=>{handleInputNumber(val, scope.row)}"></el-input-number>
+				</template>
+			</el-table-column>
+		</el-table>
+
+		<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+		<div class="white-box-footer" v-show="display" style="margin-top: 15px;">
+			<div class="flex data" style="float: left; display: inline-block; margin-top: 20px;">
+				<el-button type="primary" size="small" @click="settlement()" style="float: left;">{{ $t('shop.checkOut') }}</el-button>
+			</div>
+			<div class="flex data" style="float: right; display: inline-block; line-height: 35px; font-size: 14px; margin-top: 20px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px;">
+				<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productPrice') }}:{{ $t('currency.sign') }} {{ sellPriceSum }}</div>
+				<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productBV') }}:{{ $t('currency.sign') }} {{ pricePvSum }}</div>
+				<div style="display: inline-block;">{{ $t('shop.taxAmount') }}:{{ $t('currency.sign') }} {{ taxSum }}</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { fetchProductList, fetchProduct } from '@/api/shop'
+import waves from '@/directive/waves'
+import { parseTime } from '@/utils'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
+	name: 'carFundProducts',
+	components: { Pagination },
+	directives: { waves },
+	filters: {
+		priceFilter(price) {
+			return tool.formatPrice(price)
+		},
+		taxAmountFilter(row) {
+			return tool.calculateTax(row.SELL_PRICE, row.TAX_RATE)
+		},
+		statusFilter(status) {
+			const statusMap = {
+				Unpaid: 'info',
+				Paid: 'success',
+			}
+			return statusMap[status]
+		},
+	},
+	data() {
+		return {
+			tableKey: 0,
+			list: [],
+			total: 0,
+			tableData: [],
+			listLoading: true,
+			listQuery: {
+				categoryType: 5,
+				page: 1,
+				limit: 20,
+			},
+			tool: tool,
+			multipleSelection: [],
+			sellPriceSum: 0.00,
+			pricePvSum: 0.00,
+			taxSum: 0.00,
+			storeNums: [],
+			display: false,
+			currentPage: 1,
+		}
+	},
+	created() {
+		// 商品列表查询
+		this.getList()
+	},
+	methods: {
+		// 商品类别
+		getList() {
+			this.listLoading = true
+			fetchProductList(this.listQuery).then(response => {
+				this.list = response.data.list
+				this.total = parseInt(response.data.totalCount)
+
+				setTimeout(() => {
+					this.listLoading = false
+				}, 1.5 * 1000)
+
+				let settingObj = this.list
+				for (let i in this.list) {
+					this.storeNums[i] = 1
+					settingObj[i].chose_num = 0
+				}
+
+				this.tableData = Object.values(settingObj)
+				let pageList = this.multipleSelection[this.currentPage]
+				this.$nextTick(function () {
+					for (let i in this.tableData) {
+						for( let j in  pageList) {
+							if( pageList[j].ID === this.tableData[i].ID ) {
+								this.$data.storeNums[i] = pageList[j].chose_num
+								this.tableData[i].chose_num = pageList[j].chose_num
+								break
+							}
+						}
+					}
+				})
+			})
+		},
+		// 选择商品计数
+		handleInputNumber(val, row){
+			let pageList = this.multipleSelection[this.currentPage]
+			let selectStatus = false
+			for (let i in pageList) {
+				if( pageList[i].ID === row.ID ) {
+					pageList[i].chose_num = val
+					selectStatus = true
+					break
+				}
+			}
+
+			if (selectStatus) {
+				this.multipleSelection[this.currentPage] = pageList
+				this.handleSureChange()
+			}
+		},
+		// 统计商品
+		handleSureChange() {
+			if (this.multipleSelection.length > 0) {
+				let accumulatorSellPrice = 0, accumulatorPricePv = 0, accumulatorTax = 0
+				this.multipleSelection.forEach(item => {
+					item.forEach(accumulator => { accumulatorSellPrice += accumulator.SELL_PRICE * accumulator.chose_num * accumulator.DISCOUNT / 100; })
+					item.forEach(accumulator => { accumulatorPricePv += Number(accumulator.PRICE_PV) * Number(accumulator.chose_num) * (Number(accumulator.DISCOUNT) / 100); })
+					item.forEach(accumulator => { accumulatorTax += tool.calculateTax(Number(accumulator.SELL_PRICE), Number(accumulator.TAX_RATE), Number(accumulator.chose_num)); })
+				})
+
+				this.sellPriceSum = tool.formatPrice(accumulatorSellPrice)
+				this.pricePvSum = tool.formatPrice(accumulatorPricePv)
+				this.taxSum = tool.formatPrice(accumulatorTax)
+
+				this.display = true
+			} else {
+				this.sellPriceSum = this.pricePvSum = this.taxSum = 0.00
+				this.display = true
+			}
+		},
+		handleSelectionChange(val) {
+			let idx = -1, num
+			for (let i in this.tableData) {
+				for (let v in val) {
+					if (val[v].ID === this.tableData[i].ID) {
+						idx = i
+						num = this.storeNums[idx]
+						val[v]["chose_num"] = num
+						break
+					}
+				}
+			}
+			this.multipleSelection[this.currentPage] = val
+
+			// 计算统计
+			this.handleSureChange()
+		},
+		// 结算商品
+		settlement() {
+
+		}
+	},
+	watch: {
+		// 监听多选按钮,判断结算按钮是否可用
+		multipleSelection: function (modern) {
+			console.log(modern, modern.length)
+			this.$data.display = modern.length > 0
+		},
+	},
+}
+</script>

+ 193 - 0
src/views/shop/dec-order-list.vue

@@ -0,0 +1,193 @@
+<template>
+  <div class="app-container">
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+    >
+      <el-table-column width="120px" align="center" :label="$t('shop.productCode')" prop="SKU_CODE">
+        <template slot-scope="{row}">
+          <span>{{ row.SKU_CODE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.productName')" prop="GOODS_TITLE">
+        <template slot-scope="{row}">
+          <span>{{ row.GOODS_TITLE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.orderType')" prop="ORDER_TYPE">
+        <template slot-scope="{row}">
+          <span>{{ row.ORDER_TYPE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.productPrice')" prop="REAL_PRICE">
+        <template slot-scope="{row}">
+          <span>{{ row.REAL_PRICE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.productBV')" prop="REAL_PV">
+        <template slot-scope="{row}">
+          <span>{{ row | bvFilter }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="80px" align="center" :label="$t('shop.qty')" prop="BUY_NUMS">
+        <template slot-scope="{row}">
+          <span>{{ row.BUY_NUMS }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="80px" align="center" :label="$t('shop.taxRate')" prop="TAX_RATE">
+        <template slot-scope="{row}">
+          <span>{{ row.TAX_RATE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.taxAmount')" prop="TAX_AMOUNT">
+        <template slot-scope="{row}">
+          <span>{{ row.TAX_AMOUNT }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.totalPrice')" prop="TOTAL_AMOUNT">
+        <template slot-scope="{row}">
+          <span>{{ row.TOTAL_AMOUNT }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.orderCode')" prop="SN">
+        <template slot-scope="{row}">
+          <span>{{ row.SN }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.memberCode')" prop="USER_NAME">
+        <template slot-scope="{row}">
+          <span>{{ row.USER_NAME }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.memberName')" prop="REAL_NAME">
+        <template slot-scope="{row}">
+          <span>{{ row.REAL_NAME }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.recipientName')" prop="CONSIGNEE">
+        <template slot-scope="{row}">
+          <span>{{ row.CONSIGNEE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.phoneNumber')" prop="MOBILE">
+        <template slot-scope="{row}">
+          <span>{{ row.MOBILE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.shippingAddress')" prop="FULL_ADDRESS">
+        <template slot-scope="{row}">
+          <span>{{ row.FULL_ADDRESS }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.sponsorCode')" prop="CON_USER_NAME">
+        <template slot-scope="{row}">
+          <span>{{ row.CON_USER_NAME }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.payment')" prop="PAY_TYPE">
+        <template slot-scope="{row}">
+          <span>{{ row.PAY_TYPE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.createdTime')" prop="CREATED_AT">
+        <template slot-scope="{row}">
+          <span>{{ row.CREATED_AT | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.payTime')" prop="PAY_AT">
+        <template slot-scope="{row}">
+          <span>{{ row.PAY_AT | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column class-name="status-col" align="center" width="100" :label="$t('shop.payStatus')" prop="STATUS">
+        <template slot-scope="{row}">
+          <el-tag :type="row.STATUS | statusFilter">
+            {{ row.STATUS }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="100px" align="center" :label="$t('shop.action')" class-name="small-padding fixed-width">
+        <template slot-scope="{row}">
+          <el-button type="primary" size="mini" @click="handleDownload(row.SN)">{{ $t('shop.download') }}</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { fetchDecOrderList, downloadOrder } from '@/api/shop'
+import waves from '@/directive/waves'
+import { parseTime } from '@/utils'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
+  name: 'decOrderList',
+  components: { Pagination },
+  directives: { waves },
+  filters: {
+    bvFilter(row) {
+      return tool.calculateTax(row.REAL_PV, row.BUY_NUMS)
+    },
+    statusFilter(status) {
+      const statusMap = {
+        Unpaid: 'info',
+        Paid: 'success'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      tableKey: 0,
+      list: null,
+      total: 0,
+      listLoading: true,
+      downloadLoading: false,
+      listQuery: {
+        page: 1,
+        pageSize: 20
+      }
+    }
+  },
+  created() {
+    // 订单列表查询
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.listLoading = true
+      fetchDecOrderList(this.listQuery).then(response => {
+        this.list = response.data.list
+        this.total = parseInt(response.data.totalCount)
+
+        setTimeout(() => {
+          this.listLoading = false
+        }, 1.5 * 1000)
+      })
+    },
+    handleDownload(orderSn) {
+      this.downloadLoading = true
+      downloadOrder(orderSn).then(response => {
+        const { fileUrl, targetName } = response.data
+        const downloadElement = document.createElement('a')
+        downloadElement.target = '_blank'
+        downloadElement.href = process.env.VUE_APP_BASE_API + '/' + fileUrl
+        downloadElement.download = targetName
+        downloadElement.click()
+      })
+
+      this.downloadLoading = false
+    }
+  }
+}
+</script>

+ 249 - 0
src/views/shop/order-list.vue

@@ -0,0 +1,249 @@
+<template>
+  <div class="app-container">
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+      :span-method="objectSpanMethod"
+    >
+      <el-table-column width="120px" align="center" :label="$t('shop.productCode')" prop="SKU_CODE">
+        <template slot-scope="{row}">
+          <span>{{ row.SKU_CODE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="250px" align="center" :label="$t('shop.productName')" prop="GOODS_TITLE">
+        <template slot-scope="{row}">
+          <span>{{ row.GOODS_TITLE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.orderType')" prop="ORDER_TYPE">
+        <template slot-scope="{row}">
+          <span>{{ row.ORDER_TYPE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.productBV')" prop="REAL_PV">
+        <template slot-scope="{row}">
+          <span>{{ row | bvFilter }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.productPrice')" prop="REAL_PRICE">
+        <template slot-scope="{row}">
+          <span>{{ row.REAL_PRICE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="80px" align="center" :label="$t('shop.qty')" prop="BUY_NUMS">
+        <template slot-scope="{row}">
+          <span>{{ row.BUY_NUMS }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.totalPrice')" prop="TOTAL_AMOUNT">
+        <template slot-scope="{row}">
+          <span>{{ row.TOTAL_AMOUNT }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="80px" align="center" :label="$t('shop.taxRate')" prop="TAX_RATE">
+        <template slot-scope="{row}">
+          <span>{{ row.TAX_RATE / 100 }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.taxAmount')" prop="TAX_AMOUNT">
+        <template slot-scope="{row}">
+          <span>{{ row.TAX_AMOUNT }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="200px" align="center" :label="$t('shop.orderCode')" prop="SN">
+        <template slot-scope="{row}">
+          <span>{{ row.SN }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="120px" align="center" :label="$t('shop.memberCode')" prop="USER_NAME">
+        <template slot-scope="{row}">
+          <span>{{ row.USER_NAME }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.memberName')" prop="REAL_NAME">
+        <template slot-scope="{row}">
+          <span>{{ row.REAL_NAME }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.recipientName')" prop="CONSIGNEE">
+        <template slot-scope="{row}">
+          <span>{{ row.CONSIGNEE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.phoneNumber')" prop="MOBILE">
+        <template slot-scope="{row}">
+          <span>{{ row.MOBILE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.shippingAddress')" prop="FULL_ADDRESS">
+        <template slot-scope="{row}">
+          <span>{{ row.FULL_ADDRESS }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="130px" align="center" :label="$t('shop.payment')" prop="PAY_TYPE">
+        <template slot-scope="{row}">
+          <span>{{ row.PAY_TYPE }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.createdTime')" prop="CREATED_AT">
+        <template slot-scope="{row}">
+          <span>{{ row.CREATED_AT | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column width="150px" align="center" :label="$t('shop.payTime')" prop="PAY_AT">
+        <template slot-scope="{row}">
+          <span>{{ row.PAY_AT | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column class-name="status-col" align="center" width="100" :label="$t('shop.payStatus')" prop="STATUS">
+        <template slot-scope="{row}">
+          <el-tag :type="row.STATUS | statusFilter">
+            {{ row.STATUS }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="100px" align="center" :label="$t('shop.action')" class-name="small-padding fixed-width">
+        <template slot-scope="{row}">
+          <el-button type="primary" size="mini" @click="handleDownload(row.SN)">{{ $t('shop.download') }}</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { fetchOrderList, downloadOrder } from '@/api/shop'
+import waves from '@/directive/waves'
+import { parseTime } from '@/utils'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
+  name: 'orderList',
+  components: { Pagination },
+  directives: { waves },
+  filters: {
+    bvFilter(row) {
+      return tool.calculateBV(row.REAL_PV, row.BUY_NUMS)
+    },
+    taxAmountFilter(row) {
+      return tool.calculateTax(row.REAL_PV, row.BUY_NUMS)
+    },
+    statusFilter(status) {
+      const statusMap = {
+        Unpaid: 'info',
+        Paid: 'success'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      tableKey: 0,
+      list: null,
+      tableData: null,
+      total: 0,
+      listLoading: true,
+      downloadLoading: false,
+      listQuery: {
+        page: 1,
+        pageSize: 20
+      },
+      spanArr: [],
+      pos: 0
+    }
+  },
+  created() {
+    // 订单列表查询
+    this.getList()
+  },
+  mounted() {
+    // 第1步,根据表体信息,计算合并单元格的信息
+    // this.computeCell(this.list)
+  },
+  methods: {
+    getList() {
+      this.listLoading = true
+      fetchOrderList(this.listQuery).then(response => {
+        this.list = response.data.list
+        this.total = parseInt(response.data.totalCount)
+        const settingObj = this.list
+        const settingArr = Object.keys(this.list).map(key => {
+          return settingObj[key]
+        })
+
+        this.tableData = settingArr
+        this.getSpanArr(this.tableData)
+
+        setTimeout(() => {
+          this.listLoading = false
+        }, 1.5 * 1000)
+      })
+    },
+    handleDownload(orderSn) {
+      this.downloadLoading = true
+      downloadOrder(orderSn).then(response => {
+        const { fileUrl, targetName } = response.data
+        const downloadElement = document.createElement('a')
+        downloadElement.target = '_blank'
+        downloadElement.href = process.env.VUE_APP_BASE_API + '/' + fileUrl
+        downloadElement.download = targetName
+        downloadElement.click()
+      })
+
+      this.downloadLoading = false
+    },
+    objectSpanMethod(obj) {
+      if (obj.columnIndex > 8) {
+        const _row = this.spanArr[obj.rowIndex]
+        const _col = _row > 0 ? 1 : 0
+        return {
+          rowspan: _row,
+          colspan: _col
+        }
+      } else {
+        return false
+      }
+    },
+    getSpanArr(orderList) {
+      this.pos = 0
+      orderList.forEach((item, index) => {
+        // 判断是否是第⼀项
+        if (index === 0) {
+          this.spanArr.push(1)
+          this.pos = 0
+        } else {
+          // 不是第⼀项时,就根据标识去存储
+          if (
+            orderList[index].ORDER_SN === orderList[index - 1].ORDER_SN &&
+							orderList[index].USER_NAME === orderList[index - 1].USER_NAME &&
+							orderList[index].REAL_NAME === orderList[index - 1].REAL_NAME &&
+							orderList[index].CONSIGNEE === orderList[index - 1].CONSIGNEE &&
+							orderList[index].MOBILE === orderList[index - 1].MOBILE &&
+							orderList[index].PAY_AT === orderList[index - 1].PAY_AT &&
+							orderList[index].STATUS === orderList[index - 1].STATUS &&
+							orderList[index].PAY_TYPE === orderList[index - 1].PAY_TYPE &&
+							orderList[index].CREATED_AT === orderList[index - 1].CREATED_AT
+          ) {
+            // 查找到符合条件的数据时每次要把之前存储的数据+1
+            this.spanArr[this.pos] += 1
+            this.spanArr.push(0)
+          } else {
+            // 没有符合的数据时,要记住当前的index
+            this.spanArr.push(1)
+            this.pos = index
+          }
+        }
+      })
+    }
+  }
+}
+</script>

+ 440 - 0
src/views/shop/shopping-cart.vue

@@ -0,0 +1,440 @@
+<template>
+    <div v-loading="loading">
+        <div class="white-box">
+            <el-table :data="goods" style="width: 100%" show-summary :summary-method="getSummaries">
+                <el-table-column label="Product Picture" >
+                    <template slot-scope="{row}">
+                        <el-image
+													style="width: 100px; height: 100px"
+													:src="tool.getArImage(row.COVER, '/files/')"
+													:preview-src-list="[tool.getArImage(row.COVER, '/files/')]">
+												</el-image>
+                    </template>
+                </el-table-column>
+
+							<el-table-column label="Product Name" prop="GOODS_NAME"></el-table-column>
+
+                <el-table-column label="Product Price" prop="member_price">
+                    <template slot-scope="scope">
+                        <span>{{ Math.round(scope.row.member_price * 100) / 100 }}</span>
+                    </template>
+                </el-table-column>
+
+                <el-table-column label="Tax Rate" prop="TAX_RATE">
+                    <template slot-scope="scope">
+                        <span>{{ Math.round(scope.row.TAX_RATE * 100) / 100 }}</span>
+                    </template>
+                </el-table-column>
+
+                <el-table-column label="Tax" prop="tax_amount_plus">
+                    <template slot-scope="scope">
+                        <span>{{ Math.round(scope.row.tax_amount_plus * 100) / 100 }}</span>
+                    </template>
+                </el-table-column>
+
+                <el-table-column label="Quantity" prop="chose_num"></el-table-column>
+
+                <el-table-column label="Total Amount" prop="member_price_plus">
+                    <template slot-scope="scope">
+                        <span>{{ Math.round(scope.row.member_price_plus * 100) / 100 }}</span>
+                    </template>
+                </el-table-column>
+        </el-table>
+
+            <div class="address_box">
+                Please select the shipping address:	<!--  请选择收货地址 -->
+                <el-radio-group v-model="addressId" @change='choseAddress'>
+                    <div v-for="(item , index) in all_address" :key='index' class="address">
+                        <el-radio :label="item.ID" >
+                            Full Address:{{item.ADDRESS}}, {{item.CITY_NAME}}, {{item.LGA_NAME}}, {{item.PROVINCE_NAME}}<!-- 详细地址 -->
+                            Recipient Name:{{item.CONSIGNEE}}<!-- 收件人姓名 -->
+                            Phone Number:{{item.MOBILE}}<!-- 手机号码 -->
+                        </el-radio>
+                    </div>
+                    <div class="address">
+                        <el-radio label="100000000000000000">Self Pick-up</el-radio><!--自提-->
+                    </div>
+                </el-radio-group>
+            </div>
+
+            <div class="box address_box">
+                Total Orders:<!-- 订单合计 -->
+                <div class="sum">
+                    <div class="sum_box">
+                        <div>Freight</div><!-- 运费 -->
+                        <div>{{ pointFreight }} {{ unit }}</div>
+                    </div>
+                    <div class="sum_box">
+                        <div>Amount Paid</div><!-- 实付金额 -->
+                        <div>
+                            <span>{{ prefixSign }} {{ pointsSum }} {{ unit }}</span>
+                        </div>
+                    </div>
+                </div>
+          </div>
+
+            <div class="">
+                <el-button type="danger"  size="small" @click="cancelOrder()">Go Back</el-button>
+                <el-button type="primary" size="small" @click="goToAccounts()" :loading="submitButtonStat">Pay</el-button><!--去结算-->
+            </div>
+        </div>
+
+        <!-- payStack模态框 -->
+        <el-dialog title="Pay" v-if="visible" :visible.sync="visible" width="30%" v-loading="payStackLoading" :before-close="handleClose">
+            <section>
+                <div class="formcontainer">
+                    <el-divider></el-divider>
+                    <div class="container">
+                        <el-form :model="form">
+                            <el-form-item label="Email" label-width="100px" required>
+                                <el-input v-model="form.email" autocomplete="off"></el-input>
+                            </el-form-item>
+                            <el-form-item label="Amount" label-width="100px" required>
+                                <el-input v-model="form.amount" autocomplete="off" readonly></el-input>
+                            </el-form-item>
+                        </el-form>
+                    </div>
+                </div>
+            </section>
+            <paystack
+                :firstname="form.firstname"
+                :lastname="form.lastname"
+                :amount="form.amount * 100"
+                :email="form.email"
+                :metadata="form.metadata"
+                :currency="form.currency"
+                :paystackkey="form.publicKey"
+                :reference="reference"
+                :channels="channels"
+                :callback="processPayment"
+                :close="processClose"
+            >
+            <el-button type="primary" size="small">Pay</el-button>
+            </paystack>
+            <el-button type="danger" size="small" class="cancelButton" @click="handleClose">Cancel</el-button>
+    </el-dialog>
+
+        <!-- 倒计时页面 -->
+        <el-dialog title="Tips" :visible.sync="payDialog" :show-close="false" width="350px" :close="handleOrderList">
+            <el-result icon="success" title="the order is successful">
+                <template slot="extra">
+                    <span style="color: #008efa; font-size: 30px;">{{ countdown }}</span>
+                </template>
+            </el-result>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { fetchProductList, fetchProduct } from '@/api/shop';
+import waves from '@/directive/waves';
+import { parseTime } from '@/utils';
+import tool from '@/utils/tool';
+
+export default{
+    name: 'shoppingCart',
+    data: function () {
+        return {
+            tool: tool,
+            loading: true,
+            goods: [],
+            payList: [],
+            pointsSum: '',
+            cashSum: '',
+            freight: '',
+            pointFreight: '',
+            freeShipping: '',
+            goodsId: '',
+            goodsNum: '',
+            payPassword: '',
+            submitButtonStat: false,
+            prefixSign: '₦',
+            unit: 'NGN',
+            sn: '',
+            orderType: '',
+            payDialog: false,
+            countdown: 5,
+            visible: false,
+            payType: 'pay_stack',
+            payStackLoading: false,
+            channels: ["card", "bank", "ussd", "qr"],
+            form: {
+                publicKey: PAY_STACK_PUBLIC_KEY,
+                currency: PAY_STACK_CURRENCY,
+                firstname: userInfo.userName(),
+                lastname: '',
+                email: userInfo.userEmail(),
+                amount: 0, // kobo
+                orderSn: '',
+                metadata: {
+                    cart_id: this.sn,
+                    custom_fields: [
+                        {
+                            display_name: 'orderSn',
+                            variable_name: 'orderSn',
+                            value: this.sn,
+                        },
+                        {
+                          display_name: 'orderType',
+                          variable_name: 'orderType',
+                          value: 'baOrder'
+                        },
+                    ]
+            },
+          },
+            regionData: store.state.regionInfo.regionData,
+            addressId: '100000000000000000',
+            all_address: [],
+        }
+    },
+    components: {
+        paystack
+    },
+    created() {
+        // 加载sessionStorage中推送的商品
+        let option = sessionStorage.getItem('order_goods');
+        // 加载购物车
+        this.getShowCart();
+        if (option) {
+            let pageGoodsList = JSON.parse(option);
+            let pageList;
+            for (let i in pageGoodsList) {
+                pageList = pageGoodsList[i];
+                if (!pageList) continue;
+
+                pageList.map((pageData, index) => {
+                    if (Number(pageData.chose_num) > 0) {
+                        let discount = pageData.DISCOUNT / 100;
+                        pageData.member_price = Math.round(pageData.SELL_PRICE * discount * 100) / 100;
+                        pageData.member_price_plus = Math.round(pageData.SELL_PRICE * Number(pageData.chose_num) * discount * 100) / 100;
+                        pageData.tax_amount_plus = Math.round((pageData.member_price - pageData.member_price / (1 + pageData.TAX_RATE / 100)) * pageData.chose_num * 100) / 100;
+                        this.goods.push(pageData)
+                    }
+                })
+            }
+            // 计算价格
+          this.getSumMoney();
+        }
+    },
+    computed: {
+        // PayStack混淆串
+        reference() {
+            let text = '';
+            let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+            for (let i = 0; i < 10; i++) {
+                text += possible.charAt(Math.floor(Math.random() * possible.length));
+            }
+            return text;
+        }
+    },
+    methods: {
+        // 取消订单
+        cancelOrder() {
+            history.go(-1)
+        },
+        // 设置运费
+        setFreight() {
+            // 如果地址为自提,则运费为0
+            this.pointFreight = (this.addressId === '100000000000000000') ? Number(0) : Number(this.freight);
+        },
+        // 表格合并
+        getSummaries(param) {
+            const { columns, data } = param;
+            const sums = [];
+            columns.forEach((column, index) => {
+                if (index === 0) {
+                    sums[index] = 'Total'; // 合计
+                    return;
+                }
+                const values = data.map(item => Number(item[column.property]));
+                if ((!values.every(value => isNaN(value))) && [4, 5, 6].includes(index)) {
+                    sums[index] = values.reduce((prev, curr) => {
+                        const value = Number(curr);
+                        if (!isNaN(value)) {
+                            return Math.round((prev + curr) * 100) / 100;
+                        } else {
+                            return Math.round(prev * 100) / 100;
+                        }
+                    }, 0);
+                }
+            });
+
+            return sums;
+        },
+        // 支付
+        goToAccounts() {
+            this.submitButtonStat = true
+            this.$prompt('Please enter your payment password', 'Hint', { // '请输入支付密码', '提示'
+                confirmButtonText: 'Confirm', // 确定
+                cancelButtonText: 'Cancel', // 取消
+                inputType: 'password',
+                inputPattern: /\S+/,
+                inputErrorMessage: 'Please enter your payment password' // 请输入支付密码
+            }).then(({value}) => {
+                this.payPassword = value
+                let params = {
+                    goodsId: this.goodsId,
+                    goodsNum: this.goodsNum,
+                    payPassword: this.payPassword,
+                    email: this.form.email,
+                    addressId: this.addressId,
+                    address: this.address,
+                };
+
+                // PayStack支付
+                return network.postData('shop/ba-sure-approach-order', params).then(response => {
+                    this.submitButtonStat = false;
+                    this.form.orderSn = response.SN;
+                    this.form.amount = this.cashSum;
+                    this.form.metadata.custom_fields[0].value = response.SN;
+                    this.visible = true;
+                }).catch(_ => {
+                    this.submitButtonStat = false;
+                });
+            }).catch(_ => {
+                this.submitButtonStat = false;
+            });
+        },
+        // 计算价格
+        getSumMoney () {
+            let cash_plus_sum = [];
+            let goodsId = [];
+            let goodsNum = [];
+            let choseNum = 0;
+            this.goods.map(item => {
+                choseNum = Number(item.chose_num);
+                if (choseNum > 0) {
+                    cash_plus_sum.push(item.SELL_PRICE * choseNum * (item.DISCOUNT / 100));
+                    goodsId.push(item.ID);
+                    goodsNum.push(choseNum);
+                }
+            })
+            this.goodsNum = goodsNum;
+            this.goodsId = goodsId;
+            // 增加运费
+            let payAmount = tool.sum(cash_plus_sum);
+            // 设置运费
+            this.setFreight();
+            // 商品总价大于预定值,免运费
+            this.pointFreight = (this.pointFreight > 0) ? ((payAmount >= this.freeShipping) ? 0 : this.freight) : 0
+            // 计算总价
+            this.pointsSum = this.cashSum = this.form.amount = tool.formatPrice(tool.sum(cash_plus_sum) + this.pointFreight) ;
+        },
+        // 展示购物车信息
+        getShowCart () {
+            network.getData('shop/ba-shopping-cart', {}).then(response => {
+                this.loading = false;
+                // 免运费阈值
+                this.freeShipping = response.freeShipping;
+                // 运费
+                this.freight = response.freight;
+                // 收货地址
+                this.all_address = response.allAddress;
+                this.all_address.map((item, index) => {
+                    if (item.IS_DEFAULT === 1) {
+                        this.addressId = item.ID
+                    }
+                })
+            }).catch(_ => {
+                this.loading = false;
+            })
+        },
+        // 选择收货地址
+        choseAddress (addressId) {
+            this.addressId = addressId;
+            // 设置运费
+            this.setFreight()
+            // 计算价格
+            this.getSumMoney()
+        },
+        // 关闭支付回调
+        handleClose() {
+            let _this = this
+            _this.$confirm('Confirm to close?').then(_ => {
+                return network.postData('shop/ba-delete-approach-order', { orderSn: this.form.orderSn }).then(_ => {
+                    // 关闭支付模态框
+                    _this.visible = false
+                    // 关闭购物车页面,返回到订单列表页
+                    sessionStorage.setItem('order_goods', null)
+                    this.$router.push({path: `/shop/ba-product-list`})
+                })
+            }).catch(_ => {
+                // 关闭支付模态框
+                _this.visible = false
+                // 关闭购物车页面,返回到订单列表页
+                sessionStorage.setItem('order_goods', null)
+                this.$router.push({path: `/shop/ba-product-list`})
+            })
+        },
+        // 支付成功回调
+        processPayment() {
+            // 关闭支付页面
+            this.visible = false
+            this.payStackLoading = false
+            // 显示支付成功模态框
+            this.payDialog = true;
+            // 启动支付成功倒计时
+            this.handleCountdown()
+        },
+        // 关闭支付回调
+        processClose () {
+            // 关闭支付模态框
+            this.visible = false
+            // 关闭购物车页面,返回到订单列表页
+            sessionStorage.removeItem('order_goods')
+            this.$router.push({path: `/shop/ba-product-list`})
+        },
+        // 倒计时结束跳转
+        handleOrderList () {
+            this.$router.push({path: `/shop/ba-order-list`})
+        },
+        // 启动倒计时
+        handleCountdown () {
+            // 创建定时器
+            setInterval(() => {
+                // 每隔1秒把time的值减一,赋值给span标签
+                this.countdown--
+                if (this.countdown === 0) {
+                    // 倒计时结束,跳转到订单列表
+                    this.$router.push({path: `/shop/ba-order-list`});
+                }
+            }, 1000)
+        },
+    },
+}
+</script>
+
+<style scoped>
+.address{
+    /* height: 3rem; */
+    line-height: 3.5rem;
+
+}
+.address_box{
+     border-bottom: 1px solid #e3e3e3;
+}
+.sum{
+    display: inline-block;
+}
+.box{
+    margin: 1rem 0;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    padding-bottom: 1rem;
+}
+.sum_box{
+    display: flex;
+    margin-left: 1rem;
+}
+.sum_box > div{
+    line-height: 2rem;
+}
+.sum_box > div:nth-child(1){
+    margin-right: 1rem;
+}
+.payButton {
+    border: none;
+    padding: 0;
+}
+</style>

+ 229 - 0
src/views/shop/standard-products.vue

@@ -0,0 +1,229 @@
+<template>
+<div class="app-container">
+  <el-table
+    :key="tableKey"
+    v-loading="listLoading"
+    :data="tableData"
+    border
+    fit
+    highlight-current-row
+    style="width: 100%;"
+		ref="multipleTable"
+		@selection-change="handleSelectionChange"
+  >
+		<el-table-column type="selection" width="70" align="center"></el-table-column>
+    <el-table-column :label="$t('shop.productName')" align="center" prop="GOODS_NAME">
+      <template slot-scope="{row}">
+        <span>{{ row.GOODS_NAME }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.productPicture')" align="center" min-width="70px" prop="GOODS_NAME">
+      <template slot-scope="{row}">
+				<el-image
+					style="width: 70px; height: 70px"
+					:src="tool.getArImage(row.COVER, '/files/')"
+					:preview-src-list="[tool.getArImage(row.COVER, '/files/')]"
+				>
+				</el-image>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.productPrice')" align="center" prop="SELL_PRICE">
+      <template slot-scope="{row}">
+        <span>{{ row.SELL_PRICE }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.productBV')" align="center" prop="PRICE_PV">
+      <template slot-scope="{row}">
+        <span>{{ row.PRICE_PV }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.taxRate')" align="center" prop="TAX_RATE">
+      <template slot-scope="{row}">
+				<span>{{ row.TAX_RATE / 100  }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.taxAmount')" align="center">
+      <template slot-scope="{row}">
+				<span>{{ row | taxAmountFilter }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column :label="$t('shop.inventory')" align="center" prop="STORE_NUMS">
+      <template slot-scope="{row}">
+				<span>{{ row.STORE_NUMS }}</span>
+      </template>
+    </el-table-column>
+		<el-table-column :label="$t('shop.inventory')" min-width="90" align="center">
+			<template slot-scope="scope">
+				<el-input-number size="mini" v-model="storeNums[scope.$index]" :min="0" :max="Number(scope.row.STORE_NUMS)" @change="(val)=>{handleInputNumber(val, scope.row)}"></el-input-number>
+			</template>
+		</el-table-column>
+  </el-table>
+
+	<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+	<div class="white-box-footer" v-show="display" style="margin-top: 15px;">
+		<div class="flex data" style="float: left; display: inline-block; margin-top: 20px;">
+			<el-button type="primary" size="small" @click="settlement()" style="float: left;">{{ $t('shop.checkOut') }}</el-button>
+		</div>
+		<div class="flex data" style="float: right; display: inline-block; line-height: 35px; font-size: 14px; margin-top: 20px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px;">
+			<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productPrice') }}:{{ $t('currency.sign') }} {{ sellPriceSum }}</div>
+			<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productBV') }}:{{ $t('currency.sign') }} {{ pricePvSum }}</div>
+			<div style="display: inline-block;">{{ $t('shop.taxAmount') }}:{{ $t('currency.sign') }} {{ taxSum }}</div>
+		</div>
+	</div>
+</div>
+</template>
+
+  <script>
+  import { fetchProductList, fetchProduct } from '@/api/shop'
+  import waves from '@/directive/waves'
+  import { parseTime } from '@/utils'
+	import tool from '@/utils/tool'
+  import Pagination from '@/components/Pagination'
+
+  export default {
+    name: 'standardProducts',
+    components: { Pagination },
+    directives: { waves },
+		filters: {
+			priceFilter(price) {
+				return tool.formatPrice(price)
+			},
+			taxAmountFilter(row) {
+				return tool.calculateTax(row.SELL_PRICE, row.TAX_RATE)
+			},
+			statusFilter(status) {
+				const statusMap = {
+					Unpaid: 'info',
+					Paid: 'success',
+				}
+				return statusMap[status]
+			},
+		},
+    data() {
+      return {
+        tableKey: 0,
+        list: [],
+        total: 0,
+				tableData: [],
+        listLoading: true,
+        listQuery: {
+					categoryType: 1,
+          page: 1,
+          limit: 20,
+        },
+				tool: tool,
+				multipleSelection: [],
+				sellPriceSum: 0.00,
+				pricePvSum: 0.00,
+				taxSum: 0.00,
+				storeNums: [],
+				display: false,
+				currentPage: 1,
+      }
+    },
+    created() {
+			// 商品列表查询
+      this.getList()
+    },
+    methods: {
+			// 商品类别
+      getList() {
+        this.listLoading = true
+				fetchProductList(this.listQuery).then(response => {
+          this.list = response.data.list
+          this.total = parseInt(response.data.totalCount)
+
+					setTimeout(() => {
+						this.listLoading = false
+					}, 1.5 * 1000)
+
+					let settingObj = this.list
+					for (let i in this.list) {
+						this.storeNums[i] = 1
+						settingObj[i].chose_num = 0
+					}
+
+					this.tableData = Object.values(settingObj)
+					let pageList = this.multipleSelection[this.currentPage]
+					this.$nextTick(function () {
+						for (let i in this.tableData) {
+							for( let j in  pageList) {
+								if( pageList[j].ID === this.tableData[i].ID ) {
+									this.$data.storeNums[i] = pageList[j].chose_num
+									this.tableData[i].chose_num = pageList[j].chose_num
+									break
+								}
+							}
+						}
+					})
+        })
+      },
+			// 选择商品计数
+			handleInputNumber(val, row){
+				let pageList = this.multipleSelection[this.currentPage]
+				let selectStatus = false
+				for (let i in pageList) {
+					if( pageList[i].ID === row.ID ) {
+						pageList[i].chose_num = val
+						selectStatus = true
+						break
+					}
+				}
+
+				if (selectStatus) {
+					this.multipleSelection[this.currentPage] = pageList
+					this.handleSureChange()
+				}
+			},
+			// 统计商品
+			handleSureChange() {
+				if (this.multipleSelection.length > 0) {
+					let accumulatorSellPrice = 0, accumulatorPricePv = 0, accumulatorTax = 0
+					this.multipleSelection.forEach(item => {
+						item.forEach(accumulator => { accumulatorSellPrice += accumulator.SELL_PRICE * accumulator.chose_num * accumulator.DISCOUNT / 100; })
+						item.forEach(accumulator => { accumulatorPricePv += Number(accumulator.PRICE_PV) * Number(accumulator.chose_num) * (Number(accumulator.DISCOUNT) / 100); })
+						item.forEach(accumulator => { accumulatorTax += tool.calculateTax(Number(accumulator.SELL_PRICE), Number(accumulator.TAX_RATE), Number(accumulator.chose_num)); })
+					})
+
+					this.sellPriceSum = tool.formatPrice(accumulatorSellPrice)
+					this.pricePvSum = tool.formatPrice(accumulatorPricePv)
+					this.taxSum = tool.formatPrice(accumulatorTax)
+
+					this.display = true
+				} else {
+					this.sellPriceSum = this.pricePvSum = this.taxSum = 0.00
+					this.display = true
+				}
+			},
+			handleSelectionChange(val) {
+				let idx = -1, num
+				for (let i in this.tableData) {
+					for (let v in val) {
+						if (val[v].ID === this.tableData[i].ID) {
+							idx = i
+							num = this.storeNums[idx]
+							val[v]["chose_num"] = num
+							break
+						}
+					}
+				}
+				this.multipleSelection[this.currentPage] = val
+
+				// 计算统计
+				this.handleSureChange()
+			},
+			// 结算商品
+			settlement() {
+
+			}
+    },
+		watch: {
+			// 监听多选按钮,判断结算按钮是否可用
+			multipleSelection: function (modern) {
+				console.log(modern, modern.length)
+				this.$data.display = modern.length > 0
+			},
+		},
+  }
+</script>

+ 229 - 0
src/views/shop/villa-fund-products.vue

@@ -0,0 +1,229 @@
+<template>
+	<div class="app-container">
+		<el-table
+			:key="tableKey"
+			v-loading="listLoading"
+			:data="tableData"
+			border
+			fit
+			highlight-current-row
+			style="width: 100%;"
+			ref="multipleTable"
+			@selection-change="handleSelectionChange"
+		>
+			<el-table-column type="selection" width="70" align="center"></el-table-column>
+			<el-table-column :label="$t('shop.productName')" align="center" prop="GOODS_NAME">
+				<template slot-scope="{row}">
+					<span>{{ row.GOODS_NAME }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productPicture')" align="center" min-width="70px" prop="GOODS_NAME">
+				<template slot-scope="{row}">
+					<el-image
+						style="width: 70px; height: 70px"
+						:src="tool.getArImage(row.COVER, '/files/')"
+						:preview-src-list="[tool.getArImage(row.COVER, '/files/')]"
+					>
+					</el-image>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productPrice')" align="center" prop="SELL_PRICE">
+				<template slot-scope="{row}">
+					<span>{{ row.SELL_PRICE }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.productBV')" align="center" prop="PRICE_PV">
+				<template slot-scope="{row}">
+					<span>{{ row.PRICE_PV }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.taxRate')" align="center" prop="TAX_RATE">
+				<template slot-scope="{row}">
+					<span>{{ row.TAX_RATE / 100  }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.taxAmount')" align="center">
+				<template slot-scope="{row}">
+					<span>{{ row | taxAmountFilter }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.inventory')" align="center" prop="STORE_NUMS">
+				<template slot-scope="{row}">
+					<span>{{ row.STORE_NUMS }}</span>
+				</template>
+			</el-table-column>
+			<el-table-column :label="$t('shop.inventory')" min-width="90" align="center">
+				<template slot-scope="scope">
+					<el-input-number size="mini" v-model="storeNums[scope.$index]" :min="0" :max="Number(scope.row.STORE_NUMS)" @change="(val)=>{handleInputNumber(val, scope.row)}"></el-input-number>
+				</template>
+			</el-table-column>
+		</el-table>
+
+		<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+		<div class="white-box-footer" v-show="display" style="margin-top: 15px;">
+			<div class="flex data" style="float: left; display: inline-block; margin-top: 20px;">
+				<el-button type="primary" size="small" @click="settlement()" style="float: left;">{{ $t('shop.checkOut') }}</el-button>
+			</div>
+			<div class="flex data" style="float: right; display: inline-block; line-height: 35px; font-size: 14px; margin-top: 20px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px;">
+				<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productPrice') }}:{{ $t('currency.sign') }} {{ sellPriceSum }}</div>
+				<div style="margin-right: 2rem; display: inline-block;">{{ $t('shop.productBV') }}:{{ $t('currency.sign') }} {{ pricePvSum }}</div>
+				<div style="display: inline-block;">{{ $t('shop.taxAmount') }}:{{ $t('currency.sign') }} {{ taxSum }}</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { fetchProductList, fetchProduct } from '@/api/shop'
+import waves from '@/directive/waves'
+import { parseTime } from '@/utils'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
+	name: 'villaFundProducts',
+	components: { Pagination },
+	directives: { waves },
+	filters: {
+		priceFilter(price) {
+			return tool.formatPrice(price)
+		},
+		taxAmountFilter(row) {
+			return tool.calculateTax(row.SELL_PRICE, row.TAX_RATE)
+		},
+		statusFilter(status) {
+			const statusMap = {
+				Unpaid: 'info',
+				Paid: 'success',
+			}
+			return statusMap[status]
+		},
+	},
+	data() {
+		return {
+			tableKey: 0,
+			list: [],
+			total: 0,
+			tableData: [],
+			listLoading: true,
+			listQuery: {
+				categoryType: 6,
+				page: 1,
+				limit: 20,
+			},
+			tool: tool,
+			multipleSelection: [],
+			sellPriceSum: 0.00,
+			pricePvSum: 0.00,
+			taxSum: 0.00,
+			storeNums: [],
+			display: false,
+			currentPage: 1,
+		}
+	},
+	created() {
+		// 商品列表查询
+		this.getList()
+	},
+	methods: {
+		// 商品类别
+		getList() {
+			this.listLoading = true
+			fetchProductList(this.listQuery).then(response => {
+				this.list = response.data.list
+				this.total = parseInt(response.data.totalCount)
+
+				setTimeout(() => {
+					this.listLoading = false
+				}, 1.5 * 1000)
+
+				let settingObj = this.list
+				for (let i in this.list) {
+					this.storeNums[i] = 1
+					settingObj[i].chose_num = 0
+				}
+
+				this.tableData = Object.values(settingObj)
+				let pageList = this.multipleSelection[this.currentPage]
+				this.$nextTick(function () {
+					for (let i in this.tableData) {
+						for( let j in  pageList) {
+							if( pageList[j].ID === this.tableData[i].ID ) {
+								this.$data.storeNums[i] = pageList[j].chose_num
+								this.tableData[i].chose_num = pageList[j].chose_num
+								break
+							}
+						}
+					}
+				})
+			})
+		},
+		// 选择商品计数
+		handleInputNumber(val, row){
+			let pageList = this.multipleSelection[this.currentPage]
+			let selectStatus = false
+			for (let i in pageList) {
+				if( pageList[i].ID === row.ID ) {
+					pageList[i].chose_num = val
+					selectStatus = true
+					break
+				}
+			}
+
+			if (selectStatus) {
+				this.multipleSelection[this.currentPage] = pageList
+				this.handleSureChange()
+			}
+		},
+		// 统计商品
+		handleSureChange() {
+			if (this.multipleSelection.length > 0) {
+				let accumulatorSellPrice = 0, accumulatorPricePv = 0, accumulatorTax = 0
+				this.multipleSelection.forEach(item => {
+					item.forEach(accumulator => { accumulatorSellPrice += accumulator.SELL_PRICE * accumulator.chose_num * accumulator.DISCOUNT / 100; })
+					item.forEach(accumulator => { accumulatorPricePv += Number(accumulator.PRICE_PV) * Number(accumulator.chose_num) * (Number(accumulator.DISCOUNT) / 100); })
+					item.forEach(accumulator => { accumulatorTax += tool.calculateTax(Number(accumulator.SELL_PRICE), Number(accumulator.TAX_RATE), Number(accumulator.chose_num)); })
+				})
+
+				this.sellPriceSum = tool.formatPrice(accumulatorSellPrice)
+				this.pricePvSum = tool.formatPrice(accumulatorPricePv)
+				this.taxSum = tool.formatPrice(accumulatorTax)
+
+				this.display = true
+			} else {
+				this.sellPriceSum = this.pricePvSum = this.taxSum = 0.00
+				this.display = true
+			}
+		},
+		handleSelectionChange(val) {
+			let idx = -1, num
+			for (let i in this.tableData) {
+				for (let v in val) {
+					if (val[v].ID === this.tableData[i].ID) {
+						idx = i
+						num = this.storeNums[idx]
+						val[v]["chose_num"] = num
+						break
+					}
+				}
+			}
+			this.multipleSelection[this.currentPage] = val
+
+			// 计算统计
+			this.handleSureChange()
+		},
+		// 结算商品
+		settlement() {
+
+		}
+	},
+	watch: {
+		// 监听多选按钮,判断结算按钮是否可用
+		multipleSelection: function (modern) {
+			console.log(modern, modern.length)
+			this.$data.display = modern.length > 0
+		},
+	},
+}
+</script>

+ 1 - 1
vue.config.js

@@ -6,7 +6,7 @@ function resolve(dir) {
   return path.join(__dirname, dir)
 }
 
-const name = defaultSettings.title || 'vue Element Admin' // page title
+const name = defaultSettings.title || 'Member Management System' // page title
 
 // If your port is set to 80,
 // use administrator privileges to execute the command line.