From d57815781381a23be2255f1f93b480950c6c39a8 Mon Sep 17 00:00:00 2001
From: huangxiaoqiang <huangxiaoqiang@hnkhzn.com>
Date: 星期五, 14 十一月 2025 17:20:24 +0800
Subject: [PATCH] 新增任务看板滚动功能及优化文档布局在 Home.vue中新增任务看板滚动显示功能,优化数据总览和图表样式新增 iconfont 字体文件及相关样式,提升页面视觉效果。在 TaskService.cs和 AGVSignal.cs中新增对RelocationGroup 的任务状态处理逻辑。新增EnumHelper.cs 中的枚举描述获取方法,支持动态描述解析优化按钮逻辑,统一“其他出库”和“调拨出库”的处理方式更新文档布局文件,调整引用路径、索引和视图状态。 在StockInfoService.cs中新增任务数据查询逻辑,支持任务状态描述返回。

---
 项目代码/WMS/WIDESEA_WMSClient/src/views/Home.vue |  505 +++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 444 insertions(+), 61 deletions(-)

diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
index 832503f..c3b3de3 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
@@ -1,21 +1,113 @@
 <template>
   <div class="dashboard-container">
     <div class="overview-section">
-      <div class="data-overview">
-        <h3>鏁版嵁鎬昏</h3>
-        <div class="metrics">
-          <div class="metric-item" v-for="(item, index) in dataMetrics" :key="index">
-            <div class="metric-name">{{ item.name }}</div>
-            <div class="metric-value">{{ item.value }}</div>
+      <el-row :gutter="20">
+        <el-col :lg="24">
+          <div class="data-overview">
+            <h3 class="title">鏁版嵁鎬昏</h3>
+            <div class="metrics">
+              <div class="metric-item" v-for="(item, index) in dataMetrics" :key="index">
+                <div style="display: flex;align-items: center; margin-left: 80px;">
+                  <div class="metric-icon">
+                    <i :class="getMetricIcon(item.name)"></i>
+                  </div>
+                  <div class="metric-name">{{ item.name }}</div>
+                </div>
+                <div class="metric-value">{{ item.value }}</div>
+              </div>
+            </div>
           </div>
