浏览代码

接口调试

喻文俊 3 年之前
父节点
当前提交
a44a10f3c1
共有 42 个文件被更改,包括 2195 次插入647 次删除
  1. 4 0
      .env.development
  2. 3 0
      .env.production
  3. 36 0
      src/api/member.js
  4. 64 0
      src/api/pay.js
  5. 26 0
      src/api/user.js
  6. 二进制
      src/assets/pay/pay-faild.png
  7. 二进制
      src/assets/pay/pay-success.png
  8. 6 2
      src/components/Card/index.vue
  9. 56 0
      src/components/Empty/index.vue
  10. 22 7
      src/filters/index.js
  11. 93 13
      src/layout/components/Navbar.vue
  12. 28 6
      src/router/index.js
  13. 3 3
      src/router/module/settings.js
  14. 3 0
      src/store/getters.js
  15. 8 1
      src/store/modules/user.js
  16. 7 7
      src/utils/clipboard.js
  17. 0 1
      src/utils/request.js
  18. 9 0
      src/utils/validate.js
  19. 1 1
      src/views/admin/member/index.vue
  20. 49 21
      src/views/admin/member/record.vue
  21. 37 33
      src/views/admin/member/service.vue
  22. 83 46
      src/views/admin/member/settings/service-edit.vue
  23. 41 7
      src/views/admin/settings/accounts/edit.vue
  24. 36 0
      src/views/admin/settings/menus/children-list.vue
  25. 194 0
      src/views/admin/settings/menus/components/table-list.vue
  26. 194 25
      src/views/admin/settings/menus/edit.vue
  27. 35 91
      src/views/admin/settings/menus/index.vue
  28. 82 57
      src/views/admin/settings/roles/edit.vue
  29. 47 19
      src/views/admin/settings/roles/index.vue
  30. 2 0
      src/views/common/login/index.vue
  31. 43 0
      src/views/common/payment/faild.vue
  32. 43 0
      src/views/common/payment/success.vue
  33. 1 61
      src/views/components/PermissionButton/index.vue
  34. 159 0
      src/views/components/RadioCard/index.vue
  35. 72 3
      src/views/components/payment/pay-alipay.vue
  36. 188 10
      src/views/components/payment/pay-bank.vue
  37. 53 3
      src/views/components/payment/pay-wechat.vue
  38. 39 15
      src/views/normal/personal/index.vue
  39. 72 47
      src/views/normal/personal/set-account.vue
  40. 66 48
      src/views/normal/personal/set-mobile.vue
  41. 264 117
      src/views/normal/vip/buy.vue
  42. 26 3
      src/views/normal/vip/index.vue

+ 4 - 0
.env.development

@@ -15,3 +15,7 @@ VUE_APP_BASE_SERVER = 'https://www-b.caimei365.com'
 # 消息接口地址 WebSocket
 VUE_APP_SOCKET_SERVER = 'wss://zplma-b.caimei365.com/websocket?sessionSource=zplm_admin'
 # VUE_APP_SOCKET_SERVER = 'ws://192.168.2.68:8012/websocket?sessionSource=zplm_admin'
+
+# 支付
+VUE_APP_PAY_LOCAL = 'http://192.168.2.68:18014'
+

+ 3 - 0
.env.production

@@ -12,3 +12,6 @@ VUE_APP_BASE_SERVER = 'https://www.caimei365.com'
 
 # 消息接口地址 WebSocket
 VUE_APP_SOCKET_SERVER = 'wss://zplma.caimei365.com/websocket?sessionSource=zplm_admin'
+
+# 支付
+VUE_APP_PAY_LOCAL = https://core.caimei365.com

+ 36 - 0
src/api/member.js

@@ -54,3 +54,39 @@ export function handselVip(data) {
     data
   })
 }
+
+/** 获取会员服务菜单列表 */
+export function fetchVipMenusConfigure(params) {
+  return request({
+    url: '/vip/menus/form',
+    method: 'get',
+    params
+  })
+}
+
+/** 修改会员服务菜单列表 */
+export function updateVipMenusConfigure(data) {
+  return request({
+    url: '/vip/menus/configure',
+    method: 'post',
+    data
+  })
+}
+
+/** 为供应商配置定制化服务 */
+export function updateUserMenusConfigure(data) {
+  return request({
+    url: '/vip/services/configure',
+    method: 'post',
+    data
+  })
+}
+
+/** 查询供应商定制化服务配置 */
+export function fetchUserMenusConfigure(params) {
+  return request({
+    url: '/vip/services/form',
+    method: 'get',
+    params
+  })
+}

+ 64 - 0
src/api/pay.js

@@ -0,0 +1,64 @@
+import request from '@/utils/request'
+
+/** 网银支付开关 */
+export function payOnlineChecked(params) {
+  return request({
+    url: '/pay/online/switch',
+    method: 'GET',
+    params
+  })
+}
+
+/** 查询是否成功(微信支付专用) */
+export function checkedWinxinPaySuccess(params) {
+  return request({
+    url: '/pay/auth/vip/check',
+    method: 'GET',
+    params
+  })
+}
+
+/** 查询支付是否成功 (支付宝 银联) */
+export function checkedOtherPaySuccess(params) {
+  return request({
+    url: '/pay/result/json',
+    method: 'GET',
+    params
+  })
+}
+
+/** 获取银联支付列表 */
+export function fetchBankcodeList(params) {
+  return request({
+    url: '/pay/bankcode',
+    method: 'GET',
+    params
+  })
+}
+
+/** 支付会员预处理 */
+export function registerSuperPay(params) {
+  return request({
+    url: '/vip/pay',
+    method: 'GET',
+    params
+  })
+}
+
+/** 支付宝支付会员 */
+export function payVipWithAlipay(data) {
+  return request({
+    url: '/pay/auth/vip/alipay',
+    method: 'POST',
+    data
+  })
+}
+
+/** 银联支付会员 */
+export function payVipWithUnion(data) {
+  return request({
+    url: '/pay/auth/vip/union',
+    method: 'POST',
+    data
+  })
+}

+ 26 - 0
src/api/user.js

@@ -27,3 +27,29 @@ export function resetPassword(data) {
   })
 }
 
+// 修改手机号
+export function resetMobile(data) {
+  return request({
+    url: '/shop/mobile/change',
+    method: 'post',
+    data
+  })
+}
+
+/** 发送验证码 */
+export function sendVerifyCode(data) {
+  return request({
+    url: '/shop/verify/code/send',
+    method: 'post',
+    data
+  })
+}
+
+/** 绑定登录号 */
+export function bindLoginAccount(data) {
+  return request({
+    url: '/shop/account/bind',
+    method: 'post',
+    data
+  })
+}

二进制
src/assets/pay/pay-faild.png


二进制
src/assets/pay/pay-success.png


+ 6 - 2
src/components/Card/index.vue

@@ -44,13 +44,17 @@ export default {
       }
     }
   },
+  watch: {
+    checked(nVal) {
+      this.isChecked = nVal
+    }
+  },
   created() {
     this.isChecked = this.checked
   },
   methods: {
     handleClick() {
-      this.isChecked = !this.isChecked
-      this.$emit('change', this.isChecked)
+      this.$emit('click')
     }
   }
 }

