xw há 6 dias atrás
pai
commit
227c270a23

+ 17 - 0
src/api/live/liveGoods.js

@@ -94,3 +94,20 @@ export function handleIsShowChange(data) {
     data: data
   })
 }
+
+// 查询直播低库存提示阈值
+export function getStockHintThreshold() {
+  return request({
+    url: '/live/liveGoods/stockHintThreshold',
+    method: 'get'
+  })
+}
+
+// 更新直播低库存提示阈值
+export function updateStockHintThreshold(threshold) {
+  return request({
+    url: '/live/liveGoods/stockHintThreshold',
+    method: 'post',
+    params: { threshold }
+  })
+}

+ 172 - 88
src/views/live/comment/globalConfig.vue

@@ -16,98 +16,124 @@
         :rules="rules"
         label-width="160px"
       >
-        <el-divider content-position="left">飘屏</el-divider>
-        <el-form-item label="飘屏总开关" prop="floatEnabled">
-          <el-switch
-            v-model="form.floatEnabled"
-            :active-value="1"
-            :inactive-value="0"
-          />
-        </el-form-item>
-        <el-form-item label="用户飘屏冷却" prop="floatCooldownSec">
-          <el-input-number
-            v-model="form.floatCooldownSec"
-            :min="0"
-            :precision="0"
-            controls-position="right"
-            placeholder="秒"
-          />
-          <span class="form-tip">同一用户连续飘屏的最小间隔(秒)</span>
-        </el-form-item>
-        <el-form-item label="可飘屏角色" prop="floatRoleCodes">
-          <el-select
-            v-model="floatRoleList"
-            multiple
-            filterable
-            placeholder="请选择角色"
-            style="width: 420px"
-            :loading="rolesLoading"
-            @visible-change="ensureRolesLoaded"
-          >
-            <el-option
-              v-for="r in roleOptions"
-              :key="r"
-              :label="r"
-              :value="r"
+        <div class="feature-section section-threshold">
+          <el-divider content-position="left">库存提示阈值</el-divider>
+          <el-form-item label="阈值配置">
+            <el-input
+              v-model.trim="thresholdInput"
+              placeholder="请输入1~9999"
+              style="width: 220px"
+              @keyup.enter.native="saveStockThreshold"
             />
-          </el-select>
-          <span class="form-tip">选项来自企业角色;保存为英文逗号分隔的 role_name</span>
-        </el-form-item>
+            <el-button
+              type="primary"
+              size="mini"
+              :loading="thresholdSaving"
+              style="margin-left: 12px"
+              @click="saveStockThreshold"
+            >保存</el-button>
+            <span class="form-tip">当库存小于等于该阈值时,App端显示“仅剩X件”。</span>
+          </el-form-item>
+        </div>
 
-        <el-divider content-position="left">评论置顶</el-divider>
-        <el-form-item label="单房间最大置顶数" prop="pinMaxPerRoom">
-          <el-input-number
-            v-model="form.pinMaxPerRoom"
-            :min="1"
-            :precision="0"
-            controls-position="right"
-          />
-        </el-form-item>
-        <el-form-item label="可选置顶时长" prop="pinDurationOptions">
-          <el-select
-            v-model="pinDurationList"
-            multiple
-            placeholder="分钟;-1 表示永久"
-            style="width: 420px"
-          >
-            <el-option label="5 分钟" :value="5" />
-            <el-option label="10 分钟" :value="10" />
-            <el-option label="15 分钟" :value="15" />
-            <el-option label="30 分钟" :value="30" />
-            <el-option label="60 分钟" :value="60" />
-            <el-option label="永久 (-1)" :value="-1" />
-          </el-select>
-          <span class="form-tip">保存为逗号分隔数字,如 5,10,30,-1</span>
-        </el-form-item>
-        <el-form-item label="可置顶角色" prop="pinRoleCodes">
-          <el-select
-            v-model="pinRoleList"
-            multiple
-            filterable
-            placeholder="请选择角色"
-            style="width: 420px"
-            :loading="rolesLoading"
-            @visible-change="ensureRolesLoaded"
-          >
-            <el-option
-              v-for="r in roleOptions"
-              :key="r"
-              :label="r"
-              :value="r"
+        <div class="feature-section section-float">
+          <el-divider content-position="left">飘屏</el-divider>
+          <el-form-item label="飘屏总开关" prop="floatEnabled">
+            <el-switch
+              v-model="form.floatEnabled"
+              :active-value="1"
+              :inactive-value="0"
             />