-        </div>
-      </div>
+        </el-col>
+      </el-row>
+    </div>
+    <div class="overview-section">
+      <el-row :gutter="20">
+        <el-col :lg="24">
+          <div class="bg-color-black task-board-container">
+            <div class="d-flex pt-2 pl-2">
+              <div class="d-flex">
+                <span class="fs-xl text mx-2 title">褰撳墠鐢熶骇浠诲姟</span>
+              </div>
+            </div>
+            <div class="body-box">
+              <div class="scroll-table-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll">
+                <!-- 琛ㄥご -->
+                <div class="table-header" ref="tableHeader">
+                  <table class="header-table">
+                    <thead>
+                      <tr>
+                        <th style="width: 60px;font-size: 18px;">搴忓彿</th>
+                        <th style="width: 100px;font-size: 18px;">鎵樼洏鐮�</th>
+                        <th style="width: 120px;font-size: 18px;">璧峰鍦板潃</th>
+                        <th style="width: 120px;font-size: 18px;">鐩爣鍦板潃</th>
+                        <th style="width: 120px;font-size: 18px;">浠诲姟绫诲瀷</th>
+                        <th style="width: 120px;font-size: 18px;">浠诲姟鐘舵��</th>
+                        <th style="width: 160px;font-size: 18px;">鍒涘缓鏃堕棿</th>
+                      </tr>
+                    </thead>
+                  </table>
+                </div>
+
+                <!-- 琛ㄦ牸鍐呭鍖哄煙 -->
+                <div class="table-body-container" ref="tableBody">
+                  <div class="table-body-wrapper" :style="{ transform: `translateY(-${scrollPosition}px)` }">
+                    <!-- 鍘熷鏁版嵁 -->
+                    <table class="body-table">
+                      <tbody>
+                        <tr v-for="(row, index) in tableData" :key="index"
+                          :class="index % 2 === 0 ? 'even-row' : 'odd-row'">
+                          <td style="width: 60px; text-align: center;font-size: 16px;">{{ index + 1 }}</td>
+                          <td style="width: 100px; text-align: center;font-size: 16px;">{{ row.palletCode || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.sourceAddress || '鏃�' }}
+                          </td>
+                          <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.targetAddress || '鏃�' }}
+                          </td>
+                          <td style="width: 120px; text-align: center;font-size: 16px;">{{ row.taskType || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;font-size: 16px;">
+                            <el-tag :type="getStatusType(row.taskState)" size="small">
+                              {{ row.taskState || '鏃�' }}
+                            </el-tag>
+                          </td>
+                          <td style="width: 160px; text-align: center;font-size: 18px;">{{ row.createDate || '鏃�' }}</td>
+                        </tr>
+                      </tbody>
+                    </table>
+
+                    <!-- 澶嶅埗涓�浠芥暟鎹敤浜庢棤缂濇粴鍔� -->
+                    <table class="body-table" v-if="tableData.length > rowNum">
+                      <tbody>
+                        <tr v-for="(row, index) in tableData" :key="`copy-${index}`"
+                          :class="index % 2 === 0 ? 'even-row' : 'odd-row'">
+                          <td style="width: 60px; text-align: center;">{{ index + tableData.length + 1 }}</td>
+                          <td style="width: 100px; text-align: center;">{{ row.palletCode || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;">{{ row.sourceAddress || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;">{{ row.targetAddress || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;">{{ row.taskType || '鏃�' }}</td>
+                          <td style="width: 120px; text-align: center;">
+                            <el-tag :type="getStatusType(row.taskState)" size="small">
+                              {{ row.taskState || '鏃�' }}
+                            </el-tag>
+                          </td>
+                          <td style="width: 160px; text-align: center;">{{ row.createDate || '鏃�' }}</td>
+                        </tr>
+                      </tbody>
+                    </table>
+                  </div>
+                </div>
+              </div>
+              <div v-if="tableData.length === 0" class="no-data-board">
+                <el-empty description="鏆傛棤鐢熶骇浠诲姟鏁版嵁" :image-size="80" />
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
     </div>
     <div class="charts-section">
       <el-row :gutter="20">
         <el-col :lg="12">
           <div class="chart-container">
-            <h3>鍑哄簱閲�</h3>
+            <h3 class="title">鍑哄簱閲�</h3>
             <div v-if="loading" class="loading">鍔犺浇涓�...</div>
             <div v-else-if="error" class="error">鏁版嵁鍔犺浇澶辫触</div>
             <div v-else-if="!chartData.outbound.values.length" class="no-data">鏆傛棤鍑哄簱鏁版嵁</div>
@@ -24,7 +116,7 @@
         </el-col>
         <el-col :lg="12">
           <div class="chart-container">
-            <h3>鍏ュ簱閲�</h3>
+            <h3 class="title">鍏ュ簱閲�</h3>
             <div v-if="loading" class="loading">鍔犺浇涓�...</div>
             <div v-else-if="error" class="error">鏁版嵁鍔犺浇澶辫触</div>
             <div v-else-if="!chartData.inbound.values.length" class="no-data">鏆傛棤鍏ュ簱鏁版嵁</div>
@@ -38,11 +130,11 @@
       <el-row :gutter="20">
         <el-col :lg="24">
           <div class="chart-container">
-            <h3>鏈堝嚭鍏ュ簱閲�</h3>
+            <h3 class="title">鏈堝嚭鍏ュ簱閲�</h3>
             <div v-if="loading" class="loading">鍔犺浇涓�...</div>
             <div v-else-if="error" class="error">鏁版嵁鍔犺浇澶辫触</div>
             <div v-else-if="!chartData.monthData.inValue.length || !chartData.monthData.outValue.length" class="no-data">
-              鏆傛棤鍑哄簱鏁版嵁</div>
+              鏆傛棤鍑哄叆搴撴暟鎹�</div>
             <div v-else ref="monthDataChart" class="chart1"></div>
           </div>
         </el-col>
@@ -53,11 +145,13 @@
 
 <script setup>
 import http from '../api/http.js';
-import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
+import { ref, reactive, onMounted, onUnmounted, nextTick, computed } from 'vue';
 import * as echarts from 'echarts';
+// import { TrendCharts } from '@element-plus/icons-vue';
 
 // 鍝嶅簲寮忔暟鎹�
 const dataMetrics = ref([]);
+const tableData = ref([]);
 const chartData = reactive({
   outbound: { dates: [], values: [] },
   inbound: { dates: [], values: [] },
@@ -66,6 +160,26 @@
 const loading = ref(true);
 const error = ref(false);
 
+// 婊氬姩鐩稿叧
+const tableHeader = ref(null);
+const tableBody = ref(null);
+const scrollPosition = ref(0);
+const isScrolling = ref(true);
+let scrollInterval = null;
+const rowNum = 7; // 鏄剧ず鐨勮鏁�
+const rowHeight = 40; // 姣忚楂樺害
+
+
+const getMetricIcon = (type) => {
+  const iconMap = {
+    '浠婃棩杩涘簱閲�': 'iconfont icon-cangpeitubiao_huanhuorukutuihuoruku',
+    '浠婃棩鍑哄簱閲�': 'el-icon-takeaway-box',
+    '鏈湀杩涘簱閲�': 'iconfont icon-cangpeitubiao_huanhuorukutuihuoruku',
+    '鏈湀鍑哄簱閲�': 'el-icon-takeaway-box',
+    '搴撳瓨鎬婚噺': 'el-icon-goods',
+  };
+  return iconMap[type] || 'el-icon-data-board';
+};
 // 鍥捐〃寮曠敤鍜屽疄渚�
 const outboundChart = ref(null);
 const inboundChart = ref(null);
@@ -74,6 +188,49 @@
 const inboundInstance = ref(null);
 const monthDataInstance = ref(null);
 const charts = ref([]);
+
+// 鍚姩婊氬姩
+const startScrolling = () => {
+  if (tableData.value.length <= rowNum) {
+    isScrolling.value = false;
+    return; // 鏁版嵁灏戞椂涓嶆粴鍔�
+  }
+
+  isScrolling.value = true;
+  const totalHeight = tableData.value.length * rowHeight;
+
+  scrollInterval = setInterval(() => {
+    scrollPosition.value += 1;
+
+    // 褰撴粴鍔ㄥ埌绗竴浠芥暟鎹殑鏈熬鏃讹紝閲嶇疆浣嶇疆瀹炵幇鏃犵紳婊氬姩
+    if (scrollPosition.value >= totalHeight) {
+      scrollPosition.value = 0;
+    }
+  }, 20); // 璋冩暣婊氬姩閫熷害
+};
+
+// 鏆傚仠婊氬姩
+const pauseScroll = () => {
+  if (scrollInterval) {
+    clearInterval(scrollInterval);
+    scrollInterval = null;
+  }
+  isScrolling.value = false;
+};
+
+// 鎭㈠婊氬姩
+const resumeScroll = () => {
+  if (tableData.value.length > rowNum) {
+    startScrolling();
+  }
+};
+
+// 鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+  const statusMap = {
+  };
+  return statusMap[status] || 'primary';
+};
 
 // 鍒濆鍖栧浘琛�
 const initCharts = () => {
@@ -93,7 +250,7 @@
     tooltip: {
       trigger: 'axis',
       axisPointer: {
-        type: 'shadow' // 闃村奖鎸囩ず鍣�
+        type: 'shadow'
       },
       formatter: function (params) {
         const data = params[0];
@@ -153,7 +310,7 @@
     tooltip: {
       trigger: 'axis',
       axisPointer: {
-        type: 'line' // 绾垮瀷鎸囩ず鍣�
+        type: 'line'
       },
       formatter: function (params) {
         const data = params[0];
@@ -216,7 +373,7 @@
     tooltip: {
       trigger: 'axis',
       axisPointer: {
-        type: 'cross', // 鍗佸瓧鍑嗘槦鎸囩ず鍣�
+        type: 'cross',
         crossStyle: {
           color: '#999'
         }
@@ -346,32 +503,9 @@
   charts.value.forEach(chart => chart && chart.resize());
 };
 
-// 鏁版嵁鑾峰彇
-const fetchData = async () => {
-  try {
-    loading.value = true;
-    error.value = false;
-
-    const response = await http.post("api/StockInfo/GetStockData", {});
-    console.log('API鍝嶅簲鏁版嵁:', response.data);
-
-    if (response.data && response.data.success !== false) {
-      handleDataUpdate(response.data);
-    } else {
-      throw new Error('API杩斿洖鏁版嵁鏍煎紡閿欒');
-    }
-
-    loading.value = false;
-  } catch (err) {
-    console.error('API璇锋眰澶辫触:', err);
-    loading.value = false;
-    error.value = true;
-  }
-};
-
 // 鏁版嵁澶勭悊
 const handleDataUpdate = (data) => {
-  console.log('澶勭悊鏁版嵁:', data);
+  console.log('API鍝嶅簲鏁版嵁:', data);
 
   // 鏇存柊鏁版嵁鎸囨爣
   if (data.metrics && Array.isArray(data.metrics)) {
@@ -398,8 +532,28 @@
     chartData.monthData.outValue = data.monthData.outValue || [];
   }
 
-  console.log('鏇存柊鍚庣殑鏁版嵁鎸囨爣:', dataMetrics.value);
-  console.log('鏇存柊鍚庣殑鍥捐〃鏁版嵁:', chartData);
+  // 鏇存柊琛ㄦ牸鏁版嵁
+  if (data && data.newTask && Array.isArray(data.newTask)) {
+    tableData.value = data.newTask.map(task => ({
+      palletCode: task.palletCode || '鏃�',
+      roadway: task.roadway || '鏃�',
+      sourceAddress: task.sourceAddress || '鏃�',
+      targetAddress: task.targetAddress || '鏃�',
+      taskType: task.taskType || '鏃�',
+      taskState: task.taskState || '鏃�',
+      errorMessage: task.errorMessage || '鏃�',
+      createDate: task.createDate || '鏃�',
+    }));
+
+    // 鏁版嵁鏇存柊鍚庨噸鏂板惎鍔ㄦ粴鍔�
+    nextTick(() => {
+      pauseScroll();
+      startScrolling();
+    });
+  } else {
+    tableData.value = [];
+    pauseScroll();
+  }
 
   // 寤惰繜鍒濆鍖栧浘琛紝纭繚鏁版嵁宸叉洿鏂�
   nextTick(() => {
@@ -411,11 +565,32 @@
   });
 };
 
-// 杞鎺у埗
+// 鏁版嵁鑾峰彇
+const fetchData = async () => {
+  try {
+    loading.value = true;
+    error.value = false;
+
+    const response = await http.post("api/StockInfo/GetStockData", {});
+    console.log('API鍝嶅簲:', response);
+
+    if (response.data && response.data.success !== false) {
+      handleDataUpdate(response.data);
+    } else {
+      throw new Error('API杩斿洖鏁版嵁鏍煎紡閿欒');
+    }
+    loading.value = false;
+  } catch (err) {
+    console.error('API璇锋眰澶辫触:', err);
+    loading.value = false;
+    error.value = true;
+  }
+};
+
 const intervalId = ref(null);
 const startPolling = () => {
   fetchData();
-  intervalId.value = setInterval(fetchData, 5 * 60 * 1000);
+  intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5鍒嗛挓杞
 };
 
 const stopPolling = () => {
@@ -423,6 +598,7 @@
     clearInterval(intervalId.value);
     intervalId.value = null;
   }
+  pauseScroll();
 };
 
 // 鐢熷懡鍛ㄦ湡
@@ -438,7 +614,7 @@
 });
 </script>
 
-<style scoped>
+<style scoped lang="less">
 .dashboard-container {
   padding: 20px;
   background-color: #f5f6fa;
@@ -446,23 +622,179 @@
 }
 
 .overview-section {
-  display: flex;
-  gap: 20px;
   margin-bottom: 20px;
 }
 
 .data-overview {
-  flex: 1;
   background: white;
   padding: 16px;
   border-radius: 8px;
+  height: 200px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+  .title {
+    display: flex;
+    align-items: center;
+
+    &::before {
+      display: block;
+      content: "";
+      width: 6px;
+      height: 20px;
+      background-color: #409eff;
+      margin-right: 10px;
+    }
+  }
+}
+
+.task-board-container {
+  height: 300px;
+  border-radius: 8px;
+  background-color: white;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.d-flex {
+  display: flex;
+
+  .title {
+    display: flex;
+    align-items: center;
+
+    &::before {
+      display: block;
+      content: "";
+      width: 6px;
+      height: 20px;
+      background-color: #409eff;
+      margin-right: 10px;
+    }
+  }
+}
+
+.pt-2 {
+  padding-top: 0.5rem;
+}
+
+.pl-2 {
+  padding-left: 0.5rem;
+}
+
+.text-icon {
+  color: #409eff;
+  font-size: 18px;
+}
+
+.fs-xl {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.text {
+  color: #333;
+}
+
+.mx-2 {
+  margin-left: 0.5rem;
+  margin-right: 0.5rem;
+}
+
+.body-box {
+  flex: 1;
+  padding: 10px;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.scroll-table-container {
+  height: 250px;
+  position: relative;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.table-header {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 10;
+  background: #0066cc;
+}
+
+.header-table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.header-table th {
+  background: #0066cc;
+  color: white;
+  font-weight: bold;
+  padding: 8px 4px;
+  text-align: center;
+  border-right: 1px solid #0055aa;
+  font-size: 12px;
+}
+
+.header-table th:last-child {
+  border-right: none;
+}
+
+.table-body-container {
+  position: absolute;
+  top: 40px;
+  /* 琛ㄥご楂樺害 */
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+}
+
+.table-body-wrapper {
+  transition: transform 0.1s linear;
+}
+
+.body-table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.body-table td {
+  padding: 8px 4px;
+  border-bottom: 1px solid #f0f0f0;
+  font-size: 12px;
+  height: 40px;
+  box-sizing: border-box;
+}
+
+.even-row {
+  background-color: #f8f9fa;
+}
+
+.odd-row {
+  background-color: #ffffff;
+}
+
+.body-table tr:hover {
+  background-color: #e6f7ff !important;
+}
+
+.no-data-board {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  width: 100%;
 }
 
 .metrics {
   display: flex;
   justify-content: space-between;
-  margin-top: 16px;
+  margin-top: 15px;
 }
 
 .metric-item {
@@ -472,48 +804,60 @@
 }
 
 .metric-name {
-  font-size: 14px;
+  font-size: 24px;
   color: #666;
   margin-bottom: 8px;
 }
 
 .metric-value {
-  font-size: 20px;
+  font-size: 22px;
   font-weight: bold;
-  margin: 8px 0;
+  margin: 20px 0;
   color: #333;
 }
 
 .charts-section {
-  gap: 20px;
+  margin-bottom: 20px;
 }
 
 .chart-container {
-  flex: 1;
   background: white;
   padding: 16px;
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  height: 400px;
+
+  .title {
+    display: flex;
+    align-items: center;
+
+    &::before {
+      display: block;
+      content: "";
+      width: 6px;
+      height: 20px;
+      background-color: #409eff;
+      margin-right: 10px;
+    }
+  }
 }
 
 .chart {
-  height: 550px;
+  height: 350px;
   width: 100%;
 }
 
 .chart1 {
-  height: 550px;
+  height: 350px;
   width: 100%;
 }
 
 .loading,
 .error,
 .no-data {
-  display: flex;
-  justify-content: center;
-  align-items: center;
+  margin-top: 50px;
   height: 500px;
-  font-size: 18px;
+  font-size: 20px;
 }
 
 .loading {
@@ -521,10 +865,49 @@
 }
 
 .error {
-  color: #2d8cf0;
+  color: #f56c6c;
+}
+
+.metric-icon {
+  width: 28px;
+  height: 28px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 12px;
+  margin-bottom: 6px;
+  font-size: 24px;
+  background: rgba(255, 255, 255, 0.8);
 }
 
 .no-data {
   color: #909399;
 }
+
+/* Element Plus 鏍囩鏍峰紡璋冩暣 */
+:deep(.el-tag) {
+  border: none;
+  font-size: 12px;
+}
+
+:deep(.el-tag--success) {
+  background-color: #f0f9ff;
+  color: #67c23a;
+}
+
+:deep(.el-tag--primary) {
+  background-color: #f0f9ff;
+  color: #409eff;
+}
+
+:deep(.el-tag--info) {
+  background-color: #f4f4f5;
+  color: #909399;
+}
+
+:deep(.el-tag--danger) {
+  background-color: #fef0f0;
+  color: #f56c6c;
+}
 </style>
\ No newline at end of file

--
Gitblit v1.9.3