yjwang 18 hodín pred
rodič
commit
914cbfe33d

+ 20 - 10
src/views/his/doctor/type2.vue

@@ -650,7 +650,7 @@ export default {
         idCard: null,
         idCardFrontUrl: null,
         idCardBackUrl: null,
-        cityIds: null,
+        cityIds: [],
         province: null,
         city: null,
         district: null,
@@ -760,11 +760,13 @@ export default {
     },
 
     handleCityChange(value) {
-      var nodes=this.$refs.citySelect.getCheckedNodes();
-      this.form.cityIds=value.toString();
-      this.form.province=nodes[0].pathLabels[0];
-      this.form.city=nodes[0].pathLabels[1];
-      this.form.district=nodes[0].pathLabels[2];
+      if (!value || !this.$refs.citySelect) return;
+      var nodes = this.$refs.citySelect.getCheckedNodes();
+      if (!nodes || nodes.length === 0) return;
+      this.form.cityIds = value.toString();
+      this.form.province = nodes[0].pathLabels[0];
+      this.form.city = nodes[0].pathLabels[1];
+      this.form.district = nodes[0].pathLabels[2];
     },
 
     handledetai(row){
@@ -800,7 +802,11 @@ export default {
         if(this.form.isAudit!=null){
           this.form.isAudit = String(this.form.isAudit)
         }
-        this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+        if(this.form.cityIds!=null){
+          this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+        } else {
+          this.form.cityIds = []
+        }
       });
     },
 
@@ -1049,7 +1055,11 @@ export default {
        if(this.form.isAudit!=null){
          this.form.isAudit = String(this.form.isAudit)
        }
-      this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+      if(this.form.cityIds!=null){
+        this.form.cityIds=((this.form.cityIds).split(",")).map(Number)
+      } else {
+        this.form.cityIds = []
+      }
       });
       this.checkDetails=true;
     },