-          </el-select>
-        </el-form-item>
+          </el-form-item>
+          <el-form-item label="用户飘屏冷却" prop="floatCooldownSec">
+            <el-input-number
+              v-model="form.floatCooldownSec"
+              :min="0"
+              :precision="0"
+              controls-position="right"
+              placeholder="秒"
+            />
+            <span class="form-tip">同一用户连续飘屏的最小间隔(秒)</span>
+          </el-form-item>
+          <el-form-item label="可飘屏角色" prop="floatRoleCodes">
+            <el-select
+              v-model="floatRoleList"
+              multiple
+              filterable
+              placeholder="请选择角色"
+              style="width: 420px"
+              :loading="rolesLoading"
+              @visible-change="ensureRolesLoaded"
+            >
+              <el-option
+                v-for="r in roleOptions"
+                :key="r"
+                :label="r"
+                :value="r"
+              />
+            </el-select>
+            <span class="form-tip">选项来自企业角色;保存为英文逗号分隔的 role_name</span>
+          </el-form-item>
+        </div>
 
-        <el-divider content-position="left">其他</el-divider>
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注" />
-        </el-form-item>
-        <el-form-item label="最近更新人">
-          <span>{{ form.updateBy || '—' }}</span>
-        </el-form-item>
-        <el-form-item label="最近更新时间">
-          <span>{{ form.updateTime ? parseTime(form.updateTime) : '—' }}</span>
-        </el-form-item>
+        <div class="feature-section section-pin">
+          <el-divider content-position="left">评论置顶</el-divider>
+          <el-form-item label="单房间最大置顶数" prop="pinMaxPerRoom">
+            <el-input-number
+              v-model="form.pinMaxPerRoom"
+              :min="1"
+              :precision="0"
+              controls-position="right"
+            />
+          </el-form-item>
+          <el-form-item label="可选置顶时长" prop="pinDurationOptions">
+            <el-select
+              v-model="pinDurationList"
+              multiple
+              placeholder="分钟;-1 表示永久"
+              style="width: 420px"
+            >
+              <el-option label="5 分钟" :value="5" />
+              <el-option label="10 分钟" :value="10" />
+              <el-option label="15 分钟" :value="15" />
+              <el-option label="30 分钟" :value="30" />
+              <el-option label="60 分钟" :value="60" />
+              <el-option label="永久 (-1)" :value="-1" />
+            </el-select>
+            <span class="form-tip">保存为逗号分隔数字,如 5,10,30,-1</span>
+          </el-form-item>
+          <el-form-item label="可置顶角色" prop="pinRoleCodes">
+            <el-select
+              v-model="pinRoleList"
+              multiple
+              filterable
+              placeholder="请选择角色"
+              style="width: 420px"
+              :loading="rolesLoading"
+              @visible-change="ensureRolesLoaded"
+            >
+              <el-option
+                v-for="r in roleOptions"
+                :key="r"
+                :label="r"
+                :value="r"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <div class="feature-section section-other">
+          <el-divider content-position="left">其他</el-divider>
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注" />
+          </el-form-item>
+          <el-form-item label="最近更新人">
+            <span>{{ form.updateBy || '—' }}</span>
+          </el-form-item>
+          <el-form-item label="最近更新时间">
+            <span>{{ form.updateTime ? parseTime(form.updateTime) : '—' }}</span>
+          </el-form-item>
+        </div>
         <el-form-item>
           <el-button
             type="primary"
@@ -128,6 +154,7 @@ import {
   updateCommentFeatureConfig,
   getCommentFeatureRoles
 } from '@/api/live/commentFeature'
