Selaa lähdekoodia

订单进行修改

yuhongqi 1 päivä sitten
vanhempi
commit
c10017753f

+ 9 - 0
.env.prod-bjzm

@@ -27,6 +27,15 @@ VUE_APP_VIDEO_LINE_1 = http://bjzmkytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://bjzmobs.ylrztop.com
 
+#直播解码路径
+VUE_APP_LIVE_PATH = /live
+
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://bjzmvolcengine.ylrztop.com
+
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = bjzm-2114522511
+
 # 开发环境配置
 ENV = 'production'
 

+ 49 - 0
src/api/hisStore/storeProductTag.js

@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+
+// 分页列表(一页10条)
+export function listStoreProductTag(query) {
+  return request({
+    url: '/store/store/storeProductTag/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 关联商品列表(分页)
+export function listTagProducts(tagId, query) {
+  return request({
+    url: '/store/store/storeProductTag/products/' + tagId,
+    method: 'get',
+    params: query
+  })
+}
+
+export function getStoreProductTag(id) {
+  return request({
+    url: '/store/store/storeProductTag/' + id,
+    method: 'get'
+  })
+}
+
+export function addStoreProductTag(data) {
+  return request({
+    url: '/store/store/storeProductTag',
+    method: 'post',
+    data
+  })
+}
+
+export function updateStoreProductTag(data) {
+  return request({
+    url: '/store/store/storeProductTag',
+    method: 'put',
+    data
+  })
+}
+
+export function delStoreProductTag(ids) {
+  return request({
+    url: '/store/store/storeProductTag/' + ids,
+    method: 'delete'
+  })
+}

+ 58 - 0
src/api/hisStore/userEndCategory.js

@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+
+// 分页列表(一页10条)
+export function listUserEndCategory(query) {
+  return request({
+    url: '/store/store/userEndCategory/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 添加/编辑商品时拉取用户端分类(金刚区、瀑布流)
+export function listUserEndCategoryForProduct(storeId) {
+  return request({
+    url: '/store/store/userEndCategory/listForProduct',
+    method: 'get',
+    params: { storeId }
+  })
+}
+
+export function getUserEndCategory(id) {
+  return request({
+    url: '/store/store/userEndCategory/' + id,
+    method: 'get'
+  })
+}
+
+export function addUserEndCategory(data) {
+  return request({
+    url: '/store/store/userEndCategory',
+    method: 'post',
+    data
+  })
+}
+
+export function updateUserEndCategory(data) {
+  return request({
+    url: '/store/store/userEndCategory',
+    method: 'put',
+    data
+  })
+}
+
+export function delUserEndCategory(ids) {
+  return request({
+    url: '/store/store/userEndCategory/' + ids,
+    method: 'delete'
+  })
+}
+
+// 按用户端分类ID分页查询关联商品(商品ID、名称、售价、原价、销量、产品标签)
+export function listCategoryProducts(id, params) {
+  return request({
+    url: '/store/store/userEndCategory/products',
+    method: 'get',
+    params: { id, ...params }
+  })
+}

+ 1 - 1
src/components/TopNav/index.vue

@@ -34,7 +34,7 @@ export default {
   data() {
     return {
       // 顶部栏初始数
-      visibleNumber: 8,
+      visibleNumber: 10,
       // 是否为首次加载
       isFrist: false,
       // 当前激活菜单的 index

+ 5 - 3
src/views/components/course/userCourseCatalogDetails.vue

@@ -303,9 +303,11 @@
         </template>
 
         </el-form-item>
-        <el-form-item label="是否关联商品">
-          <el-radio v-model="form.isProduct" :label=0>否</el-radio>
-          <el-radio v-model="form.isProduct" :label=1>是</el-radio>
+        <el-form-item label="是否关联商品" prop="isProduct">
+          <el-radio-group v-model="form.isProduct">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
         </el-form-item>
         <el-form-item label="是否先导课" prop="isFirst">
           <el-radio-group v-model="form.isFirst">

+ 10 - 8
src/views/components/course/userCourseCatalogDetailsZM.vue

@@ -303,9 +303,11 @@
           </template>
 
         </el-form-item>
-        <el-form-item label="是否关联商品">
-          <el-radio v-model="form.isProduct" :label=0>否</el-radio>
-          <el-radio v-model="form.isProduct" :label=1>是</el-radio>
+        <el-form-item label="是否关联商品" prop="isProduct">
+          <el-radio-group v-model="form.isProduct">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
         </el-form-item>
         <el-form-item label="是否先导课" prop="isFirst">
           <el-radio-group v-model="form.isFirst">
@@ -1095,11 +1097,11 @@ export default {
           this.form.timeRange = [response.data.viewStartTime, response.data.viewEndTime];
         }
         // 根据商品数量设置是否关联商品状态
-        if (this.form.courseProducts && this.form.courseProducts.length > 0) {
-          this.form.isProduct = 1; // 有关联商品时设为1(是)
-        } else {
-          this.form.isProduct = 0; // 无关联商品时设为0(否)
-        }
+        // if (this.form.courseProducts && this.form.courseProducts.length > 0) {
+        //   this.form.isProduct = 1; // 有关联商品时设为1(是)
+        // } else {
+        //   this.form.isProduct = 0; // 无关联商品时设为0(否)
+        // }
         // 设置商品选择状态
         if (this.form.id) {
           this.form.showProduct = 0; // 有商品时设为0

+ 3 - 0
src/views/course/fsCourseProduct/CourseProductZM.vue

@@ -92,6 +92,9 @@ export default {
                 pageSize: 10,
                 imgUrl: null,
                 images: null,
+                isDel: 0,
+                isAudit: 1,
+                isShow: 1,
                 barCode: null,
                 sort: null,
                 stock: null,

+ 1 - 1
src/views/course/userCoursePeriod/index.vue

@@ -232,7 +232,7 @@
           <el-input v-model="form.periodName" placeholder="请输入营期名称" />
         </el-form-item>
          <el-form-item label="公司" prop="companyId">
-          <el-select v-model="form.companyId" placeholder="请选择公司" multiple>
+          <el-select v-model="form.companyId" placeholder="请选择公司" multiple filterable>
             <el-option
               v-for="item in companyOptions"
               :key="item.companyId"

+ 20 - 11
src/views/hisStore/storeOrder/healthStoreList.vue

@@ -134,16 +134,12 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <!--      <el-form-item label="订单类型" prop="orderType">
-               <el-select   v-model="queryParams.orderType" placeholder="请选择订单类型" clearable size="small" >
-               <el-option
-                      v-for="item in orderTypeOptions"
-                      :key="item.dictValue"
-                      :label="item.dictLabel"
-                      :value="item.dictValue"
-                    />
-              </el-select>
-            </el-form-item>-->
+      <el-form-item label="订单类型" prop="orderType">
+        <el-select v-model="queryParams.orderType" placeholder="请选择订单类型" clearable size="small">
+          <el-option label="商城订单" value="0" />
+          <el-option label="直播订单" value="2" />
+        </el-select>
+      </el-form-item>
       <!--      <el-form-item label="上传凭证" prop="isUpload">
                <el-select   v-model="queryParams.isUpload" placeholder="请选择" clearable size="small" >
                 <el-option key="0"  label="未上传" value="0" />
@@ -1238,7 +1234,8 @@ export default {
         companyId: null,
         salesName: null,
         productName: null,
-        payCode: null
+        payCode: null,
+        orderType: null
 
       },
       // 表单参数
@@ -2319,6 +2316,12 @@ export default {
       } else {
         this.queryParams.deliverySendTimeRange = null
       }
+      // GET 请求需将 orderCodes 转为 orderCodeList,与列表查询一致
+      if (this.queryParams.orderCodes && this.queryParams.orderCodes.length > 0) {
+        this.queryParams.orderCodeList = this.queryParams.orderCodes.join(',')
+      } else {
+        this.queryParams.orderCodeList = null
+      }
       const queryParams = this.addDateRange(this.queryParams, this.dateRange)
       this.$confirm('是否确认导出所有订单明细数据项?', '警告', {
         confirmButtonText: '确定',
@@ -2361,6 +2364,12 @@ export default {
       } else {
         this.queryParams.deliverySendTimeRange = null
       }
+      // GET 请求需将 orderCodes 转为 orderCodeList,与列表查询一致
+      if (this.queryParams.orderCodes && this.queryParams.orderCodes.length > 0) {
+        this.queryParams.orderCodeList = this.queryParams.orderCodes.join(',')
+      } else {
+        this.queryParams.orderCodeList = null
+      }
       const queryParams = this.addDateRange(this.queryParams, this.dateRange)
       this.$confirm('是否确认导出所有订单明细数据项?', '警告', {
         confirmButtonText: '确定',

+ 0 - 1
src/views/hisStore/storeProduct/index.vue

@@ -1274,7 +1274,6 @@ export default {
     },
 
     getStatusType(row) {
-      console.log(row)
       if (row.isAudit == 0) {
         return 'warning';
       }

+ 227 - 22
src/views/hisStore/storeProduct/indexZm.vue

@@ -697,9 +697,9 @@
                           <a @click="delAttrTable(scope.$index)" align="center">删除</a>
                         </div>
                         <div v-else align="center">
-                          <el-input  
-                            v-model="scope.row[scope.column.property]" 
-                            align="center" 
+                          <el-input
+                            v-model="scope.row[scope.column.property]"
+                            align="center"
                             :placeholder="scope.column.property === 'barCode' ? '必填' : ''"
                           />
                         </div>
@@ -934,7 +934,74 @@
         <el-form-item label="处方名" v-if="form.productType==2" prop="prescribeName">
           <el-input v-model="form.prescribeName" placeholder="请输入处方名" />
         </el-form-item>
+        <el-form-item label="用户端分类(多选)" prop="userEndCategoryIds">
+          <div class="quick-select-row">
+            <el-button type="text" size="small" @click="selectAllUserEndCategoryByPosition(1)">全选金刚区</el-button>
+            <el-button type="text" size="small" @click="selectAllUserEndCategoryByPosition(2)">全选瀑布流</el-button>
+          </div>
+          <div class="scroll-select-wrap">
+            <div
+              class="scroll-box scroll-box-category"
+              ref="userEndCategoryScroll"
+              @scroll="onUserEndCategoryScroll"
+              style="overflow-y: auto; max-height: 200px;"
+            >
+              <el-checkbox-group v-model="form.userEndCategoryIds" :key="'uec-' + (userEndCategoryList.length ? 'ready' : 'empty')">
+                <div class="scroll-options">
+                  <el-checkbox
+                    v-for="c in userEndCategoryList"
+                    :key="'c'+c.id"
+                    :label="Number(c.id)"
+                    border
+                    size="small"
+                    class="scroll-option-item"
+                  >
+                    {{ c.categoryName }}({{ c.position === 1 ? '金刚区' : '瀑布流' }})
+                  </el-checkbox>
+                </div>
+              </el-checkbox-group>
+              <div v-if="userEndCategoryLoading" class="scroll-loading">加载中...</div>
+              <div v-else-if="userEndCategoryList.length >= userEndCategoryTotal && userEndCategoryTotal > 0" class="scroll-end">已加载全部</div>
+              <div v-else-if="userEndCategoryList.length > 0 && userEndCategoryList.length < userEndCategoryTotal" class="scroll-more" @click="loadUserEndCategoryPage(true)">
+                <span>点击加载更多</span>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="商品标签(最多3个)" prop="tagIds">
+          <div class="scroll-select-wrap">
+            <div
+              class="scroll-box scroll-box-tag"
+              ref="tagScroll"
+              @scroll="onTagScroll"
+              style="overflow-y: auto; max-height: 200px;"
+            >
+              <el-checkbox-group v-model="form.tagIds" @change="onTagIdsChange" :key="'tag-' + (tagList.length ? 'ready' : 'empty')">
+                <div class="scroll-options">
+                  <el-checkbox
+                    v-for="t in tagList"
+                    :key="'t'+t.id"
+                    :label="Number(t.id)"
+                    :disabled="isTagDisabled(Number(t.id))"
+                    border
+                    size="small"
+                    class="scroll-option-item"
+                  >
+                    {{ t.tagName }}
+                  </el-checkbox>
+                </div>
+              </el-checkbox-group>
+              <div v-if="tagLoading" class="scroll-loading">加载中...</div>
+              <div v-else-if="tagList.length >= tagTotal && tagTotal > 0" class="scroll-end">已加载全部</div>
+              <div v-else-if="tagList.length > 0 && tagList.length < tagTotal" class="scroll-more" @click="loadTagPage(true)">
+                <span>点击加载更多</span>
+              </div>
+            </div>
+          </div>
+          <span v-if="form.tagIds && form.tagIds.length > 3" class="text-danger">最多选择3个</span>
+        </el-form-item>
       </el-form>
+
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
         <el-button @click="cancel">取 消</el-button>
@@ -973,6 +1040,8 @@
 </template>
 
 <script>
+import { listUserEndCategoryForProduct, listUserEndCategory } from "@/api/hisStore/userEndCategory";
+import { listStoreProductTag } from "@/api/hisStore/storeProductTag";
 import {
   genFormatAttr,
   listStoreProduct,
@@ -1145,6 +1214,14 @@ export default {
       },
       // 表单参数
       form: {},
+      userEndCategoryList: [],
+      userEndCategoryPageNum: 1,
+      userEndCategoryTotal: 0,
+      userEndCategoryLoading: false,
+      tagList: [],
+      tagPageNum: 1,
+      tagTotal: 0,
+      tagLoading: false,
       storeForm: {isAudit:1,status:1},
       // 表单校验
       rules: {
@@ -1276,6 +1353,8 @@ export default {
     this.getTreeselect();
     this.getList();
   },
+  computed: {
+  },
   methods: {
     getStatusText(row) {
       console.log()
@@ -1287,7 +1366,6 @@ export default {
     },
 
     getStatusType(row) {
-      console.log(row)
       if (row.isAudit == 0) {
         return 'warning';
       }
@@ -1532,6 +1610,100 @@ export default {
         this.loading = false;
       });
     },
+    loadUserEndCategoryForProduct(storeId) {
+      this.userEndCategoryPageNum = 1;
+      this.userEndCategoryList = [];
+      this.userEndCategoryTotal = 0;
+      return this.loadUserEndCategoryPage(false);
+    },
+    loadUserEndCategoryPage(append) {
+      if (this.userEndCategoryLoading) return Promise.resolve();
+      if (append && this.userEndCategoryList.length >= this.userEndCategoryTotal && this.userEndCategoryTotal > 0) return Promise.resolve();
+      this.userEndCategoryLoading = true;
+      const pageNum = append ? this.userEndCategoryPageNum : 1;
+      return listUserEndCategory({ pageNum, pageSize: 10 }).then(response => {
+        const rows = response.rows || [];
+        this.userEndCategoryTotal = response.total || 0;
+        if (append) {
+          this.userEndCategoryList = this.userEndCategoryList.concat(rows);
+          this.userEndCategoryPageNum = pageNum + 1;
+        } else {
+          this.userEndCategoryList = rows;
+          this.userEndCategoryPageNum = 2;
+        }
+      }).finally(() => {
+        this.userEndCategoryLoading = false;
+      });
+    },
+    onUserEndCategoryScroll(e) {
+      const el = e && e.target;
+      if (!el) return;
+      if (this.$refs.userEndCategoryScroll && el !== this.$refs.userEndCategoryScroll) return;
+      const scrollTop = el.scrollTop;
+      const scrollHeight = el.scrollHeight;
+      const clientHeight = el.clientHeight;
+      if (clientHeight + scrollTop >= scrollHeight - 10) {
+        this.loadUserEndCategoryPage(true);
+      }
+    },
+    selectAllUserEndCategoryByPosition(position) {
+      const list = this.userEndCategoryList || [];
+      const idsOfPosition = list.filter(c => c.position === position).map(c => Number(c.id)).filter(id => id !== 0 && !Number.isNaN(id));
+      const current = this.form.userEndCategoryIds || [];
+      const otherPositionIds = current.filter(id => {
+        const c = list.find(x => Number(x.id) === Number(id));
+        return c ? c.position !== position : true;
+      });
+      this.$set(this.form, 'userEndCategoryIds', [...new Set([...otherPositionIds, ...idsOfPosition])]);
+    },
+    loadTagPage(append) {
+      if (this.tagLoading) return Promise.resolve();
+      if (append && this.tagList.length >= this.tagTotal && this.tagTotal > 0) return Promise.resolve();
+      this.tagLoading = true;
+      const pageNum = append ? this.tagPageNum : 1;
+      return listStoreProductTag({ pageNum, pageSize: 10 }).then(response => {
+        const rows = response.rows || [];
+        this.tagTotal = response.total || 0;
+        if (append) {
+          this.tagList = this.tagList.concat(rows);
+          this.tagPageNum = pageNum + 1;
+        } else {
+          this.tagList = rows;
+          this.tagPageNum = 2;
+        }
+      }).finally(() => {
+        this.tagLoading = false;
+      });
+    },
+    onTagScroll(e) {
+      const el = e && e.target;
+      if (!el) return;
+      if (this.$refs.tagScroll && el !== this.$refs.tagScroll) return;
+      const scrollTop = el.scrollTop;
+      const scrollHeight = el.scrollHeight;
+      const clientHeight = el.clientHeight;
+      if (clientHeight + scrollTop >= scrollHeight - 10) {
+        this.loadTagPage(true);
+      }
+    },
+    onTagIdsChange(val) {
+      if (val && val.length > 3) {
+        this.$message.warning('商品标签最多选择3个');
+        this.$set(this.form, 'tagIds', val.slice(0, 3));
+      }
+    },
+    isTagDisabled(tagId) {
+      const ids = this.form.tagIds || [];
+      const n = Number(tagId);
+      if (ids.some(id => Number(id) === n)) return false;
+      return ids.length >= 3;
+    },
+    loadTagOptions() {
+      this.tagPageNum = 1;
+      this.tagList = [];
+      this.tagTotal = 0;
+      return this.loadTagPage(false);
+    },
     // 取消按钮
     cancel() {
       this.open = false;
@@ -1606,7 +1778,9 @@ export default {
         adverseReactions: null, // 不良反应
         contraindications: null, // 禁忌
         precautions: null ,// 注意事项
-        purchaseLimit: null // 限购数量
+        purchaseLimit: null, // 限购数量
+        userEndCategoryIds: [],
+        tagIds: []
       };
       // 重置药品展示图
       this.drugImageArr = [];
@@ -1658,13 +1832,16 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
-
-      this.open = true;
-      this.title = "添加商品";
-      setTimeout(() => {
-        this.$refs.myeditor.setText("");
-      }, 500);
-
+      Promise.all([
+        this.loadTagOptions(),
+        this.loadUserEndCategoryForProduct(this.form.storeId)
+      ]).then(() => {
+        this.open = true;
+        this.title = "添加商品";
+        this.$nextTick(() => {
+          if (this.$refs.myeditor) this.$refs.myeditor.setText("");
+        });
+      });
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
@@ -1706,6 +1883,26 @@ export default {
         if (response.data.companyIds != null && response.data.companyIds != undefined && response.data.companyIds.length > 0) {
           this.form.companyIds = response.data.companyIds.split(',').map(Number);
         }
+        this.$set(this.form, 'userEndCategoryIds', (response.userEndCategoryIds || []).map(id => Number(id)).filter(id => !Number.isNaN(id)));
+        this.$set(this.form, 'tagIds', (response.tagIds || []).map(id => Number(id)).filter(id => !Number.isNaN(id)));
+        Promise.all([
+          this.loadTagOptions(),
+          this.loadUserEndCategoryForProduct(this.form.storeId)
+        ]).then(() => {
+          this.open = true;
+          this.title = "修改商品";
+          this.$nextTick(() => {
+            setTimeout(() => {
+              if (this.$refs.myeditor) {
+                if (this.form.description == null) {
+                  this.$refs.myeditor.setText("");
+                } else {
+                  this.$refs.myeditor.setText(this.form.description);
+                }
+              }
+            }, 200);
+          });
+        });
         setTimeout(() => {
           that.generate();
         }, 200);
@@ -1730,28 +1927,21 @@ export default {
             }
           ]
         }
-        setTimeout(() => {
-          if(this.form.description==null){
-            this.$refs.myeditor.setText("");
-          }
-          else{
-            this.$refs.myeditor.setText(this.form.description);
-          }
-        }, 200);
         if(this.form.image!=null){
           this.imageArr=this.form.image.split(",");
         }
         if(this.form.sliderImage!=null){
           this.photoArr=this.form.sliderImage.split(",");
         }
-        this.open = true;
-        this.title = "修改商品";
       });
     },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
+          if (this.form.tagIds && this.form.tagIds.length > 3) {
+            return this.$message.warning('商品标签最多选择3个');
+          }
           if(this.form.specType ===0 ){
             this.form.items = [];
             this.form.values = this.oneFormValidate;
@@ -1851,3 +2041,18 @@ export default {
   }
 };
 </script>
+<style scoped>
+.user-end-category-wrap .category-group { margin-bottom: 12px; }
+.user-end-category-wrap .select-all-label { cursor: pointer; color: #409EFF; margin-right: 12px; }
+.user-end-category-wrap .category-children { display: inline-block; margin-top: 6px; }
+.text-danger { color: #F56C6C; font-size: 12px; }
+.scroll-select-wrap { width: 100%; }
+.quick-select-row { margin-bottom: 6px; }
+.quick-select-row .el-button { padding: 0 8px; }
+.scroll-box { max-height: 200px; overflow-y: auto; border: 1px solid #DCDFE6; border-radius: 4px; padding: 8px; background: #FAFAFA; }
+.scroll-options { display: flex; flex-wrap: wrap; gap: 8px; }
+.scroll-option-item { margin-right: 0; }
+.scroll-loading, .scroll-end { text-align: center; color: #909399; font-size: 12px; padding: 8px 0; }
+.scroll-more { text-align: center; padding: 8px 0; cursor: pointer; font-size: 12px; color: #409EFF; }
+.scroll-more:hover { text-decoration: underline; }
+</style>

+ 226 - 0
src/views/hisStore/storeProductTag/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="标签名称" prop="tagName">
+        <el-input
+          v-model="queryParams.tagName"
+          placeholder="请输入标签名称"
+          clearable
+          size="small"
+          maxlength="4"
+          show-word-limit
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['store:storeProductTag:add']">新增</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column label="序号" type="index" width="55" align="center" />
+      <el-table-column label="标签名称" align="center" prop="tagName" />
+      <el-table-column label="关联商品数量" align="center" width="120">
+        <template slot-scope="scope">
+          <el-button type="text" @click="showProducts(scope.row)">{{ scope.row.productCount || 0 }}个</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 1">显示</el-tag>
+          <el-tag v-else type="info">隐藏</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" width="80" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['store:storeProductTag:edit']">编辑</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['store:storeProductTag:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <!-- 添加/编辑标签 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="90px">
+        <el-form-item label="标签名称" prop="tagName">
+          <el-input v-model="form.tagName" placeholder="最多4个字" maxlength="4" show-word-limit />
+        </el-form-item>
+        <el-form-item label="标签状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">显示</el-radio>
+            <el-radio :label="0">隐藏</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" placeholder="排序" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 关联商品弹窗 -->
+    <el-dialog title="关联商品" :visible.sync="productsOpen" width="800px" append-to-body>
+      <div class="mb8">关联商品数量: {{ productsTotal }}个</div>
+      <el-table v-loading="productsLoading" :data="productsList" border>
+        <el-table-column label="商品ID" align="center" prop="productId" width="80" />
+        <el-table-column label="商品名称" align="center" prop="productName" min-width="120" show-overflow-tooltip />
+        <el-table-column label="主图" align="center" width="80">
+          <template slot-scope="scope">
+            <el-image v-if="scope.row.image" style="width:50px;height:50px" :src="scope.row.image" fit="contain" />
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="售价" align="center" width="100">
+          <template slot-scope="scope">
+            <span v-if="scope.row.price != null">¥{{ scope.row.price.toFixed(2) }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" width="80">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.isShow === 1" type="success">上架</el-tag>
+            <el-tag v-else type="info">未上架</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="productsTotal>0" :total="productsTotal" :page.sync="productsQuery.pageNum" :limit.sync="productsQuery.pageSize" @pagination="loadProducts" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listStoreProductTag, getStoreProductTag, addStoreProductTag, updateStoreProductTag, delStoreProductTag, listTagProducts } from '@/api/hisStore/storeProductTag'
+
+export default {
+  name: 'StoreProductTag',
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      list: [],
+      total: 0,
+      title: '',
+      open: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        tagName: null,
+        status: null
+      },
+      form: {},
+      rules: {
+        tagName: [{ required: true, message: '标签名称不能为空', trigger: 'blur' }, { max: 4, message: '最多4个字', trigger: 'blur' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+        sort: [{ required: true, message: '请输入排序', trigger: 'blur' }]
+      },
+      productsOpen: false,
+      productsLoading: false,
+      productsList: [],
+      productsTotal: 0,
+      productsQuery: { pageNum: 1, pageSize: 10 },
+      currentTagId: null
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      listStoreProductTag(this.queryParams).then(response => {
+        this.list = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      })
+    },
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+    reset() {
+      this.form = { id: null, storeId: null, tagName: null, status: 1, sort: 0 }
+      this.resetForm('form')
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    handleAdd() {
+      this.reset()
+      this.open = true
+      this.title = '添加商品标签'
+    },
+    handleUpdate(row) {
+      this.reset()
+      getStoreProductTag(row.id).then(response => {
+        this.form = { ...response.data }
+        this.open = true
+        this.title = '编辑商品标签'
+      })
+    },
+    submitForm() {
+      this.$refs['form'].validate(valid => {
+        if (!valid) return
+        if (this.form.id != null) {
+          updateStoreProductTag(this.form).then(() => {
+            this.msgSuccess('修改成功')
+            this.open = false
+            this.getList()
+          })
+        } else {
+          addStoreProductTag(this.form).then(() => {
+            this.msgSuccess('新增成功')
+            this.open = false
+            this.getList()
+          })
+        }
+      })
+    },
+    handleDelete(row) {
+      this.$confirm('是否确认删除该商品标签?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delStoreProductTag(row.id)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+    showProducts(row) {
+      this.currentTagId = row.id
+      this.productsQuery = { pageNum: 1, pageSize: 10 }
+      this.productsOpen = true
+      this.loadProducts()
+    },
+    loadProducts() {
+      if (!this.currentTagId) return
+      this.productsLoading = true
+      listTagProducts(this.currentTagId, this.productsQuery).then(response => {
+        this.productsList = response.rows || []
+        this.productsTotal = response.total || 0
+        this.productsLoading = false
+      })
+    }
+  }
+}
+</script>

+ 273 - 0
src/views/hisStore/userEndCategory/index.vue

@@ -0,0 +1,273 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="分类名称" prop="categoryName">
+        <el-input
+          v-model="queryParams.categoryName"
+          placeholder="请输入分类名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="分类位置" prop="position">
+        <el-select v-model="queryParams.position" placeholder="请选择" clearable size="small">
+          <el-option label="金刚区" :value="1" />
+          <el-option label="瀑布流" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['store:userEndCategory:add']">新增</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column label="序号" type="index" width="55" align="center" />
+      <el-table-column label="分类名称" align="center" prop="categoryName" />
+      <el-table-column label="位置" align="center" prop="position" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.position === 1" type="primary">金刚区</el-tag>
+          <el-tag v-else type="success">瀑布流</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Icon" align="center" width="80">
+        <template slot-scope="scope">
+          <el-image v-if="scope.row.icon" style="width:40px;height:40px" :src="scope.row.icon" fit="contain" />
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 1">显示</el-tag>
+          <el-tag v-else type="info">隐藏</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" width="80" />
+      <el-table-column label="关联商品" align="center" width="120">
+        <template slot-scope="scope">
+          <el-button type="text" @click="showCategoryProducts(scope.row)">查看</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['store:userEndCategory:edit']">编辑</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['store:userEndCategory:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="分类名称" prop="categoryName">
+          <el-input v-model="form.categoryName" placeholder="请输入分类名称" maxlength="32" show-word-limit />
+        </el-form-item>
+        <el-form-item label="分类位置" prop="position">
+          <el-radio-group v-model="form.position">
+            <el-radio :label="1">金刚区</el-radio>
+            <el-radio :label="2">瀑布流</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="form.position === 1" label="分类icon" prop="icon">
+          <Material v-model="iconArr" type="image" :num="1" :width="150" :height="150" />
+        </el-form-item>
+        <el-form-item label="分类状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">显示</el-radio>
+            <el-radio :label="0">隐藏</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" :min="0" placeholder="排序" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 关联商品弹窗 -->
+    <el-dialog title="关联商品" :visible.sync="productsOpen" width="960px" append-to-body>
+      <div class="mb8">关联商品数量: {{ productsTotal }}个</div>
+      <el-table v-loading="productsLoading" :data="productsList" border>
+        <el-table-column label="商品ID" align="center" prop="productId" width="80" />
+        <el-table-column label="商品名称" align="center" prop="productName" min-width="140" show-overflow-tooltip />
+        <el-table-column label="售价" align="center" width="100">
+          <template slot-scope="scope">
+            <span v-if="scope.row.price != null">¥{{ scope.row.price.toFixed(2) }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="原价" align="center" width="100">
+          <template slot-scope="scope">
+            <span v-if="scope.row.otPrice != null">¥{{ scope.row.otPrice.toFixed(2) }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="销量" align="center" prop="sales" width="80" />
+        <el-table-column label="产品标签" align="center" min-width="120">
+          <template slot-scope="scope">
+            <template v-if="scope.row.tagList && scope.row.tagList.length">
+              <el-tag v-for="(tag, i) in scope.row.tagList" :key="i" size="small" class="mr4">{{ tag }}</el-tag>
+            </template>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="productsTotal>0" :total="productsTotal" :page.sync="productsQuery.pageNum" :limit.sync="productsQuery.pageSize" @pagination="loadCategoryProducts" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserEndCategory, getUserEndCategory, addUserEndCategory, updateUserEndCategory, delUserEndCategory, listCategoryProducts } from '@/api/hisStore/userEndCategory'
+import Material from '@/components/Material'
+
+export default {
+  name: 'UserEndCategory',
+  components: { Material },
+  watch: {
+    iconArr(val) {
+      this.form.icon = val && val.length ? val.join(',') : ''
+    }
+  },
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      list: [],
+      total: 0,
+      title: '',
+      open: false,
+      iconArr: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        categoryName: null,
+        position: null,
+        status: null
+      },
+      form: {},
+      rules: {
+        categoryName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+        position: [{ required: true, message: '请选择分类位置', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+        sort: [{ required: true, message: '请输入排序', trigger: 'blur' }]
+      },
+      productsOpen: false,
+      productsLoading: false,
+      productsList: [],
+      productsTotal: 0,
+      productsQuery: { pageNum: 1, pageSize: 10 },
+      currentCategoryId: null
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      listUserEndCategory(this.queryParams).then(response => {
+        this.list = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      })
+    },
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+    reset() {
+      this.form = { id: null, storeId: null, categoryName: null, position: 1, icon: null, status: 1, sort: 0 }
+      this.iconArr = []
+      this.resetForm('form')
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    handleAdd() {
+      this.reset()
+      this.open = true
+      this.title = '添加用户端分类'
+    },
+    handleUpdate(row) {
+      this.reset()
+      getUserEndCategory(row.id).then(response => {
+        this.form = { ...response.data }
+        if (this.form.icon) this.iconArr = this.form.icon.split(',')
+        this.open = true
+        this.title = '编辑用户端分类'
+      })
+    },
+    submitForm() {
+      this.$refs['form'].validate(valid => {
+        if (!valid) return
+        if (this.form.position === 1 && !this.form.icon) {
+          this.$message.warning('金刚区需上传分类icon')
+          return
+        }
+        if (this.form.id != null) {
+          updateUserEndCategory(this.form).then(() => {
+            this.msgSuccess('修改成功')
+            this.open = false
+            this.getList()
+          })
+        } else {
+          addUserEndCategory(this.form).then(() => {
+            this.msgSuccess('新增成功')
+            this.open = false
+            this.getList()
+          })
+        }
+      })
+    },
+    handleDelete(row) {
+      this.$confirm('是否确认删除该用户端分类?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delUserEndCategory(row.id)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+    showCategoryProducts(row) {
+      this.currentCategoryId = row.id
+      this.productsQuery = { pageNum: 1, pageSize: 10 }
+      this.productsOpen = true
+      this.loadCategoryProducts()
+    },
+    loadCategoryProducts() {
+      if (!this.currentCategoryId) return
+      this.productsLoading = true
+      listCategoryProducts(this.currentCategoryId, this.productsQuery).then(response => {
+        this.productsList = response.rows || []
+        this.productsTotal = response.total || 0
+        this.productsLoading = false
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.mr4 { margin-right: 4px; }
+</style>