@@ -1077,8 +1087,8 @@ export default {
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
-                  this.form.cityIds=(this.form.cityIds).toString()
-                  this.form.auditType=(this.form.auditType).toString()
+                  this.form.cityIds = (this.form.cityIds || []).toString()
+                  this.form.auditType = (this.form.auditType || '').toString()
           if (this.form.doctorId != null) {
             updateDoctor(this.form).then(response => {
               this.msgSuccess("修改成功");

+ 7 - 1
src/views/system/config/config.vue

@@ -1366,6 +1366,11 @@
               <el-input v-model="form18.authDomainName"></el-input>
             </el-tooltip>
           </el-form-item>
+          <el-form-item label="项目注册链接域名">
+            <el-tooltip class="item" effect="dark" content="IM批量发送项目注册链接时的域名" placement="top-end">
+              <el-input v-model="form18.registerDomainName"></el-input>
+            </el-tooltip>
+          </el-form-item>
           <el-form-item label="公众号appId">
             <el-tooltip class="item" effect="dark" content="授权公众号appId" placement="top-end">
               <el-input v-model="form18.mpAppId"></el-input>
@@ -2477,7 +2482,8 @@ export default {
       form16: {},
       form17: {},
       form18: {
-        viewCommentNum: 200
+        viewCommentNum: 200,
+        registerDomainName: ''
       },
       form19: {},
       form20: {

+ 471 - 0
src/views/system/pointsRule/index.vue

@@ -0,0 +1,471 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never">
+      <div slot="header" class="clearfix">
+        <span class="card-title">
+          <i class="el-icon-s-cooperation"></i>
+          发放积分规则配置
+        </span>
+        <span style="font-size: 13px; color: #909399; margin-left: 12px;">配置消费送积分体系规则</span>
+      </div>
+
+      <el-form ref="form" :model="form" :rules="rules" label-width="160px" size="small">
+        <!-- 1. 积分比例 -->
+        <el-divider content-position="left">
+          <i class="el-icon-coin"></i> 积分比例配置
+        </el-divider>
+        <el-form-item label="积分发放比例" prop="rate">
+          <el-col :span="6">
+            <el-input-number
+              v-model="form.rate"
+              :precision="2"
+              :min="0.01"
+              :max="9999"
+              :step="0.1"
+              style="width: 100%;"
+            />
+          </el-col>
+          <span class="form-tip">1元 = {{ form.rate || 0 }} 积分</span>
+        </el-form-item>
+
+        <!-- 2. 参与范围 -->
+        <el-divider content-position="left">
+          <i class="el-icon-s-grid"></i> 参与范围限定
+        </el-divider>
+        <el-form-item label="参与范围" prop="scopeType">
+          <el-radio-group v-model="form.scopeType">
+            <el-radio :label="1">全场商品参与送积分</el-radio>
+            <el-radio :label="2">指定商品分类不送积分</el-radio>
+            <el-radio :label="3">指定单个商品不送积分</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <!-- 指定不送积分的商品分类 -->
+        <el-form-item v-if="form.scopeType === 2" label="不送积分分类" prop="excludeCategoryIds">
+          <el-select
+            v-model="form.excludeCategoryIds"
+            multiple
+            filterable
+            placeholder="请选择商品分类"
+            style="width: 480px;"
+          >
+            <el-option
+              v-for="item in categoryOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="Number(item.dictValue)"
+            />
+          </el-select>
+          <div style="margin-top: 4px; color: #909399; font-size: 12px;">
+            已选 {{ (form.excludeCategoryIds || []).length }} 个分类,这些分类下的商品不参与送积分
+          </div>
+        </el-form-item>
+
+        <!-- 指定不送积分的单个商品 -->
+        <el-form-item v-if="form.scopeType === 3" label="不送积分商品" prop="excludeProductIds">
+          <el-select
+            v-model="form.excludeProductIds"
+            multiple
+            filterable
+            placeholder="请选择商品(默认展示10条,可搜索)"
+            :loading="productLoading"
+            style="width: 560px;"
+            @visible-change="onProductSelectVisible"
+          >
+            <el-option
+              v-for="item in productOptions"
+              :key="item.productId"
+              :label="item.displayName"
+              :value="item.productId"
+            >
+              <div class="product-option">
+                <img v-if="item.image" :src="item.image" class="product-option-img" />
+                <span v-else class="product-option-img product-option-img-placeholder"></span>
+                <span class="product-option-name">{{ item.displayName }}</span>
+                <span v-if="item.storeName" class="product-option-store">{{ item.storeName }}</span>
+                <span class="product-option-price">¥{{ item.price }}</span>
+              </div>
+            </el-option>
+          </el-select>
+          <div style="margin-top: 4px; color: #909399; font-size: 12px;">
+            已选 {{ (form.excludeProductIds || []).length }} 个商品,这些商品不参与送积分
+          </div>
+        </el-form-item>
+
+        <!-- 3. 特殊规则 -->
+        <el-divider content-position="left">
+          <i class="el-icon-setting"></i> 特殊规则设置
+        </el-divider>
+        <el-form-item label="退款扣回积分" prop="refundDeduct">
+          <el-radio-group v-model="form.refundDeduct">
+            <el-radio :label="1">是,退款时自动扣回</el-radio>
+            <el-radio :label="0">否,退款不扣回</el-radio>
+          </el-radio-group>
+          <div class="form-tip">开启后,发生退款时将按实退金额比例自动扣回已发放积分</div>
+        </el-form-item>
+
+        <el-form-item label="每日积分上限" prop="dailyLimit">
+          <el-col :span="4">
+            <el-input-number
+              v-model="form.dailyLimit"
+              :min="0"
+              :max="999999"
+              :step="100"
+              style="width: 100%;"
+            />
+          </el-col>
+          <span class="form-tip">0 = 不限制,超出上限自动不再发放</span>
+        </el-form-item>
+
+        <el-form-item label="每月积分上限" prop="monthlyLimit">
+          <el-col :span="4">
+            <el-input-number
+              v-model="form.monthlyLimit"
+              :min="0"
+              :max="999999"
+              :step="100"
+              style="width: 100%;"
+            />
+          </el-col>
+          <span class="form-tip">0 = 不限制,超出上限自动不再发放</span>
+        </el-form-item>
+
+        <el-form-item label="最高抵扣积分" prop="maxDeduct">
+          <el-col :span="4">
+            <el-input-number
+              v-model="form.maxDeduct"
+              :min="0"
+              :max="999999"
+              :step="10"
+              style="width: 100%;"
+            />
+          </el-col>
+          <span class="form-tip">0 = 不限制,用户每笔订单最多可用积分抵扣的数量</span>
+        </el-form-item>
+
+        <el-form-item label="启用状态" prop="enabled">
+          <el-radio-group v-model="form.enabled">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+
+      <div style="text-align: center; margin-top: 24px; padding-top: 20px; border-top: 1px solid #EBEEF5;">
+        <el-button type="primary" :loading="submitLoading" @click="handleSubmit" icon="el-icon-check">
+          保 存 配 置
+        </el-button>
+        <el-button @click="handleReset" icon="el-icon-refresh">重 置</el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { getConfigByKey, updateConfigByKey, addConfig } from '@/api/system/config'
+import { listStoreProduct } from '@/api/hisStore/storeProduct'
+
+export default {
+  name: 'PointsRule',
+  data() {
+    const validateRate = (rule, value, callback) => {
+      if (value === null || value === undefined || value <= 0) {
+        callback(new Error('积分比例必须大于0'))
+      } else {
+        callback()
+      }
+    }
+    const validateExcludeCategoryIds = (rule, value, callback) => {
+      if (this.form.scopeType === 2 && (!value || value.length === 0)) {
+        callback(new Error('请选择至少一个商品分类'))
+      } else {
+        callback()
+      }
+    }
+    const validateExcludeProductIds = (rule, value, callback) => {
+      if (this.form.scopeType === 3 && (!value || value.length === 0)) {
+        callback(new Error('请选择至少一个商品'))
+      } else {
+        callback()
+      }
+    }
+    return {
+      configId: null,
+      submitLoading: false,
+      productLoading: false,
+      categoryOptions: [],
+      productOptions: [],
+      form: {
+        rate: 1.00,
+        scopeType: 1,
+        excludeCategoryIds: [],
+        excludeProductIds: [],
+        refundDeduct: 1,
+        dailyLimit: 0,
+        monthlyLimit: 0,
+        maxDeduct: 0,
+        enabled: 1
+      },
+      rules: {
+        rate: [
+          { required: true, message: '请输入积分发放比例', trigger: 'blur' },
+          { validator: validateRate, trigger: 'blur' }
+        ],
+        scopeType: [
+          { required: true, message: '请选择参与范围', trigger: 'change' }
+        ],
+        excludeCategoryIds: [
+          { validator: validateExcludeCategoryIds, trigger: 'change' }
+        ],
+        excludeProductIds: [
+          { validator: validateExcludeProductIds, trigger: 'change' }
+        ],
+        refundDeduct: [
+          { required: true, message: '请选择退款是否扣回积分', trigger: 'change' }
+        ],
+        dailyLimit: [
+          { required: true, message: '请输入每日积分上限', trigger: 'blur' }
+        ],
+        monthlyLimit: [
+          { required: true, message: '请输入每月积分上限', trigger: 'blur' }
+        ],
+        maxDeduct: [
+          { required: true, message: '请输入最高抵扣积分', trigger: 'blur' }
+        ],
+        enabled: [
+          { required: true, message: '请选择启用状态', trigger: 'change' }
+        ]
+      }
+    }
+  },
+  mounted() {
+    this.loadCategoryDict()
+    this.loadConfig()
+  },
+  methods: {
+    /** 加载商品分类字典 */
+    loadCategoryDict() {
+      this.getDicts('store_product_type').then(response => {
+        this.categoryOptions = response.data || []
+      })
+    },
+    /** 加载配置 */
+    loadConfig() {
+      getConfigByKey('points.grantRule').then(response => {
+        if (response.data && response.data.configValue) {
+          const configValue = response.data.configValue
+          let config = typeof configValue === 'string' ? JSON.parse(configValue) : configValue
+          this.configId = response.data.configId
+          // 合并已保存的值,保留默认值兜底
+          this.form = {
+            rate: config.rate ?? 1.00,
+            scopeType: config.scopeType ?? 1,
+            excludeCategoryIds: config.excludeCategoryIds || [],
+            excludeProductIds: config.excludeProductIds || [],
+            refundDeduct: config.refundDeduct ?? 1,
+            dailyLimit: config.dailyLimit ?? 0,
+            monthlyLimit: config.monthlyLimit ?? 0,
+            maxDeduct: config.maxDeduct ?? 0,
+            enabled: config.enabled ?? 1
+          }
+          // 回填已选商品 options
+          if (config.excludeProductIds && config.excludeProductIds.length > 0) {
+            this.loadSelectedProducts(config.excludeProductIds)
+          }
+        } else {
+          this.configId = response.data ? response.data.configId : null
+        }
+      }).catch(() => {
+        this.$message.warning('配置加载失败,将使用默认值')
+      })
+    },
+
+    /** 回填已选商品名称 */
+    loadSelectedProducts(ids) {
+      listStoreProduct({ pageSize: 999 }).then(response => {
+        const rows = response.data || response.rows || []
+        this.productOptions = rows.map(item => this.buildProductOption(item))
+      }).catch(() => {})
+    },
+
+    /** 构建商品选项对象 */
+    buildProductOption(item) {
+      const name = item.productName && item.productName !== '-' ? item.productName : (item.commonName || '')
+      return {
+        productId: item.productId || item.id,
+        productName: name,
+        displayName: name,
+        image: item.image || '',
+        price: item.price != null ? Number(item.price).toFixed(2) : '0.00',
+        storeName: item.storeName || ''
+      }
+    },
+
+    /** 加载商品分类选项(默认10条) */
+    loadCategoryOptions() {
+      this.categoryLoading = true
+      listCategory({ pageSize: 10 }).then(response => {
+        const rows = response.data || response.rows || []
+        this.categoryOptions = rows.map(item => ({
+          categoryId: item.categoryId,
+          categoryName: item.categoryName || item.name
+        }))
+        this.categoryLoading = false
+      }).catch(() => {
+        this.categoryLoading = false
+      })
+    },
+
+    /** 加载商品选项(默认10条) */
+    loadProductOptions() {
+      this.productLoading = true
+      listStoreProduct({ pageSize: 10 }).then(response => {
+        const rows = response.data || response.rows || []
+        this.productOptions = rows.map(item => this.buildProductOption(item))
+        this.productLoading = false
+      }).catch(() => {
+        this.productLoading = false
+      })
+    },
+
+    /** 分类下拉展开时加载数据 */
+    onCategorySelectVisible(visible) {
+      if (visible && this.categoryOptions.length === 0) {
+        this.loadCategoryOptions()
+      }
+    },
+
+    /** 商品下拉展开时加载数据 */
+    onProductSelectVisible(visible) {
+      if (visible && this.productOptions.length === 0) {
+        this.loadProductOptions()
+      }
+    },
+
+    /** 提交保存 */
+    handleSubmit() {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        this.submitLoading = true
+        const configValue = JSON.stringify(this.form)
+        // configId 不存在则新增,存在则更新
+        let request
+        if (this.configId) {
+          request = updateConfigByKey({
+            configId: this.configId,
+            configKey: 'points.grantRule',
+            configValue
+          })
+        } else {
+          request = addConfig({
+            configName: '发放积分规则配置',
+            configKey: 'points.grantRule',
+            configValue,
+            configType: 'Y'
+          })
+        }
+        request.then(response => {
+          if (response.code === 200) {
+            if (!this.configId) {
+              this.configId = response.data ? response.data.configId : null
+            }
+            this.$message.success('积分规则配置保存成功')
+          }
+          this.submitLoading = false
+        }).catch(() => {
+          this.submitLoading = false
+        })
+      })
+    },
+
+    /** 重置为默认值 */
+    handleReset() {
+      this.$confirm('确认重置为默认值?已保存的配置将丢失。', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.form = {
+          rate: 1.00,
+          scopeType: 1,
+          excludeCategoryIds: [],
+          excludeProductIds: [],
+          refundDeduct: 1,
+          dailyLimit: 0,
+          monthlyLimit: 0,
+          maxDeduct: 0,
+          enabled: 1
+        }
+        this.categoryOptions = []
+        this.productOptions = []
+        this.$refs.form.clearValidate()
+      }).catch(() => {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+.card-title i {
+  margin-right: 6px;
+  color: #409EFF;
+}
+.form-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-left: 12px;
+}
+.el-divider i {
+  margin-right: 6px;
+}
+
+/* 商品选项样式 */
+.product-option {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 2px 0;
+}
+.product-option-img {
+  width: 36px;
+  height: 36px;
+  border-radius: 4px;
+  object-fit: cover;
+  flex-shrink: 0;
+}
+.product-option-img-placeholder {
+  background: #f0f2f5;
+  border: 1px dashed #dcdfe6;
+}
+.product-option-name {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 13px;
+  color: #303133;
+}
+.product-option-price {
+  flex-shrink: 0;
+  color: #E6A23C;
+  font-size: 12px;
+  font-weight: 500;
+}
+.product-option-store {
+  flex-shrink: 0;
+  color: #909399;
+  font-size: 11px;
+  background: #f0f2f5;
+  padding: 1px 6px;
+  border-radius: 3px;
+  max-width: 100px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>