|
@@ -55,6 +55,73 @@
|
|
|
|
|
|
|
|
<integral-order-msg-dialog ref="integralOrderMsgDialog" @msg-read="getMsgCount" />
|
|
<integral-order-msg-dialog ref="integralOrderMsgDialog" @msg-read="getMsgCount" />
|
|
|
<complaint-msg-dialog ref="complaintMsgDialog" @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>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -69,6 +136,7 @@ import Search from '@/components/HeaderSearch'
|
|
|
import msg from "@/views/crm/components/msg";
|
|
import msg from "@/views/crm/components/msg";
|
|
|
import IntegralOrderMsgDialog from '@/components/IntegralOrderMsgDialog'
|
|
import IntegralOrderMsgDialog from '@/components/IntegralOrderMsgDialog'
|
|
|
import ComplaintMsgDialog from '@/components/ComplaintMsgDialog'
|
|
import ComplaintMsgDialog from '@/components/ComplaintMsgDialog'
|
|
|
|
|
+import { listGuCoinGoodsWarn } from '@/api/his/guCoinGoods'
|
|
|
import { getMsg, getMsgList, getMsgCount, setRead } from "@/api/crm/msg";
|
|
import { getMsg, getMsgList, getMsgCount, setRead } from "@/api/crm/msg";
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
@@ -89,6 +157,9 @@ export default {
|
|
|
'avatar',
|
|
'avatar',
|
|
|
'device'
|
|
'device'
|
|
|
]),
|
|
]),
|
|
|
|
|
+ stockWarnZeroCount() {
|
|
|
|
|
+ return this.stockWarnList.filter(item => item.stock === 0).length
|
|
|
|
|
+ },
|
|
|
setting: {
|
|
setting: {
|
|
|
get() {
|
|
get() {
|
|
|
return this.$store.state.settings.showSettings
|
|
return this.$store.state.settings.showSettings
|
|
@@ -116,14 +187,24 @@ export default {
|
|
|
},
|
|
},
|
|
|
msgPollingTimer: null,
|
|
msgPollingTimer: null,
|
|
|
isFirstCheck: true,
|
|
isFirstCheck: true,
|
|
|
|
|
+ // 谷币商品库存预警
|
|
|
|
|
+ stockWarnVisible: false,
|
|
|
|
|
+ stockWarnList: [],
|
|
|
|
|
+ stockWarnTimer: null,
|
|
|
|
|
+ stockWarnRemindedIds: [],
|
|
|
|
|
+ stockWarnClosedAt: 0
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
created() {
|
|
created() {
|
|
|
this.getMsgCount();
|
|
this.getMsgCount();
|
|
|
this.startMsgPolling();
|
|
this.startMsgPolling();
|
|
|
|
|
+ // 启动库存预警检查
|
|
|
|
|
+ this.checkStockWarn();
|
|
|
|
|
+ this.startStockWarnPolling();
|
|
|
},
|
|
},
|
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
this.stopMsgPolling();
|
|
this.stopMsgPolling();
|
|
|
|
|
+ this.stopStockWarnPolling();
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
startMsgPolling() {
|
|
startMsgPolling() {
|
|
@@ -147,6 +228,7 @@ export default {
|
|
|
if (this.$refs.complaintMsgDialog) {
|
|
if (this.$refs.complaintMsgDialog) {
|
|
|
this.$refs.complaintMsgDialog.show();
|
|
this.$refs.complaintMsgDialog.show();
|
|
|
}
|
|
}
|
|
|
|
|
+ // 谷币库存预警弹窗已自治:组件 mounted 自检 + 独立轮询,无需此处调用
|
|
|
},
|
|
},
|
|
|
getMsgCount() {
|
|
getMsgCount() {
|
|
|
getMsg().then(response => {
|
|
getMsg().then(response => {
|
|
@@ -181,6 +263,69 @@ export default {
|
|
|
location.href = '/index';
|
|
location.href = '/index';
|
|
|
})
|
|
})
|
|
|
}).catch(() => {});
|
|
}).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>
|
|
|
|
|
+
|
|
|
|
|
+<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>
|