liulijun
2026-03-25 2403e0b0a127278d40e2fc780311d93262bef52c
修复库位平面图三四五巷道部分货位没有按列显示的问题

修复库位平面图三四五巷道部分货位没有按列显示的问题
已添加2个文件
已修改8个文件
909 ■■■■■ 文件已修改
项目代码/WMS/WMSClient/src/extension/stock/ProStockView.js 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/extension/taskinfo/task.js 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/main.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/services/emptyPalletWarning.js 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/services/taskTimeout.js 259 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/store/index.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/views/Home.vue 143 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/views/Index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/views/outbound/outSGOrder.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/views/stock/ProStockView.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMS/WMSClient/src/extension/stock/ProStockView.js
@@ -19,12 +19,6 @@
  methods: {
    //下面这些方法可以保留也可以删除
    onInit() {
      // åˆå§‹åŒ–空托检查定时器
      // è®¾ç½®å®šæ—¶å™¨ï¼Œæ¯60秒检查一次
      this.checkEmptyPalletTimer = setInterval(() => {
        this.checkEmptyPalletWarning();
      }, 10000); // æ¯10秒检查一次
      // åŽŸæœ‰ä»£ç ...
      // let InOrder = this.buttons.find(x => x.value == 'StockOutbound');
      // if (InOrder) {
@@ -151,98 +145,7 @@
      //看不懂就把输出看:console.log(this.editFormOptions)
    },
    
    // æ£€æŸ¥ç©ºæ‰˜æ•°é‡
    checkEmptyPalletWarning() {
      // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore,检查是否存在相同的警告消息
      const globalObj = this.$global || window.$global || {};
      const store = this.$store || window.$store;
      const globalMessageList = globalObj.messageList || [];
      const storeMessageList = store?.state?.messageList || [];
      // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨æœªæ¸…除的空托预警消息
      const hasExistingWarning = [...globalMessageList, ...storeMessageList].some(msg =>
        msg.businessType === 'pallet_warning' && msg.type === 'warning'
      );
      if (hasExistingWarning) return;
      // èŽ·å–å½“å‰æ‰€æœ‰åº“å­˜æ•°æ®
      const stockData = this.$refs.table?.rowData || this.$refs.table?.tableData || [];
      // è®¡ç®—空托总数量:只计算proStockAttribute为5的记录
      let totalEmptyPalletCount = 0;
      // éåŽ†æ‰€æœ‰åº“å­˜è®°å½•ï¼Œç´¯åŠ ç©ºæ‰˜æ•°é‡
      stockData.forEach(stock => {
        const proStockAttribute = stock.proStockAttribute || stock.ProStockAttribute || 0;
        if (proStockAttribute === 5) {
          // è§£æžsumStock的值,提取数字部分
          const sumStocks = stock.sumStocks || stock.sumStock || 0;
          const stockQuantity = typeof sumStocks === 'string'
            ? parseInt(sumStocks.match(/\d+/)?.[0] || 0)
            : parseInt(sumStocks || 0);
          totalEmptyPalletCount += stockQuantity;
        }
      });
      // ç©ºæ‰˜é¢„警阈值为50个
      const warningThreshold = 50;
      // å¦‚果空托总数量小于阈值,发送警告消息
      if (totalEmptyPalletCount < warningThreshold) {
        this.sendPalletWarningMessage(totalEmptyPalletCount, warningThreshold);
      }
    },
    // å‘送空托警告消息
    sendPalletWarningMessage(emptyPalletCount, warningThreshold) {
      // åˆ›å»ºè­¦å‘Šæ¶ˆæ¯
      const warningMessage = {
        id: Date.now(),
        title: '空托数量预警',
        message: `成品库空托数量不足,当前总数量:${emptyPalletCount},低于预警阈值:${warningThreshold},建议及时补充!`,
        type: 'warning',
        businessType: 'pallet_warning',
        createTime: new Date().toLocaleString(),
        relatedData: {
          EmptyPalletCount: emptyPalletCount,
          Threshold: warningThreshold
        }
      };
      // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore
      const globalObj = this.$global || window.$global || {};
      const store = this.$store || window.$store;
      // æ·»åŠ æ¶ˆæ¯åˆ°store
      if (store) {
        store.commit('addMessage', warningMessage);
      }
      // æ·»åŠ æ¶ˆæ¯åˆ°å…¨å±€æ¶ˆæ¯åˆ—è¡¨ï¼Œç¡®ä¿åˆ—è¡¨å­˜åœ¨
      if (!globalObj.messageList) {
        globalObj.messageList = [];
      }
      globalObj.messageList.push(warningMessage);
      // æ˜¾ç¤ºæç¤ºæ¡†
      // const $alert = this.$alert;
      // const $message = this.$message;
      // if ($alert) {
      //   $alert(warningMessage.message, warningMessage.title, {
      //     confirmButtonText: '确定',
      //     type: warningMessage.type,
      //     closeOnClickModal: false,
      //     closeOnPressEscape: false,
      //     showCancelButton: false
      //   });
      // } else if ($message) {
      //   $message.warning(warningMessage.message);
      // } else {
      //   alert(`${warningMessage.title}: ${warningMessage.message}`);
      // }
    },
    
    // åœ¨æŸ¥è¯¢åŽå¤„理数据
    searchAfter(result) {
ÏîÄ¿´úÂë/WMS/WMSClient/src/extension/taskinfo/task.js
@@ -95,186 +95,7 @@
        }
      }
      // åˆå§‹åŒ–任务状态检查定时器
      this.taskStatusMap = {}; // å­˜å‚¨ä»»åŠ¡çŠ¶æ€å¼€å§‹æ—¶é—´
      this.taskTimeoutMinutes = 10; // ä»»åŠ¡è¶…æ—¶æ—¶é—´ï¼Œå•ä½ä¸ºåˆ†é’Ÿ
      this.checkTaskStatusTimer = setInterval(() => {
        this.checkTaskStatus();
      }, 1000); // æ¯1秒检查一次,提高检查精度,减少延迟
    },
    // æ£€æŸ¥ä»»åŠ¡çŠ¶æ€
    checkTaskStatus() {
      // èŽ·å–å½“å‰æ‰€æœ‰ä»»åŠ¡æ•°æ®
      const taskData = this.$refs.table?.rowData || this.$refs.table?.tableData || [];
      const now = new Date();
      // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore
      const globalObj = this.$global || window.$global || {};
      const store = this.$store || window.$store;
      // å¤„理消息删除逻辑,重置对应任务的定时器
      const deletedMessages = globalObj.messageDeleted || [];
      if (deletedMessages.length > 0) {
        const storeMessageList = store?.state?.messageList || [];
        // éåŽ†è¢«åˆ é™¤çš„æ¶ˆæ¯ID,重置对应任务的定时器
        deletedMessages.forEach(deletedId => {
          const deletedMessage = storeMessageList.find(msg => msg.id === deletedId);
          if (deletedMessage?.businessType === 'task_timeout' && deletedMessage.taskNum && this.taskStatusMap[deletedMessage.taskNum]) {
            this.taskStatusMap[deletedMessage.taskNum] = now;
          }
        });
        // æ¸…空已处理的删除消息列表
        globalObj.messageDeleted = [];
      }
      // èŽ·å–å½“å‰æ‰€æœ‰ä»»åŠ¡å·å’ŒçŠ¶æ€
      const currentTaskStatuses = {};
      taskData.forEach(task => {
        currentTaskStatuses[task.taskNum] = task.taskStatus;
      });
      // æ£€æŸ¥ä»»åŠ¡çŠ¶æ€
      taskData.forEach(task => {
        if (task.taskStatus === 210) {
          // å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€
          if (!this.taskStatusMap[task.taskNum]) {
            this.taskStatusMap[task.taskNum] = now;
          } else {
            // è®¡ç®—持续时间(分钟)
            const duration = (now - this.taskStatusMap[task.taskNum]) / 60000;
            if (duration >= this.taskTimeoutMinutes) {
              this.sendTaskWarningMessage(task, duration);
              delete this.taskStatusMap[task.taskNum];
            }
          }
        } else {
          // ä»»åŠ¡çŠ¶æ€å·²æ”¹å˜ï¼Œæ¸…é™¤è®°å½•
          delete this.taskStatusMap[task.taskNum];
        }
      });
      // æ¸…除已解决的任务超时消息
      this.clearResolvedTaskMessages(currentTaskStatuses);
    },
    // æ¸…除已解决的任务超时消息
      clearResolvedTaskMessages(currentTaskStatuses) {
        // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore
        const globalObj = this.$global || window.$global || {};
        const store = this.$store || window.$store;
        // èŽ·å–æ‰€æœ‰æ¶ˆæ¯åˆ—è¡¨
        const globalMessageList = globalObj.messageList || [];
        const storeMessageList = store?.state?.messageList || [];
        // åˆå¹¶æ‰€æœ‰æ¶ˆæ¯ï¼Œæ‰¾å‡ºéœ€è¦æ¸…除的任务超时消息
        const allMessages = [...globalMessageList, ...storeMessageList];
        const taskTimeoutMessages = allMessages.filter(msg => msg.businessType === 'task_timeout');
        // éåŽ†æ‰€æœ‰ä»»åŠ¡è¶…æ—¶æ¶ˆæ¯
        taskTimeoutMessages.forEach(msg => {
          const taskNum = msg.taskNum;
          // æ£€æŸ¥æ¡ä»¶ï¼š
          // 1. ä»»åŠ¡ä¸åœ¨å½“å‰ä»»åŠ¡åˆ—è¡¨ä¸­ï¼ˆå·²å®Œæˆæˆ–è¢«ç§»é™¤ï¼‰
          // 2. ä»»åŠ¡åœ¨å½“å‰ä»»åŠ¡åˆ—è¡¨ä¸­ï¼Œä½†çŠ¶æ€å·²ä¸å†æ˜¯å †åž›æœºæ‰§è¡Œä¸­
          if (!currentTaskStatuses[taskNum] || currentTaskStatuses[taskNum] !== 210) {
            // ä»»åŠ¡å·²å®Œæˆæˆ–çŠ¶æ€å·²æ”¹å˜ï¼Œæ¸…é™¤è¯¥æ¶ˆæ¯
            this.handleDeleteTaskMessage(msg);
          }
        });
      },
    // å¤„理删除单个任务消息
    handleDeleteTaskMessage(message) {
      // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore
      const globalObj = this.$global || window.$global || {};
      const store = this.$store || window.$store;
      // ä»Žå…¨å±€æ¶ˆæ¯åˆ—表中删除该消息
      if (globalObj.messageList) {
        const index = globalObj.messageList.findIndex(msg => msg.id === message.id);
        if (index !== -1) {
          globalObj.messageList.splice(index, 1);
        }
      }
      // ä»Žstore中删除该消息
      if (store) {
        store.commit('removeMessage', message.id);
      }
    },
    // å‘送任务警告消息
    sendTaskWarningMessage(task, duration) {
      // åˆ›å»ºè­¦å‘Šæ¶ˆæ¯
      const warningMessage = {
        id: Date.now(),
        title: '任务异常警告',
        message: `任务号 ${task.taskNum} å·²åœ¨å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€è¶…è¿‡${Math.round(duration)}分钟,请及时处理!`,
        type: 'warning',
        businessType: 'task_timeout',
        taskNum: task.taskNum,
        createTime: new Date().toLocaleString()
      };
      // èŽ·å–å…¨å±€å¯¹è±¡å’Œstore,检查是否存在相同的警告消息
      const globalObj = this.$global || window.$global || {};
      const store = this.$store || window.$store;
      const globalMessageList = globalObj.messageList || [];
      const storeMessageList = store?.state?.messageList || [];
      // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨ç›¸åŒçš„任务超时警告
      const hasExistingWarning = [...globalMessageList, ...storeMessageList].some(msg =>
        msg.businessType === 'task_timeout' && msg.taskNum === task.taskNum
      );
      if (hasExistingWarning) return;
      // å‘送消息到消息列表
      try {
        // æ·»åŠ æ¶ˆæ¯åˆ°store
        const $store = this.$store || window.$store;
        if ($store) {
          $store.commit('addMessage', warningMessage);
        }
        // æ·»åŠ æ¶ˆæ¯åˆ°å…¨å±€æ¶ˆæ¯åˆ—è¡¨
        if (globalObj.messageList) {
          globalObj.messageList.push(warningMessage);
        }
        // æ˜¾ç¤ºè­¦å‘Šå¯¹è¯æ¡†ï¼Œä¼˜å…ˆä½¿ç”¨$alert
        // const $global = this.$global || window.$global;
        // const alertOptions = {
        //   confirmButtonText: '确定',
        //   type: warningMessage.type,
        //   closeOnClickModal: false,
        //   closeOnPressEscape: false,
        //   showCancelButton: false
        // };
        // if (this.$alert || window.$alert) {
        //   const $alert = this.$alert || window.$alert;
        //   $alert(warningMessage.message, warningMessage.title, alertOptions);
        // } else if (this.$confirm || window.$confirm) {
        //   const $confirm = this.$confirm || window.$confirm;
        //   $confirm(warningMessage.message, warningMessage.title, alertOptions);
        // } else {
        //   // ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿalert作为备选
        //   alert(`${warningMessage.title}: ${warningMessage.message}`);
        // }
      } catch (error) {
        // å‡ºé”™æ—¶ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿalert作为最终备选
        try {
          alert(`任务异常警告: ä»»åŠ¡å· ${task.taskNum} å·²åœ¨å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€è¶…è¿‡${Math.round(duration)}分钟,请及时处理!`);
        } catch (e) {
          // å¿½ç•¥æ‰€æœ‰é”™è¯¯
        }
      }
      // ä¸å†éœ€è¦æ³¨å†Œä»»åŠ¡æ•°æ®æºï¼Œå…¨å±€ä»»åŠ¡è¶…æ—¶æœåŠ¡å·²æ”¹ä¸ºç›´æŽ¥ä»ŽAPI获取数据
    },
    onInited() {
      //框架初始化配置后
@@ -288,24 +109,6 @@
    },
    searchAfter(result) {
      // æŸ¥è¯¢åŽï¼Œresult返回的查询数据,可以在显示到表格前处理表格的值
      // å¤„理任务列表刷新后的任务超时消息清理
      // æå–当前任务状态
      const currentTaskStatuses = {};
      if (result && Array.isArray(result)) {
        result.forEach(task => {
          currentTaskStatuses[task.taskNum] = task.taskStatus;
        });
      } else if (result && result.result && Array.isArray(result.result)) {
        // å¤„理分页返回的数据格式
        result.result.forEach(task => {
          currentTaskStatuses[task.taskNum] = task.taskStatus;
        });
      }
      // æ¸…除已解决的任务超时消息
      this.clearResolvedTaskMessages(currentTaskStatuses);
      return true;
    },
    addBefore(formData) {
@@ -327,6 +130,11 @@
      //(3)this.editFormFields.字段='xxx';
      //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
      //看不懂就把输出看:console.log(this.editFormOptions)
    },
    // ç»„件销毁时清理资源
    onDestroyed() {
      // ä¸å†éœ€è¦æ¸…理任务数据源,全局任务超时服务已改为直接从API获取数据
    }
  }
};
ÏîÄ¿´úÂë/WMS/WMSClient/src/main.js
@@ -11,6 +11,10 @@
// import 'dayjs/locale/zh-cn'
// import locale from 'element-plus/lib/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// å¼•入空托预警服务
import emptyPalletWarning from './services/emptyPalletWarning'
// å¼•入全局任务超时服务
import taskTimeoutService from './services/taskTimeout'
@@ -71,3 +75,12 @@
    .mount('#app');
