浏览代码

Merge branch 'developer' into developerA

zhengjinyi 1 年之前
父节点
当前提交
233dd385bb

文件差异内容过多而无法显示
+ 264 - 616
package-lock.json


二进制
public/favicon.ico


文件差异内容过多而无法显示
+ 0 - 0
public/favicon.svg


+ 157 - 0
src/api/member/member.js

@@ -0,0 +1,157 @@
+import request from '@/utils/request'
+
+export function fetchFindProductList(params) {
+  return request({
+    url: '/svip/member/findProductList',
+    method: 'get',
+    params
+  })
+}
+// 超级会员专属优惠券列表
+export function fetchCouponList(params) {
+  return request({
+    url: '/svip/member/couponList',
+    method: 'get',
+    params
+  })
+}
+// 回显编辑超级会员优惠券
+export function svipCoupon(params) {
+  return request({
+    url: '/svip/member/svipCoupon',
+    method: 'get',
+    params
+  })
+}
+// 保存超级会员优惠券
+export function saveVipCoupon(data) {
+  return request({
+    url: '/svip/member/saveVipCoupon',
+    method: 'post',
+    data: data
+  })
+}
+// 关闭超级会员专属优惠券
+export function fetchCloseCoupon(id) {
+  return request({
+    url: '/svip/member/closeCoupon/' + id,
+    method: 'post'
+  })
+}
+// 开启超级会员专属优惠券
+export function fetchOpenCoupon(id) {
+  return request({
+    url: '/svip/member/openCoupon/' + id,
+    method: 'post'
+  })
+}
+// 删除超级会员专属优惠券
+export function fetchDeleteCoupon(id) {
+  return request({
+    url: '/svip/member/deleteCoupon/' + id,
+    method: 'post'
+  })
+}
+// 删除超级会员具体优惠券
+export function delCoupon(id) {
+  return request({
+    url: '/svip/member/delCoupon/' + id,
+    method: 'post'
+  })
+}
+// 获取供应商列表
+export function fetchFindShop(params) {
+  return request({
+    url: '/svip/member/findShopList',
+    method: 'get',
+    params
+  })
+}
+// 获取商品信息列表
+export function fetchProductList(params) {
+  return request({
+    url: '/svip/member/findProductDialogList',
+    method: 'get',
+    params
+  })
+}
+export function fetchMemberList(params) {
+  return request({
+    url: '/svip/member/memberList',
+    method: 'get',
+    params
+  })
+}
+export function fetchClubList(params) {
+  return request({
+    url: '/svip/member/findClubList',
+    method: 'get',
+    params
+  })
+}
+export function saveSvip(params) {
+  return request({
+    url: '/svip/member/saveSvip',
+    method: 'post',
+    params
+  })
+}
+export function findHistory(params) {
+  return request({
+    url: '/svip/member/findHistory',
+    method: 'get',
+    params
+  })
+}
+export function fetchFindPackage() {
+  return request({
+    url: '/svip/member/findPackage',
+    method: 'get'
+  })
+}
+export function updatePrice(params) {
+  return request({
+    url: '/svip/member/updatePrice',
+    method: 'post',
+    params
+  })
+}
+export function updateSort(params) {
+  return request({
+    url: '/svip/member/updateSort',
+    method: 'get',
+    params
+  })
+}
+export function saveSvipProduct(data) {
+  return request({
+    url: '/svip/member/saveSvipProduct',
+    method: 'post',
+    data: data
+  })
+}
+export function delSvipProduct(id) {
+  return request({
+    url: '/svip/member/delSvipProduct/' + id,
+    method: 'post'
+  })
+}
+export function editSvipProduct(id) {
+  return request({
+    url: '/svip/member/editSvipProduct/' + id,
+    method: 'post'
+  })
+}
+export function getAdsImage() {
+  return request({
+    url: '/svip/member/getAdsImage',
+    method: 'get'
+  })
+}
+export function saveAdsImage(data) {
+  return request({
+    url: '/svip/member/saveAdsImage',
+    method: 'post',
+    data: data
+  })
+}

+ 119 - 0
src/components/UploadFile/index.vue

@@ -0,0 +1,119 @@
+<template>
+  <div>
+    <el-upload
+      ref="fileUpload"
+      :data="data"
+      :auto-upload="autoUpload"
+      :class="{ 'el-upload-hidden': !chooseState}"
+      :action="action"
+      :headers="headers"
+      :on-success="uploadSuccess"
+      :on-remove="handleRemove"
+      :on-change="handleChange"
+      :before-upload="beforeUpload"
+      :on-error="uploadError"
+      :limit="limit"
+      :multiple="multiple"
+      :accept="accept"
+      :file-list="fileList"
+    >
+      <div v-if="tip" slot="tip" class="el-upload__tip">{{ tip }}</div>
+      <el-button size="mini" type="primary">选择文件</el-button>
+    </el-upload>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  name: 'UploadFile',
+  props: {
+    tip: {
+      type: String,
+      default: ''
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    limit: {
+      type: Number,
+      default: 1
+    },
+    accept: {
+      type: String,
+      default: '.doc,.ppt,.pdf'
+    },
+    listType: {
+      type: String,
+      default: 'picture-card'
+    },
+    fileList: {
+      type: Array,
+      default: () => []
+    },
+    uuid: {
+      type: Number,
+      default: 0
+    },
+    beforeUpload: {
+      type: Function,
+      default: () => true
+    },
+    autoUpload: {
+      type: Boolean,
+      default: true
+    },
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    mode: {
+      type: String,
+      default: 'brand'
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      dialogImageUrl: ''
+    }
+  },
+  computed: {
+    ...mapGetters(['token']),
+    chooseState() {
+      return this.fileList.length < this.limit
+    },
+    action() {
+      if (this.mode === 'document') {
+        return process.env.VUE_APP_UPLOAD_API + '/upload/file'
+      } else {
+        return process.env.VUE_APP_UPLOAD_API + '/shop/upload/file'
+      }
+    },
+    headers() {
+      return {
+        'X-Token': this.token
+      }
+    }
+  },
+  methods: {
+    // 上传成功
+    uploadSuccess(response, file, fileList) {
+      this.$emit('success', { response, file, fileList })
+    },
+    // 删除
+    handleRemove(file, fileList) {
+      this.$emit('remove', { file, fileList })
+    },
+    // 变化
+    handleChange(file, fileList) {
+      this.$emit('change', { file, fileList })
+    },
+    // 上传失败
+    uploadError(err, file, fileList) {
+      this.$emit('error', { err, file, fileList })
+    }
+  }
+}
+</script>

+ 112 - 0
src/components/UploadImage/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <div>
+    <el-upload
+      ref="upload"
+      :class="{ 'el-upload-hidden': !chooseState }"
+      :list-type="listType"
+      :action="action"
+      :headers="headers"
+      :on-success="uploadImageSuccess"
+      :on-remove="handleImageRemove"
+      :before-upload="beforeUpload"
+      :on-error="uploadError"
+      :on-preview="handlePictureCardPreview"
+      :limit="limit"
+      :multiple="multiple"
+      :accept="accept"
+      :file-list="imageList"
+    >
+      <div v-if="tip" slot="tip" class="el-upload__tip">{{ tip }}</div>
+      <button v-if="trigger" ref="trigger" slot="trigger" size="small" type="primary">选取文件</button>
+      <i slot="default" class="el-icon-plus" />
+    </el-upload>
+    <el-dialog :visible.sync="dialogVisible">
+      <img width="100%" :src="dialogImageUrl">
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  props: {
+    tip: {
+      type: String,
+      default: ''
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    limit: {
+      type: Number,
+      default: 1
+    },
+    accept: {
+      type: String,
+      default: '.jpg,.png,.gif'
+    },
+    listType: {
+      type: String,
+      default: 'picture-card'
+    },
+    imageList: {
+      type: Array,
+      default: () => []
+    },
+    uuid: {
+      type: Number,
+      default: 0
+    },
+    beforeUpload: {
+      type: Function,
+      default: () => true
+    },
+    trigger: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      dialogImageUrl: ''
+    }
+  },
+  computed: {
+    ...mapGetters(['token']),
+    chooseState() {
+      return this.imageList.length < this.limit
+    },
+    action() {
+      return process.env.VUE_APP_CORE_API + '/tools/image/upload/multi'
+    },
+    headers() {
+      return {
+        'X-Token': this.token
+      }
+    }
+  },
+  methods: {
+    // 上传成功
+    uploadImageSuccess(response, file, fileList) {
+      this.$emit('success', { response, file, fileList })
+    },
+    // 删除
+    handleImageRemove(file, fileList) {
+      this.$emit('remove', { file, fileList })
+    },
+    // 上传失败
+    uploadError(err, file, fileList) {
+      this.$emit('error', { err, file, fileList })
+    },
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url
+      this.dialogVisible = true
+    },
+    handleClick() {
+      this.$refs.trigger.click()
+    }
+  }
+}
+</script>

+ 7 - 5
src/layout/components/AppMain.vue

