浏览代码

谷养正道商品库存预警

wangxy 15 小时之前
父节点
当前提交
15dfc6b4d6
共有 3 个文件被更改,包括 315 次插入1 次删除
  1. 9 0
      src/api/his/guCoinGoods.js
  2. 262 0
      src/layout/components/Navbar.vue
  3. 44 1
      src/views/his/guCoinGoods/index.vue

+ 9 - 0
src/api/his/guCoinGoods.js

@@ -42,3 +42,12 @@ export function delGuCoinGoods(goodsIds) {
     method: 'delete'
   })
 }
+
+// 库存预警列表(总后台弹窗提醒用)
+// 返回上架中且库存<=预警阈值(默认初始库存10%)的商品
+export function listGuCoinGoodsWarn() {
+  return request({
+    url: '/his/guCoinGoods/warnList',
+    method: 'get'
+  })
+}

+ 262 - 0
src/layout/components/Navbar.vue

@@ -55,6 +55,73 @@
 
     <integral-order-msg-dialog ref="integralOrderMsgDialog" @msg-read="getMsgCount" />
     <complaint-msg-dialog ref="complaintMsgDialog" @msg-read="getMsgCount" />
+    <el-dialog
+      title="谷币商品库存预警"
+      :visible.sync="stockWarnVisible"
+      width="640px"
+      :close-on-click-modal="false"
+      :show-close="true"
+      custom-class="gu-coin-stock-warn-dialog"
+      @close="handleStockWarnClose"
+      center
+    >
+      <div class="warn-content">
+        <div class="warn-header">
+          <div class="warn-icon">
+            <i class="el-icon-warning"></i>
+          </div>
+          <div class="warn-text">
+            <h3>库存预警提醒</h3>
+            <p>
+              以下谷币商城商品库存已不足预警阈值(默认初始库存的10%),
+              库存耗尽后系统将自动下架,用户端将无法兑换,请及时补货。
+            </p>
+          </div>
+        </div>
+        <div class="warn-stats">
+          <div class="stat-item">
+            <span class="stat-label">预警商品数</span>
+            <span class="stat-value">{{ stockWarnList.length }}件</span>
+          </div>
+          <div class="stat-item">
+            <span class="stat-label">库存耗尽</span>
+            <span class="stat-value danger">{{ stockWarnZeroCount }}件</span>
+          </div>
+        </div>
+        <el-table :data="stockWarnList" max-height="280" size="mini" border>
+          <el-table-column label="商品" min-width="180" align="center">
+            <template slot-scope="scope">
+              <div class="goods-cell">
+                <img v-if="scope.row.imgUrl" :src="scope.row.imgUrl" class="goods-thumb">
+                <span class="goods-name">{{ scope.row.goodsName }}</span>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="当前库存" width="100" align="center">
+            <template slot-scope="scope">
+              <span :class="['stock-num', scope.row.stock === 0 ? 'zero' : 'low']">
+                {{ scope.row.stock }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="预警阈值" width="100" align="center" prop="stockWarn" />
+          <el-table-column label="状态" width="110" align="center">
+            <template slot-scope="scope">
+              <el-tag v-if="scope.row.stock === 0" type="danger" size="mini">即将自动下架</el-tag>
+              <el-tag v-else type="warning" size="mini">库存不足</el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="handleStockWarnKnow" size="medium">
+          <i class="el-icon-check"></i> 知道了
+        </el-button>
+        <el-button type="primary" @click="handleStockWarnGoProcess" size="medium">
+          <i class="el-icon-right"></i> 去补货
+        </el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
@@ -69,6 +136,7 @@ import Search from '@/components/HeaderSearch'
 import msg from "@/views/crm/components/msg";
 import IntegralOrderMsgDialog from '@/components/IntegralOrderMsgDialog'
 import ComplaintMsgDialog from '@/components/ComplaintMsgDialog'
+import { listGuCoinGoodsWarn } from '@/api/his/guCoinGoods'
 import { getMsg, getMsgList, getMsgCount, setRead } from "@/api/crm/msg";
 
 export default {
@@ -89,6 +157,9 @@ export default {
       'avatar',
       'device'
     ]),
+    stockWarnZeroCount() {
+      return this.stockWarnList.filter(item => item.stock === 0).length
+    },
     setting: {
       get() {
         return this.$store.state.settings.showSettings
@@ -116,14 +187,24 @@ export default {
       },
       msgPollingTimer: null,
       isFirstCheck: true,
+      // 谷币商品库存预警
+      stockWarnVisible: false,
+      stockWarnList: [],
+      stockWarnTimer: null,
+      stockWarnRemindedIds: [],
+      stockWarnClosedAt: 0
     }
   },
   created() {
     this.getMsgCount();
     this.startMsgPolling();
+    // 启动库存预警检查
+    this.checkStockWarn();
+    this.startStockWarnPolling();
   },
   beforeDestroy() {
     this.stopMsgPolling();
+    this.stopStockWarnPolling();
   },
   methods: {
     startMsgPolling() {
@@ -147,6 +228,7 @@ export default {
       if (this.$refs.complaintMsgDialog) {
         this.$refs.complaintMsgDialog.show();
       }
+      // 谷币库存预警弹窗已自治:组件 mounted 自检 + 独立轮询,无需此处调用
     },
     getMsgCount() {
       getMsg().then(response => {
@@ -181,6 +263,69 @@ export default {
           location.href = '/index';
         })
       }).catch(() => {});
+    },
+    // ========== 谷币商品库存预警 ==========
+    startStockWarnPolling() {
+      if (this.stockWarnTimer) {
+        clearInterval(this.stockWarnTimer);
+      }
+      // 60秒轮询一次
+      this.stockWarnTimer = setInterval(() => {
+        this.checkStockWarn();
+      }, 60 * 1000);
+    },
+    stopStockWarnPolling() {
+      if (this.stockWarnTimer) {
+        clearInterval(this.stockWarnTimer);
+        this.stockWarnTimer = null;
+      }
+    },
+    checkStockWarn() {
+      // 弹窗已打开时不重复拉取
+      if (this.stockWarnVisible) return;
+      // 关闭后10分钟静默期
+      if (this.stockWarnClosedAt && Date.now() - this.stockWarnClosedAt < 10 * 60 * 1000) return;
+      listGuCoinGoodsWarn().then(res => {
+        if (res.code === 200) {
+          const list = res.data || [];
+          this.stockWarnList = list;
+          if (list.length === 0) {
+            this.stockWarnRemindedIds = [];
+            return;
+          }
+          // 仅当出现新的预警商品时才弹窗
+          const currentIds = list.map(item => item.goodsId);
+          const hasNew = currentIds.some(id => this.stockWarnRemindedIds.indexOf(id) === -1);
+          if (hasNew) {
+            this.stockWarnRemindedIds = currentIds;
+            this.stockWarnVisible = true;
+          }
+        }
+      }).catch(() => {});
+    },
+    handleStockWarnKnow() {
+      this.stockWarnVisible = false;
+      this.stockWarnClosedAt = Date.now();
+    },
+    handleStockWarnGoProcess() {
+      this.stockWarnVisible = false;
+      this.stockWarnClosedAt = Date.now();
+      // 跳转到谷币商品页面
+      // 谷币商品组件 name 为 GuCoinGoods(src/views/his/guCoinGoods/index.vue)
+      const route = this.$router.resolve({ name: 'GuCoinGoods' });
+      if (route && route.route && route.route.name === 'GuCoinGoods' && route.route.matched.length > 0) {
+        // name 路由存在,直接跳转
+        this.$router.push({ name: 'GuCoinGoods' }).catch(() => {});
+      } else {
+        // 兜底:尝试 path 跳转
+        this.$router.push('/his/guCoinGoods').catch(() => {
+          this.$message.warning('请从左侧菜单进入"谷币商品"页面进行补货');
+        });
+      }
+    },
+    handleStockWarnClose() {
+      this.stockWarnVisible = false;
+      this.stockWarnClosedAt = Date.now();
     }
   }
 }