app.config.globalProperties.$Message = app.config.globalProperties.$message;
// åˆå§‹åŒ–空托预警服务
emptyPalletWarning.init(app);
// åˆå§‹åŒ–全局任务超时服务
taskTimeoutService.init(app);
// å°†ä»»åŠ¡è¶…æ—¶æœåŠ¡æŒ‚è½½åˆ°å…¨å±€å±žæ€§ä¸Š
app.config.globalProperties.$taskTimeoutService = taskTimeoutService;
// å°†ä»»åŠ¡è¶…æ—¶æœåŠ¡æŒ‚è½½åˆ°window对象上,方便在扩展组件中访问
window.$taskTimeoutService = taskTimeoutService;
ÏîÄ¿´úÂë/WMS/WMSClient/src/services/emptyPalletWarning.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
// ç©ºæ‰˜é¢„警服务
import http from '../api/http';
// ç©ºæ‰˜é¢„警阈值
const WARNING_THRESHOLD = 50;
// æ£€æŸ¥ç©ºæ‰˜æ•°é‡çš„æœåŠ¡
const checkEmptyPalletWarning = async (globalState, store) => {
  try {
    // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨æœªæ¸…除的空托预警消息
    const globalMessageList = globalState.messageList || [];
    const hasExistingWarning = globalMessageList.some(msg =>
      msg.businessType === 'pallet_warning' && msg.type === 'warning'
    );
    if (hasExistingWarning) {
      return;
    }
    // è°ƒç”¨API获取成品库空托数量,添加proStockAttribute等于5的过滤条件
    const requestData = {
      Page: 1,
      Rows: 9999,
      Filter: [
        {
          Name: 'proStockAttribute',
          Value: 5,
          Operator: 'eq'
        }
      ],
      Wheres: '',
      Sort: '',
      Order: 'desc'
    };
    let apiData = null;
    let apiSuccess = false;
    try {
      // è°ƒç”¨API获取成品库空托数量
      apiData = await http.post('/api/ProStockView/GetPageData', requestData);
      apiSuccess = true;
    } catch (error) {
      console.error('API请求失败:', error);
    }
    // å¦‚æžœAPI失败,不使用模拟数据,直接返回
    if (!apiSuccess || !apiData) {
      return;
    }
    let totalEmptyPalletCount = 0;
    if (apiData && apiData.rows) {
      const stockData = apiData.rows;
      // éåŽ†æ‰€æœ‰è¿”å›žè®°å½•ï¼Œç›´æŽ¥ç´¯åŠ åº“å­˜æ•°é‡ï¼ˆAPI已过滤空托)
      for (const stock of stockData) {
        // èŽ·å–åº“å­˜æ•°é‡ï¼Œå°è¯•æ‰€æœ‰å¯èƒ½çš„å­—æ®µ
        let stockQuantity = 0;
        // å°è¯•从所有可能的字段获取库存数量
        const quantityFields = ['sumStocks', '库存数量', 'quantity', 'stockCount', 'sumStock', '库存数', 'count'];
        for (const field of quantityFields) {
          if (stock[field] !== undefined && stock[field] !== null) {
            // å°è¯•解析为数字
            const parsedQuantity = typeof stock[field] === 'number' ? stock[field] : parseInt(stock[field]);
            if (!isNaN(parsedQuantity)) {
              stockQuantity = parsedQuantity;
              break;
            }
          }
        }
        // ç´¯åŠ ç©ºæ‰˜æ•°é‡
        totalEmptyPalletCount += stockQuantity;
      }
      // åªæœ‰å½“空托数量少于50时才发送预警
      if (totalEmptyPalletCount < WARNING_THRESHOLD) {
        sendPalletWarningMessage(globalState, store, totalEmptyPalletCount, WARNING_THRESHOLD);
      }
    }
  } catch (error) {
    console.error('检查空托数量失败:', error);
    console.error('错误堆栈:', error.stack);
  }
};
// å‘送空托警告消息
const sendPalletWarningMessage = (globalState, store, emptyPalletCount, warningThreshold) => {
  // åˆ›å»ºè­¦å‘Šæ¶ˆæ¯
  const warningMessage = {
    id: Date.now(),
    title: '空托数量预警',
    message: `成品库空托数量不足,当前总数量:${emptyPalletCount},低于预警阈值:${warningThreshold},建议及时补充!`,
    type: 'warning',
    businessType: 'pallet_warning',
    createTime: new Date().toLocaleString(),
    relatedData: {
      EmptyPalletCount: emptyPalletCount,
      Threshold: warningThreshold
    }
  };
  // ç›´æŽ¥æ·»åŠ æ¶ˆæ¯åˆ°å…¨å±€æ¶ˆæ¯åˆ—è¡¨
  if (globalState && globalState.messageList) {
    // æ£€æŸ¥æ¶ˆæ¯æ˜¯å¦å·²å­˜åœ¨
    const isNewMessage = !globalState.messageList.some(m => m.id === warningMessage.id);
    globalState.messageList.push(warningMessage);
  }
};
// åˆå§‹åŒ–空托预警服务
const initEmptyPalletWarning = (app) => {
  const globalState = app.config.globalProperties.$global;
  const store = app.config.globalProperties.$store;
  // è®¾ç½®å®šæ—¶å™¨ï¼Œæ¯60秒检查一次
  const checkEmptyPalletTimer = setInterval(() => {
    checkEmptyPalletWarning(globalState, store);
  }, 30000); // æ¯30秒检查一次
  // 20秒后执行初始检查
  setTimeout(() => {
    checkEmptyPalletWarning(globalState, store);
  }, 10000); // 10秒后执行初始检查
  // ä¿å­˜å®šæ—¶å™¨å¼•用,方便后续清理
  app.config.globalProperties.$global.checkEmptyPalletTimer = checkEmptyPalletTimer;
  // æ–¹ä¾¿æ‰‹åŠ¨æµ‹è¯•ï¼Œå°†check方法挂载到window对象
  window.testEmptyPalletWarning = () => {
    checkEmptyPalletWarning(globalState, store);
  };
};
export default {
  init: initEmptyPalletWarning,
  check: checkEmptyPalletWarning
};
ÏîÄ¿´úÂë/WMS/WMSClient/src/services/taskTimeout.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,259 @@
// å…¨å±€ä»»åŠ¡è¶…æ—¶æœåŠ¡
import http from '../api/http';
// ä»»åŠ¡è¶…æ—¶æœåŠ¡ç±»
class TaskTimeoutService {
  constructor() {
    this.taskStatusMap = {}; // å­˜å‚¨ä»»åŠ¡çŠ¶æ€å¼€å§‹æ—¶é—´
    this.taskTimeoutMinutes = 10; // ä»»åŠ¡è¶…æ—¶æ—¶é—´ï¼Œå•ä½ä¸ºåˆ†é’Ÿï¼Œè°ƒæ•´ä¸º10分钟便于测试
    this.checkTaskStatusTimer = null;
    this.apiTaskData = []; // å­˜å‚¨é€šè¿‡API获取的任务数据
    this.apiRefreshTimer = null; // API数据刷新定时器
    this.apiRefreshInterval = 10000; // API数据刷新间隔,调整为10秒便于测试
  }
  // åˆå§‹åŒ–任务超时服务
  init(app) {
    // è®¾ç½®å®šæ—¶å™¨ï¼Œæ¯10秒检查一次,调整为便于测试的频率
    this.checkTaskStatusTimer = setInterval(() => {
      this.checkTaskStatus();
    }, 10000);
    // è®¾ç½®API数据刷新定时器,每10秒刷新一次任务数据
    this.apiRefreshTimer = setInterval(() => {
      this.refreshTaskDataFromApi();
    }, this.apiRefreshInterval);
    // åˆå§‹åˆ·æ–°ä¸€æ¬¡ä»»åŠ¡æ•°æ®
    this.refreshTaskDataFromApi();
    // ä¿å­˜å®šæ—¶å™¨å¼•用,方便后续清理
    if (app && app.config) {
      app.config.globalProperties.$global.taskTimeoutTimer = this.checkTaskStatusTimer;
      app.config.globalProperties.$global.taskTimeoutApiTimer = this.apiRefreshTimer;
    }
    // æ–¹ä¾¿æ‰‹åŠ¨æµ‹è¯•ï¼Œå°†check方法挂载到window对象
    window.testTaskTimeout = () => {
      this.checkTaskStatus();
    };
  }
  // ä»ŽAPI刷新任务数据
  async refreshTaskDataFromApi() {
    try {
      // æž„建查询参数,获取所有任务,不限制状态,在后续处理中过滤
      const requestData = {
        Page: 1,
        Rows: 9999,
        Filter: [],
        Wheres: '',
        Sort: 'CreateDate',
        Order: 'desc'
      };
      // è°ƒç”¨API获取任务数据
      const apiData = await http.post('/api/Task/GetPageData', requestData);
      if (apiData && apiData.rows) {
        this.apiTaskData = apiData.rows;
      } else {
        this.apiTaskData = [];
      }
    } catch (error) {
      console.error('从API获取任务数据失败:', error);
      this.apiTaskData = [];
    }
  }
  // æ£€æŸ¥ä»»åŠ¡çŠ¶æ€
  checkTaskStatus() {
    // ç›´æŽ¥ä½¿ç”¨ä»ŽAPI获取的数据
    const allTaskData = this.apiTaskData || [];
    // åŽ»é‡ï¼Œç¡®ä¿æ¯ä¸ªä»»åŠ¡åªå¤„ç†ä¸€æ¬¡
    const uniqueTaskData = [];
    const taskNumSet = new Set();
    allTaskData.forEach(task => {
      if (task && task.taskNum && !taskNumSet.has(task.taskNum)) {
        uniqueTaskData.push(task);
        taskNumSet.add(task.taskNum);
      }
    });
    if (uniqueTaskData.length === 0) {
      return;
    }
    const now = new Date();
    // èŽ·å–å…¨å±€å¯¹è±¡
    const globalObj = window.$global || {};
    // å¤„理消息删除逻辑,重置对应任务的定时器
    const deletedMessages = globalObj.messageDeleted || [];
    if (deletedMessages.length > 0) {
      const globalMessageList = globalObj.messageList || [];
      // éåŽ†è¢«åˆ é™¤çš„æ¶ˆæ¯ID,重置对应任务的定时器
      deletedMessages.forEach(deletedId => {
        const deletedMessage = globalMessageList.find(msg => msg.id === deletedId);
        if (deletedMessage?.businessType === 'task_timeout' && deletedMessage.taskNum && this.taskStatusMap[deletedMessage.taskNum]) {
          this.taskStatusMap[deletedMessage.taskNum] = now;
        }
      });
      // æ¸…空已处理的删除消息列表
      globalObj.messageDeleted = [];
    }
    // èŽ·å–å½“å‰æ‰€æœ‰ä»»åŠ¡å·å’ŒçŠ¶æ€
    const currentTaskStatuses = {};
    uniqueTaskData.forEach(task => {
      currentTaskStatuses[task.taskNum] = task.taskStatus;
    });
    // æ£€æŸ¥ä»»åŠ¡çŠ¶æ€
    uniqueTaskData.forEach(task => {
      if (task.taskStatus === 210) {
        // å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€
        if (!this.taskStatusMap[task.taskNum]) {
          this.taskStatusMap[task.taskNum] = now;
        } else {
          // è®¡ç®—持续时间(分钟)
          const duration = (now - this.taskStatusMap[task.taskNum]) / 60000;
          if (duration >= this.taskTimeoutMinutes) {
            this.sendTaskWarningMessage(task, duration);
            delete this.taskStatusMap[task.taskNum];
          }
        }
      } else {
        // ä»»åŠ¡çŠ¶æ€å·²æ”¹å˜ï¼Œæ¸…é™¤è®°å½•
        delete this.taskStatusMap[task.taskNum];
      }
    });
    // æ¸…除已解决的任务超时消息
    this.clearResolvedTaskMessages(currentTaskStatuses);
  }
  // æ¸…除已解决的任务超时消息
  clearResolvedTaskMessages(currentTaskStatuses) {
    // èŽ·å–å…¨å±€å¯¹è±¡
    const globalObj = window.$global || {};
    // èŽ·å–å…¨å±€æ¶ˆæ¯åˆ—è¡¨
    const globalMessageList = globalObj.messageList || [];
    // æ‰¾å‡ºéœ€è¦æ¸…除的任务超时消息
    const taskTimeoutMessages = globalMessageList.filter(msg => msg.businessType === 'task_timeout');
    // éåŽ†æ‰€æœ‰ä»»åŠ¡è¶…æ—¶æ¶ˆæ¯
    taskTimeoutMessages.forEach(msg => {
      const taskNum = msg.taskNum;
      // æ£€æŸ¥æ¡ä»¶ï¼š
      // 1. ä»»åŠ¡ä¸åœ¨å½“å‰ä»»åŠ¡åˆ—è¡¨ä¸­ï¼ˆå·²å®Œæˆæˆ–è¢«ç§»é™¤ï¼‰
      // 2. ä»»åŠ¡åœ¨å½“å‰ä»»åŠ¡åˆ—è¡¨ä¸­ï¼Œä½†çŠ¶æ€å·²ä¸å†æ˜¯å †åž›æœºæ‰§è¡Œä¸­
      if (!currentTaskStatuses[taskNum] || currentTaskStatuses[taskNum] !== 210) {
        // ä»»åŠ¡å·²å®Œæˆæˆ–çŠ¶æ€å·²æ”¹å˜ï¼Œæ¸…é™¤è¯¥æ¶ˆæ¯
        this.handleDeleteTaskMessage(msg);
      }
    });
  }
  // å¤„理删除单个任务消息
  handleDeleteTaskMessage(message) {
    // èŽ·å–å…¨å±€å¯¹è±¡
    const globalObj = window.$global || {};
    // ç›´æŽ¥ä»Žå…¨å±€æ¶ˆæ¯åˆ—表中删除该消息
    if (globalObj.messageList) {
      const index = globalObj.messageList.findIndex(msg => msg.id === message.id);
      if (index !== -1) {
        globalObj.messageList.splice(index, 1);
      }
    }
  }
  // å‘送任务警告消息
  sendTaskWarningMessage(task, duration) {
    // åˆ›å»ºè­¦å‘Šæ¶ˆæ¯
    const warningMessage = {
      id: Date.now(),
      title: '任务异常警告',
      message: `任务号 ${task.taskNum} å·²åœ¨å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€è¶…è¿‡${Math.round(duration)}分钟,请及时处理!`,
      type: 'warning',
      businessType: 'task_timeout',
      taskNum: task.taskNum,
      createTime: new Date().toLocaleString()
    };
    // èŽ·å–å…¨å±€å¯¹è±¡
    const globalObj = window.$global || {};
    // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨ç›¸åŒçš„任务超时警告
    const globalMessageList = globalObj.messageList || [];
    const hasExistingWarning = globalMessageList.some(msg =>
      msg.businessType === 'task_timeout' && msg.taskNum === task.taskNum
    );
    if (hasExistingWarning) return;
    // å‘送消息到全局消息列表
    try {
      if (globalObj.messageList) {
        globalObj.messageList.push(warningMessage);
      }
    } catch (error) {
      // å‡ºé”™æ—¶ä½¿ç”¨æµè§ˆå™¨åŽŸç”Ÿalert作为最终备选
      try {
        alert(`任务异常警告: ä»»åŠ¡å· ${task.taskNum} å·²åœ¨å †åž›æœºæ‰§è¡Œä¸­çŠ¶æ€è¶…è¿‡${Math.round(duration)}分钟,请及时处理!`);
      } catch (e) {
        // å¿½ç•¥æ‰€æœ‰é”™è¯¯
      }
    }
  }
  // æ‰‹åŠ¨æ£€æŸ¥ä»»åŠ¡çŠ¶æ€ï¼ˆç”¨äºŽå¤–éƒ¨è°ƒç”¨ï¼‰
  manualCheckTaskStatus() {
    this.checkTaskStatus();
  }
  // è®¾ç½®ä»»åŠ¡è¶…æ—¶æ—¶é—´
  setTaskTimeoutMinutes(minutes) {
    if (typeof minutes === 'number' && minutes > 0) {
      this.taskTimeoutMinutes = minutes;
    }
  }
  // èŽ·å–å½“å‰ä»»åŠ¡è¶…æ—¶æ—¶é—´
  getTaskTimeoutMinutes() {
    return this.taskTimeoutMinutes;
  }
  // æ¸…理资源
  cleanup() {
    if (this.checkTaskStatusTimer) {
      clearInterval(this.checkTaskStatusTimer);
      this.checkTaskStatusTimer = null;
    }
    if (this.apiRefreshTimer) {
      clearInterval(this.apiRefreshTimer);
      this.apiRefreshTimer = null;
    }
    this.taskStatusMap = {};
    this.apiTaskData = [];
    delete window.testTaskTimeout;
  }
}
// åˆ›å»ºå•例实例
const taskTimeoutService = new TaskTimeoutService();
export default taskTimeoutService;
ÏîÄ¿´úÂë/WMS/WMSClient/src/store/index.js
@@ -14,8 +14,7 @@
    permission: [],
    isLoading: false,//2020.06.03增加路由切换时加载提示
    userInfo: null,
    websocke: null,//websocket
    messageList: [],//消息列表
    websocke: null//websocket
    // wcsState: true//wcs服务状态
  },
  mutations: {
@@ -43,15 +42,6 @@
    },
    setWebsocket(state, data) {
      state.websocke = data;
    },
    addMessage(state, message) {
      state.messageList.push(message);
    },
    removeMessage(state, messageId) {
      state.messageList = state.messageList.filter(message => message.id !== messageId);
    },
    clearMessages(state) {
      state.messageList = [];
    }
  }, getters: {
    getPermission: (state) => (path) => {  //调用方式 store.getters.getPermission('sys_User')
@@ -89,9 +79,6 @@
    },
    getData: (state) => () => {
      return state.data;
    },
    getMessageList: (state) => () => {
      return state.messageList;
    }
  }, actions: {
    setPermission(context, data) {
@@ -102,9 +89,6 @@
    },
    onLoading(context, flag) {
      context.commit("updateLoadingState", flag);
    },
    addMessage(context, message) {
      context.commit('addMessage', message);
    }
  }
})
ÏîÄ¿´úÂë/WMS/WMSClient/src/views/Home.vue
@@ -65,10 +65,15 @@
            <div class="roadway-section">
              <h2 class="roadway-title">{{ Area.roadwayNo }} ç¬¬1巷道第1排(东面:北>南)</h2>
              <div class="row" v-for="(item, index) in locationData.row1" :key="'row1-' + index">
                <div class="location-cell" :style="{ 'background-color': GetBgColor(column) }"
                  v-for="(column, colIndex) in item.locationObj" :key="'row1-' + item.layer + '-' + colIndex"
                  @mouseenter="showTooltip(column, $event)" @mouseleave="hideTooltip">
                  {{ getRoadwayNo(column) }}-{{ column.column }}-{{ column.layer }}
                <div
                  class="location-cell"
                  :class="{ 'empty-cell': column.location_lock === -1 }"
                  :style="{ 'background-color': column.location_lock === -1 ? 'transparent' : GetBgColor(column) }"
                  v-for="(column, colIndex) in item.locationObj"
                  :key="'row1-' + item.layer + '-' + colIndex"
                  @mouseenter="column.location_lock !== -1 && showTooltip(column, $event)"
                  @mouseleave="hideTooltip">
                  <span v-if="column.location_lock !== -1">{{ getRoadwayNo(column) }}-{{ column.column }}-{{ column.layer }}</span>
                </div>
              </div>
            </div>