+ 56 - 0
src/components/Empty/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="empty">
+    <el-image :src="image" :fit="fit" :style="{ width: width, height: height }" />
+    <div class="title" :style="{color: color}">{{ title }}</div>
+    <div class="description">
+      <slot name="description" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    image: {
+      type: String,
+      default: ''
+    },
+    fit: {
+      validator: function(value) {
+        return ['fill', 'contain', 'cover', 'none', 'scale-down'].indexOf(value) > -1
+      },
+      default: 'fill'
+    },
+    title: {
+      type: String,
+      default: '状态为空'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    },
+    color: {
+      type: String,
+      default: '#000'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.empty {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+
+  .title{
+    font-weight: bold;
+    margin-top: 16px;
+  }
+}
+</style>

+ 22 - 7
src/filters/index.js

@@ -1,5 +1,6 @@
 // import parseTime, formatTime and set to filter
-export { parseTime, formatTime } from '@/utils'
+export { parseTime } from '@/utils'
+import { formatDate } from '@/utils'
 
 /**
  * Show plural label if time is plural number
@@ -36,12 +37,12 @@ export function timeAgo(time) {
  */
 export function numberFormatter(num, digits) {
   const si = [
-    { value: 1E18, symbol: 'E' },
-    { value: 1E15, symbol: 'P' },
-    { value: 1E12, symbol: 'T' },
-    { value: 1E9, symbol: 'G' },
-    { value: 1E6, symbol: 'M' },
-    { value: 1E3, symbol: 'k' }
+    { value: 1e18, symbol: 'E' },
+    { value: 1e15, symbol: 'P' },
+    { value: 1e12, symbol: 'T' },
+    { value: 1e9, symbol: 'G' },
+    { value: 1e6, symbol: 'M' },
+    { value: 1e3, symbol: 'k' }
   ]
   for (let i = 0; i < si.length; i++) {
     if (num >= si[i].value) {
@@ -66,3 +67,17 @@ export function toThousandFilter(num) {
 export function uppercaseFirst(string) {
   return string.charAt(0).toUpperCase() + string.slice(1)
 }
+
+export function formatTime(time) {
+  if (!time) {
+    return ''
+  }
+  return formatDate(time, 'yyyy-MM-DD HH:mm:ss')
+}
+
+export function formatPrice(price) {
+  if (!price) {
+    return 0
+  }
+  return parseInt(price).toFixed(2)
+}

+ 93 - 13
src/layout/components/Navbar.vue

@@ -14,23 +14,40 @@
         <!-- <search id="header-search" class="right-menu-item" /> -->
         <!-- <notice-todo v-if="userIdentity === 1 && openSoket" /> -->
         <!-- <error-log class="errLog-container right-menu-item hover-effect" /> -->
-
         <screenfull id="screenfull" class="right-menu-item hover-effect" />
       </template>
-
+      <!-- <div class="user-info">
+        <div>
+          <span>{{ loginAccount || mobile || name }}</span>
+          <span>2022-02-11 会员到期续费购买记录</span>
+        </div>
+        <i v-if="userIdentity === 2" class="vip-icon close" />
+        <span>开通会员</span>
+      </div> -->
       <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
         <div class="avatar-wrapper">
-          <!-- <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" class="user-avatar"> -->
-          <span>{{ userName }}</span>
+          <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" class="user-avatar">
+          <span>{{ loginAccount || mobile || name }}</span>
+          <i v-if="userIdentity === 2" class="vip-icon close" />
           <i class="el-icon-caret-bottom" />
         </div>
-        <el-dropdown-menu slot="dropdown">
+        <el-dropdown-menu slot="dropdown" :class="{user: userIdentity === 2}">
           <template v-if="userIdentity === 2">
-            <router-link to="/vip/vip-open">
-              <el-dropdown-item>会员中心</el-dropdown-item>
-            </router-link>
+            <el-dropdown-item class="user-info">
+              <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" class="user-avatar">
+              <div class="right-content">
+                <div class="name">
+                  <span>{{ loginAccount || mobile || name }}</span>
+                  <i class="vip-icon close" />
+                </div>
+                <div class="info">
+                  <span class="time">到期时间:2020-12-21</span>
+                  <span class="record">购买记录</span>
+                </div>
+              </div>
+            </el-dropdown-item>
             <router-link to="/personal/info">
-              <el-dropdown-item divided>个人资料</el-dropdown-item>
+              <el-dropdown-item>个人资料</el-dropdown-item>
             </router-link>
             <router-link to="/personal/account">
               <el-dropdown-item>绑定登录账号</el-dropdown-item>
@@ -78,7 +95,7 @@ export default {
     // Search
   },
   computed: {
-    ...mapGetters(['sidebar', 'device', 'name', 'userIdentity', 'openSoket', 'proxyState']),
+    ...mapGetters(['sidebar', 'device', 'name', 'userIdentity', 'openSoket', 'proxyState', 'mobile', 'loginAccount']),
     userName() {
       return this.name
     }
@@ -110,7 +127,70 @@ export default {
 }
 </script>
 
+<style scoped>
+.el-dropdown-menu.user >>> .popper__arrow::after {
+  border-bottom-color: #2c3e50;
+}
+</style>
+
 <style lang="scss" scoped>
+.el-dropdown-menu {
+  &.user {
+    padding-top: 0;
+    width: 280px;
+    border-radius: 8px;
+  }
+}
+.user-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: #2c3e50;
+  color: #f1f1f1;
+  padding-top: 12px;
+  padding-bottom: 12px;
+  margin-bottom: 6px;
+  border-radius: 8px 8px 0 0;
+  .user-avatar {
+    width: 48px;
+    height: 48px;
+    border-radius: 10px;
+  }
+  .right-content {
+    width: 180px;
+    .name {
+      font-size: 20px;
+    }
+    .info {
+      font-size: 12px;
+      .record {
+        margin-left: 6px;
+        text-decoration: underline;
+      }
+    }
+  }
+
+  &:hover {
+    background: #2c3e50;
+    color: #f1f1f1;
+  }
+}
+
+.vip-icon {
+  display: inline-block;
+  width: 40px;
+  height: 32px;
+  margin: 0 4px;
+  background: url(~@/assets/img/vip-icon.png) no-repeat;
+  background-size: 40px;
+  vertical-align: -5px;
+
+  &.close {
+    filter: grayscale(1);
+    opacity: 0.6;
+  }
+}
+
 .navbar {
   height: 50px;
   overflow: hidden;
@@ -161,9 +241,9 @@ export default {
         cursor: pointer;
         transition: background 0.3s;
 
-        &:hover {
-          background: rgba(0, 0, 0, 0.025);
-        }
+        // &:hover {
+        //   background: rgba(0, 0, 0, 0.025);
+        // }
       }
     }
 

+ 28 - 6
src/router/index.js

@@ -51,12 +51,14 @@ export const constantRoutes = [
     name: 'Password',
     meta: { title: '密码管理' },
     hidden: true,
-    children: [{
-      path: 'edit',
-      meta: { title: '修改密码' },
-      component: () => import(/* webpackChunkName: "common-page" */ '@/views/common/password'),
-      hidden: true
-    }]
+    children: [
+      {
+        path: 'edit',
+        meta: { title: '修改密码' },
+        component: () => import(/* webpackChunkName: "common-page" */ '@/views/common/password'),
+        hidden: true
+      }
+    ]
   },
   {
     path: '/proxy',
@@ -67,6 +69,26 @@ export const constantRoutes = [
     path: '/test',
     component: () => import('@/views/test')
   },
+  {
+    path: '/pay',
+    component: Layout,
+    name: 'PayStatus',
+    meta: { title: '支付状态' },
+    hidden: true,
+    redirect: '/pay/success',
+    children: [
+      {
+        path: 'success',
+        component: () => import(/* webpackChunkName: "common-page" */ '@/views/common/payment/success'),
+        hidden: true
+      },
+      {
+        path: 'faild',
+        component: () => import(/* webpackChunkName: "common-page" */ '@/views/common/payment/faild'),
+        hidden: true
+      }
+    ]
+  },
   {
     path: '/404',
     component: () => import(/* webpackChunkName: "common-page" */ '@/views/common/error-page/404'),

+ 3 - 3
src/router/module/settings.js

@@ -30,11 +30,11 @@ const settingRoutes = [
         component: () => import('@/views/admin/settings/menus/edit')
       },
       {
-        path: 'menus/children',
+        path: 'menus/children/:menuType/:id/:title',
         name: 'SettingsChildrenMenus',
         hidden: true,
-        meta: { title: '子菜单管理', roles: ['admin'], noCache: true },
-        component: () => import('@/views/admin/settings/menus')
+        meta: { title: '子菜单管理', roles: ['admin'], noCache: false },
+        component: () => import('@/views/admin/settings/menus/children-list')
       },
       {
         path: 'menus/children/add',

+ 3 - 0
src/store/getters.js

@@ -8,6 +8,9 @@ const getters = {
   cachedViews: state => state.tagsView.cachedViews,
   token: state => state.user.token,
   name: state => state.user.name,
+  loginAccount: state => state.user.loginAccount,
+  mobile: state => state.user.mobile,
+  vipStatus: state => state.user.vipStatus,
   roles: state => state.user.roles,
   authUserId: state => state.user.authUserId,
   userIdentity: state => state.user.userIdentity,

+ 8 - 1
src/store/modules/user.js

@@ -23,6 +23,9 @@ const state = {
   userIdentity: '',
   shopType: '',
   brandId: '',
+  loginAccount: '',
+  mobile: '',
+  vipStatus: '',
   userInfo: null
 }
 
@@ -36,6 +39,9 @@ const mutations = {
     state.userIdentity = userInfo.userIdentity || ''
     state.shopType = userInfo.shopType || ''
     state.brandId = userInfo.brandId || ''
+    state.loginAccount = userInfo.loginAccount || ''
+    state.mobile = userInfo.mobile || ''
+    state.vipStatus = userInfo.vipStatus || ''
   },
   // 设置token
   SET_TOKEN: (state, playload) => {
@@ -64,7 +70,8 @@ const actions = {
         setUserInfo(res.data)
         commit('SAVE_USER_INOF', res.data)
         commit('SET_TOKEN', res.data.token)
-      }).catch(() => {
+      })
+      .catch(() => {
         commit('SAVE_USER_INOF', {})
         commit('SET_TOKEN', '')
         commit('SET_ROLES', [])

+ 7 - 7
src/utils/clipboard.js

@@ -1,31 +1,31 @@
 import Vue from 'vue'
 import Clipboard from 'clipboard'
 
-function clipboardSuccess() {
+function clipboardSuccess(message) {
   Vue.prototype.$message({
-    message: 'Copy successfully',
+    message: message || 'Copy successfully',
     type: 'success',
     duration: 1500
   })
 }
 
-function clipboardError() {
+function clipboardError(message) {
   Vue.prototype.$message({
-    message: 'Copy failed',
+    message: message || 'Copy failed',
     type: 'error'
   })
 }
 
-export default function handleClipboard(text, event) {
+export default function handleClipboard(text, message, event) {
   const clipboard = new Clipboard(event.target, {
     text: () => text
   })
   clipboard.on('success', () => {
-    clipboardSuccess()
+    clipboardSuccess(message)
     clipboard.destroy()
   })
   clipboard.on('error', () => {
-    clipboardError()
+    clipboardError(message)
     clipboard.destroy()
   })
   clipboard.onClick(event)

+ 0 - 1
src/utils/request.js

@@ -25,7 +25,6 @@ service.interceptors.request.use(
     if (config.headers['Content-Type'] === 'application/json') {
       config.data = JSON.stringify(config.data)
     }
-
     const token = getToken()
     if (token) {
       config.headers['X-Token'] = token

+ 9 - 0
src/utils/validate.js

@@ -121,3 +121,12 @@ export function isNumber(arg) {
   const reg = /\d+$/
   return reg.test(arg)
 }
+
+/**
+ * @param {String} arg
+ * @returns {Boolean}
+ */
+export function isAccount(arg) {
+  const reg = /^[a-z|A-Z|0-9]+$/
+  return reg.test(arg)
+}

+ 1 - 1
src/views/admin/member/index.vue

@@ -71,7 +71,7 @@
       <el-table-column label="操作" align="center" width="280">
         <template slot-scope="{row}">
           <el-button type="primary" size="mini" @click="$_navigationTo(`/member/record?id=${row.authUserId}`)">购买记录</el-button>
-          <el-button type="primary" size="mini" @click="$_navigationTo('/member/service')">配置定制化服务</el-button>
+          <el-button type="primary" size="mini" @click="$_navigationTo(`/member/service?id=${row.authUserId}`)">配置定制化服务</el-button>
         </template>
       </el-table-column>
     </el-table>

+ 49 - 21
src/views/admin/member/record.vue

@@ -2,11 +2,11 @@
   <div class="app-container">
     <!-- 供应商基本信息 -->
     <div class="info">
-      <div class="row"><span>供应商名称:</span><span>上海品辉国际医疗</span></div>
-      <div class="row"><span>供应商类型:</span><span>代理商</span></div>
-      <div class="row"><span>登录账号:</span><span>shanghaipinhui</span></div>
-      <div class="row"><span>手机号:</span><span>15900004040</span></div>
-      <div class="row"><span>联系人:</span><span>黄XX</span></div>
+      <div class="row"><span>供应商名称:</span><span>{{ supplierInfo.shopName }}</span></div>
+      <div class="row"><span>供应商类型:</span><span>{{ supplierInfo.shopType === 1 ? '品牌方':'代理商' }}</span></div>
+      <div class="row"><span>登录账号:</span><span>{{ supplierInfo.loginAccount || '--' }}</span></div>
+      <div class="row"><span>手机号:</span><span>{{ supplierInfo.mobile }}</span></div>
+      <div class="row"><span>联系人:</span><span>{{ supplierInfo.linkMan }}</span></div>
     </div>
     <el-divider />
     <!-- 搜索区域 -->
@@ -15,17 +15,17 @@
         <span>购买套餐:</span>
         <el-select v-model="listQuery.vipPackageId" placeholder="购买套餐" clearable @change="getList">
           <el-option label="不限" value="" />
-          <el-option label="套餐一" :value="1" />
-          <el-option label="套餐二" :value="2" />
+          <el-option v-for="item in packageList" :key="item.id" :label="item.name" :value="item.id" />
         </el-select>
       </div>
       <div class="filter-control">
         <span>会员状态:</span>
+        <!-- 会员状态:0已过期,1生效中,2待生效,3非会员 -->
         <el-select v-model="listQuery.vipStatus" placeholder="会员状态" clearable @change="getList">
           <el-option label="不限" value="" />
-          <el-option label="待生效" :value="1" />
-          <el-option label="生效中" :value="2" />
-          <el-option label="已过期" :value="3" />
+          <el-option label="已过期" :value="0" />
+          <el-option label="生效中" :value="1" />
+          <el-option label="待生效" :value="2" />
         </el-select>
       </div>
       <div class="filter-control">
@@ -76,28 +76,33 @@
         </template>
       </el-table-column>
       <el-table-column label="购买方式" align="center">
+        <!-- 1平台赠送,2支付宝,3微信,4企业网银,5个人网银 -->
         <template slot-scope="{ row }">
-          <span v-if="row.payWay === 1">微信支付</span>
-          <span v-if="row.payWay === 2">支付宝支付</span>
-          <span v-if="row.payWay === 3">企业网银支付</span>
-          <span v-if="row.payWay === 4">个人网银支付</span>
+          <span v-if="row.payWay === 1">平台赠送</span>
+          <span v-if="row.payWay === 2">支付宝</span>
+          <span v-if="row.payWay === 3">微信</span>
+          <span v-if="row.payWay === 4">企业网银</span>
+          <span v-if="row.payWay === 5">个人网银</span>
         </template>
       </el-table-column>
       <el-table-column label="会员状态" align="center">
+        <!-- 会员状态:0已过期,1生效中,2待生效,3非会员 -->
         <template slot-scope="{ row }">
-          <span v-if="row.vipStatus === 1" class="status warning">待生效</span>
-          <span v-if="row.vipStatus === 2" class="status success">生效中</span>
-          <span v-if="row.vipStatus === 3" class="status danger">已过期</span>
+          <span v-if="row.vipStatus === 0" class="status danger">已过期</span>
+          <span v-if="row.vipStatus === 1" class="status success">生效中</span>
+          <span v-if="row.vipStatus === 2" class="status warning">待生效</span>
         </template>
       </el-table-column>
       <el-table-column label="购买时间" align="center">
         <template slot-scope="{ row }">
-          <span>{{ row.payTime }}</span>
+          <span v-if="row.payTime">{{ row.payTime | formatTime }}</span>
+          <span v-else>—</span>
         </template>
       </el-table-column>
       <el-table-column label="到期时间" align="center">
         <template slot-scope="{ row }">
-          <span>{{ row.endTime }}</span>
+          <span v-if="row.endTime">{{ row.endTime | formatTime }}</span>
+          <span v-else>—</span>
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center">
@@ -121,7 +126,8 @@
 </template>
 
 <script>
-import { fetchVipRecord } from '@/api/member'
+import { fetchVipRecord, fetchConfigureList } from '@/api/member'
+import { getSupplierById } from '@/api/supplier'
 export default {
   data() {
     return {
@@ -141,12 +147,16 @@ export default {
         pageNum: 1
       },
       list: [],
-      demandList: []
+      demandList: [],
+      packageList: [],
+      supplierInfo: {}
     }
   },
   created() {
     this.listQuery.authUserId = this.$route.query.id
     this.fetchVipRecord()
+    this.fetchConfigureList()
+    this.fetchSupplierInfo()
   },
   methods: {
     indexMethod(index) {
@@ -184,6 +194,24 @@ export default {
     handleShowDemand(row) {
       this.dialogTableVisible = true
       this.demandList = row.services
+    },
+    // 获取套餐列表
+    fetchConfigureList() {
+      fetchConfigureList().then(res => {
+        this.packageList = res.data.packageList.map((item, index) => {
+          item.name = `套餐${index + 1}`
+          // item.originalPrice = `${item.originalPrice.toFixed(2)}`
+          // item.price = item.price.toFixed(2)
+          return item
+        })
+      })
+    },
+    // 获取供应商信息
+    fetchSupplierInfo() {
+      getSupplierById({ authUserId: this.listQuery.authUserId }).then(res => {
+        console.log(res)
+        this.supplierInfo = res.data
+      })
     }
   }
 }

+ 37 - 33
src/views/admin/member/service.vue

@@ -1,24 +1,17 @@
 <template>
-  <div class="app-container">
+  <div v-loading="isLoading" class="app-container">
     <!-- 搜索区域 -->
     <div class="filter-container">
       <div class="filter-control tip">配置订制化服务功能<span>(以下为设置会员特定的订制服务功能)</span></div>
-      <div class="filter-control">
-        <span>配置定制服务:</span>
-        <el-select v-model="listQuery.pacakgeId" clearable size="mini" @change="getList">
-          <el-option label="不限" value="" />
-          <el-option label="1年" :value="1" />
-          <el-option label="3年" :value="2" />
-        </el-select>
-      </div>
+      <div class="filter-control" />
     </div>
     <!-- 表格区域 -->
     <el-table :data="serviceList" border fit height="600" header-row-class-name="tableHeader">
       <el-table-column label="序号" type="index" width="80" align="center" />
-      <el-table-column label="定制需求" prop="demand" align="center" />
+      <el-table-column label="定制需求" prop="title" align="center" />
       <el-table-column label="是否配置" align="center" width="120">
         <template slot-scope="{ row }">
-          <el-checkbox v-model="row.isChecked" />
+          <el-checkbox v-model="row.checkFlag" :true-label="1" :false-label="0" />
         </template>
       </el-table-column>
     </el-table>
@@ -26,39 +19,50 @@
     <!-- 确认 取消 -->
     <div class="control-box">
       <el-button type="warning">返回</el-button>
-      <el-button type="primary">保存</el-button>
+      <el-button type="primary" @click="updateUserMenusConfigure">保存</el-button>
     </div>
   </div>
 </template>
 
 <script>
+import { updateUserMenusConfigure, fetchUserMenusConfigure } from '@/api/member'
 export default {
   data() {
     return {
-      listQuery: {
-        pacakgeId: ''
-      },
-      serviceList: [
-        {
-          id: 1,
-          demand: '认证授权图片模板',
-          isChecked: false
-        },
-        {
-          id: 2,
-          demand: '认证授权物料制作',
-          isChecked: false
-        },
-        {
-          id: 3,
-          demand: '品牌资料库',
-          isChecked: true
-        }
-      ]
+      isLoading: false,
+      authUserId: '',
+      serviceList: [],
+      roleIds: []
     }
   },
+  created() {
+    this.authUserId = parseInt(this.$route.query.id)
+    this.fetchUserMenusConfigure()
+  },
   methods: {
-    getList() {}
+    // 修改列表
+    updateUserMenusConfigure() {
+      this.roleIds = this.serviceList.filter(item => item.checkFlag === 1).map(item => item.roleId)
+      this.isLoading = true
+      updateUserMenusConfigure({ roleIds: this.roleIds.join(','), authUserId: this.authUserId }).then(res => {
+        this.$message.success('保存成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      }).finally(() => {
+        this.isLoading = false
+      })
+    },
+    // 获取列表
+    fetchUserMenusConfigure() {
+      this.isLoading = true
+      fetchUserMenusConfigure({
+        authUserId: this.authUserId
+      }).then(res => {
+        this.serviceList = res.data
+      }).finally(() => {
+        this.isLoading = false
+      })
+    }
   }
 }
 </script>

+ 83 - 46
src/views/admin/member/settings/service-edit.vue

@@ -1,28 +1,25 @@
 <template>
-  <div class="app-container">
+  <div v-loading="isLoading" class="app-container">
     <div class="list">
       <!-- 会员基础功能服务配置 -->
       <div class="section">
         <!-- 搜索区域 -->
         <div class="filter-container">
           <div class="filter-control tip">会员基础功能服务配置<span>(以下为会员基础拥有功能)</span></div>
-          <div class="filter-control">
-            <span>基础功能:</span>
-            <el-select v-model="listQuery.pacakgeId" clearable size="mini" @change="getList">
-              <el-option label="不限" value="" />
-              <el-option label="1年" :value="1" />
-              <el-option label="3年" :value="2" />
-            </el-select>
-          </div>
+          <div class="filter-control" />
         </div>
         <!-- 表格区域 -->
-        <el-table :data="serviceList" border fit height="600">
-          <el-table-column type="selection" width="55" />
+        <el-table :data="baseMenuList" border fit height="600" highlight-current-row @cell-click="handleCellClick">
+          <el-table-column label="选择" align="center" width="120">
+            <template slot-scope="{ row }">
+              <el-checkbox v-model="row.checked" :disabled="row.moveFlag === 0" />
+            </template>
+          </el-table-column>
           <el-table-column label="序号" type="index" width="80" align="center" />
-          <el-table-column label="定制需求" prop="demand" align="center" />
+          <el-table-column label="定制需求" prop="title" align="center" />
           <el-table-column label="是否配置" align="center" width="120">
             <template slot-scope="{ row }">
-              <el-checkbox v-model="row.isChecked" disabled />
+              <el-checkbox v-model="row.status" :true-label="0" :false-label="1" :disabled="row.moveFlag === 0" />
             </template>
           </el-table-column>
         </el-table>
@@ -31,10 +28,10 @@
       <!-- 交换按钮 -->
       <div class="switch-control">
         <el-tooltip class="item" effect="dark" content="将右侧勾选项加入左侧列表" placement="top">
-          <el-button type="primary" icon="el-icon-arrow-left" />
+          <el-button type="primary" icon="el-icon-arrow-left" @click="handleJoinLeft" />
         </el-tooltip>
         <el-tooltip class="item" effect="dark" content="将左侧勾选项加入右侧列表" placement="top">
-          <el-button type="primary" icon="el-icon-arrow-right" />
+          <el-button type="primary" icon="el-icon-arrow-right" @click="handleJoinRight" />
         </el-tooltip>
       </div>
 
@@ -43,23 +40,20 @@
         <!-- 搜索区域 -->
         <div class="filter-container">
           <div class="filter-control tip">会员订制功能服务配置<span>(以下为设置会员特定的订制服务功能)</span></div>
-          <div class="filter-control">
-            <span>基础功能:</span>
-            <el-select v-model="listQuery.pacakgeId" clearable size="mini" @change="getList">
-              <el-option label="不限" value="" />
-              <el-option label="1年" :value="1" />
-              <el-option label="3年" :value="2" />
-            </el-select>
-          </div>
+          <div class="filter-control" />
         </div>
         <!-- 表格区域 -->
-        <el-table :data="serviceList" border fit height="600">
-          <el-table-column type="selection" width="55" />
+        <el-table :data="upMenuList" border fit height="600" highlight-current-row @cell-click="handleCellClick">
+          <el-table-column label="选择" align="center" width="120">
+            <template slot-scope="{ row }">
+              <el-checkbox v-model="row.checked" :disabled="row.moveFlag === 0" />
+            </template>
+          </el-table-column>
           <el-table-column label="序号" type="index" width="80" align="center" />
-          <el-table-column label="定制需求" prop="demand" align="center" />
+          <el-table-column label="定制需求" prop="title" align="center" />
           <el-table-column label="是否配置" align="center" width="120">
             <template slot-scope="{ row }">
-              <el-checkbox v-model="row.isChecked" disabled />
+              <el-checkbox v-model="row.status" :true-label="0" :false-label="1" :disabled="row.moveFlag === 0" />
             </template>
           </el-table-column>
         </el-table>
@@ -69,39 +63,82 @@
     <!-- 确认 取消 -->
     <div class="control-box">
       <el-button type="warning">返回</el-button>
-      <el-button type="primary">保存</el-button>
+      <el-button type="primary" @click="updateVipMenusConfigure">保存</el-button>
     </div>
   </div>
 </template>
 
 <script>
+import { fetchVipMenusConfigure, updateVipMenusConfigure } from '@/api/member'
 export default {
   data() {
     return {
+      isLoading: false,
       listQuery: {
         pacakgeId: ''
       },
-      serviceList: [
-        {
-          id: 1,
-          demand: '认证授权图片模板',
-          isChecked: true
-        },
-        {
-          id: 2,
-          demand: '认证授权物料制作',
-          isChecked: true
-        },
-        {
-          id: 3,
-          demand: '品牌资料库',
-          isChecked: true
-        }
-      ]
+      baseMenuList: [],
+      upMenuList: []
     }
   },
+  created() {
+    this.fetchVipMenusConfigure()
+  },
   methods: {
-    getList() {}
+    getList() {},
+    // 获取菜单配置列表
+    fetchVipMenusConfigure() {
+      this.isLoading = true
+      fetchVipMenusConfigure().then(res => {
+        console.log(res)
+        this.baseMenuList = res.data.baseMenuList.map(item => {
+          item.checked = false
+          return item
+        })
+        this.upMenuList = res.data.upMenuList.map(item => {
+          item.checked = false
+          return item
+        })
+      }).finally(() => {
+        this.isLoading = false
+      })
+    },
+    // 修改菜单配置列表
+    updateVipMenusConfigure() {
+      this.isLoading = true
+      updateVipMenusConfigure({
+        upMenuList: this.upMenuList,
+        baseMenuList: this.baseMenuList
+      }).then(res => {
+        this.$message.success('保存配置成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      }).finally(() => {
+        this.isLoading = false
+      })
+    },
+    // 添加到左边
+    handleJoinLeft() {
+      const beforeJoinList = this.upMenuList.filter(item => item.checked)
+      this.upMenuList = this.upMenuList.filter(item => !item.checked)
+      this.baseMenuList.push(...beforeJoinList.map(item => {
+        item.checked = false
+        return item
+      }))
+    },
+    // 添加到右边
+    handleJoinRight() {
+      const beforeJoinList = this.baseMenuList.filter(item => item.checked)
+      this.baseMenuList = this.baseMenuList.filter(item => !item.checked)
+      this.upMenuList.push(...beforeJoinList.map(item => {
+        item.checked = false
+        return item
+      }))
+    },
+    handleCellClick(row) {
+      console.log(row)
+      row.checked = !row.checked
+    }
   }
 }
 </script>

+ 41 - 7
src/views/admin/settings/accounts/edit.vue

@@ -2,23 +2,23 @@
   <div class="app-container menus-edit">
     <el-form label-width="100px">
       <el-form-item label="用户头像:">
-        <el-input v-show="false" />
+        <el-input v-show="false" v-model="user.avatar" />
         <upload-image tip="建议尺寸:60 * 60px" />
       </el-form-item>
       <el-form-item label="姓名:">
-        <el-input placeholder="姓名" />
+        <el-input v-model="user.username" placeholder="姓名" />
       </el-form-item>
       <el-form-item label="登录名:">
-        <el-input placeholder="登录名" />
+        <el-input v-model="user.fullName" placeholder="登录名" />
       </el-form-item>
       <el-form-item label="密码:">
-        <el-input placeholder="密码" />
+        <el-input v-model="user.password" placeholder="密码" />
       </el-form-item>
       <el-form-item label="手机号:">
-        <el-input placeholder="手机号" />
+        <el-input v-model="user.phone" placeholder="手机号" />
       </el-form-item>
       <el-form-item label="状态:">
-        <el-radio-group>
+        <el-radio-group v-model="user.loginFlag">
           <el-radio>启用</el-radio>
           <el-radio>停用</el-radio>
         </el-radio-group>
@@ -45,13 +45,47 @@
 
 <script>
 import UploadImage from '@/components/UploadImage'
+import { fetchRoleList } from '@/api/system'
 export default {
   components: {
     UploadImage
   },
   data() {
     return {
-      checkList: []
+      user: {
+        id: 0,
+        username: '',
+        password: '',
+        fullName: '',
+        phone: '',
+        avatar: 'https://img.caimei365.com/group1/M00/03/ED/rB-lGGHpCyKAGi-aAAAYvvHB_HE522.gif',
+        roleIds: '',
+        loginFlag: 0
+      },
+      roleIdList: [],
+      roleList: [],
+      rules: {
+        username: [
+          { required: true, message: '请输入登录名', trigger: 'blur' },
+          { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
+        ],
+        password: [
+          { required: true, message: '请输入密码', trigger: 'blur' },
+          { min: 6, max: 30, message: '长度在 6 到 30 个字符', trigger: 'blur' }
+        ],
+        fullName: [
+          { required: true, message: '请输入姓名', trigger: 'blur' },
+          { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  methods: {
+    // 获取角色列表
+    fetchRoleList() {
+      fetchRoleList(this.listQuery).then(res => {
+        this.list = [...this.list, ...res.data.list]
+      })
     }
   }
 }

+ 36 - 0
src/views/admin/settings/menus/children-list.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="app-container">
+    <el-page-header :content="title" @back="goBack" />
+    <table-list :menu-type="menuType" />
+  </div>
+</template>
+
+<script>
+import TableList from './components/table-list.vue'
+export default {
+  components: {
+    TableList
+  },
+  data() {
+    return {
+      menuType: 1,
+      title: ''
+    }
+  },
+  created() {
+    this.menuType = parseInt(this.$route.params.menuType)
+    this.title = `所属父级菜单:${this.$route.params.title}`
+  },
+  methods: {
+    goBack() {
+      this.$router.back()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.el-page-header{
+  margin-bottom: 16px;
+}
+</style>

+ 194 - 0
src/views/admin/settings/menus/components/table-list.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="table-list">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>菜单状态:</span>
+        <el-select
+          v-model="listQuery.status"
+          placeholder="启用状态"
+          clearable
+          style="width: 200px"
+          class="filter-item"
+          @change="getList"
+        >
+          <el-option label="全部" value="" />
+          <el-option label="已启用" :value="0" />
+          <el-option label="未启用" :value="1" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="handleAddMenu()">添加菜单</el-button>
+      </div>
+    </div>
+    <!-- 搜索区域END -->
+    <el-table
+      v-loading="listLoading"
+      :data="list"
+      style="width: 100%"
+      border
+      fit
+      header-row-class-name="tableHeader"
+      class="table-cell"
+    >
+      <el-table-column :index="indexMethod" label="序号" type="index" width="80" align="center" />
+      <el-table-column prop="title" label="菜单名称" align="center" />
+      <el-table-column prop="name" label="路由名称" align="center" />
+      <el-table-column label="展示图标" align="center" width="100">
+        <template slot-scope="{ row }">
+          <i v-if="row.icon" :class="row.icon" />
+          <span v-else>无</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="启用状态" align="center" width="100">
+        <template slot-scope="{ row }">
+          <el-switch v-model="row.status" :active-value="0" :inactive-value="1" @change="updateMenuStatus(row)" />
+        </template>
+      </el-table-column>
+      <el-table-column prop="sort" label="排序" align="center" width="100">
+        <template slot-scope="{ row }">
+          <el-input v-model="row.sort" @change="updateMenuSort(row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="子菜单管理" align="center" width="240">
+        <template slot-scope="{ row }">
+          <el-button size="mini" @click="handleSearchChildren(row)">查看子菜单</el-button>
+          <el-button size="mini" @click="handleAddMenu(row)">添加子菜单</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="200">
+        <template slot-scope="{ row }">
+          <el-button
+            type="primary"
+            size="mini"
+            @click="$_navigationTo(`/settings/menus/edit?id=${row.id}&type=edit`)"
+          >编辑</el-button>
+          <el-button type="danger" size="mini" @click="deleteMenu(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { fetchMenuList, deleteMenu, updateMenuSelective } from '@/api/system'
+export default {
+  name: 'TableList',
+  props: {
+    menuType: {
+      type: Number,
+      default: 1
+    }
+  },
+  data() {
+    return {
+      listLoading: false,
+      // 查询参数
+      listQuery: {
+        parentId: '',
+        menuType: '', // 1:管理员 2:供应商
+        status: '',
+        pageSize: 10,
+        pageNum: 1
+      },
+      // 菜单列表
+      list: []
+    }
+  },
+  created() {
+    this.initQuery()
+    this.getList()
+  },
+  methods: {
+    // 初始化参数
+    initQuery() {
+      const { id = '', title = '' } = this.$route.params
+      this.listQuery.menuType = this.menuType
+      this.parentTitle = title
+      this.listQuery.parentId = id
+    },
+    // 添加菜单
+    handleAddMenu(row) {
+      if (row) {
+        this.$router.push({
+          path: '/settings/menus/children/add',
+          query: {
+            type: 'add',
+            parentId: row.id,
+            menuType: this.menuType
+          }
+        })
+      } else if (this.listQuery.parentId) {
+        this.$router.push({
+          path: '/settings/menus/children/add',
+          query: {
+            type: 'add',
+            parentId: this.listQuery.parentId,
+            menuType: this.menuType
+          }
+        })
+      } else {
+        this.$router.push({
+          path: '/settings/menus/add',
+          query: {
+            type: 'add',
+            menuType: this.menuType
+          }
+        })
+      }
+    },
+    // 查看子菜单
+    handleSearchChildren(row) {
+      this.listQuery.parentId = row.id
+      this.$router.replace(`/settings/menus/children/${this.menuType}/${row.id}/${row.title}`)
+    },
+    // 获取列表
+    getList() {
+      this.listQuery.pageNum = 1
+      this.list = []
+      this.fetchMenuList()
+    },
+    // 获取菜单列表
+    fetchMenuList() {
+      fetchMenuList(this.listQuery).then(res => {
+        this.list = [...this.list, ...res.data.list]
+      })
+    },
+    // 更新状态
+    updateMenuStatus(row) {
+      updateMenuSelective(row.id, { status: row.status }).then(() => {
+        this.$message.success('操作成功')
+      })
+    },
+    // 更新排序
+    updateMenuSort(row) {
+      updateMenuSelective(row.id, { sort: row.sort }).then(() => {
+        this.$message.success('操作成功')
+        this.getList()
+      })
+    },
+    // 删除菜单
+    deleteMenu(row) {
+      this.$confirm('此操作将永久删除该菜单及其子菜单, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          deleteMenu(row.id).then(res => {
+            this.$message.success('删除成功')
+            this.getList()
+          })
+        })
+        .catch(() => {
+          this.$message.info('已取消删除')
+        })
+    },
+    // 表格索引
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style scoped></style>

+ 194 - 25
src/views/admin/settings/menus/edit.vue

@@ -1,46 +1,73 @@
 <template>
   <div class="app-container menus-edit">
-    <el-form label-width="100px">
-      <el-form-item label="上级菜单:">
-        <el-input placeholder="上级菜单" />
+    <el-form ref="menuFrom" label-width="100px" :model="menuData" :rules="rules">
+      <el-form-item v-if="parentMenuData" label="上级菜单:">
+        <el-input v-model="parentMenuData.title" placeholder="上级菜单" readonly />
       </el-form-item>
-      <el-form-item label="菜单名称:">
-        <el-input placeholder="菜单名称" />
+      <el-form-item label="菜单名称:" prop="title">
+        <el-input v-model="menuData.title" placeholder="菜单名称" />
       </el-form-item>
-      <el-form-item label="路由名称:">
-        <el-input placeholder="路由名称" />
+      <el-form-item label="路由名称:" prop="name">
+        <el-input v-model="menuData.name" placeholder="路由名称" />
       </el-form-item>
       <el-form-item label="显示隐藏:">
-        <el-radio-group>
-          <el-radio>显示</el-radio>
-          <el-radio>隐藏</el-radio>
+        <el-radio-group v-model="menuData.hidden">
+          <el-radio :label="0">显示</el-radio>
+          <el-radio :label="1">隐藏</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="前端图标:">
-        <el-input placeholder="前端图标" />
+        <el-input v-model="menuData.icon" style="width: 80%" placeholder="系统图标里拷贝Class" />
+        <template>
+          <i v-if="isElementIcon(menuData.icon)" :class="menuData.icon" />
+          <svg-icon v-else-if="menuData.icon" :icon-class="menuData.icon" />
+        </template>
       </el-form-item>
+      <template v-if="!parentMenuData">
+        <el-form-item label="功能类型:" prop="baseFlag">
+          <el-radio-group v-model="menuData.baseFlag">
+            <el-radio :label="1">基础功能</el-radio>
+            <el-radio :label="0">会员特定</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="功能切换:" prop="moveFlag">
+          <el-radio-group v-model="menuData.moveFlag">
+            <el-radio :label="1">允许</el-radio>
+            <el-radio :label="0">禁止</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </template>
       <el-form-item label="状态:">
-        <el-radio-group>
-          <el-radio>启用</el-radio>
-          <el-radio>停用</el-radio>
+        <el-radio-group v-model="menuData.status">
+          <el-radio :label="0">启用</el-radio>
+          <el-radio :label="1">停用</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="排序:">
-        <el-input placeholder="排序" />
+        <el-input v-model="menuData.sort" />
       </el-form-item>
       <el-form-item label="按钮权限:">
         <template>
-          <el-table border>
-            <el-table-column label="按钮名称" align="center" />
-            <el-table-column label="权限标识" align="center" />
+          <el-input v-show="false" v-model="menuData.permissionJson" />
+          <el-table border :data="permissions">
+            <el-table-column label="按钮名称" align="center">
+              <template slot-scope="{row}">
+                <el-input v-model="row.title" size="mini" />
+              </template>
+            </el-table-column>
+            <el-table-column label="权限标识" align="center">
+              <template slot-scope="{row}">
+                <el-input v-model="row.name" size="mini" />
+              </template>
+            </el-table-column>
             <el-table-column label="操作" align="center">
-              <template>
-                <el-button type="danger">删除按钮</el-button>
+              <template slot-scope="{ row }">
+                <el-button type="danger" size="mini" @click="removeOne(row.uuid)">删除按钮</el-button>
               </template>
             </el-table-column>
           </el-table>
           <div style="text-align:center;margin-top: 24px">
-            <el-button type="primary" size="mini">添加按钮</el-button>
+            <el-button type="primary" size="mini" @click="insertOne">添加按钮</el-button>
           </div>
           <el-divider />
         </template>
@@ -48,22 +75,164 @@
       <el-form-item>
         <!-- 确认 取消 -->
         <div class="control-box">
-          <el-button type="primary">保存</el-button>
+          <el-button type="primary" @click="submit">保存</el-button>
           <el-button type="info">重置</el-button>
           <el-button type="warning">返回</el-button>
         </div>
       </el-form-item>
     </el-form>
-
   </div>
 </template>
 
 <script>
-export default {}
+import { createMenu, getMenu, updateMenu } from '@/api/system'
+let uuid = 0
+export default {
+  data() {
+    return {
+      // 编辑类型
+      editType: 'add',
+      // 上级菜单信息
+      parentMenuData: null,
+      // 表单参数
+      menuData: {
+        id: '',
+        title: '',
+        name: '',
+        icon: '',
+        hidden: 0,
+        status: 0,
+        sort: 0,
+        childCount: 0,
+        permissionJson: '',
+        parentId: 0,
+        baseFlag: 1,
+        moveFlag: 1,
+        menuType: 1
+      },
+      // 按钮权限列表
+      permissions: [],
+      // 验证规则
+      rules: {
+        title: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
+        name: [{ required: true, message: '路由名称不能为空', trigger: 'blur' }]
+      }
+    }
+  },
+  watch: {
+    // 处理权限列表
+    permissions(nVal) {
+      if (this.permissions.length > 0) {
+        this.menuData.permissionJson = JSON.stringify(this.permissions)
+      } else {
+        this.menuData.permissionJson = ''
+      }
+    }
+  },
+  created() {
+    this.editType = this.$route.query.type
+    this.init()
+  },
+  methods: {
+    init() {
+      // 编辑修改菜单
+      if (this.editType === 'edit') {
+        this.menuData.id = parseInt(this.$route.query.id)
+        this.fetchMenuInfo()
+      }
+      // 添加菜单
+      if (this.editType === 'add') {
+        this.menuData.parentId = parseInt(this.$route.query.parentId)
+        this.menuData.menuType = parseInt(this.$route.query.menuType)
+        if (this.menuData.parentId > 0) {
+          this.fetchParentMenuInfo()
+        }
+      }
+    },
+    // 获取父菜单信息
+    fetchParentMenuInfo() {
+      getMenu(this.menuData.parentId).then(res => {
+        this.parentMenuData = res.data
+        console.log(res.data)
+      })
+    },
+    // 获取菜单数据
+    fetchMenuInfo() {
+      getMenu(this.menuData.id).then(res => {
+        console.log(res)
+        this.setMenuData(res.data)
+      })
+    },
+    // 设置菜单数据
+    setMenuData(data) {
+      for (const key in this.menuData) {
+        if (Object.hasOwnProperty.call(data, key)) {
+          this.menuData[key] = data[key]
+        }
+      }
+      // 按钮权限列表
+      if (this.permissionJson) {
+        this.permissions = JSON.parse(this.permissionJson)
+      }
+      // 父级菜单信息
+      if (this.menuData.parentId > 0) {
+        this.fetchParentMenuInfo()
+      }
+    },
+    // 判断图标类型
+    isElementIcon(value) {
+      return value && value.substr(0, 7) === 'el-icon'
+    },
+    // 提交
+    submit() {
+      console.log(this.menuData)
+      this.$refs.menuFrom.validate(valide => {
+        if (!valide) return
+        if (this.editType === 'add') {
+          this.createMenu()
+        } else {
+          this.updateMenu()
+        }
+      })
+    },
+    // 修改菜单
+    updateMenu() {
+      updateMenu(this.menuData.id, this.menuData).then(res => {
+        this.$message.success('修改菜单成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
+    },
+    // 添加菜单
+    createMenu() {
+      console.log(this.menuData)
+      // return
+      createMenu(this.menuData).then(res => {
+        console.log(res)
+        this.$message.success('添加菜单成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
+    },
+    // 添加按钮权限
+    insertOne() {
+      this.permissions.push({
+        uuid: ++uuid,
+        name: '',
+        title: ''
+      })
+    },
+    // 移除一条按钮权限
+    removeOne(uuid) {
+      const index = this.permissions.findIndex(item => item.uuid === uuid)
+      this.permissions.splice(index, 1)
+    }
+  }
+}
 </script>
 
 <style scoped lang="scss">
-.menus-edit{
+.menus-edit {
   width: 600px;
   margin: 0 auto;
   margin-top: 40px;

+ 35 - 91
src/views/admin/settings/menus/index.vue

@@ -1,107 +1,51 @@
 <template>
   <div class="app-container">
-    <div class="filter-container">
-      <div class="filter-control">
-        <span>菜单状态:</span>
-        <el-select
-          v-model="listQuery.status"
-          placeholder="启用状态"
-          clearable
-          style="width: 200px"
-          class="filter-item"
-          @change="getList()"
-        >
-          <el-option label="全部" value="" />
-          <el-option label="已启用" :value="0" />
-          <el-option label="未启用" :value="1" />
-        </el-select>
-      </div>
-      <div class="filter-control">
-        <el-button type="primary" @click="$_navigationTo('/settings/menus/add')">添加菜单</el-button>
-      </div>
-    </div>
-    <!-- 搜索区域END -->
-    <el-table
-      v-loading="listLoading"
-      :data="list"
-      style="width: 100%"
-      border
-      fit
-      header-row-class-name="tableHeader"
-      class="table-cell"
-    >
-      <el-table-column :index="indexMethod" label="序号" type="index" width="80" align="center" />
-      <!-- <el-table-column prop="title" label="菜单级别" align="center">
-        <template slot-scope="{ row }">{{ row.parentId ? '二级' : '一级' }}</template>
-      </el-table-column> -->
-      <el-table-column prop="title" label="菜单名称" align="center" />
-      <el-table-column prop="name" label="路由名称" align="center" />
-      <el-table-column label="展示图标" align="center" width="100">
-        <template slot-scope="{ row }">
-          <i v-if="row.icon" :class="row.icon" />
-          <span v-else>无</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="启用状态" align="center" width="100">
-        <template slot-scope="{ row }">
-          <el-switch v-model="row.status" :active-value="1" :inactive-value="0" />
-        </template>
-      </el-table-column>
-      <el-table-column prop="sort" label="排序" align="center" width="100">
-        <template slot-scope="{ row }">
-          <el-input v-model="row.sort" />
-        </template>
-      </el-table-column>
-      <el-table-column label="子菜单管理" align="center" width="240">
-        <template>
-          <el-button size="mini" @click="$_navigationTo('/settings/menus/children/add')">添加子菜单</el-button>
-          <el-button size="mini" @click="$_navigationTo(`/settings/menus/children?id=${Math.random()}`)">查看子菜单</el-button>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="200">
-        <template>
-          <el-button type="primary" size="mini" @click="$_navigationTo('menus/edit')">编辑</el-button>
-          <el-button type="danger" size="mini">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+    <el-tabs v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane v-for="item in menusCategory" :key="item.menuType" :label="item.title" :name="item.name" :lazy="true">
+        <table-list :key="item.menuType" :menu-type="item.menuType" />
+      </el-tab-pane>
+      <!-- <el-tab-pane label="供应商" name="second">配置管理</el-tab-pane> -->
+    </el-tabs>
   </div>
 </template>
 
 <script>
+import TableList from './components/table-list.vue'
 export default {
+  components: {
+    TableList
+  },
   data() {
     return {
-      listLoading: false,
-      listQuery: {
-        status: '',
-        pageSize: 10,
-        pageNum: 1
-      },
-      list: [{
-        childCount: 4,
-        createTime: '2021-12-27 16:49:41',
-        delFlag: 0,
-        hidden: 0,
-        icon: 'el-icon-s-tools',
-        id: 1,
-        name: 'SysSetting',
-        parentId: 0,
-        permissionJson: null,
-        permissions: null,
-        sort: 0,
-        status: 1,
-        title: '系统设置'
-      }]
+      activeName: 'manager',
+      menusCategory: [
+        {
+          id: 1,
+          title: '管理员菜单管理',
+          name: 'manager',
+          menuType: 1
+        },
+        {
+          id: 2,
+          title: '供应商管理',
+          name: 'supplier',
+          menuType: 2
+        }
+      ]
     }
   },
+  created() {
+    this.activeName = this.$route.query.activeName || 'manager'
+  },
   methods: {
-    indexMethod(index) {
-      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
-    },
-    getList() {}
+    handleClick() {}
   }
 }
 </script>
 
-<style scoped></style>
+<style scoped>
+.page-title {
+  font-size: 18px;
+  color: #404040;
+}
+</style>

+ 82 - 57
src/views/admin/settings/roles/edit.vue

@@ -1,27 +1,29 @@
 <template>
   <div class="app-container roles-edit">
-    <el-form label-width="100px">
-      <el-form-item label="角色名称:">
-        <el-input placeholder="角色名称" />
+    <el-form ref="ruleRef" label-width="100px" :model="role" :rules="rules">
+      <el-form-item label="角色名称:" prop="roleName">
+        <el-input v-model="role.roleName" placeholder="角色名称" maxlength="20" />
       </el-form-item>
-      <el-form-item label="角色描述:">
-        <el-input placeholder="角色描述" />
+      <el-form-item label="角色描述:" prop="roleDesc">
+        <el-input v-model="role.roleDesc" placeholder="角色描述" maxlength="20" />
       </el-form-item>
-      <el-form-item label="角色授权:">
+      <el-form-item label="角色授权:" prop="menuIds">
+        <el-input v-show="false" v-model="role.menuIds" />
         <el-tree
           ref="tree"
-          :data="data"
+          :data="menuTree"
           show-checkbox
           default-expand-all
           node-key="id"
           highlight-current
           :props="defaultProps"
+          @check-change="getCheckedKeys"
         />
       </el-form-item>
       <el-form-item>
         <!-- 确认 取消 -->
         <div class="control-box">
-          <el-button type="primary">保存</el-button>
+          <el-button type="primary" @click="handleSubmit">保存</el-button>
           <el-button type="info">重置</el-button>
           <el-button type="warning">返回</el-button>
         </div>
@@ -31,71 +33,94 @@
 </template>
 
 <script>
+import { sysMenuTree, createRole, getRole, updateRole } from '@/api/system'
 export default {
   data() {
     return {
-      data: [{
-        id: 1,
-        label: '一级 1',
-        children: [{
-          id: 4,
-          label: '二级 1-1',
-          children: [{
-            id: 9,
-            label: '三级 1-1-1'
-          }, {
-            id: 10,
-            label: '三级 1-1-2'
-          }]
-        }]
-      }, {
-        id: 2,
-        label: '一级 2',
-        children: [{
-          id: 5,
-          label: '二级 2-1'
-        }, {
-          id: 6,
-          label: '二级 2-2'
-        }]
-      }, {
-        id: 3,
-        label: '一级 3',
-        children: [{
-          id: 7,
-          label: '二级 3-1'
-        }, {
-          id: 8,
-          label: '二级 3-2'
-        }]
-      }],
+      editType: '',
+      role: {
+        id: '',
+        roleName: '',
+        roleDesc: '',
+        menuIds: ''
+      },
+      rules: {
+        roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
+        menuIds: [{ required: true, message: '角色授权不能为空', trigger: 'change' }]
+      },
+      menuTree: [],
       defaultProps: {
-        children: 'children',
-        label: 'label'
+        children: 'subMenus',
+        label: 'title'
       }
     }
   },
+  created() {
+    this.editType = this.$route.query.type
+    if (this.editType === 'edit') {
+      this.role.id = parseInt(this.$route.query.id)
+      this.fetchRoleInfo()
+    }
+    this.getMenuTree()
+  },
   methods: {
     getCheckedNodes() {
       console.log(this.$refs.tree.getCheckedNodes())
     },
     getCheckedKeys() {
       console.log(this.$refs.tree.getCheckedKeys())
+      this.role.menuIds = this.$refs.tree.getCheckedKeys().join(',')
+    },
+    // 获取菜单权限列表
+    getMenuTree() {
+      sysMenuTree().then(res => {
+        this.menuTree = res.data
+      })
+    },
+    // 设置角色信息
+    setRoleInfo(data) {
+      for (const key in this.role) {
+        if (Object.hasOwnProperty.call(this.role, key)) {
+          this.role[key] = data[key]
+        }
+      }
+      this.$refs.tree.setCheckedKeys([...this.role.menuIds.split(',')])
+    },
+    // 获取角色信息
+    fetchRoleInfo() {
+      getRole(this.role.id).then(res => {
+        console.log(res.data)
+        this.setRoleInfo(res.data)
+      })
     },
-    setCheckedNodes() {
-      this.$refs.tree.setCheckedNodes([{
-        id: 5,
-        label: '二级 2-1'
-      }, {
-        id: 9,
-        label: '三级 1-1-1'
-      }])
+    // 修改角色
+    updateRole() {
+      updateRole(this.role.id, this.role).then(res => {
+        console.log(res)
+        this.$message.success('修改角色成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
     },
-    setCheckedKeys() {
-      this.$refs.tree.setCheckedKeys([3])
+    // 创建角色
+    createRole() {
+      createRole(this.role).then(res => {
+        console.log(res)
+        this.$message.success('添加角色成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
     },
-    resetChecked() {
-      this.$refs.tree.setCheckedKeys([])
+    // 提交
+    handleSubmit() {
+      this.$refs.ruleRef.validate(valide => {
+        if (!valide) return
+        if (this.editType === 'add') {
+          this.createRole()
+        } else {
+          this.updateRole()
+        }
+      })
     }
   }
 }

+ 47 - 19
src/views/admin/settings/roles/index.vue

@@ -2,7 +2,7 @@
   <div class="app-container">
     <div class="filter-container">
       <div class="filter-control">
-        <el-button type="primary" @click="$_navigationTo('roles/add')">添加角色</el-button>
+        <el-button type="primary" @click="$_navigationTo('roles/add?type=add')">添加角色</el-button>
       </div>
     </div>
     <!-- 搜索区域END -->
@@ -17,21 +17,28 @@
     >
       <el-table-column :index="indexMethod" label="序号" type="index" width="80" align="center" />
       <el-table-column prop="roleName" label="角色名" align="center" />
-      <el-table-column prop="roleDesc" label="角色描述" align="center" />
+      <el-table-column label="角色描述" align="center">
+        <template slot-scope="{ row }">
+          <span v-if="row.roleDesc">{{ row.roleDesc }}</span>
+          <span v-else>—</span>
+        </template>
+      </el-table-column>
       <el-table-column label="创建时间" align="center">
         <template slot-scope="{ row }">
-          <span>{{ row.createTime | formatTime }}</span>
+          <span v-if="row.createTime">{{ row.createTime | formatTime }}</span>
+          <span v-else>—</span>
         </template>
       </el-table-column>
       <el-table-column label="更新时间" align="center">
         <template slot-scope="{ row }">
-          <span>{{ row.updateTime | formatTime }}</span>
+          <span v-if="row.updateTime">{{ row.updateTime | formatTime }}</span>
+          <span v-else>—</span>
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center" width="200">
-        <template>
-          <el-button type="primary" size="mini" @click="$_navigationTo('roles/edit')">编辑</el-button>
-          <el-button type="danger" size="mini">删除</el-button>
+        <template slot-scope="{ row }">
+          <el-button type="primary" size="mini" @click="$_navigationTo(`roles/edit?type=edit&id=${row.id}`)">编辑</el-button>
+          <el-button type="danger" size="mini" @click="removeRole(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -40,6 +47,7 @@
 
 <script>
 import { formatDate } from '@/utils'
+import { fetchRoleList, deleteRole } from '@/api/system'
 export default {
   filters: {
     formatTime(time) {
@@ -57,24 +65,44 @@ export default {
         pageSize: 10,
         pageNum: 1
       },
-      list: [
-        {
-          createTime: '2021-12-27 16:45:33',
-          delFlag: 0,
-          id: 1,
-          menuIds: null,
-          roleDesc: '管理员',
-          roleName: 'admin',
-          updateTime: '2022-02-09 04:19:51'
-        }
-      ]
+      list: []
     }
   },
+  created() {
+    this.fetchRoleList()
+  },
   methods: {
     indexMethod(index) {
       return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
     },
-    getList() {}
+    getList() {
+      this.list = []
+      this.listQuery.pageNum = 1
+      this.fetchRoleList()
+    },
+    // 获取角色列表
+    fetchRoleList() {
+      fetchRoleList(this.listQuery).then(res => {
+        this.list = [...this.list, ...res.data.list]
+      })
+    },
+    // 删除角色
+    removeRole(row) {
+      this.$confirm('此操作将永久删除该角色, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          deleteRole(row.id).then(res => {
+            this.$message.success('删除角色成功')
+            this.getList()
+          })
+        })
+        .catch(() => {
+          this.$message.info('已取消删除')
+        })
+    }
   }
 }
 </script>

+ 2 - 0
src/views/common/login/index.vue

@@ -144,6 +144,8 @@ export default {
             path: this.redirect || initGoPage(),
             query: this.otherQuery
           })
+        }).finally(() => {
+          this.loading = false
         })
       })
     },

+ 43 - 0
src/views/common/payment/faild.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="pay-status">
+    <empty title="支付失败" :image="payFaild" color="#FF6D6D" />
+    <div class="info">
+      <span>待付金额:</span>
+      <span class="price">¥{{ payAmount | formatPrice }}</span>
+    </div>
+  </div>
+</template>
+
+<script>
+import Empty from '@/components/Empty'
+import payFaild from '@/assets/pay/pay-faild.png'
+export default {
+  components: {
+    Empty
+  },
+  data() {
+    return {
+      payFaild,
+      payAmount: ''
+    }
+  },
+  created() {
+    this.payAmount = this.$route.query.payAmount
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.pay-status{
+  margin-top: 160px;
+}
+.info{
+  text-align: center;
+  margin-top: 16px;
+  font-size: 14px;
+  .price{
+    font-size: 20px;
+    color: #FF6D6D
+  }
+}
+</style>

+ 43 - 0
src/views/common/payment/success.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="pay-status">
+    <empty title="支付成功" :image="paySuccess" color="#1890FF" />
+    <div class="info">
+      <span>支付金额:</span>
+      <span class="price">¥{{ payAmount | formatPrice }}</span>
+    </div>
+  </div>
+</template>
+
+<script>
+import Empty from '@/components/Empty'
+import paySuccess from '@/assets/pay/pay-success.png'
+export default {
+  components: {
+    Empty
+  },
+  data() {
+    return {
+      paySuccess,
+      payAmount: ''
+    }
+  },
+  created() {
+    this.payAmount = this.$route.query.payAmount
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.pay-status{
+  margin-top: 160px;
+}
+.info{
+  text-align: center;
+  margin-top: 16px;
+  font-size: 14px;
+  .price{
+    font-size: 20px;
+    color: #FF6D6D
+  }
+}
+</style>

+ 1 - 61
src/views/components/PermissionButton/index.vue

@@ -1,17 +1,5 @@
 <template>
-  <el-button
-    :size="size"
-    :type="type"
-    :plain="plain"
-    :round="round"
-    :circle="circle"
-    :loading="loading"
-    :disabled="disabled"
-    :icon="icon"
-    :autofocus="autofocus"
-    :native-type="nativeType"
-    @click="handleClick"
-  ><slot /></el-button>
+  <el-button v-bind="$attrs" v-on="$listeners"><slot /></el-button>
 </template>
 
 <script>
@@ -19,54 +7,6 @@ import checkPermission from '@/utils/permission'
 import jumpAction from './jumpAction'
 export default {
   name: 'PermissionButton',
-  props: {
-    permission: {
-      type: Array,
-      default: () => []
-    },
-    size: {
-      type: String,
-      default: 'medium',
-      validator: value => ['medium', 'small', 'mini'].indexOf(value) !== -1
-    },
-    type: {
-      type: String,
-      default: 'primary',
-      validator: value => ['primary', 'success', 'warning', 'danger', 'info', 'text'].indexOf(value) !== -1
-    },
-    plain: {
-      type: Boolean,
-      default: false
-    },
-    round: {
-      type: Boolean,
-      default: false
-    },
-    circle: {
-      type: Boolean,
-      default: false },
-    loading: {
-      type: Boolean,
-      default: false
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    icon: {
-      type: String,
-      default: ''
-    },
-    autofocus: {
-      type: Boolean,
-      default: false
-    },
-    nativeType: {
-      type: String,
-      default: 'button',
-      validator: value => ['button', 'submit', 'reset'].indexOf(value) !== -1
-    }
-  },
   data() {
     return {
       hasPermission: true

+ 159 - 0
src/views/components/RadioCard/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="radio-card">
+    <!-- 单选 -->
+    <el-radio-group v-if="type === 'radio'" v-model="radio" @change="value => $emit('change', value)">
+      <el-radio v-for="item in list" :key="item.id" :label="item.id" :style="{ marginRight: gutter,marginBottom: gutter }">
+        <div
+          class="radio__card"
+          :class="{
+            radio__active: item.id === radio,
+            redio__hidden_icon: !showUnActiveIcon && item.id !== radio,
+            radio__border: border
+          }"
+        >
+          <slot :row="item" name="item" />
+        </div>
+      </el-radio>
+    </el-radio-group>
+    <!-- 多选 -->
+    <el-checkbox-group v-else v-model="checkList" @change="value => $emit('change', value)">
+      <el-checkbox v-for="item in list" :key="item.id" :label="item.id" :style="{ marginRight: gutter ,marginBottom: gutter }">
+        <div class="radio__card" :class="objectClass(item.id)">
+          <slot :row="item" name="item" />
+        </div>
+      </el-checkbox>
+    </el-checkbox-group>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RadioCard',
+  model: {
+    event: 'change',
+    prop: 'value'
+  },
+  props: {
+    type: {
+      type: String,
+      default: 'radio'
+    },
+    list: {
+      type: Array,
+      default: () => [
+        {
+          id: 1
+        },
+        {
+          id: 2
+        },
+        {
+          id: 3
+        }
+      ]
+    },
+    value: {
+      type: [Number, String, Array],
+      default: ''
+    },
+    border: {
+      type: Boolean,
+      default: true
+    },
+    borderColor: {
+      type: String,
+      default: '#ff6d6d'
+    },
+    showUnActiveIcon: {
+      type: Boolean,
+      default: true
+    },
+    gutter: {
+      type: String,
+      default: '16px'
+    }
+  },
+  data() {
+    return {
+      radio: 0,
+      checkList: []
+    }
+  },
+  watch: {
+    value() {
+      this.radio = this.checkList = this.value
+    }
+  },
+  methods: {
+    objectClass(id) {
+      return {
+        radio__active: this.checkList.includes(id),
+        redio__hidden_icon: !this.showUnActiveIcon && this.checkList.includes(id),
+        radio__border: this.border
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.radio-card >>> .el-radio__input,
+.radio-card >>> .el-checkbox__input {
+  display: none;
+}
+.radio-card >>> .el-radio__label,
+.radio-card >>> .el-checkbox__label {
+  padding: 0;
+}
+</style>
+
+<style scoped lang="scss">
+.radio-card {
+  .el-checkbox,
+  .el-radio {
+    &:last-of-type {
+      margin-right: 0 !important;
+    }
+  }
+
+  .radio__card {
+    position: relative;
+    box-sizing: border-box;
+    border-radius: 4px;
+
+    // 图标样式(未选中)
+    &::after {
+      content: '';
+      display: block;
+      position: absolute;
+      width: 26px;
+      height: 26px;
+      background: url(~@/assets/img/icon-select.png) no-repeat center;
+      right: 0;
+      bottom: 0;
+    }
+
+    &.radio__border {
+      border-width: 1px;
+      border-style: solid;
+      border-color: #d8d8d8;
+    }
+
+    // 选中时图标样式
+    &.radio__active {
+      &::after {
+        background: url(~@/assets/img/icon-select-active.png) no-repeat center;
+      }
+      &.radio__border {
+        border-color: #ff6d6d;
+      }
+    }
+    // 隐藏选中图标
+    &.redio__hidden_icon {
+      &::after {
+        display: none;
+      }
+    }
+  }
+}
+</style>

+ 72 - 3
src/views/components/payment/pay-alipay.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="pay-code">
-    <div class="paymount">扫码付款:<span class="exp">¥</span><span class="price">5980.00</span></div>
-    <el-image src="https://picsum.photos/200/200" class="qrcode" />
+    <div class="paymount">扫码付款:<span class="exp">¥</span><span class="price">{{ data.payAmount | formatPrice }}</span></div>
+    <el-image v-loading="isLoading" :src="payUrl" class="qrcode" />
     <div class="pay-tip">
       <i class="icon-alipay" />
       <span>使用支付宝扫码</span>
@@ -10,7 +10,76 @@
 </template>
 
 <script>
-export default {}
+import qrcode from 'qrcode'
+import { payVipWithAlipay } from '@/api/pay'
+export default {
+  props: {
+    data: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      payData: {},
+      isLoading: false,
+      payUrl: '',
+      timer: null
+    }
+  },
+  created() {
+    this.init()
+  },
+  beforeDestroy() {
+    clearInterval(this.timer)
+  },
+  methods: {
+    init() {
+      const params = {
+        bankCode: 'ALIPAY',
+        returnUrl: 'https://www.caimei365.com/',
+        vipRecordId: this.data.vipRecordId
+      }
+      this.isLoading = true
+      payVipWithAlipay(params).then(res => {
+        const result = JSON.parse(res.data)
+        if (result.code === '000000') {
+          this.payData = result.data
+          this.drawQrcode()
+        } else {
+          this.$message.error('支付系统遇到点小问题,请稍后重试')
+          this.isLoading = false
+        }
+      })
+    },
+    // 绘制支付二维码
+    drawQrcode() {
+      qrcode
+        .toDataURL(this.payData.payUrl, { width: 180, margin: 0, errorCorrectionLevel: 'H', type: 'image/jpeg', scale: 4 })
+        .then(url => {
+          this.payUrl = url
+          this.checkedPayConfirm()
+        })
+        .catch(err => {
+          console.error(err)
+        }).finally(() => {
+          this.isLoading = false
+        })
+    },
+    // 验证确认支付
+    checkedPayConfirm() {
+      this.timer = setInterval(() => {
+        this.$emit('pay-confirm', {
+          type: 'ALIPAY',
+          data: {
+            ...this.payData,
+            ...this.data
+          }
+        })
+      }, 1000)
+    }
+  }
+}
 </script>
 
 <style scoped lang="scss">

+ 188 - 10
src/views/components/payment/pay-bank.vue

@@ -1,28 +1,206 @@
 <template>
-  <div class="pay-bank">
-    <card v-for="i in 24" :key="i" class="card">
-      <el-image src="https://picsum.photos/144/48" class="bank-icon" />
-    </card>
+  <div v-loading="isLoading" class="pay-bank">
+    <div class="paymount">
+      支付金额:<span class="exp">¥</span><span class="price">{{ data.payAmount | formatPrice }}</span>
+    </div>
+    <radio-card
+      v-model="checkedBank"
+      :list="bankList"
+      :border="false"
+      :show-un-active-icon="false"
+      @change="handleBankChange"
+    >
+      <template #item="{ row }">
+        <el-image :src="row.bankLogo" class="bank-icon" />
+      </template>
+    </radio-card>
+    <div v-if="checkedBank" class="pay-submit">
+      <div class="tip">
+        <div class="copy-tip">
+          若您在公司的职位无法直接使用企业网银付款,请点击右侧“复制支付链接”按钮,将该链接发送给公司财务人员进行企业网银付款
+        </div>
+        <el-tag type="danger" @click="handleClipboard($event)">复制支付链接</el-tag>
+      </div>
+      <el-button type="danger" @click="onSubmit">立即支付</el-button>
+    </div>
+    <!-- 确认支付 -->
+    <el-dialog title="支付确认" :visible.sync="confirmVisiable" width="20%" center>
+      <span>请在您新打开的页面上完成付款已完成付款完成付款后请点击下面的按钮!</span>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="danger" size="mini" @click="handleConfirmPay">确 定</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import Card from '@/components/Card'
+import handleClipboard from '@/utils/clipboard'
+// 单选 / 多选
+import RadioCard from '@/views/components/RadioCard'
+import { fetchBankcodeList, payVipWithUnion } from '@/api/pay'
 export default {
-  components: { Card },
+  components: { RadioCard },
+  props: {
+    data: {
+      type: Object,
+      default: () => {}
+    },
+    payWay: {
+      type: Number,
+      default: 0
+    }
+  },
   data() {
     return {
-
+      isLoading: false,
+      bankList: [],
+      checkedBank: '',
+      payData: {},
+      confirmVisiable: false,
+      userType: ''
+    }
+  },
+  created() {
+    this.fetchBankcodeList()
+  },
+  methods: {
+    // 获取网银支付列表
+    fetchBankcodeList() {
+      fetchBankcodeList().then(res => {
+        const result = JSON.parse(res.data)
+        this.bankList = result.list
+      })
+    },
+    // 确认支付弹窗
+    handleConfirmPay() {
+      this.confirmVisiable = false
+      this.checkedPayConfirm()
+    },
+    // 银行切换
+    handleBankChange() {
+      this.isLoading = true
+      this.handlePay()
+    },
+    // 复制支付链接
+    handleClipboard($event) {
+      this.confirmVisiable = true
+      handleClipboard(this.payData.payUrl, '支付链接已复制到剪切板', $event)
+    },
+    // 提交
+    onSubmit() {
+      // this.handlePay()
+      this.confirmVisiable = true
+      window.open(this.payData.payUrl, '_blank')
+    },
+    // 提交支付
+    handlePay() {
+      if (this.payWay === 3) {
+        this.userType = 'ENTERPRISE'
+      }
+      if (this.payWay === 4) {
+        this.userType = 'USER'
+      }
+      const params = {
+        bankCode: parseInt(this.checkedBank),
+        returnUrl: 'https://www.caimei365.com',
+        userType: this.userType,
+        vipRecordId: this.data.vipRecordId
+      }
+      // 提交支付信息生成支付链接
+      payVipWithUnion(params)
+        .then(res => {
+          const result = JSON.parse(res.data)
+          if (result.code === '000000') {
+            this.payData = result.data
+          } else {
+            this.$message.error('支付系统遇到点小问题,请稍后重试')
+            this.isLoading = false
+          }
+        })
+        .finally(() => {
+          this.isLoading = false
+        })
+    },
+    // 验证确认支付
+    checkedPayConfirm() {
+      this.confirmVisiable = false
+      this.$emit('pay-confirm', {
+        type: this.userType,
+        data: {
+          ...this.payData,
+          ...this.data
+        }
+      })
     }
   }
 }
 </script>
 
-<style  lang="scss">
-.card{
+<style scoped>
+.pay-bank >>> .el-dialog__body {
+  padding: 15px !important;
+  line-height: 1.6;
+}
+</style>
+
+<style lang="scss">
+.card {
   margin-bottom: 16px;
 }
-.bank-icon{
+.bank-icon {
   display: block;
+  width: 128px;
+  height: 44px;
+  background: #999;
+}
+
+.paymount {
+  margin-bottom: 16px;
+  font-size: 16px;
+  color: #101010;
+
+  .exp,
+  .price {
+    color: #ff6d6d;
+  }
+
+  .exp {
+    font-size: 14px;
+  }
+
+  .price {
+    font-size: 20px;
+    font-weight: bold;
+  }
+}
+
+.pay-submit {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  margin: 40px 0;
+  .tip {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    width: 100%;
+    .copy-tip {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+  .el-tag {
+    border-color: #ff6d6d;
+    background: #ffe8e8;
+    cursor: pointer;
+    margin-left: 26px;
+  }
+  .el-button {
+    background-color: #ff6d6d;
+    width: 200px;
+    height: 40px;
+    margin-top: 26px;
+  }
 }
 </style>

+ 53 - 3
src/views/components/payment/pay-wechat.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="pay-code">
-    <div class="paymount">扫码付款:<span class="exp">¥</span><span class="price">5980.00</span></div>
-    <el-image src="https://picsum.photos/200/200" class="qrcode" />
+    <div class="paymount">扫码付款:<span class="exp">¥</span><span class="price">{{ data.payAmount | formatPrice }}</span></div>
+    <el-image v-loading="isLoading" :src="payUrl" class="qrcode" />
     <div class="pay-tip">
       <i class="icon-wechat" />
       <span>使用微信扫码</span>
@@ -10,7 +10,57 @@
 </template>
 
 <script>
-export default {}
+import qrcode from 'qrcode'
+export default {
+  props: {
+    data: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      isLoading: false,
+      payUrl: '',
+      timer: null
+    }
+  },
+  created() {
+    this.drawQrcode()
+  },
+  beforeDestroy() {
+    clearInterval(this.timer)
+  },
+  methods: {
+    // 绘制支付二维码
+    drawQrcode() {
+      const wxPayUri = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx91c4152b60ca91a3&redirect_uri=https://www.caimei365.com/pay/wechatpay.html&response_type=code&scope=snsapi_base&state=${JSON.stringify(this.data)}#wechat_redirect`
+      qrcode
+        .toDataURL(wxPayUri, { width: 180, margin: 0, errorCorrectionLevel: 'H', type: 'image/jpeg', scale: 4 })
+        .then(url => {
+          this.payUrl = url
+          this.checkedPayConfirm()
+        })
+        .catch(err => {
+          console.error(err)
+        }).finally(() => {
+          this.isLoading = false
+        })
+    },
+    // 验证确认支付
+    checkedPayConfirm() {
+      this.timer = setInterval(() => {
+        this.$emit('pay-confirm', {
+          type: 'WEIXIN',
+          data: {
+            ...this.payData,
+            ...this.data
+          }
+        })
+      }, 1000)
+    }
+  }
+}
 </script>
 
 <style scoped lang="scss">

+ 39 - 15
src/views/normal/personal/index.vue

@@ -4,17 +4,17 @@
     <el-divider />
     <el-row>
       <el-col :span="5">公司名称:</el-col>
-      <el-col :span="19">上海品牌医疗国际有限公司</el-col>
+      <el-col :span="19">{{ supplierInfo.shopName }}</el-col>
     </el-row>
     <el-divider />
     <el-row>
       <el-col :span="5">所属类型:</el-col>
-      <el-col :span="8">代理商</el-col>
+      <el-col :span="8">{{ supplierInfo.shopType === 1 ? '品牌方' : '代理商' }}</el-col>
     </el-row>
     <el-divider />
     <el-row>
       <el-col :span="5">手机号:</el-col>
-      <el-col :span="8">158729590490</el-col>
+      <el-col :span="8">{{ supplierInfo.mobile }}</el-col>
       <el-col :span="8">
         <router-link class="link" to="/personal/mobile"><i class="el-icon-edit" /><span>修改手机号</span></router-link>
       </el-col>
@@ -22,35 +22,59 @@
     <el-divider />
     <el-row>
       <el-col :span="5">登录账号:</el-col>
-      <el-col :span="8">暂未绑定</el-col>
-      <el-col :span="8">
+      <el-col :span="8">{{ supplierInfo.loginAccount ? supplierInfo.loginAccount : '暂未绑定' }}</el-col>
+      <el-col v-if="!supplierInfo.loginAccount" :span="8">
         <router-link class="link" to="/personal/account"><i class="el-icon-link" /><span>去绑定</span></router-link>
       </el-col>
     </el-row>
     <el-divider />
-    <el-row>
+    <el-row v-if="supplierInfo.shopType === 2">
       <el-col :span="5">代理品牌:</el-col>
-      <el-col :span="8">skinrex</el-col>
+      <el-col :span="8">{{ brandList }}</el-col>
     </el-row>
     <el-divider />
     <el-row>
       <el-col :span="5">会员状态:</el-col>
-      <el-col :span="8">非会员</el-col>
       <el-col :span="8">
-        <router-link class="link" to="/vip/vip-open"><i class="el-icon-thumb" /><span>开通会员</span></router-link>
+        <span v-if="supplierInfo.vipStatus === 1">会员</span>
+        <span v-if="supplierInfo.vipStatus === 3">非会员</span>
+      </el-col>
+      <el-col :span="8">
+        <router-link v-if="supplierInfo.vipStatus === 3" class="link" to="/vip/vip-open?type=1">
+          <i class="el-icon-thumb" /><span>开通会员</span>
+        </router-link>
+        <router-link v-if="supplierInfo.vipStatus === 1" class="link" to="/vip/vip-open?type=2">
+          <i class="el-icon-thumb" /><span>续费会员</span>
+        </router-link>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import { mapGetters } from 'vuex'
+import { getSupplierById } from '@/api/supplier'
 export default {
   data() {
-    return {}
+    return {
+      supplierInfo: {}
+    }
   },
   computed: {
-    ...mapGetters(['userInfo'])
+    brandList() {
+      return this.supplierInfo.shopInfo.map(item => item.brandName).join('/')
+    }
+  },
+  created() {
+    this.fetchSupplierInfo()
+  },
+  methods: {
+    // 获取供应商信息
+    fetchSupplierInfo() {
+      getSupplierById({ authUserId: this.$store.getters.authUserId }).then(res => {
+        console.log(res)
+        this.supplierInfo = res.data
+      })
+    }
   }
 }
 </script>
@@ -62,15 +86,15 @@ export default {
   margin-top: 60px;
   color: #101010;
   .link {
-    color: #409EFF;
+    color: #409eff;
     font-size: 14px;
     &:hover {
       text-decoration: underline;
     }
   }
 
-  .el-row{
-    .el-col:nth-child(2){
+  .el-row {
+    .el-col:nth-child(2) {
       color: #404040;
     }
   }

+ 72 - 47
src/views/normal/personal/set-account.vue

@@ -1,23 +1,21 @@
 <template>
   <div>
     <el-form ref="form" :model="formData" :rules="formRules" label-width="120px" class="form">
-      <el-form-item label="手机号" prop="oldPassword">
-        <el-input v-model="formData.oldPassword" type="password" placeholder="请输入手机号" />
+      <el-form-item label="手机号" prop="mobile">
+        <el-input v-model="formData.mobile" placeholder="请输入手机号" />
       </el-form-item>
-      <el-form-item label="验证码" prop="newPassword">
+      <el-form-item label="验证码" prop="verifyCode">
         <el-row>
-          <el-col
-            :span="18"
-          ><el-input
-            v-model="formData.newPassword"
-            type="password"
-            placeholder="请输入验证码"
-          /></el-col>
-          <el-col :span="6" class="send-code"><el-button type="primary">获取验证码</el-button></el-col>
+          <el-col :span="18">
+            <el-input v-model="formData.verifyCode" placeholder="请输入验证码" />
+          </el-col>
+          <el-col :span="6" class="send-code">
+            <el-button type="primary" @click="handleSendVerifyCode">{{ sendCodeBtnText }}</el-button>
+          </el-col>
         </el-row>
       </el-form-item>
-      <el-form-item label="绑定登录账号" prop="confirmPwd">
-        <el-input v-model="formData.confirmPwd" type="password" placeholder="请输入登录账号" />
+      <el-form-item label="绑定登录账号" prop="loginAccount">
+        <el-input v-model="formData.loginAccount" placeholder="请输入登录账号" />
       </el-form-item>
     </el-form>
     <div class="submit-btn">
@@ -28,59 +26,86 @@
 </template>
 
 <script>
-import { modifyPassword } from '@/api/user'
+import { sendVerifyCode, bindLoginAccount } from '@/api/user'
+import { isMobile, isAccount } from '@/utils/validate'
 export default {
   data() {
-    var confirmPassword = (rule, value, callback) => {
-      if (value === '') {
-        callback(new Error('请输入新手机号'))
+    const valideMobile = (rule, value, callback) => {
+      if (isMobile(value)) {
+        callback()
       } else {
+        callback(new Error('请输入正确的手机号'))
+      }
+    }
+    const valideAccount = (rule, value, callback) => {
+      if (isAccount(value)) {
         callback()
+      } else {
+        callback(new Error('仅支持英文字母和数字'))
       }
     }
     return {
       formData: {
-        oldPassword: '',
-        newPassword: '',
-        confirmPwd: ''
+        mobile: '',
+        verifyCode: '',
+        loginAccount: ''
       },
+      sendStatus: 0,
       formRules: {
-        oldPassword: [{ required: true, message: '请输入原来的手机号', trigger: 'blur' }],
-        newPassword: [{ required: true, trigger: ['change', 'blur'], message: '请输入旧手机号收到的验证码' }],
-        confirmPwd: [
-          { required: true, message: '请输入新手机号' },
-          { validator: confirmPassword, trigger: 'change' }
+        mobile: [
+          { required: true, message: '请输入手机号', trigger: 'blur' },
+          { validator: valideMobile, trigger: 'blur' }
+        ],
+        verifyCode: [{ required: true, message: '请输入收到的验证码', trigger: 'blur' }],
+        loginAccount: [
+          { required: true, message: '请输入要绑定的登录号', trigger: 'blur' },
+          { validator: valideAccount, trigger: 'blur' }
         ]
       }
     }
   },
+  computed: {
+    sendCodeBtnText() {
+      return this.sendStatus === 0 ? '发送验证码' : `再次发送${this.sendStatus}s`
+    }
+  },
   methods: {
     submit() {
-      const that = this
       this.$refs.form.validate(valide => {
         if (!valide) return
-        const authUserId = this.$store.getters.authUserId
-        modifyPassword({ authUserId, ...that.formData })
-          .then(res => {
-            if (res.code === 0) {
-              this.$refs.form.resetFields()
-              this.$message.success('密码修改成功,请重新登录')
-              this.logout()
-            }
-          })
-          .catch(err => {
-            console.log(err)
-          })
+        this.handleBindLoginAccount()
+      })
+    },
+    // 绑定登录号
+    handleBindLoginAccount() {
+      this.formData.authUserId = this.$store.getters.authUserId
+      bindLoginAccount(this.formData).then(res => {
+        this.$message.success('绑定登录号成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
+    },
+    // 发送验证码
+    handleSendVerifyCode() {
+      if (!isMobile(this.formData.oldMobile)) {
+        this.$message.warning('请输入正确的手机号')
+        return
+      }
+      sendVerifyCode({ mobile: this.formData.oldMobile }).then(res => {
+        this.$message.success('验证码已发送')
+        this.countdown()
       })
     },
-    async logout() {
-      setTimeout(() => {
-        // 退出登录重置用户信息
-        this.$store.dispatch('user/logout')
-        // 重置相关state
-        this.$store.dispatch('resetState')
-        this.$router.replace('/login')
-      }, 500)
+    // 验证码倒计时
+    countdown() {
+      this.sendStatus = 30
+      const timer = setInterval(() => {
+        if (this.sendStatus === 0) {
+          clearInterval(timer)
+          return
+        }
+        this.sendStatus--
+      }, 1000)
     }
   }
 }
@@ -93,7 +118,7 @@ export default {
     width: 140px;
   }
 }
-.send-code{
+.send-code {
   text-align: right;
 }
 .form {

+ 66 - 48
src/views/normal/personal/set-mobile.vue

@@ -1,23 +1,21 @@
 <template>
   <div>
     <el-form ref="form" :model="formData" :rules="formRules" label-width="80px" class="form">
-      <el-form-item label="原手机号" prop="oldPassword">
-        <el-input v-model="formData.oldPassword" type="password" placeholder="请输入原手机号" />
+      <el-form-item label="原手机号" prop="oldMobile">
+        <el-input v-model="formData.oldMobile" placeholder="请输入原手机号" />
       </el-form-item>
-      <el-form-item label="验证码" prop="newPassword">
+      <el-form-item label="验证码" prop="verifyCode">
         <el-row>
-          <el-col
-            :span="18"
-          ><el-input
-            v-model="formData.newPassword"
-            type="password"
-            placeholder="请输入验证码"
-          /></el-col>
-          <el-col :span="6" class="send-code"><el-button type="primary">获取验证码</el-button></el-col>
+          <el-col :span="18">
+            <el-input v-model="formData.verifyCode" placeholder="请输入验证码" />
+          </el-col>
+          <el-col :span="6" class="send-code">
+            <el-button type="primary" @click="handleSendVerifyCode()">{{ sendCodeBtnText }}</el-button>
+          </el-col>
         </el-row>
       </el-form-item>
-      <el-form-item label="新手机号" prop="confirmPwd">
-        <el-input v-model="formData.confirmPwd" type="password" placeholder="请输入新手机号" />
+      <el-form-item label="新手机号" prop="newMobile">
+        <el-input v-model="formData.newMobile" placeholder="请输入新手机号" />
       </el-form-item>
     </el-form>
     <div class="submit-btn">
@@ -28,59 +26,79 @@
 </template>
 
 <script>
-import { modifyPassword } from '@/api/user'
+import { resetMobile, sendVerifyCode } from '@/api/user'
+import { isMobile } from '@/utils/validate'
 export default {
   data() {
-    var confirmPassword = (rule, value, callback) => {
-      if (value === '') {
-        callback(new Error('请输入新手机号'))
-      } else {
+    const valideMobile = (rule, value, callback) => {
+      if (isMobile(value)) {
         callback()
+      } else {
+        callback(new Error('请输入正确的手机号'))
       }
     }
     return {
       formData: {
-        oldPassword: '',
-        newPassword: '',
-        confirmPwd: ''
+        oldMobile: '',
+        newMobile: '',
+        verifyCode: ''
       },
+      sendStatus: 0,
       formRules: {
-        oldPassword: [{ required: true, message: '请输入原来的手机号', trigger: 'blur' }],
-        newPassword: [{ required: true, trigger: ['change', 'blur'], message: '请输入旧手机号收到的验证码' }],
-        confirmPwd: [
-          { required: true, message: '请输入新手机号' },
-          { validator: confirmPassword, trigger: 'change' }
+        oldMobile: [
+          { required: true, message: '请输入原来的手机号', trigger: 'blur' },
+          { validator: valideMobile, trigger: 'blur' }
+        ],
+        verifyCode: [{ required: true, trigger: 'blur', message: '请输入旧手机号收到的验证码' }],
+        newMobile: [
+          { required: true, message: '请输入新的手机号', trigger: 'blur' },
+          { validator: valideMobile, trigger: 'blur' }
         ]
       }
     }
   },
+  computed: {
+    sendCodeBtnText() {
+      return this.sendStatus === 0 ? '发送验证码' : `再次发送${this.sendStatus}s`
+    }
+  },
   methods: {
     submit() {
-      const that = this
       this.$refs.form.validate(valide => {
         if (!valide) return
-        const authUserId = this.$store.getters.authUserId
-        modifyPassword({ authUserId, ...that.formData })
-          .then(res => {
-            if (res.code === 0) {
-              this.$refs.form.resetFields()
-              this.$message.success('密码修改成功,请重新登录')
-              this.logout()
-            }
-          })
-          .catch(err => {
-            console.log(err)
-          })
+        this.saveMobile()
+      })
+    },
+    // 保存
+    saveMobile() {
+      this.formData.authUserId = this.$store.getters.authUserId
+      resetMobile(this.formData).then(res => {
+        this.$message.success('修改手机号成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.back()
+      })
+    },
+    // 发送验证码
+    handleSendVerifyCode() {
+      if (!isMobile(this.formData.oldMobile)) {
+        this.$message.warning('请输入正确的手机号')
+        return
+      }
+      sendVerifyCode({ mobile: this.formData.oldMobile }).then(res => {
+        this.$message.success('验证码已发送')
+        this.countdown()
       })
     },
-    async logout() {
-      setTimeout(() => {
-        // 退出登录重置用户信息
-        this.$store.dispatch('user/logout')
-        // 重置相关state
-        this.$store.dispatch('resetState')
-        this.$router.replace('/login')
-      }, 500)
+    // 验证码倒计时
+    countdown() {
+      this.sendStatus = 30
+      const timer = setInterval(() => {
+        if (this.sendStatus === 0) {
+          clearInterval(timer)
+          return
+        }
+        this.sendStatus--
+      }, 1000)
     }
   }
 }
@@ -93,7 +111,7 @@ export default {
     width: 140px;
   }
 }
-.send-code{
+.send-code {
   text-align: right;
 }
 .form {

+ 264 - 117
src/views/normal/vip/buy.vue

@@ -1,134 +1,281 @@
 <template>
-  <div class="container">
+  <div v-loading="isLoading" class="container">
     <div class="title">购买会员</div>
     <!-- 会员套餐 -->
     <div class="section">
       <div class="subtitle">会员套餐<span class="tip">(基础服务功能)</span></div>
-      <div class="list">
-        <card :always-show-icon="false" :checked="true">
+      <radio-card
+        v-model="checkedPackage"
+        :list="packageList"
+        :show-un-active-icon="false"
+        @change="handlePackageChange"
+      >
+        <template #item="{ row }">
           <div class="vip-package">
-            <div class="time">1年</div>
+            <div class="time">{{ row.duration + (row.unit === 1 ? '月' : '') }}</div>
             <div>
               <span>¥</span>
-              <span class="price">5980</span>
-              <span class="deleted">¥7980</span>
+              <span class="price">{{ row.price }}</span>
+              <span class="deleted">¥{{ row.originalPrice }}</span>
             </div>
           </div>
-        </card>
-        <card :always-show-icon="false">
-          <div class="vip-package">
-            <div class="time">1年</div>
-            <div>
-              <span>¥</span>
-              <span class="price">5980</span>
-              <span class="deleted">¥7980</span>
-            </div>
-          </div>
-        </card>
-      </div>
+        </template>
+      </radio-card>
     </div>
     <el-divider class="divider" />
     <!-- 定制服务 -->
     <div class="section">
-      <div class="subtitle">订制服务<span class="tip">(勾选且付费后,将会有专人与您进行沟通)</span></div>
-      <div class="list">
-        <card>
-          <div class="service-package">
-            <div class="name">认证授权图片模板</div>
-            <div class="subname">定制要求与费用另议</div>
-          </div>
-        </card>
-        <card>
-          <div class="service-package">
-            <div class="name">认证授权物料制作</div>
-            <div class="subname">定制要求与费用另议</div>
-          </div>
-        </card>
-        <card>
+      <div class="subtitle">定制服务<span class="tip">(勾选且付费后,将会有专人与您进行沟通)</span></div>
+      <radio-card v-model="checkedServiceList" :list="serviceList" type="checkbox">
+        <template #item="{ row }">
           <div class="service-package">
-            <div class="name">品牌资料库</div>
+            <div class="name">{{ row.name }}</div>
             <div class="subname">定制要求与费用另议</div>
           </div>
-        </card>
-        <card>
-          <div class="service-package">
-            <div class="name">其他</div>
-            <div class="subname">定制要求与费用另议</div>
-          </div>
-        </card>
-      </div>
+        </template>
+      </radio-card>
     </div>
     <el-divider class="divider" />
-    <!-- 选择支付方式 -->
-    <div class="section">
-      <div class="subtitle">选择支付方式</div>
-      <div class="list">
-        <card v-for="item in payWayList" :key="item.id" :border="false" :always-show-icon="false" :checked="item.id === 0">
-          <el-image :src="item.image" class="pay-way" @click="handleChoosePayWay(item)" />
-        </card>
+    <template v-if="paySwitch">
+      <!-- 选择支付方式 -->
+      <div class="section">
+        <div class="subtitle">选择支付方式</div>
+        <radio-card v-model="checkedPayWay" :list="payWayList" :border="false" :show-un-active-icon="false">
+          <template #item="{ row }">
+            <el-image :src="row.image" class="pay-way" />
+          </template>
+        </radio-card>
       </div>
-    </div>
-    <!-- 支付 -->
-    <div class="pay-action">
+      <!-- 支付 -->
       <!-- 阿里云支付 -->
-      <pay-alipay v-if="selectPayWay === 0" class="pay-code" />
+      <pay-alipay
+        v-if="checkedPayWay === 1"
+        class="pay-code"
+        :data="orderInfo"
+        :pay-way="checkedPayWay"
+        @pay-confirm="handlePayConfirm"
+        @hook:created="handleScanPayStart"
+      />
       <!-- 微信支付 -->
-      <pay-wechat v-if="selectPayWay === 1" class="pay-code" />
+      <pay-wechat
+        v-if="checkedPayWay === 2"
+        class="pay-code"
+        :data="orderInfo"
+        :pay-way="checkedPayWay"
+        @pay-confirm="handlePayConfirm"
+        @hook:created="handleScanPayStart"
+      />
       <!-- 企业网银支付 & 个人网银支付-->
-      <pay-bank v-if="selectPayWay === 2 || selectPayWay === 3" class="pay-bank" />
-    </div>
+      <template v-if="checkedPayWay === 3 || checkedPayWay === 4">
+        <pay-bank
+          :key="checkedPayWay"
+          class="pay-bank"
+          :data="orderInfo"
+          :pay-way="checkedPayWay"
+          @pay-confirm="handlePayConfirm"
+          @hook:created="handleOtherPayStart"
+        />
+      </template>
+    </template>
+    <template v-else>
+      <el-alert show-icon title="支付系统遇到点小问题,请稍后重试或联系管理员处理" type="error" effect="dark" />
+    </template>
   </div>
 </template>
 
 <script>
+// 图标
 import imgPayAli from '@/assets/pay/pay-ali.png'
 import imgBank from '@/assets/pay/pay-bank.png'
 import imgSelfBank from '@/assets/pay/pay-self-bank.png'
 import imgWechat from '@/assets/pay/pay-wechat.png'
-
 import PayAlipay from '@/views/components/payment/pay-alipay.vue'
 import PayWechat from '@/views/components/payment/pay-wechat.vue'
 import PayBank from '@/views/components/payment/pay-bank.vue'
 
-import Card from '@/components/Card'
+// 单选 / 多选
+import RadioCard from '@/views/components/RadioCard'
+
+// 接口
+import { fetchConfigureList } from '@/api/member'
+import { registerSuperPay, payOnlineChecked, checkedWinxinPaySuccess, checkedOtherPaySuccess } from '@/api/pay'
+
 export default {
   components: {
-    Card,
+    RadioCard,
     PayAlipay,
     PayWechat,
     PayBank
   },
   data() {
     return {
-      selectPayWay: 0,
+      isLoading: true,
+      orderInfo: {
+        pageType: 4,
+        payAmount: '',
+        vipId: '',
+        vipRecordId: ''
+      },
+      packageList: [],
+      serviceList: [],
+      checkedPackage: '',
+      checkedServiceList: [],
+      checkedPayWay: 0,
+      paySwitch: true,
+      redirectUrlQuery: {},
+      // 扫码支付倒计时
+      scanToPayStartTime: 0,
+      scanToPayEndTime: 0,
+      payType: '',
       payWayList: [
         {
           image: imgPayAli,
           name: '阿里云支付',
-          id: 0
+          id: 1,
+          checked: true
         },
         {
           image: imgWechat,
           name: '微信支付',
-          id: 1
+          id: 2,
+          checked: false
         },
         {
           image: imgBank,
           name: '企业网银支付',
-          id: 2
+          id: 3,
+          checked: false
         },
         {
           image: imgSelfBank,
           name: '个人网银支付',
-          id: 3
+          id: 4,
+          checked: false
         }
       ]
     }
   },
+  created() {
+    this.fetchConfigureList()
+    this.payOnlineChecked()
+  },
   methods: {
-    handleChoosePayWay(item) {
-      console.log(item)
-      this.selectPayWay = item.id
+    // 获取套餐 服务配置列表
+    fetchConfigureList() {
+      fetchConfigureList()
+        .then(res => {
+          this.packageList = res.data.packageList
+          this.serviceList = res.data.serviceList
+          // 设置初始值
+          this.checkedPackage = this.packageList[0].id
+          this.registerSuperPay()
+          this.setOrderInfo()
+        })
+        .finally(() => {
+          this.isLoading = false
+        })
+    },
+    // 设置订单信息(这个假订单)
+    setOrderInfo() {
+      const findOne = this.packageList.find(item => item.id === this.checkedPackage)
+      if (findOne) {
+        this.orderInfo.payAmount = findOne.price
+        this.orderInfo.vipId = findOne.id
+      }
+    },
+    // 套餐修改
+    handlePackageChange() {
+      this.checkedPayWay = 0
+      this.registerSuperPay()
+      this.setOrderInfo()
+    },
+    // 在线支付开通会员预处理
+    registerSuperPay() {
+      registerSuperPay({
+        authUserId: this.$store.getters.authUserId,
+        packageId: this.checkedPackage
+      }).then(res => {
+        this.orderInfo.vipRecordId = res.data
+      })
+    },
+    // 在线支付开关检测
+    payOnlineChecked() {
+      payOnlineChecked().then(res => {
+        if (res.data === '1') {
+          this.paySwitch = true
+        } else {
+          this.paySwitch = false
+        }
+      })
+    },
+    // 确认支付是否完成
+    handlePayConfirm(e) {
+      this.redirectUrlQuery = {
+        payAmount: e.data.payAmount
+      }
+      this.payType = e.type
+      if (e.type === 'WEIXIN') {
+        this.checkedWinxinPaySuccess({ vipRecordId: e.data.vipRecordId })
+      } else {
+        this.checkedOtherPaySuccess({ mbOrderId: e.data.mbOrderId })
+      }
+    },
+    // 查询是否成功(微信支付专用)
+    checkedWinxinPaySuccess(params) {
+      checkedWinxinPaySuccess(params).then(res => {
+        this.handlePayResult(res)
+      })
+    },
+    // 查询支付是否成功 (支付宝 银联)
+    checkedOtherPaySuccess(params) {
+      checkedOtherPaySuccess(params).then(res => {
+        this.handlePayResult(res)
+      })
+    },
+    // 处理支付查询结果
+    handlePayResult(result) {
+      result = JSON.parse(result.data)
+      if (result.data.status === '1') {
+        this.$router.push({
+          path: '/pay/success',
+          query: this.redirectUrlQuery
+        })
+      } else {
+        this.diffScanOverTime(60, () => {
+          this.$router.push({
+            path: '/pay/faild',
+            query: this.redirectUrlQuery
+          })
+        })
+      }
+    },
+    // 扫码支付开始
+    handleScanPayStart() {
+      const h = this.$createElement
+      this.$notify({
+        title: '扫码支付提示',
+        message: h('span', { style: 'color: #ff6d6d' }, '请在5分钟内完成扫码支付')
+      })
+      this.scanToPayStartTime = new Date().getTime()
+      console.log('扫码支付开始', this.scanToPayStartTime)
+    },
+    // 其他支付方式(非扫码支付)
+    handleOtherPayStart() {
+      this.scanToPayStartTime = 0
+    },
+    diffScanOverTime(time, callback) {
+      if (this.scanToPayStartTime > 0) {
+        this.scanToPayEndTime = new Date().getTime()
+        const subTime = this.scanToPayEndTime - this.scanToPayStartTime
+        const maxTime = time * 1000
+        if (subTime > maxTime) {
+          console.log('放行')
+          callback()
+        }
+      } else {
+        console.log('放行')
+        callback()
+      }
     }
   }
 }
@@ -147,7 +294,7 @@ export default {
   margin: 40px 0;
 }
 
-.pay-bank{
+.pay-bank {
   margin: 25px 0;
 }
 
@@ -170,62 +317,62 @@ export default {
     }
   }
 
-  .list {
-    .pay-way {
-      display: block;
-      width: 128px;
-      height: 44px;
-    }
+  .vip-package {
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
 
-    .service-package {
-      height: 100%;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      flex-direction: column;
+    width: 232px;
+    height: 128px;
 
-      width: 232px;
-      height: 128px;
+    .time {
+      margin-bottom: 16px;
+      font-size: 18px;
+      font-weight: bold;
+      color: #404040;
+    }
 
-      .name {
-        margin-bottom: 16px;
-        font-size: 18px;
-        color: #404040;
-      }
-      .subname {
-        font-size: 14px;
-        color: #ff6d6d;
-      }
+    span {
+      color: #ff6d6d;
     }
 
-    .vip-package {
-      height: 100%;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      flex-direction: column;
-
-      width: 232px;
-      height: 128px;
-
-      .time {
-        margin-bottom: 16px;
-        font-size: 18px;
-        font-weight: bold;
-        color: #404040;
-      }
+    .price {
+      font-size: 36px;
+      font-weight: bold;
+      color: #ff6d6d;
+    }
+    .deleted {
+      font-size: 14px;
+      color: #b2b2b2;
+      text-decoration: line-through;
+    }
+  }
+  .service-package {
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
 
-      .price {
-        font-size: 36px;
-        font-weight: bold;
-        color: #ff6d6d;
-      }
-      .deleted {
-        font-size: 14px;
-        color: #b2b2b2;
-        text-decoration: line-through;
-      }
+    width: 232px;
+    height: 128px;
+
+    .name {
+      margin-bottom: 16px;
+      font-size: 18px;
+      color: #404040;
     }
+    .subname {
+      font-size: 14px;
+      color: #ff6d6d;
+    }
+  }
+  .pay-way {
+    display: block;
+    width: 128px;
+    height: 44px;
   }
 }
 </style>

+ 26 - 3
src/views/normal/vip/index.vue

@@ -9,7 +9,7 @@
           <div class="subname" v-text="item.subName" />
         </div>
       </div>
-      <el-button class="open-vip" type="primary">开通会员</el-button>
+      <el-button class="open-vip" type="primary" :style="[themeStyle.border, themeStyle.background]" @click="toPayPage">{{ themeText }}</el-button>
     </div>
     <div class="section benefits">
       <div class="title">会员权益对比</div>
@@ -18,9 +18,9 @@
       </div>
     </div>
     <!-- 开通、续费会员 -->
-    <div class="open-control">
+    <div class="open-control" :style="themeStyle.border">
       <div class="tip"><i class="vip-icon" /><span>会员</span>享有机构授权认证、设备授权认证等特权</div>
-      <el-button class="submit" type="primary">开通会员</el-button>
+      <el-button class="submit" type="primary" :style="[themeStyle.border, themeStyle.background]" @click="toPayPage">{{ themeText }}</el-button>
     </div>
   </div>
 </template>
@@ -35,11 +35,13 @@ import vipIcon6 from '@/assets/img/vip-icon-06.png'
 import vipIcon7 from '@/assets/img/vip-icon-07.png'
 
 import MemberBenefits from '@/views/components/MemberBenefits'
+import { mapGetters } from 'vuex'
 
 export default {
   components: {
     MemberBenefits
   },
+
   data() {
     return {
       privilegeList: [
@@ -80,6 +82,27 @@ export default {
         }
       ]
     }
+  },
+  computed: {
+    ...mapGetters['vipStatus'],
+    themeText() {
+      return this.vipStatus === 1 ? '续费会员' : '开通会员'
+    },
+    themeStyle() {
+      return {
+        border: {
+          borderColor: this.vipStatus === 1 ? '#FF6D6D' : '#1890ff'
+        },
+        background: {
+          backgroundColor: this.vipStatus === 1 ? '#FF6D6D' : '#1890ff'
+        }
+      }
+    }
+  },
+  methods: {
+    toPayPage() {
+      this.$router.push('/vip/vip-pay')
+    }
   }
 }
 </script>