@@ -286,3 +431,120 @@ export default {
   }
 }
 </style>
+
+<style>
+/* 谷币商品库存预警弹窗样式 */
+.gu-coin-stock-warn-dialog {
+  border-radius: 12px;
+  overflow: hidden;
+}
+.gu-coin-stock-warn-dialog .el-dialog__header {
+  background: linear-gradient(135deg, #ff9a44 0%, #fc6076 100%);
+  padding: 16px 20px;
+}
+.gu-coin-stock-warn-dialog .el-dialog__title {
+  color: #fff;
+  font-size: 16px;
+  font-weight: 600;
+}
+.gu-coin-stock-warn-dialog .el-dialog__headerbtn .el-dialog__close {
+  color: #fff;
+  font-size: 18px;
+}
+.gu-coin-stock-warn-dialog .el-dialog__body {
+  padding: 24px 20px 0;
+}
+.warn-content {
+  padding: 8px 4px 0;
+}
+.warn-header {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 16px;
+}
+.warn-icon {
+  flex-shrink: 0;
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #ff9a44 0%, #fc6076 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16px;
+}
+.warn-icon i {
+  font-size: 26px;
+  color: #fff;
+}
+.warn-text {
+  flex: 1;
+}
+.warn-text h3 {
+  margin: 0 0 8px 0;
+  font-size: 17px;
+  font-weight: 600;
+  color: #303133;
+}
+.warn-text p {
+  margin: 0;
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.6;
+}
+.warn-stats {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 16px;
+  padding: 12px 16px;
+  background: #fff7e6;
+  border-radius: 8px;
+}
+.warn-stats .stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+}
+.warn-stats .stat-label {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+.warn-stats .stat-value {
+  font-size: 20px;
+  font-weight: 600;
+  color: #e6a23c;
+}
+.warn-stats .stat-value.danger {
+  color: #f56c6c;
+}
+.goods-cell {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.goods-thumb {
+  width: 36px;
+  height: 36px;
+  object-fit: cover;
+  border-radius: 4px;
+  margin-right: 8px;
+}
+.goods-name {
+  flex: 1;
+  text-align: left;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 130px;
+}
+.stock-num.low {
+  color: #e6a23c;
+  font-weight: 600;
+}
+.stock-num.zero {
+  color: #f56c6c;
+  font-weight: 600;
+}
+</style>

+ 44 - 1
src/views/his/guCoinGoods/index.vue

@@ -97,7 +97,21 @@
       <el-table-column label="所需谷币" align="center" prop="guCoin" />
       <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="stock">
+        <template slot-scope="scope">
+          <span
+            :class="{
+              'stock-zero': scope.row.stock === 0,
+              'stock-low': scope.row.stock !== 0
+                && scope.row.stockWarn != null
+                && scope.row.stock <= scope.row.stockWarn
+            }"
+          >
+            {{ scope.row.stock }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="预警阈值" align="center" prop="stockWarn" width="90" />
       <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">
@@ -170,6 +184,15 @@
         <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="stockWarn">
+          <el-input-number
+            v-model="form.stockWarn"
+            :min="0"
+            label="库存预警阈值"
+            placeholder="留空则按库存10%自动计算"
+          ></el-input-number>
+          <span class="stock-warn-tip">库存≤此值触发预警,留空按库存10%自动计算</span>
+        </el-form-item>
         <el-form-item label="状态">
           <el-radio-group v-model="form.status">
             <el-radio
@@ -274,6 +297,7 @@ export default {
         cash: null,
         sort: null,
         stock: null,
+        stockWarn: null,
         descs: null,
         barCode: null,
         tempId: null
@@ -360,3 +384,22 @@ export default {
   }
 };
 </script>
+
+<style scoped>
+/* 库存预警高亮 */
+.stock-low {
+  color: #e6a23c;
+  font-weight: 600;
+}
+
+.stock-zero {
+  color: #f56c6c;
+  font-weight: 600;
+}
+
+.stock-warn-tip {
+  margin-left: 8px;
+  font-size: 12px;
+  color: #909399;
+}
+</style>