@@ -77,10 +82,15 @@
            <div class="roadway-section">
              <h2 class="roadway-title">{{ Area.roadwayNo }} ç¬¬1巷道第2排(西面:北>南)</h2>
              <div class="row" v-for="(item, index) in locationData.row2" :key="'row2-' + index">
                <div class="location-cell" :style="{ 'background-color': GetBgColor(column) }"
                  v-for="(column, colIndex) in item.locationObj" :key="'row2-' + item.layer + '-' + colIndex"
                  @mouseenter="showTooltip(column, $event)" @mouseleave="hideTooltip">
                  {{ getRoadwayNo(column) }}-{{ column.column }}-{{ column.layer }}
                <div
                  class="location-cell"
                  :class="{ 'empty-cell': column.location_lock === -1 }"
                  :style="{ 'background-color': column.location_lock === -1 ? 'transparent' : GetBgColor(column) }"
                  v-for="(column, colIndex) in item.locationObj"
                  :key="'row2-' + item.layer + '-' + colIndex"
                  @mouseenter="column.location_lock !== -1 && showTooltip(column, $event)"
                  @mouseleave="hideTooltip">
                  <span v-if="column.location_lock !== -1">{{ getRoadwayNo(column) }}-{{ column.column }}-{{ column.layer }}</span>
                </div>
              </div>
            </div>