+import { getStockHintThreshold, updateStockHintThreshold } from '@/api/live/liveGoods'
 
 const splitCodes = (s) => {
   if (s == null || String(s).trim() === '') return []
@@ -145,6 +172,8 @@ export default {
     return {
       loading: false,
       saving: false,
+      thresholdSaving: false,
+      thresholdInput: '',
       /** 下拉候选项(GET /live/commentFeature/roles),并与已选值合并以便回显 */
       roleOptions: [],
       rolesLoaded: false,
@@ -172,8 +201,40 @@ export default {
   },
   created() {
     this.loadConfig()
+    this.loadStockThreshold()
   },
   methods: {
+    isValidThreshold(value) {
+      return /^[1-9]\d{0,3}$/.test(String(value))
+    },
+    loadStockThreshold() {
+      getStockHintThreshold()
+        .then((response) => {
+          const threshold = response && response.threshold
+          this.thresholdInput = threshold != null ? String(threshold) : ''
+        })
+        .catch((error) => {
+          this.$message.error((error && error.msg) || (error && error.message) || '阈值加载失败')
+        })
+    },
+    saveStockThreshold() {
+      if (!this.isValidThreshold(this.thresholdInput)) {
+        this.$message.error('阈值范围需在1~9999')
+        return
+      }
+      this.thresholdSaving = true
+      updateStockHintThreshold(Number(this.thresholdInput))
+        .then(() => {
+          this.msgSuccess('保存成功')
+          this.loadStockThreshold()
+        })
+        .catch((error) => {
+          this.$message.error((error && error.msg) || (error && error.message) || '保存失败')
+        })
+        .finally(() => {
+          this.thresholdSaving = false
+        })
+    },
     /** 将已选 role_name 合并进 options,避免接口未返回历史已选项时标签不显示 */
     mergeSelectedIntoRoleOptions() {
       const set = new Set(Array.isArray(this.roleOptions) ? this.roleOptions : [])
@@ -278,4 +339,27 @@ export default {
   color: #909399;
   font-size: 12px;
 }
+.feature-section {
+  padding: 10px 12px 4px;
+  margin-bottom: 14px;
+  border-radius: 6px;
+  border: 1px solid #ebeef5;
+  background: #fafafa;
+}
+.section-threshold {
+  border-left: 4px solid #e6a23c;
+  background: #fffaf2;
+}
+.section-float {
+  border-left: 4px solid #409eff;
+  background: #f4f8ff;
+}
+.section-pin {
+  border-left: 4px solid #67c23a;
+  background: #f6fcf2;
+}
+.section-other {
+  border-left: 4px solid #909399;
+  background: #f8f8f9;
+}
 </style>

+ 1 - 1
src/views/live/comment/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-tabs v-model="activeName" type="card" @tab-click="handleTabClick">
-      <el-tab-pane label="评论飘屏全局配置" name="global">
+      <el-tab-pane label="直播运营设置" name="global">
         <GlobalConfig v-if="activeName === 'global'" />
       </el-tab-pane>
       <el-tab-pane label="飘屏发送记录" name="floatMsgLog">

+ 152 - 12
src/views/system/config/integralConfig.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form ref="form11" :model="form11" label-width="180px">
+    <el-form ref="form11" :model="form11" :rules="rules" label-width="180px">
       <el-row>
         <el-col :span="12">
           <el-form-item  label="注册任务积分" prop="integralRegister">
@@ -16,7 +16,7 @@
             </el-tooltip>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
+        <!-- <el-col :span="12">
           <el-form-item  label="完成专家咨询获得积分" prop="integralFinishConsultation">
             <el-tooltip class="item" effect="dark" content="首次完成专家咨询,赠送多少积分" placement="top-end">
               <el-input-number  v-model="form11.integralFinishConsultation"   ></el-input-number>
@@ -31,7 +31,7 @@
               <el-input-number  v-model="form11.integralAddPatient"   ></el-input-number>
             </el-tooltip>
           </el-form-item>
-        </el-col>
+        </el-col> -->
         <el-col :span="12">
           <el-form-item  label="填写收货地址获得积分" prop="integralAddUserAddress">
             <el-tooltip class="item" effect="dark" content="首次填写收货地址,赠送多少积分" placement="top-end">
@@ -40,6 +40,57 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="首次登录App实物奖励配置">
+            <span class="config-title-tip">首次登录App后赠送实物奖励信息</span>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="商品名称" prop="firstLoginGiftName">
+            <el-input
+              v-model="form11.firstLoginGiftName"
+              maxlength="50"
+              show-word-limit
+              placeholder="请输入商品名称"
+              clearable
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="商品价格" prop="firstLoginGiftPrice">
+            <el-input
+              v-model="form11.firstLoginGiftPrice"
+              maxlength="20"
+              show-word-limit
+              placeholder="请输入商品价格(例如:99 或 99元)"
+              clearable
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="商品图片" prop="firstLoginGiftImage">
+            <div ref="giftImageUploader" class="gift-image-config">
+              <el-input
+                v-model="form11.firstLoginGiftImage"
+                maxlength="500"
+                show-word-limit
+                placeholder="请输入商品图片URL"
+                clearable
+              />
+              <ImageUpload v-model="form11.firstLoginGiftImage" :limit="1" :showMaterialBtn="false" />
+              <el-button type="primary" plain @click="triggerGiftImageUpload">上传图片</el-button>
+            </div>
+            <el-image
+              v-if="form11.firstLoginGiftImage"
+              class="gift-image-preview"
+              :src="form11.firstLoginGiftImage"
+              fit="contain"
+              :preview-src-list="[form11.firstLoginGiftImage]"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
       <el-row>
         <el-col :span="12">
           <el-form-item  label="邀请新用户获得积分" prop="integralInvite">
@@ -191,6 +242,15 @@ export default {
         integralTypeByOneDay: null,
         integralSubscriptCourse: null,
         integralFirstLoginApp: null,
+        firstLoginGiftName: '',
+        firstLoginGiftPrice: '',
+        firstLoginGiftImage: '',
+      },
+      rawIntegralConfig: {},
+      rules: {
+        firstLoginGiftName: [{ max: 50, message: '商品名称最大长度为50', trigger: 'blur' }],
+        firstLoginGiftPrice: [{ max: 20, message: '商品价格最大长度为20', trigger: 'blur' }],
+        firstLoginGiftImage: [{ max: 500, message: '图片URL最大长度为500', trigger: 'blur' }],
       },
       saveLoading: false,
     }
@@ -206,24 +266,104 @@ export default {
       getConfigByKey(key).then(response => {
         this.configId=response.data.configId;
         this.configKey=response.data.configKey;
-        this.form11 =JSON.parse(response.data.configValue);
+        const configObj = response.data.configValue ? JSON.parse(response.data.configValue) : {};
+        this.rawIntegralConfig = configObj;
+        this.form11 = {
+          ...this.form11,
+          ...configObj,
+          firstLoginGiftName: this.getGiftFieldValue(configObj, [
+            'firstLoginGiftName',
+            'integralFirstLoginGiftName',
+            'newcomerGiftName',
+            'giftName'
+          ]),
+          firstLoginGiftPrice: this.getGiftFieldValue(configObj, [
+            'firstLoginGiftPrice',
+            'integralFirstLoginGiftPrice',
+            'newcomerGiftPrice',
+            'giftPrice'
+          ]),
+          firstLoginGiftImage: this.getGiftFieldValue(configObj, [
+            'firstLoginGiftImage',
+            'integralFirstLoginGiftImage',
+            'newcomerGiftImage',
+            'giftImage'
+          ]),
+        };
       });
     },
     submitForm11(){
-      this.saveLoading = true
-      const param={configId:this.configId,configValue:JSON.stringify(this.form11)}
-      updateConfigByKey(param).then(response => {
-        const {code} = response
-        if (code === 200) {
-          this.msgSuccess("修改成功");
+      this.$refs.form11.validate(valid => {
+        if (!valid) {
+          return;
         }
-        this.saveLoading = false
+        this.saveLoading = true
+        const payload = {
+          ...this.rawIntegralConfig,
+          ...this.form11,
+          firstLoginGiftName: this.form11.firstLoginGiftName || '',
+          firstLoginGiftPrice: this.form11.firstLoginGiftPrice || '',
+          firstLoginGiftImage: this.form11.firstLoginGiftImage || '',
+        };
+        const param={configId:this.configId,configValue:JSON.stringify(payload)}
+        updateConfigByKey(param).then(response => {
+          const {code} = response
+          if (code === 200) {
+            this.msgSuccess("修改成功");
+            this.rawIntegralConfig = payload;
+          }
+          this.saveLoading = false
+        }).catch(() => {
+          this.saveLoading = false
+        });
       });
     },
+    getGiftFieldValue(configObj, keys = []) {
+      for (let i = 0; i < keys.length; i++) {
+        const value = configObj[keys[i]];
+        if (value !== undefined && value !== null) {
+          return String(value);
+        }
+      }
+      return '';
+    },
+    triggerGiftImageUpload() {
+      const uploaderWrap = this.$refs.giftImageUploader;
+      if (!uploaderWrap || !uploaderWrap.querySelector) {
+        return;
+      }
+      const uploadButton = uploaderWrap.querySelector('.el-upload--picture-card');
+      if (uploadButton) {
+        uploadButton.click();
+      }
+    },
   }
 }
 </script>
 
 <style scoped>
-
+.config-title-tip {
+  color: #909399;
+}
+.gift-image-config {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+.gift-image-config ::v-deep .component-upload-image .el-upload--picture-card {
+  width: 60px;
+  height: 60px;
+  line-height: 60px;
+}
+.gift-image-config ::v-deep .component-upload-image .el-upload-list--picture-card .el-upload-list__item {
+  width: 60px;
+  height: 60px;
+}
+.gift-image-preview {
+  margin-top: 10px;
+  width: 120px;
+  height: 120px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+}
 </style>