Ver código fonte

积分相关代码提交

yjwang 15 horas atrás
pai
commit
315989977e

+ 10 - 1
src/api/his/integralGoods.js

@@ -55,4 +55,13 @@ export function exportIntegralGoods(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+// 获取选择积分商品列表
+export function getChooseIntegralGoodsList(query) {
+  return request({
+    url: '/his/integralGoods/getChooseIntegralGoodsList',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/hisStore/integralGoods.js

@@ -57,6 +57,15 @@ export function exportIntegralGoods(query) {
   })
 }
 
+// 批量修改有效期
+export function batchUpdateValidPeriod(data) {
+  return request({
+    url: '/his/integralGoods/batchUpdateValidPeriod',
+    method: 'put',
+    data: data
+  })
+}
+
 // 获取选择积分商品列表
 export function getChooseIntegralGoodsList(query) {
   return request({

+ 15 - 3
src/components/Material/index.vue

@@ -3,7 +3,17 @@
     <ul v-for="(item,index) in value" :key="index" class="el-upload-list el-upload-list--picture-card">
       <li tabindex="0" class="el-upload-list__item is-ready" :style="'width: '+width+'px;height: '+height+'px'">
         <div>
-          <img :src="item" alt="" class="el-upload-list__item-thumbnail">
+          <el-image
+            ref="imagePreview"
+            :src="item"
+            alt=""
+            class="el-upload-list__item-thumbnail"
+            fit="cover"
+            :preview-src-list="value"
+            :initial-index="index"
+            :z-index="10000"
+            style="width: 100%; height: 100%"
+          />
           <span class="el-upload-list__item-actions">
             <span v-if="index != 0" class="el-upload-list__item-preview" @click="moveMaterial(index,'up')">
               <i class="el-icon-back" />
@@ -294,8 +304,10 @@ export default {
       }
     },
     zoomMaterial(index) {
-      this.dialogVisible = true
-      this.url = this.value[index]
+      // 触发 el-image 的全屏预览(支持放大、缩小、旋转)
+      if (this.$refs.imagePreview && this.$refs.imagePreview[index]) {
+        this.$refs.imagePreview[index].clickHandler()
+      }
     },
     deleteMaterial(index) {
       const that = this

+ 42 - 2
src/views/components/his/integralOrderDetails.vue

@@ -89,6 +89,23 @@
                   <p v-else>-</p>
                 </template>
               </el-table-column>
+              <el-table-column label="是否药品" width="90" align="center" prop="isDrug">
+                <template slot-scope="scope">
+                  <el-tag :type="scope.row.isDrug === 1 ? 'danger' : 'info'" size="mini">
+                    {{ scope.row.isDrug === 1 ? '是' : '否' }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="溯源码" width="180" align="center" prop="verifyCode" show-overflow-tooltip>
+                <template slot-scope="scope">
+                  <span>{{ scope.row.verifyCode || '-' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="批次号" width="180" align="center" prop="batchNumber" show-overflow-tooltip>
+                <template slot-scope="scope">
+                  <span>{{ scope.row.batchNumber || '-' }}</span>
+                </template>
+              </el-table-column>
             </el-table>
        </div>
 
@@ -369,13 +386,36 @@ import { getIntegralLogsByOrderId } from "@/api/his/userIntegralLogs";
         this.integralLogs = [];
         getIntegralOrder(orderId).then(response => {
             this.item = response.data;
-            this.parseItemJson(this.item.itemJson);
+            this.parseProdList(this.item);
             // 同时加载当前订单的积分变动明细
             this.loadIntegralLogs(orderId);
         });
       },
       /**
-       * 解析积分订单 itemJson(兼容单对象和数组两种格式)
+       * 解析商品列表:优先用后端返回的 itemList(含溯源码/批次号/是否药品),
+       * 无 itemList 时回退解析 itemJson(兼容历史订单,无溯源码/批次号/是否药品列)。
+       * itemList 中 jsonInfo 存了商品快照(imgUrl/otPrice/integral 等),需合并展示。
+       */
+      parseProdList(order) {
+        if (!order) {
+          this.prod = [];
+          return;
+        }
+        if (order.itemList && order.itemList.length > 0) {
+          // 合并明细表字段与 jsonInfo 快照字段
+          this.prod = order.itemList.map(item => {
+            let snapshot = {};
+            if (item.jsonInfo) {
+              try { snapshot = JSON.parse(item.jsonInfo); } catch (e) { snapshot = {}; }
+            }
+            return Object.assign({}, snapshot, item);
+          });
+        } else {
+          this.parseItemJson(order.itemJson);
+        }
+      },
+      /**
+       * 解析积分订单 itemJson(兼容单对象和数组两种格式,历史订单兜底)
        */
       parseItemJson(itemJson) {
         if (!itemJson) {

+ 89 - 0
src/views/his/components/integralGoodsDetails.vue

@@ -0,0 +1,89 @@
+<template>
+    <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+      <div style="padding: 20px; background-color: #fff;">
+         积分商品详情
+      </div>
+<div class="contentx" v-if="item!=null">
+        <div class="desct"> 积分商品信息</div>
+        <el-descriptions title="" :column="3" border>
+          <el-descriptions-item label="商品名称"><span v-if="item!=null">{{item.goodsName}}</span></el-descriptions-item>
+          <el-descriptions-item label="封面图"> <el-image v-if="item.imgUrl!=null"
+                  style="width: 100px"
+                  :src="item.imgUrl"
+                  :preview-src-list="[item.imgUrl]">
+              </el-image></el-descriptions-item>
+          <el-descriptions-item label="商品分类"> <dict-tag :options="goodsTypeOptions" :value="item.goodsType"/></el-descriptions-item>
+          <el-descriptions-item label="原价"><span v-if="item!=null">{{item.otPrice}}</span></el-descriptions-item>
+
+
+          <el-descriptions-item label="所需积分"><span v-if="item!=null">{{item.integral}}</span></el-descriptions-item>
+          <el-descriptions-item label="排序"><span v-if="item!=null">{{item.sort}}</span></el-descriptions-item>
+          <el-descriptions-item label="库存"><span v-if="item!=null">{{item.stock}}</span></el-descriptions-item>
+          <el-descriptions-item label="状态"><span v-if="item!=null"> <dict-tag :options="statusOptions" :value="item.status"/></span></el-descriptions-item>
+          <el-descriptions-item label="创建时间"><span v-if="item!=null">{{item.createTime}}</span></el-descriptions-item>
+        </el-descriptions>
+    </div>
+    <div class="contentx" v-if="item!=null">
+        <div class="desct"> 积分商品信息</div>
+    <span v-html="item.descs"></span>
+    </div>
+
+    </div>
+</template>
+
+<script>
+import { listIntegralGoods, getIntegralGoods, delIntegralGoods, addIntegralGoods, updateIntegralGoods, exportIntegralGoods } from "@/api/hisStore/integralGoods";
+  export default {
+    name: "coupon",
+    data() {
+      return {
+        // 商品分类字典
+        goodsTypeOptions: [],
+        // 状态字典
+        statusOptions: [],
+        item:null,
+      }
+    },
+    created() {
+        this.getDicts("sys_integral_goods_type").then(response => {
+          this.goodsTypeOptions = response.data;
+        });
+        this.getDicts("sys_company_status").then(response => {
+          this.statusOptions = response.data;
+        });
+    },
+    methods: {
+      getDetails(orderId) {
+        this.item=null;
+        getIntegralGoods(orderId).then(response => {
+            this.item = response.data;
+
+        });
+      },
+    }
+  }
+</script>
+<style>
+  .contentx{
+      height: 100%;
+      background-color: #fff;
+      padding: 0px 20px 20px;
+
+
+      margin: 20px;
+  }
+  .el-descriptions-item__label.is-bordered-label{
+    font-weight: normal;
+  }
+  .el-descriptions-item__content {
+    max-width: 150px;
+    min-width: 100px;
+  }
+  .desct{
+      padding-top: 20px;
+      padding-bottom: 20px;
+      color: #524b4a;
+      font-weight: bold;
+    }
+
+</style>

+ 42 - 3
src/views/his/integralAfterSales/components/integralAfterSalesDetails.vue

@@ -153,6 +153,23 @@
               <span v-else>-</span>
             </template>
           </el-table-column>
+          <el-table-column label="是否药品" width="90" align="center" prop="isDrug">
+            <template slot-scope="scope">
+              <el-tag :type="scope.row.isDrug === 1 ? 'danger' : 'info'" size="mini">
+                {{ scope.row.isDrug === 1 ? '是' : '否' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="溯源码" width="180" align="center" prop="verifyCode" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ scope.row.verifyCode || '-' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="批次号" width="180" align="center" prop="batchNumber" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ scope.row.batchNumber || '-' }}</span>
+            </template>
+          </el-table-column>
         </el-table>
 
         <div class="section-title" style="margin-top: 20px">订单发货物流</div>
@@ -309,7 +326,7 @@ export default {
       this.prodList = [];
       getIntegralAfterSales(id).then(response => {
         this.afterSales = response.data;
-        this.parseItemJson(this.afterSales.itemJson);
+        this.parseProdList(this.afterSales);
         this.loading = false;
       }).catch(() => {
         this.loading = false;
@@ -323,7 +340,7 @@ export default {
       getIntegralAfterSalesByOrderCode(orderCode).then(response => {
         if (response.data) {
           this.afterSales = response.data;
-          this.parseItemJson(this.afterSales.itemJson);
+          this.parseProdList(this.afterSales);
         } else {
           this.afterSales = null;
           this.prodList = [];
@@ -334,7 +351,29 @@ export default {
       });
     },
     /**
-     * 解析积分订单 itemJson(兼容单对象和数组两种格式)
+     * 解析商品列表:优先用后端返回的 itemList(含溯源码/批次号/是否药品),
+     * 无 itemList 时回退解析 itemJson(兼容历史订单,无溯源码/批次号/是否药品列)。
+     * itemList 中 jsonInfo 存了商品快照(imgUrl/otPrice/integral 等),需合并展示。
+     */
+    parseProdList(afterSales) {
+      if (!afterSales) {
+        this.prodList = [];
+        return;
+      }
+      if (afterSales.itemList && afterSales.itemList.length > 0) {
+        this.prodList = afterSales.itemList.map(item => {
+          let snapshot = {};
+          if (item.jsonInfo) {
+            try { snapshot = JSON.parse(item.jsonInfo); } catch (e) { snapshot = {}; }
+          }
+          return Object.assign({}, snapshot, item);
+        });
+      } else {
+        this.parseItemJson(afterSales.itemJson);
+      }
+    },
+    /**
+     * 解析积分订单 itemJson(兼容单对象和数组两种格式,历史订单兜底)
      */
     parseItemJson(itemJson) {
       if (!itemJson) {

+ 182 - 4
src/views/his/integralGoods/index.vue

@@ -30,6 +30,16 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="是否药品" prop="isDrug">
+        <el-select v-model="queryParams.isDrug" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="item in isDrugOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -69,6 +79,17 @@
           v-hasPermi="['his:integralGoods:remove']"
         >删除</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-time"
+          size="mini"
+          :disabled="multiple"
+          @click="handleBatchValidPeriod"
+          v-hasPermi="['his:integralGoods:edit']"
+        >批量修改有效期</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -116,6 +137,13 @@
           <dict-tag :options="goodsTypeOptions" :value="scope.row.goodsType"/>
         </template>
       </el-table-column>
+      <el-table-column label="是否药品" align="center" prop="isDrug" width="90">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isDrug === 1 ? 'danger' : 'info'" size="mini">
+            {{ scope.row.isDrug === 1 ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="状态" align="center" prop="status">
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>
@@ -125,6 +153,21 @@
       <el-table-column label="需支付金额" align="center" prop="cash" />
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="库存" align="center" prop="stock" />
+      <el-table-column label="限购" align="center" prop="limitNum">
+        <template slot-scope="scope">
+          <span v-if="scope.row.limitNum > 0">{{ scope.row.limitNum }}件/人</span>
+          <span v-else>不限购</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="有效期" align="center" prop="isLongTerm" width="180">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.isLongTerm === 0" type="success" size="mini">长期有效</el-tag>
+          <div v-else>
+            <div style="font-size: 12px;">开始: {{ scope.row.validStartTime }}</div>
+            <div style="font-size: 12px;">结束: {{ scope.row.validEndTime }}</div>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="150">
         <template slot-scope="scope">
@@ -195,6 +238,15 @@
         <el-form-item label="商品编号" prop="barCode">
           <el-input v-model="form.barCode" placeholder="请输入商品编号"  style="width: 200px;"/>
         </el-form-item>
+        <el-form-item label="是否药品" prop="isDrug">
+          <el-radio-group v-model="form.isDrug">
+            <el-radio
+              v-for="item in isDrugOptions"
+              :key="item.value"
+              :label="item.value"
+            >{{ item.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
 
         <el-form-item label="排序" prop="sort">
           <el-input-number v-model="form.sort"  :min="0"  label="排序"></el-input-number>
@@ -206,6 +258,10 @@
         <el-form-item label="库存" prop="stock">
           <el-input-number v-model="form.stock"  :min="0"  label="库存"></el-input-number>
         </el-form-item>
+        <el-form-item label="限购数量" prop="limitNum">
+          <el-input-number v-model="form.limitNum" :min="0" :step="1" :precision="0" label="限购数量"></el-input-number>
+          <span style="margin-left: 10px; color: #909399; font-size: 12px;">0表示不限购,大于0表示每用户限购件数</span>
+        </el-form-item>
         <el-form-item label="状态">
           <el-radio-group v-model="form.status">
             <el-radio
@@ -256,19 +312,54 @@
           </div>
         </el-dialog>
 
-
+    <!-- 批量修改有效期对话框 -->
+    <el-dialog title="批量修改有效期" :visible.sync="validPeriodOpen" width="500px" append-to-body>
+      <el-form ref="validPeriodForm" :model="validPeriodForm" :rules="validPeriodRules" label-width="120px">
+        <el-form-item label="已选商品" prop="goodsIds">
+          <span>共 {{ validPeriodForm.goodsIds.length }} 件商品</span>
+        </el-form-item>
+        <el-form-item label="是否长期有效" prop="isLongTerm">
+          <el-radio-group v-model="validPeriodForm.isLongTerm" @change="handleLongTermChange">
+            <el-radio :label="0">是</el-radio>
+            <el-radio :label="1">否</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="validPeriodForm.isLongTerm === 1" label="兑换开始时间" prop="validStartTime">
+          <el-date-picker
+            v-model="validPeriodForm.validStartTime"
+            type="datetime"
+            placeholder="请选择兑换开始时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width: 100%;"
+          />
+        </el-form-item>
+        <el-form-item v-if="validPeriodForm.isLongTerm === 1" label="兑换结束时间" prop="validEndTime">
+          <el-date-picker
+            v-model="validPeriodForm.validEndTime"
+            type="datetime"
+            placeholder="请选择兑换结束时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width: 100%;"
+          />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitValidPeriodForm">确 定</el-button>
+        <el-button @click="validPeriodOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { importTemplate,listIntegralGoods, getIntegralGoods, delIntegralGoods, addIntegralGoods, updateIntegralGoods, exportIntegralGoods } from "@/api/his/integralGoods";
+import { importTemplate,listIntegralGoods, getIntegralGoods, delIntegralGoods, addIntegralGoods, updateIntegralGoods, exportIntegralGoods, batchUpdateValidPeriod } from "@/api/hisStore/integralGoods";
 import Material from '@/components/Material';
 import Editor from '@/components/Editor/wang';
 import { getToken } from "@/utils/auth";
-import integralGoodsDetails from '../../components/his/integralGoodsDetails';
+import integralGoodsDetails from '../components/integralGoodsDetails';
 export default {
   name: "IntegralGoods",
-  components: {Material,Editor,integralGoodsDetails},
+  components: { Material,Editor,integralGoodsDetails},
   watch: {
     imageArr: function(val) {
       this.form.imgUrl = val.join(',')
@@ -322,6 +413,11 @@ export default {
       goodsTypeOptions: [],
       // 状态字典
       statusOptions: [],
+      // 是否药品选项
+      isDrugOptions: [
+        { value: 0, label: "否" },
+        { value: 1, label: "是" }
+      ],
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -329,11 +425,33 @@ export default {
         goodsName: null,
         goodsType: null,
         status: null,
+        isDrug: null,
         integral: null,
         createTime: null
       },
       // 表单参数
       form: {},
+      // 批量修改有效期弹窗
+      validPeriodOpen: false,
+      // 批量修改有效期表单
+      validPeriodForm: {
+        goodsIds: [],
+        isLongTerm: 0,
+        validStartTime: null,
+        validEndTime: null
+      },
+      // 批量修改有效期校验
+      validPeriodRules: {
+        isLongTerm: [
+          { required: true, message: "请选择是否长期有效", trigger: "change", type: "number" }
+        ],
+        validStartTime: [
+          { required: true, message: "请选择兑换开始时间", trigger: "change" }
+        ],
+        validEndTime: [
+          { required: true, message: "请选择兑换结束时间", trigger: "change" }
+        ]
+      },
       // 表单校验
       rules: {
         imgUrl: [
@@ -342,6 +460,9 @@ export default {
         goodsName: [
           { required: true, message: "商品名称不能为空", trigger: "blur" }
         ],
+        isDrug: [
+          { required: true, message: "请选择是否药品", trigger: "change", type: "number" }
+        ],
       }
     };
   },
@@ -419,9 +540,13 @@ export default {
         cash: null,
         sort: null,
         stock: null,
+        limitNum: 0,
         descs: null,
+        isDrug: 0,
         createTime: null
       };
+      this.imageArr = []
+      this.photoArr = []
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -465,6 +590,7 @@ export default {
          if(this.form.images!=null){
              this.photoArr=this.form.images.split(",");
            }
+
         //修改按钮
         setTimeout(() => {
                   if(this.form.descs==null){
@@ -524,6 +650,58 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
+    },
+    /** 批量修改有效期按钮操作 */
+    handleBatchValidPeriod() {
+      if (this.ids.length === 0) {
+        this.msgError("请至少选择一条数据");
+        return;
+      }
+      this.validPeriodForm = {
+        goodsIds: this.ids,
+        isLongTerm: 0,
+        validStartTime: null,
+        validEndTime: null
+      };
+      this.validPeriodOpen = true;
+      this.$nextTick(() => {
+        if (this.$refs.validPeriodForm) {
+          this.$refs.validPeriodForm.clearValidate();
+        }
+      });
+    },
+    // 是否长期有效变化
+    handleLongTermChange(val) {
+      if (val === 0) {
+        this.validPeriodForm.validStartTime = null;
+        this.validPeriodForm.validEndTime = null;
+        this.$nextTick(() => {
+          if (this.$refs.validPeriodForm) {
+            this.$refs.validPeriodForm.clearValidate(['validStartTime', 'validEndTime']);
+          }
+        });
+      }
+    },
+    // 提交批量修改有效期
+    submitValidPeriodForm() {
+      this.$refs["validPeriodForm"].validate(valid => {
+        if (valid) {
+          // 非长期有效时校验开始时间不晚于结束时间
+          if (this.validPeriodForm.isLongTerm === 1) {
+            const start = new Date(this.validPeriodForm.validStartTime).getTime();
+            const end = new Date(this.validPeriodForm.validEndTime).getTime();
+            if (start >= end) {
+              this.msgError("兑换开始时间不能晚于或等于结束时间");
+              return;
+            }
+          }
+          batchUpdateValidPeriod(this.validPeriodForm).then(response => {
+            this.msgSuccess("批量修改有效期成功");
+            this.validPeriodOpen = false;
+            this.getList();
+          });
+        }
+      });
     }
   }
 };

+ 36 - 0
src/views/hisStore/integralGoods/index.vue

@@ -30,6 +30,16 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="是否药品" prop="isDrug">
+        <el-select v-model="queryParams.isDrug" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="item in isDrugOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -116,6 +126,13 @@
           <dict-tag :options="goodsTypeOptions" :value="scope.row.goodsType"/>
         </template>
       </el-table-column>
+      <el-table-column label="是否药品" align="center" prop="isDrug" width="90">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isDrug === 1 ? 'danger' : 'info'" size="mini">
+            {{ scope.row.isDrug === 1 ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="状态" align="center" prop="status">
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>
@@ -201,6 +218,15 @@
         <el-form-item label="商品编号" prop="barCode">
           <el-input v-model="form.barCode" placeholder="请输入商品编号"  style="width: 200px;"/>
         </el-form-item>
+        <el-form-item label="是否药品" prop="isDrug">
+          <el-radio-group v-model="form.isDrug">
+            <el-radio
+              v-for="item in isDrugOptions"
+              :key="item.value"
+              :label="item.value"
+            >{{ item.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
 
         <el-form-item label="排序" prop="sort">
           <el-input-number v-model="form.sort"  :min="0"  label="排序"></el-input-number>
@@ -330,6 +356,11 @@ export default {
       goodsTypeOptions: [],
       // 状态字典
       statusOptions: [],
+      // 是否药品选项
+      isDrugOptions: [
+        { value: 0, label: "否" },
+        { value: 1, label: "是" }
+      ],
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -337,6 +368,7 @@ export default {
         goodsName: null,
         goodsType: null,
         status: null,
+        isDrug: null,
         integral: null,
         createTime: null
       },
@@ -350,6 +382,9 @@ export default {
         goodsName: [
           { required: true, message: "商品名称不能为空", trigger: "blur" }
         ],
+        isDrug: [
+          { required: true, message: "请选择是否药品", trigger: "change", type: "number" }
+        ],
       }
     };
   },
@@ -429,6 +464,7 @@ export default {
         stock: null,
         limitNum: 0,
         descs: null,
+        isDrug: 0,
         createTime: null
       };
       this.imageArr = []

+ 190 - 0
src/views/system/config/config.vue

@@ -2410,6 +2410,46 @@
         </el-form>
       </el-tab-pane>
 
+      <!-- 应用配置(YML) -->
+      <el-tab-pane label="应用配置(YML)" name="sys.application.config">
+        <div class="app-config-container">
+          <div class="app-config-header">
+            <div class="app-config-title">
+              <i class="el-icon-document"></i>
+              <span>sys.application.config</span>
+              <el-tag size="mini" type="warning" class="app-config-tag">YAML</el-tag>
+            </div>
+            <div class="app-config-toolbar">
+              <el-button type="primary" size="small" icon="el-icon-check" @click="submitAppConfig">保存配置</el-button>
+              <el-button size="small" icon="el-icon-magic-stick" @click="formatYaml">格式化</el-button>
+              <el-button size="small" icon="el-icon-refresh" @click="resetAppConfig">重新加载</el-button>
+            </div>
+          </div>
+          <el-alert
+            title="修改后需重启应用才能生效(@Value 注入的配置在启动时一次性加载)"
+            type="warning"
+            :closable="false"
+            show-icon
+            class="app-config-alert"
+          />
+          <div class="yaml-editor-wrapper">
+            <el-input
+              type="textarea"
+              v-model="appConfigContent"
+              :rows="32"
+              placeholder="请输入 YML 格式配置"
+              class="yaml-editor"
+              spellcheck="false"
+              resize="vertical"
+            />
+            <div class="yaml-editor-footer">
+              <span class="yaml-line-info">共 {{ appConfigLineCount }} 行</span>
+              <span class="yaml-char-info">{{ appConfigCharCount }} 字符</span>
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
+
     </el-tabs>
 
 
@@ -2599,6 +2639,9 @@ export default {
       form29: {
         signPageUrl: ''
       },
+      // 应用配置(YML格式,类似nacos配置管理)
+      appConfigContent: '',
+      appConfigId: null,
       storeProductScrmColumns:[],
       storeScrmColumns: [],
       photoArr: [],
@@ -2699,6 +2742,18 @@ export default {
       this.form25.images = val.join(',')
     }
   },
+  computed: {
+    // 应用配置(YML)行数
+    appConfigLineCount() {
+      if (!this.appConfigContent) return 0
+      return this.appConfigContent.split('\n').length
+    },
+    // 应用配置(YML)字符数
+    appConfigCharCount() {
+      if (!this.appConfigContent) return 0
+      return this.appConfigContent.length
+    }
+  },
   methods: {
 
     // 处理开关配置
@@ -3063,6 +3118,12 @@ export default {
             this.form29 = {...this.form29, ...JSON.parse(response.data.configValue)}
           }
         }
+        if (key == 'sys.application.config') {
+          if (response.data && response.data.configValue) {
+            this.appConfigContent = response.data.configValue
+            this.appConfigId = response.data.configId
+          }
+        }
       })
     },
     /** 提交按钮 */
@@ -3368,6 +3429,60 @@ export default {
         }
       })
     },
+    /** 保存应用配置(YML) */
+    submitAppConfig() {
+      if (!this.appConfigContent || !this.appConfigContent.trim()) {
+        this.$message.warning('配置内容不能为空')
+        return
+      }
+      this.$confirm('保存后需重启应用才能生效,确认保存?', '提示', {
+        confirmButtonText: '确定保存',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.doSubmitAppConfig()
+      }).catch(() => {})
+    },
+    doSubmitAppConfig() {
+      if (this.appConfigId) {
+        var param = {
+          configId: this.appConfigId,
+          configKey: 'sys.application.config',
+          configValue: this.appConfigContent
+        }
+        updateConfigByKey(param).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess('保存成功,需重启应用后生效')
+          }
+        })
+      } else {
+        var addParam = {
+          configName: '应用配置(YML)',
+          configKey: 'sys.application.config',
+          configValue: this.appConfigContent,
+          configType: 'Y'
+        }
+        addConfig(addParam).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess('保存成功,需重启应用后生效')
+            this.getConfigByKey('sys.application.config')
+          }
+        })
+      }
+    },
+    /** 格式化 YML(简单去除行尾空格) */
+    formatYaml() {
+      if (!this.appConfigContent) return
+      var lines = this.appConfigContent.split('\n')
+      this.appConfigContent = lines.map(function(line) {
+        return line.replace(/\s+$/, '')
+      }).join('\n')
+      this.$message.success('格式化完成')
+    },
+    /** 重新加载配置 */
+    resetAppConfig() {
+      this.getConfigByKey('sys.application.config')
+    },
     formatColumns(){
       console.log(this.form27.pass_columns)
 
@@ -3478,4 +3593,79 @@ export default {
   align-items: flex-end;
   justify-content: flex-end;
 }
+/* 应用配置(YML)容器 - 类似 nacos 配置管理 */
+.app-config-container {
+  padding: 16px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+}
+.app-config-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background-color: #fff;
+  border: 1px solid #e4e7ed;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+  padding: 12px 16px;
+}
+.app-config-title {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+}
+.app-config-title i {
+  color: #409eff;
+  margin-right: 8px;
+  font-size: 16px;
+}
+.app-config-tag {
+  margin-left: 8px;
+}
+.app-config-toolbar {
+  display: flex;
+  gap: 8px;
+}
+.app-config-alert {
+  margin: 10px 0;
+  border-radius: 0;
+}
+.yaml-editor-wrapper {
+  background-color: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 0 0 4px 4px;
+  overflow: hidden;
+}
+.yaml-editor >>> .el-textarea__inner {
+  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+  font-size: 13px;
+  line-height: 1.6;
+  background-color: #fafafa;
+  color: #2c3e50;
+  border: none;
+  border-radius: 0;
+  tab-size: 2;
+  padding: 12px 16px;
+}
+.yaml-editor >>> .el-textarea__inner:focus {
+  border: none;
+  box-shadow: none;
+  background-color: #fff;
+}
+.yaml-editor-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+  padding: 8px 16px;
+  background-color: #f5f7fa;
+  border-top: 1px solid #e4e7ed;
+  font-size: 12px;
+  color: #909399;
+}
+.yaml-line-info,
+.yaml-char-info {
+  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+}
 </style>

+ 174 - 13
src/views/system/pointsRule/index.vue

@@ -267,7 +267,36 @@
         </div>
       </el-form>
 
-      <!-- 5. 积分计算示例 -->
+      <!-- 5. ERP信息设置 -->
+      <div class="section-block section-erp">
+        <div class="section-header">
+          <span class="section-icon erp"><i class="el-icon-office-building"></i></span>
+          <span class="section-label">ERP信息设置</span>
+          <span class="section-desc">聚水潭退件相关配置</span>
+        </div>
+        <div class="section-body">
+          <el-form ref="erpForm" :model="erpForm" :rules="erpRules" label-width="150px" size="small">
+            <el-form-item label="仓库编码" prop="warehouseCode">
+              <el-input v-model="erpForm.warehouseCode" placeholder="请输入仓库编码" class="input-number-wide" />
+              <span class="tip-box tip-info">
+                <i class="el-icon-info"></i>
+                聚水潭仓库编码,如:18849902
+              </span>
+            </el-form-item>
+            <el-form-item label="退件收货人" prop="returnConsignee">
+              <el-input v-model="erpForm.returnConsignee" placeholder="请输入退件收货人" class="input-number-wide" />
+            </el-form-item>
+            <el-form-item label="退件收货人手机号" prop="returnPhone">
+              <el-input v-model="erpForm.returnPhone" placeholder="请输入退件收货人手机号" class="input-number-wide" maxlength="11" />
+            </el-form-item>
+            <el-form-item label="退件收货地址" prop="returnAddress">
+              <el-input v-model="erpForm.returnAddress" placeholder="请输入退件收货地址" class="select-wide" />
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+
+      <!-- 6. 积分计算示例 -->
       <div class="section-block section-preview">
         <div class="section-header">
           <span class="section-icon preview"><i class="el-icon-cpu"></i></span>
@@ -378,6 +407,29 @@ export default {
         amount: 100,
         integral: 500
       },
+      // ERP信息设置
+      erpConfigId: null,
+      erpForm: {
+        warehouseCode: '',
+        returnConsignee: '',
+        returnPhone: '',
+        returnAddress: ''
+      },
+      erpRules: {
+        warehouseCode: [
+          { required: true, message: '请输入仓库编码', trigger: 'blur' }
+        ],
+        returnConsignee: [
+          { required: true, message: '请输入退件收货人', trigger: 'blur' }
+        ],
+        returnPhone: [
+          { required: true, message: '请输入退件收货人手机号', trigger: 'blur' },
+          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
+        ],
+        returnAddress: [
+          { required: true, message: '请输入退件收货地址', trigger: 'blur' }
+        ]
+      },
       form: {
         rate: 1.00,
         payRate: 100,
@@ -508,6 +560,7 @@ export default {
   mounted() {
     this.loadCategoryDict()
     this.loadConfig()
+    this.loadErpConfig()
   },
   methods: {
     loadCategoryDict() {
@@ -552,6 +605,63 @@ export default {
       })
     },
 
+    // 加载 ERP 信息配置
+    loadErpConfig() {
+      getConfigByKey('sys.erp.returnInfo').then(response => {
+        if (response.data && response.data.configValue) {
+          const configValue = response.data.configValue
+          let config = typeof configValue === 'string' ? JSON.parse(configValue) : configValue
+          this.erpConfigId = response.data.configId
+          this.erpForm = {
+            warehouseCode: config.warehouseCode || '',
+            returnConsignee: config.returnConsignee || '',
+            returnPhone: config.returnPhone || '',
+            returnAddress: config.returnAddress || ''
+          }
+        } else {
+          this.erpConfigId = response.data ? response.data.configId : null
+        }
+      }).catch(() => {})
+    },
+
+    // 保存 ERP 信息配置
+    submitErpForm() {
+      this.$refs.erpForm.validate(valid => {
+        if (!valid) return
+        const configValue = JSON.stringify(this.erpForm)
+        let request
+        if (this.erpConfigId) {
+          request = updateConfigByKey({
+            configId: this.erpConfigId,
+            configKey: 'sys.erp.returnInfo',
+            configValue
+          })
+        } else {
+          request = addConfig({
+            configName: 'ERP退件信息配置',
+            configKey: 'sys.erp.returnInfo',
+            configValue,
+            configType: 'Y'
+          })
+        }
+        request.then(response => {
+          if (response.code === 200) {
+            if (!this.erpConfigId) {
+              this.erpConfigId = response.data ? response.data.configId : null
+            }
+            this.$message.success('ERP信息配置保存成功')
+            this.loadErpConfig()
+          }
+        })
+      })
+    },
+
+    // 重置 ERP 信息表单
+    resetErpForm() {
+      this.$refs.erpForm.clearValidate()
+      this.loadErpConfig()
+    },
+
     loadSelectedProducts(ids) {
       listStoreProduct({ pageSize: 999 }).then(response => {
         const rows = response.data || response.rows || []
@@ -589,33 +699,55 @@ export default {
     },
 
     handleSubmit() {
-      this.$refs.form.validate(valid => {
-        if (!valid) return
+      // 同时校验积分规则表单和ERP信息表单
+      Promise.all([this.validateForm(), this.validateErpForm()]).then(results => {
+        if (!results[0] || !results[1]) return
         this.submitLoading = true
         const configValue = JSON.stringify(this.form)
-        let request
+        const erpConfigValue = JSON.stringify(this.erpForm)
+        const tasks = []
+        // 积分规则保存
         if (this.configId) {
-          request = updateConfigByKey({
+          tasks.push(updateConfigByKey({
             configId: this.configId,
             configKey: 'points.grantRule',
             configValue
-          })
+          }))
         } else {
-          request = addConfig({
+          tasks.push(addConfig({
             configName: '发放积分规则配置',
             configKey: 'points.grantRule',
             configValue,
             configType: 'Y'
-          })
+          }))
         }
-        request.then(response => {
-          if (response.code === 200) {
+        // ERP信息保存
+        if (this.erpConfigId) {
+          tasks.push(updateConfigByKey({
+            configId: this.erpConfigId,
+            configKey: 'sys.erp.returnInfo',
+            configValue: erpConfigValue
+          }))
+        } else {
+          tasks.push(addConfig({
+            configName: 'ERP退件信息配置',
+            configKey: 'sys.erp.returnInfo',
+            configValue: erpConfigValue,
+            configType: 'Y'
+          }))
+        }
+        Promise.all(tasks).then(responses => {
+          const allSuccess = responses.every(r => r.code === 200)
+          if (allSuccess) {
             if (!this.configId) {
-              this.configId = response.data ? response.data.configId : null
+              this.configId = responses[0].data ? responses[0].data.configId : null
             }
-            this.$message.success('积分规则配置保存成功')
-            // 保存成功后重新加载以刷新更新时间/更新人
+            if (!this.erpConfigId) {
+              this.erpConfigId = responses[1].data ? responses[1].data.configId : null
+            }
+            this.$message.success('配置保存成功')
             this.loadConfig()
+            this.loadErpConfig()
           }
           this.submitLoading = false
         }).catch(() => {
@@ -624,6 +756,18 @@ export default {
       })
     },
 
+    // Promise 包装的表单校验
+    validateForm() {
+      return new Promise(resolve => {
+        this.$refs.form.validate(valid => resolve(valid))
+      })
+    },
+    validateErpForm() {
+      return new Promise(resolve => {
+        this.$refs.erpForm.validate(valid => resolve(valid))
+      })
+    },
+
     handleReset() {
       this.$confirm('确认重置为默认值?已保存的配置将丢失。', '提示', {
         confirmButtonText: '确定',
@@ -645,9 +789,16 @@ export default {
           deductEnabled: 1,
           enabled: 1
         }
+        this.erpForm = {
+          warehouseCode: '',
+          returnConsignee: '',
+          returnPhone: '',
+          returnAddress: ''
+        }
         this.categoryOptions = []
         this.productOptions = []
         this.$refs.form.clearValidate()
+        this.$refs.erpForm.clearValidate()
       }).catch(() => {})
     }
   }
@@ -763,6 +914,7 @@ export default {
 .section-icon.deduct  { background: linear-gradient(135deg, #E6A23C, #cf9236); }
 .section-icon.rules   { background: linear-gradient(135deg, #9B59B6, #7d3c98); }
 .section-icon.preview { background: linear-gradient(135deg, #00B8A9, #00857a); }
+.section-icon.erp     { background: linear-gradient(135deg, #F56C6C, #c45656); }
 
 .section-label {
   font-size: 14px;
@@ -856,11 +1008,20 @@ export default {
   color: #909399;
   border: 1px solid #e4e7ed;
 }
+.tip-info {
+  background: #f0f9eb;
+  color: #909399;
+  border: 1px solid #e4e7ed;
+}
 
 /* ===== 积分计算示例 ===== */
 .section-preview .section-header {
   background: linear-gradient(135deg, #f0fffe 0%, #e6faf8 100%);
 }
+/* ===== ERP信息设置 ===== */
+.section-erp .section-header {
+  background: linear-gradient(135deg, #fef0f0 0%, #fde6e6 100%);
+}
 .preview-body {
   padding: 20px 20px 16px;
 }