@@ -13,9 +13,11 @@ export default {
   name: 'AppMain',
   computed: {
     cachedViews() {
+      console.log('cachedViews', this.$store.state.tagsView.cachedViews)
       return this.$store.state.tagsView.cachedViews
     },
     key() {
+      console.log('path', this.$route.path)
       return this.$route.path
     }
   }
@@ -28,7 +30,7 @@ export default {
   min-height: calc(100vh - 50px);
   width: 100%;
   position: relative;
-  // overflow: hidden;
+  overflow: hidden;
 }
 
 .fixed-header + .app-main {
@@ -36,10 +38,10 @@ export default {
 }
 
 .hasTagsView {
-  // .app-main {
-  //   /* 84 = navbar + tags-view = 50 + 34 */
-  //   // min-height: calc(100vh - 84px);
-  // }
+  .app-main {
+    /* 84 = navbar + tags-view = 50 + 34 */
+    min-height: calc(100vh - 84px);
+  }
 
   .fixed-header + .app-main {
     padding-top: 84px;

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

@@ -25,7 +25,7 @@ export default {
   data() {
     return {
       title: '采美后台',
-      logo: '/favicon.svg'
+      logo: '/favicon.ico'
     }
   }
 }

+ 29 - 33
src/router/index.js

@@ -14,35 +14,38 @@ import webPageRouter from './modules/webPage'
 import productRouter from './modules/product'
 import financeRouter from './modules/finance'
 import keywordLibraryRouter from './modules/keywordLibrary'
+import memberRouter from './modules/member'
 // import tableRouter from './modules/table'
 // import nestedRouter from './modules/nested'
 
 /**
- * Note: sub-menu only appear when route children.length >= 1
- * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
- *
- * hidden: true                   if set true, item will not show in the sidebar(default is false)
- * alwaysShow: true               if set true, will always show the root menu
- *                                if not set alwaysShow, when item has more than one children route,
- *                                it will becomes nested mode, otherwise not show the root menu
- * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
- * name:'router-name'             the name is used by <keep-alive> (must set!!!)
- * meta : {
-    roles: ['admin','editor']    control the page roles (you can set multiple roles)
-    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
-    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
-    noCache: true                if set true, the page will no be cached(default is false)
-    affix: true                  if set true, the tag will affix in the tags-view
-    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
-    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
+  // 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
+  hidden: true // (默认 false)
+  //当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
+  redirect: 'noRedirect'
+  // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
+  // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
+  // 若你想不管路由下面的 children 声明的个数都显示你的根路由
+  // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
+  alwaysShow: true
+
+  name: 'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
+  meta: {
+    roles: ['admin', 'editor'] // 设置该路由进入的权限,支持多个权限叠加
+    title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
+    icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon
+    noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
+    breadcrumb: false //  如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)
+    affix: true // 如果设置为true,它则会固定在tags-view中(默认 false)
+
+    // 当路由设置了该属性,则会高亮相对应的侧边栏。
+    // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list
+    // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置
+    activeMenu: '/article/list'
   }
  */
 
-/**
- * constantRoutes
- * a base page that does not have permission requirements
- * all roles can be accessed
- */
+// 基础路由
 export const constantRoutes = [
   {
     path: '/',
@@ -89,10 +92,7 @@ export const constantRoutes = [
   }
 ]
 
-/**
- * asyncRoutes
- * the routes that need to be dynamically loaded based on user roles
- */
+// 默认路由列表
 export const asyncRoutes = [
   sysRouter,
   userRouter,
@@ -100,12 +100,8 @@ export const asyncRoutes = [
   productRouter,
   financeRouter,
   wechatRouter,
+  memberRouter,
   keywordLibraryRouter
-  /** when your routing map is too long, you can split it into small modules **/
-  // componentsRouter,
-  // chartsRouter,
-  // nestedRouter,
-  // tableRouter,
 ]
 
 /**
@@ -137,7 +133,7 @@ export const afterRoutes = [
   // 404 page must be placed at the end !!!
   { path: '*', redirect: '/404', hidden: true }
 ]
-
+// 创建路由对象
 const createRouter = () =>
   new Router({
     // mode: 'history', // require service support
@@ -147,7 +143,7 @@ const createRouter = () =>
 
 const router = createRouter()
 
-// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+// 重置路由
 export function resetRouter() {
   const newRouter = createRouter()
   router.matcher = newRouter.matcher // reset router

+ 91 - 0
src/router/modules/member.js

@@ -0,0 +1,91 @@
+import Layout from '@/layout'
+
+const memberRouter = {
+  path: '/member',
+  component: Layout,
+  redirect: '/member/member',
+  alwaysShow: true, // will always show the root menu
+  name: 'MemberSetting', // name必须和后台配置一致,不然匹配不到
+  meta: { title: '超级会员管理', icon: 'component' },
+  children: [
+    {
+      path: 'member',
+      component: () => import('@/views/index'),
+      redirect: '/member/member/list',
+      name: 'MemberManage',
+      meta: { title: '超级会员列表' },
+      children: [
+        {
+          path: 'list',
+          hidden: true,
+          component: () => import('@/views/member/member/list.vue'),
+          name: 'MemberList',
+          meta: { title: '会员列表', activeMenu: '/finance/member' }
+        },
+        {
+          path: 'record-list',
+          hidden: true,
+          component: () => import('@/views/member/member/record-list.vue'),
+          name: 'MemberRecordList',
+          meta: { title: '购买记录', activeMenu: '/member/member' }
+        }
+      ]
+    },
+    {
+      path: 'coupon',
+      component: () => import('@/views/index'),
+      redirect: '/member/coupon/list',
+      name: 'MemberCoupon',
+      meta: { title: '超级会员专属优惠券' },
+      children: [
+        {
+          path: 'list',
+          hidden: true,
+          component: () => import('@/views/member/coupon/list.vue'),
+          name: 'MemberCouponList',
+          meta: { title: '优惠券列表', activeMenu: '/member/coupon' }
+        },
+        {
+          path: 'edit',
+          hidden: true,
+          component: () => import('@/views/member/coupon/edit.vue'),
+          name: 'MemberEdit',
+          meta: { title: '编辑优惠券', activeMenu: '/member/coupon' }
+        }
+      ]
+    },
+    {
+      path: 'product',
+      component: () => import('@/views/index'),
+      redirect: '/member/product/list',
+      name: 'MemberProduct',
+      meta: { title: '超级会员优惠商品' },
+      children: [
+        {
+          path: 'list',
+          hidden: true,
+          component: () => import('@/views/member/product/list.vue'),
+          name: 'MemberProductList',
+          meta: { title: '优惠商品', activeMenu: '/member/product' }
+        }
+      ]
+    },
+    {
+      path: 'meal',
+      component: () => import('@/views/index'),
+      redirect: '/member/meal/list',
+      name: 'MemberMeal',
+      meta: { title: '超级会员套餐配置' },
+      children: [
+        {
+          path: 'edit',
+          hidden: true,
+          component: () => import('@/views/member/meal/form.vue'),
+          name: 'MemberMealEdit',
+          meta: { title: '套餐配置', activeMenu: '/member/meal' }
+        }
+      ]
+    }
+  ]
+}
+export default memberRouter

+ 11 - 53
src/router/modules/user.js

@@ -5,65 +5,23 @@ import Layout from '@/layout'
 const userRouter = {
   path: '/user',
   component: Layout,
-  redirect: '/user/club',
+  redirect: '/user/list',
   alwaysShow: true, // will always show the root menu
-  name: 'userSetting', // name必须和后台配置一致,不然匹配不到
+  name: 'UserSitting', // name必须和后台配置一致,不然匹配不到
   meta: { title: '用户管理', icon: 'user' },
   children: [
     {
-      path: 'record',
-      component: () => import('@/views/index'),
-      redirect: '/user/record/list',
-      name: 'UserRecordMenu',
-      meta: { title: '用户行为记录', icon: 'international' },
-      children: [
-        {
-          path: 'list',
-          hidden: true,
-          component: () => import('@/views/user/record/list'),
-          name: 'RecordList',
-          meta: { title: '用户行为记录', activeMenu: '/user/record' }
-        },
-        {
-          path: 'detail',
-          hidden: true,
-          component: () => import('@/views/user/record/detail-list.vue'),
-          name: 'RecordDtails',
-          meta: { title: '查看详情', activeMenu: '/user/record/' }
-        }
-      ]
+      path: 'record-list',
+      component: () => import('@/views/user/record/list'),
+      name: 'RecordList',
+      meta: { title: '用户行为记录', icon: 'international', activeMenu: '/user/list' }
     },
     {
-      path: 'club',
-      name: 'UserClubMenu',
-      redirect: '/user/club/list',
-      alwaysShow: true,
-      component: () => import('@/views/index'),
-      meta: { title: '机构管理' },
-      children: [
-        {
-          path: 'list',
-          name: 'ClubList',
-          component: () => import('@/views/user/club/list'),
-          meta: { title: '机构列表' }
-        }
-      ]
-    },
-    {
-      path: 'supplier',
-      name: 'UserSupplierMenu',
-      redirect: '/user/supplier/list',
-      alwaysShow: true,
-      component: () => import('@/views/index'),
-      meta: { title: '供应商管理', useDefault: true },
-      children: [
-        {
-          path: 'list',
-          name: 'SupplierAllList',
-          component: () => import('@/views/user/supplier/list'),
-          meta: { title: '供应商列表' }
-        }
-      ]
+      path: 'detail-list',
+      hidden: true,
+      component: () => import('@/views/user/record/detail-list'),
+      name: 'RecordDtails',
+      meta: { title: '查看详情', noCache: true, activeMenu: '/user/list' }
     }
   ]
 }

+ 19 - 9
src/store/modules/tagsView.js

@@ -1,6 +1,6 @@
 const state = {
   visitedViews: [],
-  cachedViews: ['AppChildMain']
+  cachedViews: []
 }
 
 const mutations = {
@@ -28,8 +28,13 @@ const mutations = {
     }
   },
   DEL_CACHED_VIEW: (state, view) => {
-    const index = state.cachedViews.indexOf(view.name)
-    index > -1 && state.cachedViews.splice(index, 1)
+    for (const i of state.cachedViews) {
+      if (i === view.name) {
+        const index = state.cachedViews.indexOf(i)
+        state.cachedViews.splice(index, 1)
+        break
+      }
+    }
   },
 
   DEL_OTHERS_VISITED_VIEWS: (state, view) => {
@@ -38,12 +43,12 @@ const mutations = {
     })
   },
   DEL_OTHERS_CACHED_VIEWS: (state, view) => {
-    const index = state.cachedViews.indexOf(view.name)
-    if (index > -1) {
-      state.cachedViews = state.cachedViews.slice(index, index + 1)
-    } else {
-      // if index = -1, there is no cached tags
-      state.cachedViews = []
+    for (const i of state.cachedViews) {
+      if (i === view.name) {
+        const index = state.cachedViews.indexOf(i)
+        state.cachedViews = state.cachedViews.slice(index, index + 1)
+        break
+      }
     }
   },
 
@@ -63,6 +68,11 @@ const mutations = {
         break
       }
     }
+  },
+  // 清空全部页面
+  CLEAR_ALL_VIEW: (state) => {
+    state.visitedViews = []
+    state.cachedViews = []
   }
 }
 

+ 3 - 3
src/styles/index.scss

@@ -241,9 +241,9 @@ aside {
 }
 //添加、更新表单样式
 .form-container {
-  position: absolute;
-  left: 0;
-  right: 0;
+  // position: absolute;
+  // left: 0;
+  // right: 0;
   width: 720px;
   padding: 35px 35px 15px 35px;
   margin: 20px auto;

+ 13 - 9
src/utils/request.js

@@ -12,7 +12,7 @@ const service = axios.create({
 
 // request interceptor
 service.interceptors.request.use(
-  config => {
+  (config) => {
     // do something before request is sent
 
     if (store.getters.token) {
@@ -23,7 +23,7 @@ service.interceptors.request.use(
     }
     return config
   },
-  error => {
+  (error) => {
     // do something with request error
     console.log(error) // for debug
     return Promise.reject(error)
@@ -42,7 +42,7 @@ service.interceptors.response.use(
    * Here is just an example
    * You can also judge the status by HTTP Status Code
    */
-  response => {
+  (response) => {
     const res = response.data
 
     // if the custom code is -1, it is judged as an error.
@@ -56,11 +56,15 @@ service.interceptors.response.use(
       // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
       if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
         // to re-login
-        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
-          confirmButtonText: 'Re-Login',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(() => {
+        MessageBox.confirm(
+          'You have been logged out, you can cancel to stay on this page, or log in again',
+          'Confirm logout',
+          {
+            confirmButtonText: 'Re-Login',
+            cancelButtonText: 'Cancel',
+            type: 'warning'
+          }
+        ).then(() => {
           store.dispatch('user/resetToken').then(() => {
             location.reload()
           })
@@ -82,7 +86,7 @@ service.interceptors.response.use(
       return res
     }
   },
-  error => {
+  (error) => {
     console.log('err' + error) // for debug
     Message({
       message: error.msg || error.message,

+ 1 - 17
src/views/index.vue

@@ -1,19 +1,3 @@
 <template>
-  <keep-alive :include="cachedViews">
-    <router-view :key="key" />
-  </keep-alive>
+  <router-view />
 </template>
-
-<script>
-export default {
-  name: 'AppChildMain',
-  computed: {
-    cachedViews() {
-      return this.$store.state.tagsView.cachedViews
-    },
-    key() {
-      return this.$route.path
-    }
-  }
-}
-</script>

+ 162 - 0
src/views/member/coupon/components/good-dialog.vue

@@ -0,0 +1,162 @@
+<template>
+  <el-dialog
+    title="添加商品"
+    :visible.sync="visible"
+    width="1100px"
+    :close-on-click-modal="false"
+    :show-close="false"
+  >
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>商品ID:</span>
+        <el-input
+          v-model="listQuery.productId"
+          placeholder="商品ID"
+          clearable
+          @input="e => (listQuery.productId= checkedInput(e,1))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>商品名称:</span>
+        <el-input
+          v-model="listQuery.name"
+          placeholder="商品名称"
+          clearable
+          @input="e => (listQuery.name= checkedInput(e,2))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>供应商名称:</span>
+        <el-input
+          v-model="listQuery.shopName"
+          placeholder="供应商名称"
+          clearable
+          style="width:160px;"
+          @input="e => (listQuery.shopName= checkedInput(e,2))"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+      </div>
+    </div>
+    <el-table ref="table" v-loading="isLoading" :data="list" height="400px" border @select="handleSelect">
+      <el-table-column type="selection" width="50" />
+      <el-table-column prop="productId" label="商品ID" align="center" width="80" />
+      <el-table-column prop="coupon" label="商品图片" align="center" width="100">
+        <template v-if="row.productId" slot-scope="{ row }">
+          <el-popover
+            placement="top-start"
+            title=""
+            width="180"
+            trigger="hover"
+          >
+            <img :src="row.mainImage" alt="" style="width:100px;height:100px;">
+            <img slot="reference" :src="row.mainImage" alt="" style="width:50px;height:50px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column prop="name" label="商品名称" align="center" />
+      <el-table-column prop="shopName" label="供应商" align="center" width="250" />
+      <el-table-column prop="price" label="机构价" align="center" width="160">
+        <template slot-scope="{ row }">
+          ¥{{ row.price ? row.price : '0.00' }}
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page-sizes="[20]"
+      :page-size="20"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="getList"
+    />
+    <div slot="footer">
+      <el-button @click="handleCanle">取 消</el-button>
+      <el-button type="primary" :disabled="disabled" @click="handleAddProConfirm(productRadio)">确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { fetchProductList } from '@/api/member/member'
+
+export default {
+  name: 'ProDialog',
+  filters: {
+    NumFormat(value) {
+      // 处理金额
+      return Number(value).toFixed(2)
+    }
+  },
+  data() {
+    return {
+      visible: true,
+      listQuery: {
+        productId: '', // 商品id
+        name: '', // 商品名称
+        shopName: '', // 供应商名称
+        pageNum: 1,
+        pageSize: 20
+      },
+      list: [],
+      total: 0,
+      productRadio: null,
+      isLoading: true
+    }
+  },
+  computed: {
+    disabled() {
+      return this.productRadio === null
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 商品列表
+    async getList() {
+      this.isLoading = true
+      const res = await fetchProductList(this.listQuery)
+      this.list = res.data.results
+      this.total = res.data.totalRecord
+      this.isLoading = false
+    },
+    // 选择商品
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.productRadio = row
+    },
+    handleAddProConfirm() {
+      // 确认选择商品
+      this.productRadio.pcStatus = '1'
+      this.productRadio.appletsStatus = '1'
+      this.productRadio.sort = 1
+      this.$emit('confirm', this.productRadio)
+    },
+    handleCanle() {
+      // 取消弹窗
+      this.$emit('cancel')
+    },
+    checkedInput(event, type) {
+      let pattern = ''
+      switch (type) {
+        case 1:
+          pattern = /[^\d]/g
+          break
+        case 2:
+          pattern = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
+          break
+      }
+      return event.replace(pattern, '')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep{
+  thead .el-checkbox{display: none !important;}
+}
+</style>

+ 137 - 0
src/views/member/coupon/components/shop-dialog.vue

@@ -0,0 +1,137 @@
+<template>
+  <el-dialog title="选择供应商" :visible.sync="visible" width="1100px">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>供应商Id:</span>
+        <el-input
+          v-model="listQuery.shopId"
+          placeholder="供应商Id"
+          clearable
+          @input="e => (listQuery.shopId= checkedInput(e,1))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>供应商名称:</span>
+        <el-input
+          v-model="listQuery.name"
+          placeholder="供应商名称"
+          clearable
+          @input="e => (listQuery.name= checkedInput(e,2))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>供应商简称:</span>
+        <el-input
+          v-model="listQuery.sname"
+          placeholder="供应商简称"
+          clearable
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">搜索</el-button>
+      </div>
+    </div>
+    <el-table ref="table" :data="list" height="350px" border @select="handleSelect">
+      <el-table-column type="selection" width="55" />
+      <el-table-column property="shopId" label="供应商ID" width="150" />
+      <el-table-column property="name" label="供应商名称" width="150" />
+      <el-table-column property="sname" label="供应商简称" width="200">
+        <template slot-scope="{ row }">
+          {{ row.sname ? row.sname : '---' }}
+        </template>
+      </el-table-column>
+      <el-table-column property="linkMan" label="联系人" />
+      <el-table-column property="contractMobile" label="手机号">
+        <template slot-scope="{ row }">
+          {{ row.contractMobile ? row.contractMobile : '---' }}
+        </template>
+      </el-table-column>
+    </el-table>
+    <div slot="footer">
+      <el-button @click="handleCanle">取 消</el-button>
+      <el-button type="primary" :disabled="disabled" @click="handleAddProConfirm(shopRadio)">确 定</el-button>
+    </div>
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page-sizes="[20]"
+      :page-size="20"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="getList"
+    />
+  </el-dialog>
+</template>
+
+<script>
+import { fetchFindShop } from '@/api/member/member'
+
+export default {
+  name: 'ShopDialog',
+  data() {
+    return {
+      visible: true,
+      listQuery: {
+        shopId: '', // 供应商id
+        name: '', // 供应商名称
+        sname: '', // 供应商简称
+        pageNum: 1,
+        pageSize: 20
+      },
+      list: [],
+      total: 0,
+      shopRadio: null,
+      isLoading: true
+    }
+  },
+  computed: {
+    disabled() {
+      return this.shopRadio === null
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 获取供应商列表
+    async getList() {
+      this.isLoading = true
+      const res = await fetchFindShop(this.listQuery)
+      this.list = res.data.results
+      this.total = res.data.totalRecord
+      this.isLoading = false
+    },
+    // 选择供应商
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.shopRadio = row
+    },
+    handleAddProConfirm() {
+      // 确认选择商品
+      this.$emit('confirm', this.shopRadio)
+    },
+    handleCanle() {
+      // 取消弹窗
+      this.$emit('cancel')
+    },
+    checkedInput(event, type) {
+      let pattern = ''
+      switch (type) {
+        case 1:
+          pattern = /[^\d]/g
+          break
+        case 2:
+          pattern = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
+          break
+      }
+      return event.replace(pattern, '')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep{
+  thead .el-checkbox{display: none !important;}
+}
+</style>

+ 448 - 0
src/views/member/coupon/edit.vue

@@ -0,0 +1,448 @@
+<template>
+  <div ref="scrollDiv" class="app-container">
+    <div v-if="editType === 'add'" class="filter-container">
+      <div class="filter-control">
+        <span style="color: red;">在本页面,对每三个月给超级会员自动发放的优惠券进行配置,配置完成后下一阶段才生效(最少配置1个,最多5个)。</span>
+        <el-button type="primary" icon="el-icon-plus" size="mini" :disabled="disabled" @click="handleAddList">新增优惠券</el-button>
+      </div>
+    </div>
+    <div v-else class="filter-container">
+      <div class="filter-control">
+        <span>优惠月份:</span>
+        <el-input
+          v-model="formParams.month"
+          placeholder="开始月份"
+          clearable
+          suffix-icon="el-icon-date"
+          :disabled="true"
+          style="width:100px;"
+        />
+        -
+        <el-input
+          v-model="formParams.endMonth"
+          placeholder="结束月份"
+          clearable
+          suffix-icon="el-icon-date"
+          :disabled="true"
+          style="width:100px;"
+        />
+        <el-button type="primary" icon="el-icon-plus" size="mini" :disabled="disabled" style="margin-left: 30px;" @click="handleAddList">新增优惠券</el-button>
+      </div>
+    </div>
+    <el-form ref="couponFrom" :model="form" label-width="100px">
+      <div v-for="(formItem,index) in form.formList" :key="formItem.key" class="form-cell" :data-key="formItem.key">
+        <el-form-item :label="`优惠券${index+1}:`" style="margin-bottom: 10px;" :rules="rules.couponType">
+          <el-radio-group v-model="formItem.couponType">
+            <el-radio :label="0">活动券</el-radio>
+            <el-radio :label="1">品类券</el-radio>
+            <el-radio :label="3">店铺券</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <template v-if="formItem.couponType === 3">
+          <el-form-item label="供应商" :prop="`formList.${index}.shopData`" :rules="rules.shopData">
+            <template v-if="formItem.shopData && formItem.shopData.length === 0">
+              <el-button type="primary" autofocussize="mini" size="mini" icon="el-icon-plus" @click="handleShopDialogVisible(index)">选择供应商</el-button>
+              <el-checkbox-group v-show="false" v-model="formItem.shopData" />
+            </template>
+            <template v-else>
+              <el-table :data="formItem.shopData" border width="1000">
+                <el-table-column prop="name" label="供应商名称" align="center" width="300" />
+                <el-table-column prop="sname" label="供应商简称" align="center" width="200" />
+                <el-table-column prop="linkMan" label="联系人" align="center" width="100" />
+                <el-table-column prop="contractMobile" label="手机号" align="center" width="150">
+                  <template slot-scope="{ row }">
+                    {{ row.contractMobile ? row.contractMobile : '---' }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" align="center">
+                  <el-button type="text" @click="deleteShop(formItem,index)">删除</el-button>
+                </el-table-column>
+              </el-table>
+            </template>
+          </el-form-item>
+        </template>
+        <el-form-item label="优惠券金额:" :prop="`formList.${index}.couponAmount`" :rules="rules.couponAmount">
+          <el-input v-model="formItem.couponAmount" style="width: 150px" size="mini" />
+        </el-form-item>
+        <el-form-item label="优惠条件:" :prop="`formList.${index}.touchPrice`" :rules="rules.touchPrice">
+          <el-input v-model="formItem.touchPrice" style="width: 220px" size="mini">
+            <template slot="prepend" style="padding: 0 10px;">订单商品总额满:</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="优惠商品:" :hidden="formItem.couponType === 0 ? false : true" style="margin-bottom: 10px;" :rules="rules.productType">
+          <el-radio-group v-model="formItem.productType">
+            <el-radio :label="'1'">全商城商品</el-radio>
+            <el-radio :label="'2'">指定商品</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <template v-if="formItem.productType === '2'&& formItem.couponType === 0">
+          <el-form-item label="商品信息" :prop="`formList.${index}.associateList`" :rules="rules.associateList">
+            <el-button type="primary" icon="el-icon-sort" size="mini" :disabled="disabled3(formItem.associateList)" @click="handleOnInputBlur(formItem,index)">一键排序</el-button>
+            <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleGoodDialogVisible(index)">添加商品</el-button>
+            <el-checkbox-group v-show="false" v-model="formItem.associateList" />
+            <el-table v-show="formItem.associateList && formItem.associateList.length>0" :data="formItem.associateList" border>
+              <el-table-column property="mainImage" label="商品图片" align="center" width="80">
+                <template slot-scope="{ row }">
+                  <el-popover
+                    placement="top-start"
+                    title=""
+                    width="180"
+                    trigger="hover"
+                  >
+                    <img :src="row.mainImage" alt="" style="width:150px;height:150px;">
+                    <img slot="reference" :src="row.mainImage" alt="" style="width:30px;height:30px;">
+                  </el-popover>
+                </template>
+              </el-table-column>
+              <el-table-column property="name" label="商品名称" align="center" />
+              <el-table-column property="shopName" label="供应商" align="center" />
+              <el-table-column label="网站状态" align="center" width="80">
+                <template slot-scope="scope">
+                  <el-switch
+                    v-model="scope.row.pcStatus"
+                    active-color="#1890ff"
+                    inactive-color="#DCDFE6"
+                    active-value="1"
+                    inactive-value="0"
+                  />
+                </template>
+              </el-table-column>
+              <el-table-column label="小程序状态" align="center" width="100">
+                <template slot-scope="scope">
+                  <el-switch
+                    v-model="scope.row.appletsStatus"
+                    active-color="#1890ff"
+                    inactive-color="#DCDFE6"
+                    active-value="1"
+                    inactive-value="0"
+                  />
+                </template>
+              </el-table-column>
+              <el-table-column label="排序" align="center" width="100">
+                <template slot-scope="{row}">
+                  <el-input v-model="row.sort" maxlength="4" minlength="1" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" align="center">
+                <template slot-scope="scope">
+                  <el-button type="text" @click="handleDeletePros(formItem,scope.$index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-form-item>
+        </template>
+        <el-form-item label="优惠品类:" :hidden="formItem.couponType ===1 ? false : true" :rules="rules.categoryType">
+          <el-radio-group v-model="formItem.categoryType">
+            <el-radio :label="'1'">产品</el-radio>
+            <el-radio :label="'2'">仪器</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-button class="delete_label" :disabled="disabled1" icon="el-icon-delete" size="mini" type="danger" @click="handleDelete(formItem,index)">删除</el-button>
+      </div>
+    </el-form>
+    <div class="el-dialog__footer">
+      <el-button type="primary" @click="onSubmit('couponFrom')">保存</el-button>
+    </div>
+    <!-- 供应商弹框 -->
+    <shop-dialog v-if="dialogShopVisible" ref="shopDialog" @cancel="handleShopCancel" @confirm="handleShopConfirm" />
+    <!-- 商品弹框 -->
+    <good-dialog v-if="dialogGoodVisible" ref="goodDialog" @cancel="handleGoodCancel" @confirm="handleGoodConfirm" />
+  </div>
+</template>
+
+<script>
+import { svipCoupon, saveVipCoupon, delCoupon } from '@/api/member/member'
+import goodDialog from './components/good-dialog'
+import shopDialog from './components/shop-dialog'
+const defaultForm = () => {
+  return {
+    shopId: 0,
+    shopData: [],
+    couponType: 0,
+    couponAmount: '',
+    touchPrice: '',
+    productType: '1',
+    categoryType: '1',
+    associateList: []// 指定商品信息
+  }
+}
+export default {
+  name: 'MemberEdit',
+  components: { goodDialog, shopDialog },
+  data() {
+    return {
+      defaultShop: {
+        shopId: 0,
+        shopData: []
+      },
+      form: {
+        formList: []
+      },
+      formParams: {
+        id: '',
+        month: '',
+        endMonth: '',
+        status: '',
+        configure: 1, // 专属优惠券配置  1是、0否
+        coupons: []
+      },
+      doctorId: '',
+      editType: 'add',
+      time: '',
+      dialogTableVisible: false,
+      shopTotal: 0,
+      rows: [],
+      // 供应商列表
+      shopTableData: [],
+      // 商品列表
+      dialogProductVisible: false,
+      productTableData: [],
+      productTotal: 0,
+      addIndex: 0,
+      dialogShopVisible: false,
+      dialogGoodVisible: false,
+      rules: {
+        couponType: [{ required: true, message: '请选择优惠券类型', trigger: 'blur' }],
+        shopId: [{ required: true, message: '请选择供应商', trigger: 'blur' }],
+        productType: [{ required: true, message: '请选择优惠商品', trigger: 'blur' }],
+        shopData: [{ required: true, type: 'array', message: '请添加一个供应商', trigger: ['change'] }],
+        associateList: [{ required: true, type: 'array', message: '请至少添加一个商品', trigger: ['change'] }],
+        categoryType: [{ required: true, message: '请选择优惠品类', trigger: 'blur' }],
+        couponAmount: [{ required: true, message: '优惠券金额不能为空', trigger: 'blur' }],
+        touchPrice: [{ required: true, message: '优惠条件不能为空', trigger: 'blur' }]
+      },
+      delFlag: false
+    }
+  },
+  computed: {
+    disabled() {
+      return this.form.formList.length === 5
+    },
+    disabled1() {
+      return this.form.formList.length === 1
+    }
+  },
+  created() {
+    this.editType = this.$route.query.type || 'edit'
+    console.log('editType', this.editType)
+    this.initForm(this.editType)
+  },
+  methods: {
+    initForm(type) {
+      if (type === 'add') {
+        console.log('add')
+        this.svipCoupon(type, 1, 0)
+      } else {
+        console.log('edit')
+        this.svipCoupon(type, 0, this.$route.query.id)
+      }
+    },
+    async svipCoupon(type, configure, id) {
+      const res = await svipCoupon({ id: id, configure: configure })
+      const data = res.data.svipcouponForm
+      this.formParams = { ...this.formParams, ...data }
+      if (data.coupons.length > 0) {
+        this.delFlag = true
+        this.form.formList = data.coupons.map((el) => {
+          el = { ...el, ...this.defaultShop }
+          if (el.shop) {
+            el.shopData.push(el.shop)
+          }
+          return el
+        })
+        console.log('formList', this.form.formList)
+      } else {
+        for (let i = 0; i <= 2; i++) {
+          this.form.formList.push(defaultForm())
+        }
+      }
+    },
+    handleShopDialogVisible(index) {
+      // 显示选择供应商弹窗
+      this.addIndex = index
+      this.dialogShopVisible = true
+    },
+    handleGoodDialogVisible(index) {
+      // 显示选择商品弹窗
+      this.addIndex = index
+      this.dialogGoodVisible = true
+    },
+
+    deleteShop(formItem, index) {
+      // 清除供应商
+      formItem.shopId = ''
+      formItem.shopData = []
+      this.$nextTick(() => {
+        console.log('index', formItem)
+        this.form.formList[index].shopId
+        this.form.formList[index].shopData
+      })
+    },
+    handleShopConfirm(data) {
+      // 确认选择供应商
+      this.form.formList[this.addIndex].shopId = data.shopId
+      this.form.formList[this.addIndex].shopData.push(data)
+      console.log('formList', this.form.formList[this.addIndex])
+      this.handleShopCancel()
+    },
+    handleGoodConfirm(data) {
+      // 确认选择商品
+      console.log('data', data)
+      console.log('addIndex', this.addIndex)
+      const from = this.form.formList[this.addIndex]
+      from.associateList.push(data)
+      console.log('from', this.form.formList)
+      this.handleGoodCancel()
+    },
+    handleDeletePros(formItem, index) {
+      // 删除商品
+      this.$confirm('确定删除吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        formItem.associateList.splice(index, 1)
+      })
+    },
+    onSubmit(formName) {
+      this.$refs[formName].validate(valid => {
+        if (valid) {
+          this.handleFormParams()
+          this.$confirm('是否提交数据', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(() => {
+            console.log('formParams', this.formParams)
+            if (this.editType === 'add') {
+              this.formParams.configure = 1
+            } else {
+              this.formParams.configure = 0
+            }
+            this.saveVipCoupon(this.formParams)
+          })
+        } else {
+          return false
+        }
+      })
+    },
+    handleFormParams() {
+      // 处理参数
+      this.formParams.coupons = this.form.formList.map((el) => {
+        if (el.associateList && el.associateList.length > 0) {
+          el.associateList = el.associateList.map((pro) => {
+            return {
+              sort: pro.sort,
+              pcStatus: pro.pcStatus,
+              appletsStatus: pro.appletsStatus,
+              name: pro.name,
+              shopName: pro.shopName,
+              mainImage: pro.mainImage,
+              productId: pro.productId
+            }
+          })
+        }
+        return el
+      })
+    },
+    async saveVipCoupon(params) {
+      // 保存超级会员优惠券
+      await saveVipCoupon(params)
+      this.$message.success('保存成功')
+      setTimeout(() => {
+        this.$router.push({ path: '/member/coupon/list' })
+      }, 1000)
+    },
+    handleShopCancel() {
+      // 取消选择供应商
+      this.dialogShopVisible = false
+      this.$refs.shopDialog.visible = false
+    },
+    handleGoodCancel() {
+      // 取消编辑商品
+      this.dialogGoodVisible = false
+      this.$refs.goodDialog.visible = false
+    },
+    handleAddList() {
+      // 新增优惠券
+      this.form.formList.push(defaultForm())
+    },
+    handleDelete(item, index) {
+      // 删除单挑优惠券
+      this.$confirm('确定删除此优惠券吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        if (this.delFlag) {
+          this.delCoupon(item.id, index)
+        } else {
+          this.form.formList.splice(index, 1)
+        }
+      })
+    },
+    async delCoupon(id, index) {
+      // 保存超级会员优惠券
+      if (id) {
+        try {
+          await delCoupon(id)
+        } catch (error) {
+          console.log(error)
+        }
+      }
+      this.form.formList.splice(index, 1)
+    },
+    handleOnInputBlur(formItem) {
+      // 商品排序
+      formItem.associateList.sort(this.sortByEnent('sort'))
+    },
+    sortByEnent(i) {
+      // 排序
+      return function(a, b) {
+        return a[i] - b[i] //  a[i] - b[i]为正序,倒叙为  b[i] - a[i]
+      }
+    },
+    handlePcStatusChange(row) {
+      // PC状态开启关闭
+      console.log('row', row)
+      if (row.pcStatus === '0') {
+        row.pcStatus = '1'
+      } else {
+        row.pcStatus = '0'
+      }
+    },
+    handleAppStatusChange(row) {
+      // 小程序状态开启关闭
+      if (row.appletsStatus === '0') {
+        row.appletsStatus = '1'
+      } else {
+        row.appletsStatus = '0'
+      }
+    },
+    disabled3(array) {
+      if (array && array.length >= 2) {
+        return false
+      } else {
+        return true
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.form-cell{
+  border: 1px solid #e1e1e1;
+  border-radius: 8px;
+  box-sizing: border-box;
+  box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.07);
+  padding: 20px;
+  position: relative;
+  margin-bottom: 20px;
+}
+.delete_label{
+  position: absolute;
+  right: 2%;
+  top: 20px;
+}
+</style>

+ 267 - 0
src/views/member/coupon/list.vue

@@ -0,0 +1,267 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>优惠券类型:</span>
+        <el-select v-model="listQuery.couponType" style="width:120px;" clearable @change="getList">
+          <el-option value="" label="请选择" />
+          <el-option :value="0" label="活动券" />
+          <el-option :value="1" label="品类券" />
+          <el-option :value="1" label="店铺券" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>优惠状态:</span>
+        <el-select v-model="listQuery.status" style="width:120px;" clearable @change="getList">
+          <el-option value="" label="请选择" />
+          <el-option value="0" label="未生效" />
+          <el-option value="1" label="已生效" />
+          <el-option value="2" label="已关闭" />
+          <el-option value="3" label="已失效" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>优惠月份:</span>
+        <el-date-picker
+          v-model="time"
+          type="daterange"
+          unlink-panels
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :picker-options="pickerOptions"
+          @change="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+        <el-button type="primary" @click="handleEdit('add')">专属优惠券配置</el-button>
+      </div>
+    </div>
+    <el-table :data="tableData" height="650" border style="width: 100%">
+      <el-table-column label="优惠月份" align="center" width="180">
+        <template slot-scope="{ row }">
+          {{ row.useTime }} -- {{ row.endTime }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="coupon" label="优惠券" align="left" width="700">
+        <template slot-scope="{ row }">
+          <p v-for="(item,index) in row.couponList" :key="index">
+            优惠券
+            {{ index + 1 }} : <b>{{ item.couponType === 0 ? '活动券' : item.couponType === 1 ? '品类券' : '店铺券' }}</b>
+            ¥{{ item.couponAmount }},满{{ item.touchPrice }}可用
+            {{ item.productType ? item.productType === '1' ? ',全商城商品通用' : ',仅可购买指定商品' : '' }}
+            {{ item.categoryType ? item.categoryType === '1' ? ',仅可购买产品类商品' : ',仅可购买仪器类商品' : '' }}
+            <span v-if="item.couponType === 3">,仅可购买店铺“{{ item.shopName }}”的商品</span>
+          </p>
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="优惠状态" align="center" width="80">
+        <template slot-scope="{ row }">
+          <span :class="{
+            purple: row.status == '0',
+            green: row.status == '1',
+            red: row.status == '2',
+            orange: row.status == '3',
+          }"
+          >
+            {{ row.status | statusFilters }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template slot-scope="{ row }">
+          <el-button type="primary" size="mini" @click="handleEdit('edit',row.id)">编辑</el-button>
+          <el-button type="danger" size="mini" :hidden="row.status !== 2 ? false : true" @click="handleCloseCoupon(row)">关闭</el-button>
+          <el-button type="primary" size="mini" :hidden="row.status === 2 ? false : true" @click="handleOpenCoupon(row)">开启</el-button>
+          <el-button type="danger" size="mini" @click="handleDeleteCoupon(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page-sizes="[20, 30, 50, 100]"
+      :page-size="20"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+    />
+  </div>
+</template>
+<script>
+import { fetchCouponList, fetchCloseCoupon, fetchOpenCoupon, fetchDeleteCoupon } from '@/api/member/member'
+export default {
+  name: 'MemberCouponList',
+  filters: {
+    statusFilters(value) {
+      console.log('value', value)
+      // 处理状态显示
+      const map = {
+        '0': '未生效',
+        '1': '已生效',
+        '2': '已关闭',
+        '3': '已失效'
+      }
+      return map[value]
+    }
+
+  },
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      activeName: 'first',
+      pickerOptions,
+      listQuery: {
+        couponType: '',
+        status: '',
+        startDate: '',
+        endDate: '',
+        pageNum: 1,
+        pageSize: 20
+      },
+      // 超级会员优惠券
+      total: 0,
+      time: '',
+      tableData: [],
+      dialogTableVisible: false,
+      msg: ''
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      if (this.time && this.time.length > 0) {
+        this.listQuery.startDate = this.time[0]
+        this.listQuery.endDate = this.time[1]
+      } else {
+        this.listQuery.startDate = ''
+        this.listQuery.endDate = ''
+      }
+      const res = await fetchCouponList(this.listQuery)
+      this.tableData = res.data.results
+      this.total = res.data.totalRecord
+    },
+    // 关闭超级会员专属优惠券
+    handleCloseCoupon(row) {
+      this.$confirm('是否关闭超级会员专属优惠券?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        fetchCloseCoupon(row.id).then(response => {
+          this.$message({
+            message: '操作成功',
+            type: 'success',
+            duration: 1000
+          })
+          this.getList()
+        })
+      })
+    },
+    // 开启超级会员专属优惠券
+    handleOpenCoupon(row) {
+      this.$confirm('是否开启超级会员专属优惠券?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        fetchOpenCoupon(row.id).then(response => {
+          this.$message({
+            message: '操作成功',
+            type: 'success',
+            duration: 1000
+          })
+          this.getList()
+        })
+      })
+    },
+    // 删除超级会员专属优惠券
+    handleDeleteCoupon(row) {
+      this.$confirm('是否要删除该优惠券套餐?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        fetchDeleteCoupon(row.id).then(response => {
+          this.$message({
+            message: '操作成功',
+            type: 'success',
+            duration: 1000
+          })
+          this.getList()
+        })
+      })
+    },
+    handleEdit(type, id) {
+      // 跳转编辑页
+      this.$router.push({ path: '/member/coupon/edit', query: { type: type, id: id }})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.purple {
+  color: #800080;
+}
+.green {
+  color: #00CC66;
+}
+.red {
+  color: red;
+}
+.orange {
+  color: #FF6600;
+}
+</style>

+ 84 - 0
src/views/member/meal/form.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="app-container">
+    <div class="club-container" style="width: 100%;height: 600px;padding-top: 40px;">
+      <div class="filter-container">
+        <div class="filter-control">
+          <el-form :model="form" label-width="140px">
+            <el-form-item label="套餐1:" prop="price1" :rules="rules.price1">
+              <span>12个月超级会员,售价:</span>
+              <el-input v-model="form.price1" style="width: 150px">
+                <template slot="append">元</template>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="套餐2:" prop="price2" :rules="rules.price2">
+              <span>3个月超级会员,售价:</span>
+              <el-input v-model="form.price2" style="width: 150px">
+                <template slot="append">元</template>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="采美豆兑换套餐:" prop="proportion" :rules="rules.proportion">
+              <span>采美豆兑换超会员套餐比例为:</span>
+              <el-input v-model="form.proportion" style="width: 150px">
+                <template slot="append">:1</template>
+              </el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="updateMeal">保存</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { fetchFindPackage, updatePrice } from '@/api/member/member'
+
+export default {
+  name: 'MemberMeal',
+  data() {
+    return {
+      form: {
+        price1: '',
+        price2: '',
+        proportion: ''
+      },
+      rules: {
+        price1: [{ required: true, message: '请输入套餐1价格', trigger: 'blur' }],
+        price2: [{ required: true, message: '请输入套餐2价格', trigger: 'blur' }],
+        proportion: [{ required: true, message: '请输入采美豆', trigger: 'blur' }]
+      },
+      dialogTableVisible: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      // 获取超级会员套餐配置
+      const res = await fetchFindPackage()
+      this.form.price1 = res.data.price1
+      this.form.price2 = res.data.price2
+      this.form.proportion = res.data.proportion
+    },
+    // 修改超级会员套餐配置
+    async updateMeal() {
+      try {
+        await updatePrice(this.form)
+        this.$message.success('保存超级会员套餐成功')
+      } catch (error) {
+        console.log(error)
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.label_width{
+  width: 150px;
+  text-align: right;
+}
+</style>

+ 503 - 0
src/views/member/member/list.vue

@@ -0,0 +1,503 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeName" type="border-card">
+      <el-tab-pane label="超级会员列表" name="first" />
+      <el-tab-pane label="赠送超级会员" name="second" />
+      <template v-if="activeName === 'first'">
+        <div class="filter-container">
+          <div class="filter-control">
+            <span>机构名称:</span>
+            <el-input
+              v-model="listQuery.clubName"
+              placeholder="机构名称"
+              clearable
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>联系人:</span>
+            <el-input
+              v-model="listQuery.linkMan"
+              placeholder="联系人"
+              clearable
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>手机号:</span>
+            <el-input
+              v-model="listQuery.mobile"
+              placeholder="手机号"
+              clearable
+              style="width:160px;"
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>购买套餐:</span>
+            <el-select v-model="listQuery.packageId" style="width:120px;" clearable @change="getList">
+              <el-option value="" label="请选择" />
+              <el-option :value="0" label="系统赠送" />
+              <el-option :value="1" label="1个月" />
+              <el-option :value="3" label="3个月" />
+              <el-option :value="12" label="12个月" />
+            </el-select>
+          </div>
+          <div class="filter-control">
+            <span>会员状态:</span>
+            <el-select v-model="listQuery.status" style="width:120px;" clearable @change="getList">
+              <el-option value="" label="请选择" />
+              <el-option :value="1" label="已生效" />
+              <el-option :value="2" label="已过期" />
+            </el-select>
+          </div><br>
+          <div class="filter-control">
+            <span>购买时间:</span>
+            <el-date-picker
+              v-model="time"
+              type="daterange"
+              unlink-panels
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              :picker-options="pickerOptions"
+              @change="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>到期时间:</span>
+            <el-date-picker
+              v-model="dueTime"
+              type="daterange"
+              unlink-panels
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              :picker-options="pickerEndOptions"
+              @change="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <el-button type="primary" @click="getList">查询</el-button>
+          </div>
+        </div>
+        <el-table v-loading="isLoading" :data="tableData" border style="width: 100%" :height="580">
+          <el-table-column prop="clubName" label="机构名称" align="center" />
+          <el-table-column prop="linkMan" label="联系人" align="center" />
+          <el-table-column prop="mobile" label="手机号" align="center" />
+          <el-table-column prop="status" label="会员状态" align="center" width="180">
+            <template slot-scope="{ row }">
+              <span :class="{
+                'el-span-status': row.status == '1',
+                'el-span-notStatus': row.status == '2',
+                'el-span-notEffective': row.status == '3',
+              }"
+              >
+                {{ row.status | statusFilters }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" width="180">
+            <template slot-scope="{ row }">
+              <el-button type="primary" size="mini" @click="handleTagsList(row)">购买记录</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 页码 -->
+        <pagination
+          :total="total"
+          :page-sizes="[20, 30, 50, 100]"
+          :page-size="20"
+          :page.sync="listQuery.pageNum"
+          :limit.sync="listQuery.pageSize"
+          @pagination="getList"
+        />
+      </template>
+      <template v-else>
+        <div class="club-container" style="width: 100%;height: 600px;padding-top: 40px;">
+          <div class="filter-container">
+            <div class="filter-control">
+              <el-form :model="vipForm" :rules="rules" label-width="120px">
+                <el-form-item label="机构" prop="clubId" :hidden="!hiddenItem">
+                  <el-button type="primary" size="mini" icon="el-icon-plus" @click="handleDialogVisible">请选择机构</el-button>
+                  <el-checkbox-group v-show="false" v-model="vipForm.clubId" />
+                </el-form-item>
+                <el-form-item label="机构" :hidden="hiddenItem">
+                  <el-table :data="clubData" border width="1000">
+                    <el-table-column prop="clubId" label="机构ID" align="center" width="120" />
+                    <el-table-column prop="name" label="机构名称" align="center" width="250" />
+                    <el-table-column prop="linkMan" label="联系人" align="center" width="120" />
+                    <el-table-column prop="contractMobile" label="手机号" align="center" width="120" />
+                    <el-table-column label="操作" align="center" width="120">
+                      <el-button type="danger" size="mini" @click="deleteClub">删除</el-button>
+                    </el-table-column>
+                  </el-table>
+                </el-form-item>
+                <el-form-item label="赠送会员" :rules="rules.month">
+                  <el-input v-model="vipForm.month" style="width: 150px">
+                    <template slot="append">个月</template>
+                  </el-input>
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" :disabled="saveDisabled" @click="handleSaveVip">保存</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </div>
+        </div>
+      </template>
+    </el-tabs>
+    <!-- 机构弹框 -->
+    <el-dialog title="选择机构" :visible.sync="dialogTableVisible" width="1000px">
+      <div class="filter-container">
+        <div class="filter-control">
+          <span>机构ID:</span>
+          <el-input
+            v-model="clubForm.clubId"
+            placeholder="机构Id"
+            clearable
+            @input="checkedInput"
+          />
+        </div>
+        <div class="filter-control">
+          <span>机构名称:</span>
+          <el-input
+            v-model="clubForm.name"
+            placeholder="机构名称"
+            clearable
+          />
+        </div>
+        <div class="filter-control">
+          <span>机构简称:</span>
+          <el-input
+            v-model="clubForm.shortName"
+            placeholder="机构简称"
+            clearable
+          />
+        </div>
+        <div class="filter-control">
+          <el-button type="primary" @click="searchGetclubList">搜索</el-button>
+        </div>
+      </div>
+      <el-table ref="table" :data="clubTableData" height="350px" border @select="handleSelect">
+        <el-table-column type="selection" width="55" />
+        <el-table-column property="clubId" label="机构Id" align="center" width="150" />
+        <el-table-column property="name" label="机构名称" align="center" width="150" />
+        <el-table-column property="shortName" label="机构简称" align="center" width="200" />
+        <el-table-column property="linkMan" label="联系人" align="center" />
+        <el-table-column property="contractMobile" label="手机号" align="center" />
+      </el-table>
+      <div slot="footer">
+        <el-button @click="dialogTableVisible = false">取消</el-button>
+        <el-button type="primary" :disabled="disabled" @click="pushClub()">确定</el-button>
+      </div>
+      <!-- 页码 -->
+      <pagination
+        :total="clubTotal"
+        :page-sizes="[20, 30, 50, 100]"
+        :page-size="20"
+        :page.sync="clubForm.pageNum"
+        :limit.sync="clubForm.pageSize"
+        @pagination="getclubList"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+
+import { fetchMemberList, fetchClubList, saveSvip } from '@/api/member/member'
+
+const defaultListQuery = {
+  clubName: '', // 机构名称
+  linkMan: '', // 联系人
+  mobile: '', // 手机号
+  packageId: '', // 购买套餐
+  status: '', // 会员状态
+  startPayTime: '', // 购买开始时间
+  endPayTime: '', // 购买结束时间
+  startEndTime: '', //  到期开始时间
+  endEndTime: '', // 到期结束时间
+  pageNum: 1,
+  pageSize: 20
+}
+export default {
+  name: 'MemberList',
+  filters: {
+    statusFilters(value) {
+      console.log('value', value)
+      // 处理状态显示
+      const map = {
+        '0': '未生效',
+        '1': '已生效',
+        '2': '已过期',
+        '3': '暂未生效'
+      }
+      return map[value]
+    }
+  },
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    const pickerEndOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      isLoading: true,
+      activeName: 'first',
+      pickerOptions,
+      pickerEndOptions,
+      time: '',
+      dueTime: '',
+      listQuery: Object.assign({}, defaultListQuery),
+      // 会员列表总数
+      total: 0,
+      tableData: [],
+      vipForm: {
+        clubId: '',
+        userId: '',
+        contractMobile: '',
+        month: ''
+      },
+      dialogTableVisible: false,
+      // 选择机构总数
+      clubTotal: 0,
+      clubForm: {
+        clubId: '',
+        name: '',
+        shortName: '',
+        pageNum: 1,
+        pageSize: 20
+      },
+      hiddenItem: true,
+      clubTableData: [],
+      clubRadio: null,
+      rows: [], // 选择机构
+      clubData: [], // 机构数据
+      rules: {
+        month: [{ required: true, message: '请输入赠送数量', trigger: 'blur' }],
+        clubId: [{ required: true, message: '请选择机构', trigger: 'blur' }]
+      }
+    }
+  },
+  computed: {
+    disabled() {
+      return this.clubRadio === null
+    },
+    saveDisabled() {
+      return !(this.vipForm.clubId > 0 && this.vipForm.month !== '')
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 选择机构
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.clubRadio = row.clubId
+      this.vipForm.clubId = row.clubId
+      this.vipForm.userId = row.userId
+      this.vipForm.contractMobile = row.contractMobile
+      this.rows = row
+      console.log('clubRadio', this.clubRadio)
+    },
+    initTime() {
+      // 初始化获取时间筛选值
+      if (this.time && this.time.length > 0) {
+        this.listQuery.startPayTime = this.time[0]
+        this.listQuery.endPayTime = this.time[1]
+      } else {
+        this.listQuery.startPayTime = ''
+        this.listQuery.endPayTime = ''
+      }
+      if (this.dueTime && this.dueTime.length > 0) {
+        this.listQuery.startEndTime = this.dueTime[0]
+        this.listQuery.endEndTime = this.dueTime[1]
+      } else {
+        this.listQuery.startEndTime = ''
+        this.listQuery.endEndTime = ''
+      }
+    },
+    // 会员列表数据
+    async getList() {
+      this.isLoading = true
+      this.initTime()
+      const res = await fetchMemberList(this.listQuery)
+      this.tableData = res.data.results
+      this.total = res.data.totalRecord
+      this.isLoading = false
+    },
+    handleDialogVisible() {
+      // 宣誓弹窗
+      this.dialogTableVisible = true
+      this.getclubList()
+    },
+    searchGetclubList() {
+      // 搜索
+      this.clubForm.pageNum = 1
+      this.getclubList()
+    },
+    // 机构数据
+    async getclubList() {
+      const res = await fetchClubList(this.clubForm)
+      this.clubTableData = res.data.results
+      this.clubTotal = res.data.totalRecord
+    },
+    // 机构选择确认
+    pushClub() {
+      this.clubData.push(this.rows)
+      this.dialogTableVisible = false
+      this.row = []
+      this.hiddenItem = false
+      console.log(this.clubData)
+    },
+    // 清除机构
+    deleteClub() {
+      this.clubData = []
+      this.hiddenItem = true
+    },
+    async handleSaveVip() {
+      try {
+        await saveSvip(this.vipForm)
+        this.$message.success('操作成功')
+        setTimeout(() => {
+          this.activeName = 'first'
+          this.getList()
+          this.deleteClub()
+          this.vipForm.month = ''
+        }, 2000)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    handleTagsList(row) {
+      const query = {
+        userId: row.userId,
+        clubName: row.clubName, // 机构名称
+        linkMan: row.linkMan, // 联系人
+        mobile: row.mobile// 手机号
+      }
+      this.$router.push({ path: '/member/member/record-list', query: query })
+    },
+    checkedInput(event) {
+      this.clubForm.clubId = event.replace(/[^\d]/g, '')
+      console.log('event', this.clubForm.clubId)
+    }
+  }
+}
+
+</script>
+
+<style lang="scss" scoped>
+::v-deep{
+  thead .el-checkbox{display: none !important;}
+}
+.el-span-status {
+  color: rgb(16, 230, 8);
+}
+.el-span-notStatus {
+  color: rgb(245, 13, 13);
+}
+.el-span-notEffective {
+  color: rgb(245, 13, 13);
+}
+</style>

+ 379 - 0
src/views/member/member/record-list.vue

@@ -0,0 +1,379 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>机构名称:</span>
+        <el-input
+          v-model="clubInfo.clubName"
+          placeholder="机构名称"
+          clearable
+          :disabled="true"
+          style="color: #333;"
+        />
+      </div>
+      <div class="filter-control">
+        <span>联系人:</span>
+        <el-input
+          v-model="clubInfo.linkMan"
+          placeholder="联系人"
+          clearable
+          :disabled="true"
+          style="color: #333;"
+        />
+      </div>
+      <div class="filter-control">
+        <span>手机号:</span>
+        <el-input
+          v-model="clubInfo.mobile"
+          placeholder="机构名称"
+          clearable
+          :disabled="true"
+          style="color: #333;"
+        />
+      </div>
+    </div>
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>购买套餐:</span>
+        <el-select v-model="listQuery.packageId" style="width:120px;" clearable @change="getList">
+          <el-option value="" label="请选择" />
+          <el-option :value="0" label="系统赠送" />
+          <el-option :value="1" label="1个月" />
+          <el-option :value="3" label="3个月" />
+          <el-option :value="12" label="12个月" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>会员状态:</span>
+        <el-select v-model="listQuery.status" style="width:120px;" clearable @change="getList">
+          <el-option value="" label="请选择" />
+          <el-option :value="1" label="已生效" />
+          <el-option :value="2" label="已过期" />
+          <el-option :value="3" label="暂未生效" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>购买时间:</span>
+        <el-date-picker
+          v-model="purchaseTime"
+          type="daterange"
+          unlink-panels
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          :picker-options="pickerOptions"
+          @change="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <span>到期时间:</span>
+        <el-date-picker
+          v-model="expireTime"
+          type="daterange"
+          unlink-panels
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          :picker-options="pickerEndOptions"
+          @change="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+      </div>
+    </div>
+    <el-table v-loading="isLoading" :data="tableData" border style="width: 100%">
+      <el-table-column label="购买套餐" align="center" width="180">
+        <template slot-scope="{ row }">
+          <span>{{ packageFilters(row) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="购买价格" align="center" width="180">
+        <template slot-scope="{ row }">
+          <span> {{ priceFilters(row) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="购买渠道" align="center">
+        <template slot-scope="{ row }">
+          <span class="el-span-pay">{{ payWayFilters(row) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="会员状态" align="center">
+        <template slot-scope="{ row }">
+          <span :class="{
+            'el-span-status': row.status == '1',
+            'el-span-notStatus': row.status == '2',
+            'el-span-notEffective': row.status == '3',
+          }"
+          >
+            {{ row.status | statusFilters }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="payTime" label="购买时间" align="center" />
+      <el-table-column prop="beginTime" label="生效时间" align="center" />
+      <el-table-column prop="endTime" label="到期时间" align="center" />
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { findHistory } from '@/api/member/member'
+
+export default {
+  name: 'MemberRecord',
+  filters: {
+    statusFilters(value) {
+      // 处理状态显示
+      const map = {
+        '0': '未生效',
+        '1': '已生效',
+        '2': '已过期',
+        '3': '暂未生效'
+      }
+      return map[value]
+    },
+    priceFormat(value) {
+      return Number(value).toFixed(2)
+    }
+  },
+  data() {
+    // 购买时间
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    // 到期时间
+    const pickerEndOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      pickerOptions,
+      pickerEndOptions,
+      // 购买时间
+      purchaseTime: '',
+      // 到期时间
+      expireTime: '',
+      isLoading: true,
+      listQuery: {
+        userId: 0, // 用户Id
+        packageId: '', // 购买套餐
+        status: '', // 会员状态
+        startPayTime: '', // 购买开始时间
+        endPayTime: '', // 购买结束时间
+        startEndTime: '', //  到期开始时间
+        endEndTime: '', // 到期结束时间
+        pageNum: 1,
+        pageSize: 20
+      },
+      clubInfo: {
+        clubName: '', // 机构名称
+        linkMan: '', // 联系人
+        mobile: '' // 手机号
+      },
+      tableData: [],
+      total: 0
+    }
+  },
+  created() {
+    this.clubInfo = { ...this.clubInfo, ...this.$route.query }
+    this.listQuery.userId = this.clubInfo.userId
+    this.getList()
+  },
+  methods: {
+    payWayFilters(row) {
+      // 处理购买渠道显示
+      const payTypeMap = {
+        '1': '建设银行7297',
+        '2': '广发银行0115',
+        '3': '中信银行7172',
+        '4': '中信银行0897',
+        '5': '中信银行0897-财付通',
+        '6': '中信银行0897-支付宝',
+        '7': '线上-支付宝',
+        '8': '线上-微信支付',
+        '9': '线上-快钱支付',
+        '10': '口头返佣',
+        '11': '广发银行5461',
+        '12': 'PC-B2B网银',
+        '13': 'PC-微信支付',
+        '14': 'PC-支付宝',
+        '15': '小程序-微信支付',
+        '16': '余额抵扣',
+        '17': 'PC-B2C网银'
+      }
+      const payWayMap = {
+        '2': '线下',
+        '3': '余额抵扣',
+        '4': '采美豆抵扣',
+        '5': '系统赠送'
+      }
+      if (row.payWay === '0') {
+        return '未知'
+      } else if (row.payWay === '1') {
+        return payTypeMap[row.payType]
+      } else {
+        return payWayMap[row.payWay]
+      }
+    },
+    packageFilters(row) {
+      // 套餐
+      const map = {
+        0: `${row.giveMonth}个月`,
+        1: '1个月',
+        3: '3个月',
+        12: '12个月'
+      }
+      return map[row.packageId]
+    },
+    priceFilters(row) {
+      // 处理购买价格
+      const map = {
+        '0': row.price,
+        '1': row.price,
+        '2': row.price,
+        '3': row.price,
+        '4': `${row.userBeans}采美豆`,
+        '5': '--'
+      }
+      return map[row.payWay]
+    },
+    async getList() {
+      this.isLoading = true
+      // 购买时间
+      if (this.purchaseTime && this.purchaseTime.length > 0) {
+        this.listQuery.startPayTime = this.purchaseTime[0]
+        this.listQuery.endPayTime = this.purchaseTime[1]
+      } else {
+        this.listQuery.startPayTime = ''
+        this.listQuery.endPayTime = ''
+      }
+      // 到期时间
+      if (this.expireTime && this.expireTime.length > 0) {
+        this.listQuery.startEndTime = this.expireTime[0]
+        this.listQuery.endEndTime = this.expireTime[1]
+      } else {
+        this.listQuery.startEndTime = ''
+        this.listQuery.endEndTime = ''
+      }
+      // 获取数据列表
+      try {
+        const res = await findHistory(this.listQuery)
+        this.tableData = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log('error', error)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-span-status {
+  color: rgb(16, 230, 8);
+}
+.el-span-notStatus {
+  color: rgb(245, 13, 13);
+}
+.el-span-notEffective {
+  color: rgb(245, 13, 13);
+}
+.el-span-pay {
+  color:#07c160;
+}
+</style>

+ 158 - 0
src/views/member/product/components/pro-dialog.vue

@@ -0,0 +1,158 @@
+<template>
+  <el-dialog
+    title="添加商品"
+    :visible.sync="visible"
+    width="1100px"
+    :close-on-click-modal="false"
+    :show-close="false"
+  >
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>商品ID:</span>
+        <el-input
+          v-model="listQuery.productId"
+          placeholder="商品ID"
+          clearable
+          @input="e => (listQuery.productId= checkedInput(e,1))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>商品名称:</span>
+        <el-input
+          v-model="listQuery.name"
+          placeholder="商品名称"
+          clearable
+          @input="e => (listQuery.name= checkedInput(e,2))"
+        />
+      </div>
+      <div class="filter-control">
+        <span>供应商名称:</span>
+        <el-input
+          v-model="listQuery.shopName"
+          placeholder="供应商名称"
+          clearable
+          style="width:160px;"
+          @input="e => (listQuery.shopName= checkedInput(e,2))"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+      </div>
+    </div>
+    <el-table ref="table" v-loading="isLoading" :data="list" height="400px" border @select="handleSelect">
+      <el-table-column type="selection" width="50" />
+      <el-table-column prop="productId" label="商品ID" align="center" width="80" />
+      <el-table-column prop="coupon" label="商品图片" align="center" width="100">
+        <template v-if="row.productId" slot-scope="{ row }">
+          <el-popover
+            placement="top-start"
+            title=""
+            width="180"
+            trigger="hover"
+          >
+            <img :src="row.mainImage" alt="" style="width:100px;height:100px;">
+            <img slot="reference" :src="row.mainImage" alt="" style="width:50px;height:50px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column prop="name" label="商品名称" align="center" />
+      <el-table-column prop="shopName" label="供应商" align="center" width="250" />
+      <el-table-column prop="price" label="机构价" align="center" width="160">
+        <template slot-scope="{ row }">
+          ¥{{ row.price ? row.price : '0.00' }}
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page-sizes="[20]"
+      :page-size="20"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="getList"
+    />
+    <div slot="footer">
+      <el-button @click="handleCanle">取 消</el-button>
+      <el-button type="primary" :disabled="disabled" @click="handleAddProConfirm(productRadio)">确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { fetchProductList } from '@/api/member/member'
+
+export default {
+  name: 'ProDialog',
+  filters: {
+    NumFormat(value) {
+      // 处理金额
+      return Number(value).toFixed(2)
+    }
+  },
+  data() {
+    return {
+      visible: true,
+      listQuery: {
+        productId: '', // 商品id
+        name: '', // 商品名称
+        shopName: '', // 供应商名称
+        pageNum: 1,
+        pageSize: 20
+      },
+      list: [],
+      total: 0,
+      productRadio: null,
+      isLoading: true
+    }
+  },
+  computed: {
+    disabled() {
+      return this.productRadio === null
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      this.isLoading = true
+      const res = await fetchProductList(this.listQuery)
+      this.list = res.data.results
+      this.total = res.data.totalRecord
+      this.isLoading = false
+    },
+    // 选择供应商
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.productRadio = row.productId
+    },
+    handleAddProConfirm() {
+      // 确认选择商品
+      this.$emit('confirm', this.productRadio)
+    },
+    handleCanle() {
+      // 取消弹窗
+      this.$emit('cancel')
+    },
+    checkedInput(event, type) {
+      let pattern = ''
+      switch (type) {
+        case 1:
+          pattern = /[^\d]/g
+          break
+        case 2:
+          pattern = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
+          break
+      }
+      return event.replace(pattern, '')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep{
+  thead .el-checkbox{display: none !important;}
+}
+</style>

+ 153 - 0
src/views/member/product/components/pro-edit.vue

@@ -0,0 +1,153 @@
+<template>
+  <el-dialog
+    title="编辑商品"
+    :visible.sync="visible"
+    width="1100px"
+    :close-on-click-modal="false"
+    :show-close="false"
+  >
+    <div class="edit-container">
+      <el-form ref="goodsFrom" :model="product" label-width="150px">
+        <el-form-item label="商品ID:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="product.productId" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="商品名称:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="product.productName" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="供应商:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="product.shopName" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="是否含税:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="ncludedTaxtEXT" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="机构税率:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="product.taxPoint" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="供应商税率:" prop="product" style="margin-bottom: 10px;">
+          <el-input v-model="product.supplierTaxPoint" :disabled="true" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="排序:" style="margin-bottom: 10px;">
+          <el-input v-model="product.sort" style="width: 400px;" />
+        </el-form-item>
+        <el-form-item label="优惠状态:" style="margin-bottom: 10px;">
+          <el-select v-model="product.status" style="width:120px;" clearable>
+            <el-option :value="0" label="已上架" />
+            <el-option :value="1" label="已下架" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="商品信息:" prop="product">
+          <el-table row-key="title" :data="product.skus" border>
+            <el-table-column label="商品规格" prop="title" align="center">
+              <template slot-scope="{row}">
+                <span>{{ row.unit }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="机构价" prop="name" align="center">
+              <template slot-scope="{row}">
+                <span>{{ row.price | NumFormat }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="成本类型" prop="name" align="center">
+              <template slot-scope="{row}">
+                <span>{{ row.costCheckFlag === 1 ? '固定成本' : '比例成本' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="成本价/成本比例" prop="name" align="center">
+              <template slot-scope="{row}">
+                <span>{{ row.costCheckFlag === 1 ? row.costPrice : row.costProportional }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="会员折扣(%)" prop="name" align="center">
+              <template slot-scope="{row}">
+                <el-input v-model="row.discount" :disabled="row.discountPrice ? true : false">
+                  <template slot="append">%</template>
+                </el-input>
+              </template>
+            </el-table-column>
+            <el-table-column label="会员价" prop="name" align="center">
+              <template slot-scope="{row}">
+                <el-input v-model="row.discountPrice" :disabled="row.discount ? true : false" />
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div slot="footer">
+      <el-button @click="handleCanle">取 消</el-button>
+      <el-button type="primary" @click="handleConfirm">确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { editSvipProduct } from '@/api/member/member'
+
+export default {
+  name: 'ProEdit',
+  filters: {
+    NumFormat(value) {
+      // 处理金额
+      return Number(value).toFixed(2)
+    }
+  },
+  props: {
+    proId: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      visible: false,
+      product: {},
+      productRadio: null
+    }
+  },
+  computed: {
+    ncludedTaxtEXT() {
+      // 是否含税文案
+      const taxMap = {
+        '0': '不含税',
+        '1': '含税',
+        '2': '未知'
+      }
+      const typrMap = {
+        '1': '开增值税专用发票',
+        '2': '开增值税普通发票',
+        '3': ''
+      }
+      let text = ''
+      if (typrMap) {
+        text = `${this.product.invoiceType ? typrMap[this.product.invoiceType] : '不能开票'}`
+      }
+      return `${taxMap[this.product.includedTax]}-${text}`
+    }
+  },
+  created() {
+    this.editSvipProduct(this.proId)
+  },
+  methods: {
+    async editSvipProduct(id) {
+      const res = await editSvipProduct(id)
+      this.product = res.data
+      this.visible = true
+    },
+    handleConfirm() {
+      // 确认选择商品
+      this.$emit('confirm', this.product)
+    },
+    handleCanle() {
+      // 取消弹窗
+      this.$emit('cancel')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+  .edit-container{
+    width: 100%;
+    height: 500px;
+    overflow-y: scroll;
+  }
+</style>

+ 365 - 0
src/views/member/product/list.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeName" type="border-card" @tab-click="handleClick">
+      <el-tab-pane label="商品列表" name="first" />
+      <el-tab-pane label="宣传图" name="second" />
+      <template v-if="activeName === 'first'">
+        <div class="filter-container">
+          <div class="filter-control">
+            <span>商品ID:</span>
+            <el-input
+              v-model="listQuery.productId"
+              placeholder="商品ID"
+              clearable
+              @input="e => (listQuery.productId= checkedInput(e,1))"
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>商品名称:</span>
+            <el-input
+              v-model="listQuery.productName"
+              placeholder="商品名称"
+              clearable
+              @input="e => (listQuery.productName= checkedInput(e,2))"
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>供应商:</span>
+            <el-input
+              v-model="listQuery.shopName"
+              placeholder="供应商"
+              clearable
+              @keyup.enter.native="getList"
+              @clear="getList"
+            />
+          </div>
+          <div class="filter-control">
+            <span>优惠状态:</span>
+            <el-select v-model="listQuery.status" style="width:120px;" clearable @change="getList">
+              <el-option value="" label="请选择" />
+              <el-option :value="0" label="已上架" />
+              <el-option :value="1" label="已下架" />
+            </el-select>
+          </div>
+          <div class="filter-control">
+            <el-button type="primary" @click="getList">查询</el-button>
+            <el-button type="primary" @click="proDialogVisible =true">添加</el-button>
+          </div>
+        </div>
+        <el-table v-loading="isLoading" :data="tableData" border style="width: 100%">
+          <el-table-column prop="productId" label="商品ID" align="center" />
+          <el-table-column prop="coupon" label="商品图片" align="center">
+            <template v-if="row.productId" slot-scope="{ row }">
+              <el-popover
+                placement="top-start"
+                title=""
+                width="180"
+                trigger="hover"
+              >
+                <img :src="row.productImage" alt="" style="width:100px;height:100px;">
+                <img slot="reference" :src="row.productImage" alt="" style="width:50px;height:50px;">
+              </el-popover>
+            </template>
+          </el-table-column>
+          <el-table-column prop="productName" label="商品名称" align="center" />
+          <el-table-column prop="shopName" label="供应商" align="center" width="250" />
+          <el-table-column prop="price" label="机构价" align="center" />
+          <el-table-column prop="discount" label="会员折扣" align="center">
+            <template slot-scope="{ row }">
+              {{ row.priceType === 1 ? row.discount+'%' : '---' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="discountPrice" label="会员价" align="center">
+            <template slot-scope="{ row }">
+              ¥{{ row.priceType === 2 ? row.discountPrice : row.price * row.discount * 0.01 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="排序" width="80" align="center">
+            <template slot-scope="{row}">
+              <el-input v-model="row.sort" maxlength="4" minlength="1" @blur="handleOnInputBlur(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" label="优惠状态" align="center" width="100">
+            <template slot-scope="{ row }">
+              <span v-if="row.status === 0" class="el-span-zero">
+                已上架
+              </span>
+              <span v-else class="el-span-one">
+                已下架
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="addTime" label="添加时间" align="center" width="100" />
+          <el-table-column label="操作" align="center" width="180">
+            <template slot-scope="{ row }">
+              <el-button type="primary" size="mini" @click="handleEdit(row.id)">编辑</el-button>
+              <el-button type="danger" size="mini" @click="handeleDelPro(row.id)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 页码 -->
+        <pagination
+          :total="total"
+          :page-sizes="[20]"
+          :page-size="20"
+          :page.sync="listQuery.pageNum"
+          :limit.sync="listQuery.pageSize"
+        />
+        <!--  选择商品弹窗 -->
+        <pro-dialog v-if="proDialogVisible" ref="proDialog" @confirm="handleAddProductConfirm" @cancel="handleCancel" />
+        <!-- 商品编辑弹窗 -->
+        <pro-edit v-if="proEditVisible" ref="proEdit" :pro-id="handleProId" @confirm="handleEditConfirm" @cancel="handleEditCancel" />
+      </template>
+      <template v-else>
+        <div class="club-container" style="width: 100%;height: 650px;padding-top: 40px;">
+          <el-form ref="submitForm" class="doctor-edit-form" label-width="140px" :model="formData" :rules="rules">
+            <el-form-item label="PC端:" prop="doctorImage">
+              <div class="form-label-tip">宣传图</div>
+              <el-input v-show="false" v-model="formData.pcImage" />
+              <upload-image
+                tip="提示:建议图片分辨率1920px*510px"
+                :image-list="pcImageList"
+                :before-upload="beforeDoctorImageUpload"
+                @success="uploadDoctorImageSuccess"
+                @remove="handleDoctorImageRemove"
+              />
+            </el-form-item>
+            <el-form-item label="小程序端:" prop="banner">
+              <div class="form-label-tip">宣传图</div>
+              <el-input v-show="false" v-model="formData.appletsImage" />
+              <upload-image
+                tip="提示:建议图片分辨率351px*170px"
+                :image-list="appletsImageList"
+                :before-upload="beforeBannerUpload"
+                @success="uploadBannerSuccess"
+                @remove="handleBannerRemove"
+              />
+            </el-form-item>
+          </el-form>
+          <div class="submit-btn" style="padding-left: 137px;">
+            <el-button type="primary" :disabled="disabled" @click="submitSave">保存</el-button>
+          </div>
+        </div>
+      </template>
+    </el-tabs>
+  </div>
+</template>
+<script>
+import { fetchFindProductList, updateSort, saveAdsImage, getAdsImage } from '@/api/member/member'
+import { delSvipProduct, saveSvipProduct } from '@/api/member/member'
+import UploadImage from '@/components/UploadImage'
+import ProDialog from './components/pro-dialog'
+import ProEdit from './components/pro-edit'
+export default {
+  name: 'MemberProduct',
+  components: { UploadImage, ProDialog, ProEdit },
+  data() {
+    return {
+      activeName: 'first',
+      listQuery: {
+        productId: null,
+        productName: '',
+        shopName: '',
+        status: '',
+        pageNum: 0,
+        pageSize: 20
+      },
+      formData: {
+        pcImage: '',
+        appletsImage: ''
+      },
+      // PC端宣传图
+      pcImageList: [],
+      // 小程序端宣传图
+      appletsImageList: [],
+      rules: {
+        pcImage: [{ required: true, message: '请上传PC端宣传图', trigger: 'change' }],
+        appletsImage: [{ required: true, message: '请上传小程序端宣传图', trigger: 'change' }]
+      },
+      // 超级会员商品列表
+      total: 0,
+      isLoading: true,
+      tableData: [],
+      proDialogVisible: false,
+      proEditVisible: false,
+      handleProId: null
+    }
+  },
+  computed: {
+    disabled() {
+      return !(this.formData.pcImage !== '' > 0 && this.formData.appletsImage !== '')
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      this.isLoading = true
+      const res = await fetchFindProductList(this.listQuery)
+      this.tableData = res.data.results
+      this.total = res.data.totalRecord
+      this.isLoading = false
+    },
+    handleOnInputBlur(row) {
+      // 更新排序
+      updateSort({ id: row.id, sort: row.sort }).then(response => {
+        this.$message({
+          message: '操作成功',
+          type: 'success',
+          duration: 1000
+        })
+        this.getList()
+      })
+    },
+    // tab切换
+    handleClick(tab, event) {
+      if (tab.name === 'second') {
+        this.getAdsImageData()
+      }
+    },
+    async getAdsImageData() {
+      try {
+        this.pcImageList = []
+        this.appletsImageList = []
+        const res = await getAdsImage(this.formData)
+        console.log('res', res.data)
+        this.formData.pcImage = res.data.pcImage
+        this.formData.appletsImage = res.data.appletsImage
+        this.pcImageList.push({ url: res.data.pcImage, name: 'doctor' })
+        this.appletsImageList.push({ url: res.data.appletsImage, name: 'doctor' })
+        console.log('pcImageList', this.pcImageList)
+        console.log('appletsImageList', this.appletsImageList)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    async submitSave() {
+      // 保存
+      try {
+        console.log('formData', this.formData)
+        await saveAdsImage(this.formData)
+        this.$message.success('保存成功')
+        setTimeout(() => {
+          this.getAdsImageData()
+        }, 2000)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // PC端宣传图上传
+    uploadDoctorImageSuccess({ response, file, fileList }) {
+      this.pcImageList = fileList
+      this.formData.pcImage = response.data
+    },
+    handleDoctorImageRemove({ file, fileList }) {
+      this.pcImageList = fileList
+      this.formData.pcImage = ''
+    },
+    beforeDoctorImageUpload(file) {
+      const flag = file.size / 1024 / 1024 < 2
+      if (!flag) {
+        this.$message.error('上传图片大小不能超过 2MB!')
+      }
+      return flag
+    },
+    // 小程序端宣传图上传
+    uploadBannerSuccess({ response, file, fileList }) {
+      this.appletsImageList = fileList
+      this.formData.appletsImage = fileList.length || ''
+    },
+    handleBannerRemove({ file, fileList }) {
+      this.appletsImageList = fileList
+      this.formData.appletsImage = fileList.length || ''
+    },
+    beforeBannerUpload(file) {
+      const flag = file.size / 1024 / 1024 < 2
+      if (!flag) {
+        this.$message.error('上传图片大小不能超过 2MB!')
+      }
+      return flag
+    },
+    async handeleDelPro(id) {
+      await this.$confirm('确认要删除该商品吗?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+      this.delSvipProductSubmit(id)
+    },
+    async delSvipProductSubmit(id) {
+      try {
+        await delSvipProduct(id)
+        this.$message.success('操作成功')
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    async handleAddProductConfirm(productId) {
+      // 确认添加商品
+      try {
+        await saveSvipProduct({ productId: productId })
+        this.$message.success('添加成功')
+        this.handleCancel()
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    handleCancel() {
+      // 取消选择添加商品
+      this.proDialogVisible = false
+      this.$refs.proDialog.visible = false
+    },
+    handleEdit(id) {
+      // 编辑商品
+      this.handleProId = id
+      this.proEditVisible = true
+    },
+    handleEditCancel() {
+      // 取消编辑商品
+      this.proEditVisible = false
+      this.$refs.proEdit.visible = false
+    },
+    async handleEditConfirm(data) {
+      // 保存编辑商品
+      console.log('data', data)
+      try {
+        await saveSvipProduct(data)
+        this.$message.success('操作成功')
+        this.handleEditCancel()
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    checkedInput(event, type) {
+      let pattern = ''
+      switch (type) {
+        case 1:
+          pattern = /[^\d]/g
+          break
+        case 2:
+          pattern = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
+          break
+      }
+      return event.replace(pattern, '')
+    }
+  }
+}
+</script>
+
+<style>
+.el-span-zero {
+  color: #0bd81c;
+}
+.el-span-one {
+  color: #ff353f;
+}
+</style>

+ 1 - 0
src/views/user/record/detail-list.vue

@@ -78,6 +78,7 @@
 import { fetchBehaviorList } from '@/api/user/record/record'
 
 export default {
+  name: 'RecordDtails',
   filters: {
     accessSourceFilters(value) {
       // 公司类型

+ 98 - 68
src/views/user/record/list.vue

@@ -17,13 +17,7 @@
         </div>
         <div class="filter-control">
           <span>IP:</span>
-          <el-input
-            v-model="listQuery.ip"
-            placeholder="IP"
-            clearable
-            @keyup.enter.native="getList"
-            @clear="getList"
-          />
+          <el-input v-model="listQuery.ip" placeholder="IP" clearable @keyup.enter.native="getList" @clear="getList" />
         </div>
         <div class="filter-control">
           <span>手机号:</span>
@@ -31,7 +25,7 @@
             v-model="listQuery.phoneNumber"
             placeholder="手机号"
             clearable
-            style="width:160px;"
+            style="width: 160px"
             @keyup.enter.native="getList"
             @clear="getList"
           />
@@ -42,7 +36,7 @@
             v-model="listQuery.contacts"
             placeholder="联系人"
             clearable
-            style="width:160px;"
+            style="width: 160px"
             @keyup.enter.native="getList"
             @clear="getList"
           />
@@ -53,14 +47,14 @@
             v-model="listQuery.spName"
             placeholder="协销"
             clearable
-            style="width:120px;"
+            style="width: 120px"
             @keyup.enter.native="getList"
             @clear="getList"
           />
         </div>
         <div class="filter-control">
           <span>公司类型:</span>
-          <el-select v-model="listQuery.companyType" style="width:120px;" clearable @change="getList">
+          <el-select v-model="listQuery.companyType" style="width: 120px" clearable @change="getList">
             <el-option value="" label="请选择" />
             <el-option :value="1" label="游客" />
             <el-option :value="2" label="机构" />
@@ -69,7 +63,7 @@
         </div>
         <div class="filter-control">
           <span>访问客户端:</span>
-          <el-select v-model="listQuery.accessClient" style="width:120px;" clearable @change="getList">
+          <el-select v-model="listQuery.accessClient" style="width: 120px" clearable @change="getList">
             <el-option value="" label="请选择" />
             <el-option :value="0" label="网站" />
             <el-option :value="1" label="小程序" />
@@ -77,24 +71,15 @@
         </div>
         <div class="filter-control">
           <span>关联供应商:</span>
-          <el-select v-model="listQuery.relevanceShop" clearable @change="getList">
-            <el-option
-              v-for="item in supplierList"
-              :key="item.shopId"
-              :label="item.shopName"
-              :value="item.shopName"
-            />
+          <el-select v-model="listQuery.relevanceShop" filterable @change="getList">
+            <el-option value="" label="请选择" />
+            <el-option v-for="item in supplierList" :key="item.shopId" :label="item.shopName" :value="item.shopName" />
           </el-select>
         </div>
         <div class="filter-control">
-          <span>标签:</span>
+          <span>供应商标签:</span>
           <el-select v-model="tagsList" multiple filterable placeholder="请选择" @change="handleTagsOptions">
-            <el-option
-              v-for="item in tagsOptions"
-              :key="item.id"
-              :label="item.value"
-              :value="item.value"
-            />
+            <el-option v-for="item in tagsOptions" :key="item.id" :label="item.value" :value="item.value" />
           </el-select>
         </div>
         <div v-if="tabsCurrent === 1" class="filter-control">
@@ -103,6 +88,7 @@
             v-model="time"
             type="daterange"
             unlink-panels
+            value-format="yyyy-MM-dd"
             range-separator="至"
             start-placeholder="开始日期"
             end-placeholder="结束日期"
@@ -112,13 +98,11 @@
         </div>
         <div class="filter-control">
           <el-button type="primary" @click="getList">查询</el-button>
-          <el-button v-permission="'keyword:list:export'" type="primary" @click="handleExport">
-            导出
-          </el-button>
+          <el-button v-permission="'keyword:list:export'" type="primary" @click="handleExport"> 导出 </el-button>
         </div>
       </div>
       <!-- 关键词列表 -->
-      <el-table v-loading="isLoading" :data="list" border style="width:100%;" :height="tableHeight">
+      <el-table v-loading="isLoading" :data="list" border style="width: 100%" :height="tableHeight">
         <el-table-column prop="ip" fixed label="IP" align="center" width="80" />
         <el-table-column prop="region" fixed label="地区" align="center" width="100" />
         <el-table-column prop="accessClient" fixed label="访问客户端" align="center" width="70">
@@ -134,7 +118,7 @@
         <el-table-column prop="corporateName" label="公司名称" align="center">
           <template slot-scope="{ row }">
             <template v-if="row.companyType === '2'">
-              <a style="color:#2fa4e7;" target="_blank" :href="handleClubHrefLink(row)">
+              <a style="color: #2fa4e7" target="_blank" :href="handleClubHrefLink(row)">
                 {{ row.corporateName ? row.corporateName : '---' }}
               </a>
             </template>
@@ -146,7 +130,7 @@
         <el-table-column prop="contacts" label="联系人" align="center">
           <template slot-scope="{ row }">
             <template v-if="row.companyType === '2'">
-              <a style="color:#2fa4e7;" target="_blank" :href="handleClubHrefLink(row)">
+              <a style="color: #2fa4e7" target="_blank" :href="handleClubHrefLink(row)">
                 {{ row.contacts ? row.contacts : '---' }}
               </a>
             </template>
@@ -175,9 +159,13 @@
             <p v-else>---</p>
           </template>
         </el-table-column>
-        <el-table-column prop="spName" label="标签" align="center" width="120">
+        <el-table-column prop="spName" label="供应商标签" align="center" width="120">
           <template slot-scope="{ row }">
-            {{ row.label ? row.label : '---' }}
+            <el-popover placement="top-start" width="400" trigger="hover">
+              <span>{{ row.label ? row.label : '---' }}</span>
+              <span slot="reference">{{ row.label ? row.label.substr(0, 9) + '...' : '--' }}</span>
+            </el-popover>
+            <!-- {{ row.label ? row.label : '---' }} -->
           </template>
         </el-table-column>
         <el-table-column prop="addTime" label="注册时间" align="center" width="100">
@@ -225,7 +213,7 @@ const defaultListQuery = {
   companyType: '', // 公司类型
   accessClient: '', // 访问客户端
   relevanceShop: '', // 供应商名称
-  label: '', // 标签
+  label: '', // 供应商标签
   contacts: '', //  联系人
   phoneNumber: '', // 手机号
   spName: '', // 协销
@@ -240,17 +228,17 @@ export default {
     companyTypeFilters(value) {
       // 公司类型
       const map = {
-        '1': '游客',
-        '2': '机构',
-        '3': '供应商'
+        1: '游客',
+        2: '机构',
+        3: '供应商'
       }
       return map[value]
     },
     accessClientFilters(value) {
       // 访问客户端
       const map = {
-        '0': '网站',
-        '1': '小程序'
+        0: '网站',
+        1: '小程序'
       }
       return map[value]
     }
@@ -329,9 +317,7 @@ export default {
       screenHeight: window.innerHeight // 内容区域高度
     }
   },
-  computed: {
-
-  },
+  computed: {},
   watch: {
     // 监听screenHeight从而改变table的高度
     screenHeight(val) {
@@ -343,7 +329,7 @@ export default {
     this.getHeigth()
     this.getList()
   },
-  mounted: function() {
+  mounted() {
     // window.onresize:浏览器尺寸变化响应事件
     window.onresize = () => {
       return (() => {
@@ -419,7 +405,7 @@ export default {
       }
     },
 
-    // 获取标签库列表选项
+    // 获取供应商标签库列表选项
     async searchBehaviorList() {
       try {
         const res = await searchBehaviorList()
@@ -430,40 +416,82 @@ export default {
     },
     // 机构跳转
     handleClubHrefLink(row) {
-      console.log('VUE_APP_ADMIN_URL', process.env.VUE_APP_ADMIN_URL)
-      return process.env.VUE_APP_ADMIN_URL + `/a/user/jumpLink/toOldAdmin?type=1&clubId=${row.clubId}&clubName=${row.corporateName}`
+      return (
+        process.env.VUE_APP_ADMIN_URL +
+        `/a/user/jumpLink/toOldAdmin?type=1&clubId=${row.clubId}&clubName=${row.corporateName}`
+      )
     },
     // 查看详情
     handleRecordDetail(row) {
-      this.$router.push({ path: '/user/record/detail', query: { ip: row.ip, accessDate: row.accessDate, userId: row.userId }})
+      this.$router.push({
+        path: '/user/detail-list',
+        query: { ip: row.ip, accessDate: row.accessDate, userId: row.userId }
+      })
     },
 
     // 导出
     async handleExport() {
       if (this.tabsCurrent === 0) {
         // 导出今日数据
-        const url = process.env.VUE_APP_BASE_API + '/user/behavior/exportToday'
+        const url = process.env.VUE_APP_BASE_API + '/user/behavior/exportToday?todayType=0'
         downloadWithUrl(url, '用户行为记录')
       } else {
+        const url =
+          process.env.VUE_APP_BASE_API +
+          `/user/behavior/exportToday?todayType=1&ip=${this.listQuery.ip}&corporateName=${this.listQuery.corporateName}&companyType=${this.listQuery.companyType}&accessClient=${this.listQuery.accessClient}&relevanceShop=${this.listQuery.relevanceShop}&label=${this.listQuery.label}&contacts=${this.listQuery.contacts}&phoneNumber=${this.listQuery.phoneNumber}&spName=${this.listQuery.spName}&startTime=${this.listQuery.startTime}&endTime=${this.listQuery.endTime}`
+        downloadWithUrl(url, '用户行为记录')
         // 导出以往当前数据
-        try {
-          await this.$confirm('确定将用户行为记录导出为xlsx?', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning'
-          })
-          // 导出数据格式化
-          const filterVal = ['ip', 'region', 'accessClient', 'companyType', 'corporateName', 'contacts', 'phoneNumber', 'spName', 'relevanceShop', 'label', 'addTime', 'numbers', 'accessDuration', 'accessDate']
-          const data = this.formatJson(filterVal, this.list.slice(0))
-          export_json_to_excel({
-            header: ['IP', '地区', '访问客户端', '公司类型', '公司名称', '联系人', '手机号', '所属协销', '关联供应商', '标签', '注册时间', '访问页面数量', '总时长', '访问日期'],
-            data,
-            filename: '用户行为记录'
-          })
-        } catch (error) {
-          console.log(error)
-          this.$message.info('已取消导出操作')
-        }
+      }
+    },
+    async export_json_to_excel() {
+      // 前端导出
+      try {
+        await this.$confirm('确定将用户行为记录导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = [
+          'ip',
+          'region',
+          'accessClient',
+          'companyType',
+          'corporateName',
+          'contacts',
+          'phoneNumber',
+          'spName',
+          'relevanceShop',
+          'label',
+          'addTime',
+          'numbers',
+          'accessDuration',
+          'accessDate'
+        ]
+        const data = this.formatJson(filterVal, this.list.slice(0))
+        export_json_to_excel({
+          header: [
+            'IP',
+            '地区',
+            '访问客户端',
+            '公司类型',
+            '公司名称',
+            '联系人',
+            '手机号',
+            '所属协销',
+            '关联供应商',
+            '供应商标签',
+            '注册时间',
+            '访问页面数量',
+            '总时长',
+            '访问日期'
+          ],
+          data,
+          filename: '用户行为记录'
+        })
+      } catch (error) {
+        console.log(error)
+        this.$message.info('已取消导出操作')
       }
     },
     formatJson(filterVal, jsonData) {
@@ -483,7 +511,9 @@ export default {
     },
     handleRelevanceShop(value) {
       const array = []
-      if (!value) { return array }
+      if (!value) {
+        return array
+      }
       return value.split(',')
     }
   }

部分文件因为文件数量过多而无法显示