From 1f9a89458a66c12c1f7dfbceb2588ada8fbf16f4 Mon Sep 17 00:00:00 2001
From: huangxiaoqiang <huangxiaoqiang@hnkhzn.com>
Date: 星期四, 13 十一月 2025 17:35:36 +0800
Subject: [PATCH] 新增移库页面与WebSocket支持,优化任务逻辑新增移库页面支持移库任务输入与提交。新增 WebSocket 支持(`websocket.js`、`WebSocketSetup.cs`),实现实时通信功能。 在任务状态枚举中新增 AGV 移库任务状`TaskStatusEnum.cs`)- 重构库存数据展示页面(`Home.vue`),新增数据总览与图表展示功能。 - 新增库存数据接口(`StockInfoService.cs`、`StockInfoController.cs`),支持库存统计与展示。 - 优化任务状态管理逻辑,支持 AGV 移库任务(`Dt_TaskService.cs`)。

---
 项目代码/WMS/WIDESEA_WMSClient/src/views/Home.vue |  592 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 498 insertions(+), 94 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 17c134c..832503f 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,126 +1,530 @@
 <template>
-  <div class="chart_left" style="margin-left: 20px;">
-    <div class="titles">
-      <el-icon class="icons" :size="24" color="#409EFF">
-        <Checked />
-      </el-icon>
-      浠诲姟杩涜涓�
+  <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>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="charts-section">
+      <el-row :gutter="20">
+        <el-col :lg="12">
+          <div class="chart-container">
+            <h3>鍑哄簱閲�</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>
+            <div v-else ref="outboundChart" class="chart"></div>
+          </div>
+        </el-col>
+        <el-col :lg="12">
+          <div class="chart-container">
+            <h3>鍏ュ簱閲�</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>
+            <div v-else ref="inboundChart" class="chart"></div>
+          </div>
+        </el-col>
+      </el-row>
     </div>
     <div style="margin-top: 20px;"></div>
-    <div class="item_center">
-      <Task-List :data="chartData" :options="chartOptions" />
-    </div>
-  </div>
-  <div class="chart_left" style="margin-left: 20px;">
-    <div class="titles">
-      <el-icon class="icons" :size="24" color="#409EFF">
-        <Checked />
-      </el-icon>
-      浠诲姟杩涜涓�
-    </div>
-    <div style="margin-top: 20px;"></div>
-    <div class="item_center">
-      <Task-List :data="chartData" :options="chartOptions" />
+    <div class="charts-section">
+      <el-row :gutter="20">
+        <el-col :lg="24">
+          <div class="chart-container">
+            <h3>鏈堝嚭鍏ュ簱閲�</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 v-else ref="monthDataChart" class="chart1"></div>
+          </div>
+        </el-col>
+      </el-row>
     </div>
   </div>
 </template>
-<script setup>
-import TaskList from '../components/index/TaskList.vue';
-import http from '../api/http.js';
-import { ref, onMounted, onUnmounted, watch } from 'vue'; 
-const taskListRef = ref(null); 
-// 绀轰緥鏁版嵁
-const chartData = ref([
-  // { value: 103, name: '宸插彇娑�', itemStyle: { color: '#FF6B6B' } },
-  // { value: 735, name: '宸插畬鎴�', itemStyle: { color: '#4ECDC4' } },
-]);
 