@@ -97,7 +107,7 @@
              <!-- åŽŸæ–™åº“æ˜¾ç¤ºå†…å®¹ -->
              <div v-if="Area.warehouse === '原料库'">
                <p><strong>纸卷:</strong><span>{{ currentLocation.paperRoll || "无" }}</span></p>
                <p><strong>门幅:</strong><span>{{ currentLocation.width ? currentLocation.width + "m" : "无" }}</span></p>
                <p><strong>门幅:</strong><span>{{ currentLocation.width ? currentLocation.width : "无" }}</span></p>
                <p><strong>条码:</strong><span>{{ currentLocation.barcode || "无" }}</span></p>
                <p><strong>RFID:</strong><span>{{ currentLocation.rfid || "无" }}</span></p>
              </div>
@@ -317,37 +327,71 @@
          if (x.data) {
            this.locationData = x.data;
            // å¯¹ç¬¬1排数据进行排序
            if (this.locationData.row1) {
            // ç¡®ä¿è´§ä½æŒ‰åˆ—号排序并计算最大列数,用于对齐
            const ensureColumnsAlignment = (rowData) => {
              if (!rowData || rowData.length === 0) return;
              // æŒ‰å±‚号从高到低排序
              this.locationData.row1.sort((a, b) => parseInt(b.layer) - parseInt(a.layer));
              // å¯¹æ¯å±‚内的货位按列号从北到南排序(01-64)
              this.locationData.row1.forEach(layer => {
              rowData.sort((a, b) => parseInt(b.layer) - parseInt(a.layer));
              // ç¡®å®šæœ€å¤§åˆ—数(假设是64列,从01-64)
              const maxColumns = 64;
              // å¤„理每层数据
              rowData.forEach(layer => {
                // å¯¹æ¯å±‚内的货位按列号从北到南排序(01-64)
                layer.locationObj.sort((a, b) => {
                  // ç¡®ä¿åˆ—号按数字顺序排列
                  const colA = parseInt(a.column);
                  const colB = parseInt(b.column);
                  return colA - colB;
                });
              });
            }
            // å¯¹ç¬¬2排数据进行同样的排序
            if (this.locationData.row2) {
              // æŒ‰å±‚号从高到低排序
              this.locationData.row2.sort((a, b) => parseInt(b.layer) - parseInt(a.layer));
              // å¯¹æ¯å±‚内的货位按列号从北到南排序(01-64)
              this.locationData.row2.forEach(layer => {
                layer.locationObj.sort((a, b) => {
                  // ç¡®ä¿åˆ—号按数字顺序排列
                  const colA = parseInt(a.column);
                  const colB = parseInt(b.column);
                  return colA - colB;
                // åˆ›å»ºå®Œæ•´çš„列数组
                const completeColumns = [];
                // å½“前已有的货位,按列号映射
                const existingLocations = {};
                layer.locationObj.forEach(loc => {
                  existingLocations[parseInt(loc.column)] = loc;
                });
                // éåŽ†æ‰€æœ‰å¯èƒ½çš„åˆ—å·ï¼ˆ1-64)
                for (let col = 1; col <= maxColumns; col++) {
                  if (existingLocations[col]) {
                    // å¦‚果该列有货位数据,直接使用
                    completeColumns.push(existingLocations[col]);
                  } else {
                    // å¦‚果该列没有货位数据,添加空占位符
                    completeColumns.push({
                      locationCode: `empty-${layer.row}-${String(col).padStart(2, '0')}-${layer.layer}`,
                      row: layer.row,
                      column: String(col).padStart(2, '0'),
                      layer: layer.layer,
                      location_lock: -1, // ç‰¹æ®Šæ ‡è®°ï¼Œç”¨äºŽè¡¨ç¤ºç©ºå ä½ç¬¦
                      paperRoll: '',
                      productName: '',
                      width: 0,
                      quantity: 0,
                      barcode: '',
                      rfid: '',
                      rfidCode: '',
                      inDate: null,
                      warehouseId: layer.warehouseId || 0
                    });
                  }
                }
                // æ›´æ–°è¯¥å±‚的货位数据为完整列
                layer.locationObj = completeColumns;
              });
            }
            };
            // å¤„理第1排数据
            ensureColumnsAlignment(this.locationData.row1);
            // å¤„理第2排数据
            ensureColumnsAlignment(this.locationData.row2);
          }
          // æå–所有货位编号
@@ -654,8 +698,8 @@
.location-view {
  flex: 1;
  width: 470%;
  max-width: 470%;
  width: 360%;
  max-width: 360%;
  overflow: auto;
  padding: 20px;
  background-color: #f5f7fa;
@@ -667,12 +711,13 @@
  margin-bottom: 8px;
  cursor: pointer;
  flex-wrap: nowrap;
  width: 100%;
  min-width: max-content;
}
.location-cell {
  width: 120px;
  flex: 0 0 120px;
  height: 50px;
  margin: 5px;
  text-align: center;
  font-size: 16px;
  border-radius: 4px;
@@ -680,6 +725,34 @@
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
  border: 1px solid #ccc;
  background-color: #f9f9f9;
  margin: 0 5px;
}
/* ç©ºå ä½ç¬¦æ ·å¼ */
.location-cell.empty-cell {
  background-color: transparent;
  border: none;
  box-shadow: none;
  cursor: default;
}
/* æ·»åŠ è´§ä½ç½‘æ ¼å®¹å™¨æ ·å¼ï¼Œç¡®ä¿æ‰€æœ‰è¡Œå¯¹é½ */
.roadway-section {
  overflow-x: auto;
  overflow-y: hidden;
}
.location-cell {
  width: 120px;
  height: 50px;
  text-align: center;
  font-size: 16px;
  border-radius: 4px;
  line-height: 50px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
  border: 1px solid #ccc;
  background-color: #f9f9f9;
  margin: 0;
}
/* å··é“区域样式 */
ÏîÄ¿´úÂë/WMS/WMSClient/src/views/Index.vue
@@ -332,8 +332,21 @@
    const handleMessage = (e) => {
      let data = JSON.parse(e.data);
      // ä½¿ç”¨store的addMessage mutation添加消息
      store.commit('addMessage', data);
      // ç›´æŽ¥æ·»åŠ åˆ°å…¨å±€æ¶ˆæ¯åˆ—è¡¨
      if (_config.$global.messageList) {
        // ç¡®ä¿æ¶ˆæ¯æœ‰å”¯ä¸€ID
        const messageWithId = {
          ...data,
          id: data.id || Date.now() + Math.random().toString(36).substr(2, 9)
        };
        // æ£€æŸ¥æ¶ˆæ¯æ˜¯å¦å·²å­˜åœ¨
        const isNewMessage = !_config.$global.messageList.some(m => m.id === messageWithId.id);
        _config.$global.messageList.push(messageWithId);
      }
      ElNotification({
        title: data.title,
        message: h("i", { style: "color: teal" }, data.message),
@@ -679,12 +692,6 @@
        _config.$global.messageList = _config.$global.messageList.filter(msg => msg.id !== item.id);
      }
      
      // ä»Žstore中删除该消息
      if (store) {
        // ä½¿ç”¨mutation删除单个消息
        store.commit('removeMessage', item.id);
      }
      // å°†åˆ é™¤çš„æ¶ˆæ¯ID添加到messageDeleted数组,用于通知组件重置定时器
      if (_config.$global.messageDeleted && Array.isArray(_config.$global.messageDeleted)) {
        _config.$global.messageDeleted.push(item.id);
@@ -705,9 +712,7 @@
      // èŽ·å–æ‰€æœ‰å½“å‰æ¶ˆæ¯çš„ID,用于重置定时器
      const messageIds = _config.$global.messageList.map(msg => msg.id);
      
      // å…ˆæ¸…空store中的消息列表
      store.commit('clearMessages');
      // ç„¶åŽæ¸…空全局消息列表,使用重新赋值的方式确保响应式更新
      // æ¸…空全局消息列表,使用重新赋值的方式确保响应式更新
      _config.$global.messageList = [];
      
      // å°†æ‰€æœ‰åˆ é™¤çš„æ¶ˆæ¯ID添加到messageDeleted数组,用于通知组件重置定时器
ÏîÄ¿´úÂë/WMS/WMSClient/src/views/outbound/outSGOrder.vue
@@ -54,6 +54,7 @@
      [
        { title: "仓库", field: "warehouseId", type: "selectList", dataKey: "warehouses", data: [], },
        { title: "客户简称", field: "shortName", type: "like" },
        { title: "创建时间", field: "createDate", type: "datetime", },
      ],
    ]);
    const columns = ref([
ÏîÄ¿´úÂë/WMS/WMSClient/src/views/stock/ProStockView.vue
@@ -50,6 +50,7 @@
        [
          { title: "所属仓库", field: "warehouseId",type: "select",dataKey: "warehouses",data: []},
          { title: "库存属性", field: "proStockAttribute" ,type: "selectList",dataKey: "proStockAttributeEnum",data: [],},
          { title: "创建时间", field: "createDate",type: "datetime",},
        ],
      ]);
      const columns = ref([