+<script setup>
+import http from '../api/http.js';
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
+import * as echarts from 'echarts';
+
+// 鍝嶅簲寮忔暟鎹�
+const dataMetrics = ref([]);
+const chartData = reactive({
+  outbound: { dates: [], values: [] },
+  inbound: { dates: [], values: [] },
+  monthData: { dates: [], inValue: [], outValue: [] }
+});
+const loading = ref(true);
+const error = ref(false);
+
+// 鍥捐〃寮曠敤鍜屽疄渚�
+const outboundChart = ref(null);
+const inboundChart = ref(null);
+const monthDataChart = ref(null);
+const outboundInstance = ref(null);
+const inboundInstance = ref(null);
+const monthDataInstance = ref(null);
+const charts = ref([]);
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+  if (!outboundChart.value || !inboundChart.value || !monthDataChart.value) {
+    console.log('鍥捐〃瀹瑰櫒鏈壘鍒帮紝寤惰繜鍒濆鍖�');
+    return;
+  }
+
+  outboundInstance.value = echarts.init(outboundChart.value);
+  inboundInstance.value = echarts.init(inboundChart.value);
+  monthDataInstance.value = echarts.init(monthDataChart.value);
+
+  charts.value = [outboundInstance.value, inboundInstance.value, monthDataInstance.value];
+
+  // 鍑哄簱閲忓浘琛ㄩ厤缃紙鏌辩姸鍥撅級
+  const outboundOption = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow' // 闃村奖鎸囩ず鍣�
+      },
+      formatter: function (params) {
+        const data = params[0];
+        return `
+          <div style="font-weight: bold; margin-bottom: 5px;">${data.name}</div>
+          <div style="display: flex; align-items: center;">
+            <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 5px;"></span>
+            <span>${data.seriesName}: </span>
+            <span style="font-weight: bold; margin-left: 5px;">${data.value}</span>
+          </div>
+        `;
+      },
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#ddd',
+      borderWidth: 1,
+      textStyle: {
+        color: '#333'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: chartData.outbound.dates,
+      axisLabel: {
+        rotate: 45
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '鏁伴噺'
+    },
+    series: [{
+      name: '鍑哄簱閲�',
+      data: chartData.outbound.values,
+      type: 'bar',
+      itemStyle: {
+        color: '#e06e6e'
+      },
+      barWidth: '60%',
+      animation: true,
+      label: {
+        show: true,
+        position: 'top',
+        formatter: '{c}',
+        color: '#e06e6e'
+      }
+    }]
+  };
+
+  // 鍏ュ簱閲忓浘琛ㄩ厤缃紙鎶樼嚎鍥撅級
+  const inboundOption = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line' // 绾垮瀷鎸囩ず鍣�
+      },
+      formatter: function (params) {
+        const data = params[0];
+        return `
+          <div style="font-weight: bold; margin-bottom: 5px;">${data.name}</div>
+          <div style="display: flex; align-items: center;">
+            <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 5px;"></span>
+            <span>${data.seriesName}: </span>
+            <span style="font-weight: bold; margin-left: 5px;">${data.value}</span>
+          </div>
+        `;
+      },
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#ddd',
+      borderWidth: 1,
+      textStyle: {
+        color: '#333'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: chartData.inbound.dates,
+      axisLabel: {
+        rotate: 45
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '鏁伴噺'
+    },
+    series: [{
+      name: '鍏ュ簱閲�',
+      data: chartData.inbound.values,
+      type: 'line',
+      itemStyle: {
+        color: '#4a7bff'
+      },
+      lineStyle: {
+        width: 3
+      },
+      smooth: true,
+      animation: true,
+      label: {
+        show: true,
+        position: 'top',
+        formatter: '{c}',
+        color: '#4a7bff'
+      }
+    }]
+  };
+
+  // 鏈堝嚭鍏ュ簱閲忓浘琛ㄩ厤缃紙鍙屾姌绾垮浘锛�
+  const monthDataOption = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross', // 鍗佸瓧鍑嗘槦鎸囩ず鍣�
+        crossStyle: {
+          color: '#999'
+        }
+      },
+      formatter: function (params) {
+        let html = `<div style="font-weight: bold; margin-bottom: 5px;">${params[0].name}</div>`;
+        params.forEach(param => {
+          html += `
+            <div style="display: flex; align-items: center; margin: 2px 0;">
+              <span style="display: inline-block; width: 10px; height: 10px; background: ${param.color}; border-radius: 50%; margin-right: 5px;"></span>
+              <span>${param.seriesName}: </span>
+              <span style="font-weight: bold; margin-left: 5px;">${param.value}</span>
+            </div>
+          `;
+        });
+        return html;
+      },
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#ddd',
+      borderWidth: 1,
+      textStyle: {
+        color: '#333'
+      }
+    },
+    legend: {
+      data: ['鍏ュ簱閲�', '鍑哄簱閲�'],
+      bottom: 0
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '10%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: chartData.monthData.dates,
+      axisLabel: {
+        rotate: 45
+      },
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '鏁伴噺'
+    },
+    series: [
+      {
+        name: '鍏ュ簱閲�',
+        data: chartData.monthData.inValue,
+        type: 'line',
+        itemStyle: {
+          color: '#4a7bff'
+        },
+        lineStyle: {
+          width: 3
+        },
+        smooth: true,
+        animation: true,
+        label: {
+          show: true,
+          position: 'top',
+          formatter: '{c}',
+          color: '#4a7bff'
+        }
+      },
+      {
+        name: '鍑哄簱閲�',
+        data: chartData.monthData.outValue,
+        type: 'line',
+        itemStyle: {
+          color: '#e06e6e'
+        },
+        lineStyle: {
+          width: 3
+        },
+        smooth: true,
+        animation: true,
+        label: {
+          show: true,
+          position: 'top',
+          formatter: '{c}',
+          color: '#e06e6e'
+        }
+      }
+    ]
+  };
+
+  outboundInstance.value.setOption(outboundOption);
+  inboundInstance.value.setOption(inboundOption);
+  monthDataInstance.value.setOption(monthDataOption);
+};
+
+// 鏇存柊鍥捐〃鏁版嵁
+const updateCharts = () => {
+  nextTick(() => {
+    if (outboundInstance.value && chartData.outbound.values.length > 0) {
+      outboundInstance.value.setOption({
+        xAxis: { data: chartData.outbound.dates },
+        series: [{ data: chartData.outbound.values }]
+      });
+    }
+
+    if (inboundInstance.value && chartData.inbound.values.length > 0) {
+      inboundInstance.value.setOption({
+        xAxis: { data: chartData.inbound.dates },
+        series: [{ data: chartData.inbound.values }]
+      });
+    }
+
+    if (monthDataInstance.value && chartData.monthData.dates.length > 0) {
+      monthDataInstance.value.setOption({
+        xAxis: { data: chartData.monthData.dates },
+        series: [
+          { data: chartData.monthData.inValue },
+          { data: chartData.monthData.outValue }
+        ]
+      });
+    }
+  });
+};
+
+// 鍝嶅簲寮忕獥鍙h皟鏁�
+const handleResize = () => {
+  charts.value.forEach(chart => chart && chart.resize());
+};
+
+// 鏁版嵁鑾峰彇
 const fetchData = async () => {
   try {
-    const response = await http.post("api/Task/GetTaskData", {});
-    chartData.value = response.data; 
+    loading.value = true;
+    error.value = false;
 
-    if (taskListRef.value) taskListRef.value.initChart();
-  } catch (error) {
-    console.error('API璇锋眰澶辫触:', error);
+    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 intervalId = ref(null)
-const startPolling = () => {
-  fetchData(); // 鍒濆鍔犺浇
-  intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5鍒嗛挓
+
+// 鏁版嵁澶勭悊
+const handleDataUpdate = (data) => {
+  console.log('澶勭悊鏁版嵁:', data);
+
+  // 鏇存柊鏁版嵁鎸囨爣
+  if (data.metrics && Array.isArray(data.metrics)) {
+    dataMetrics.value = data.metrics.map(item => ({
+      name: item.name || item.Name || '鏈煡鎸囨爣',
+      value: item.value != null ? item.value : item.Value || 0
+    }));
+  }
+
+  // 鏇存柊鍥捐〃鏁版嵁
+  if (data.outbound) {
+    chartData.outbound.dates = data.outbound.dates || [];
+    chartData.outbound.values = data.outbound.values || [];
+  }
+
+  if (data.inbound) {
+    chartData.inbound.dates = data.inbound.dates || [];
+    chartData.inbound.values = data.inbound.values || [];
+  }
+
+  if (data.monthData) {
+    chartData.monthData.dates = data.monthData.dates || [];
+    chartData.monthData.inValue = data.monthData.inValue || [];
+    chartData.monthData.outValue = data.monthData.outValue || [];
+  }
+
+  console.log('鏇存柊鍚庣殑鏁版嵁鎸囨爣:', dataMetrics.value);
+  console.log('鏇存柊鍚庣殑鍥捐〃鏁版嵁:', chartData);
+
+  // 寤惰繜鍒濆鍖栧浘琛紝纭繚鏁版嵁宸叉洿鏂�
+  nextTick(() => {
+    if (!outboundInstance.value || !inboundInstance.value || !monthDataInstance.value) {
+      initCharts();
+    } else {
+      updateCharts();
+    }
+  });
 };
 
-// 娓呯悊瀹氭椂鍣�
+// 杞鎺у埗
+const intervalId = ref(null);
+const startPolling = () => {
+  fetchData();
+  intervalId.value = setInterval(fetchData, 5 * 60 * 1000);
+};
+
 const stopPolling = () => {
   if (intervalId.value) {
     clearInterval(intervalId.value);
-    console.log('宸插仠姝㈡暟鎹疆璇�');
+    intervalId.value = null;
   }
 };
+
+// 鐢熷懡鍛ㄦ湡
 onMounted(() => {
   startPolling();
+  window.addEventListener('resize', handleResize);
 });
+
 onUnmounted(() => {
   stopPolling();
-});
-const chartOptions = ref({
-  tooltip: {
-    formatter: '{b}: {c} ({d}%)'
-  },
-  legend: {
-    itemWidth: 14,
-    itemHeight: 14,
-    textStyle: { fontSize: 12 }
-  }
+  charts.value.forEach(chart => chart && chart.dispose());
+  window.removeEventListener('resize', handleResize);
 });
 </script>
+
 <style scoped>
-.dashboard {
-  width: 100%;
-  height: 100vh;
+.dashboard-container {
   padding: 20px;
-  box-sizing: border-box;
+  background-color: #f5f6fa;
+  min-height: 100vh;
 }
 
-.m-charts {
-  display: grid;
-  margin: 10px;
-  grid-template-columns: 33% 33% 95%;
-  justify-content: space-between;
-}
-
-.labelContent {
-  padding-top: 3vh;
-}
-
-.chart_left {
-  position: relative;
-  border-radius: 10px;
-  background-color: white;
-  box-shadow: 0px 0px 10px 0px #ccc;
-  height: 100%;
-  width: 550px;
-}
-
-.icons {
-  margin-right: 5px;
-}
-
-.indexModel .item_center {
-  height: 90vh;
-  width: 98%;
-  margin: 0vh 20%;
-}
-
-.titles {
-  width: 200px;
-  height: 5vh;
+.overview-section {
   display: flex;
+  gap: 20px;
+  margin-bottom: 20px;
+}
+
+.data-overview {
+  flex: 1;
+  background: white;
+  padding: 16px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.metrics {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 16px;
+}
+
+.metric-item {
+  text-align: center;
+  flex: 1;
+  padding: 10px;
+}
+
+.metric-name {
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+}
+
+.metric-value {
+  font-size: 20px;
+  font-weight: bold;
+  margin: 8px 0;
+  color: #333;
+}
+
+.charts-section {
+  gap: 20px;
+}
+
+.chart-container {
+  flex: 1;
+  background: white;
+  padding: 16px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.chart {
+  height: 550px;
+  width: 100%;
+}
+
+.chart1 {
+  height: 550px;
+  width: 100%;
+}
+
+.loading,
+.error,
+.no-data {
+  display: flex;
+  justify-content: center;
   align-items: center;
-  font-size: 1.5rem;
-  font-weight: 600;
-  position: absolute;
-  top: 0px;
-  left: 15px;
-}</style>
\ No newline at end of file
+  height: 500px;
+  font-size: 18px;
+}
+
+.loading {
+  color: #999;
+}
+
+.error {
+  color: #2d8cf0;
+}
+
+.no-data {
+  color: #909399;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3