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 | 717 ++++++++++++++++++++++++++++++++++++++++++----------------
1 files changed, 514 insertions(+), 203 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 89f5a59..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,219 +1,530 @@
<template>
- <div class="home-contianer">
-
+ <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="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>
-import { ref, onMounted, onUnmounted } from 'vue';
-export default {
- components: {},
- data() {
- return {
-
- n: 90,
- value1: '1',
- };
- },
- setup() {
- let open = (item) => {
- window.open(item.url, '_blank');
- };
- let interval;
- onMounted(() => {
- // interval = setInterval(() => {
- // chart2.xAxis[0].data.splice(0, 1);
- // let lastYear =
- // chart2.xAxis[0].data[chart2.xAxis[0].data.length - 1] * 1 + 1;
- // chart2.xAxis[0].data.push(lastYear);
+<script setup>
+import http from '../api/http.js';
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
+import * as echarts from 'echarts';
- // chart2.series[0].data.splice(0, 1);
- // chart2.series[0].data.push(~~(Math.random() * 1000));
+// 鍝嶅簲寮忔暟鎹�
+const dataMetrics = ref([]);
+const chartData = reactive({
+ outbound: { dates: [], values: [] },
+ inbound: { dates: [], values: [] },
+ monthData: { dates: [], inValue: [], outValue: [] }
+});
+const loading = ref(true);
+const error = ref(false);
- // chart2.series[1].data.splice(0, 1);
- // chart2.series[1].data.push(~~(Math.random() * 1000));
- // $chart2.setOption(chart2);
- // }, 2000);
- });
- onUnmounted(() => {
+// 鍥捐〃寮曠敤鍜屽疄渚�
+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([]);
- });
- return { open };
- },
- destroyed() {
+// 鍒濆鍖栧浘琛�
+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 {
+ 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;
}
};
-// window.addEventListener("resize", function () {
-// $chart2.setOption(chart2);
-// });
+
+// 鏁版嵁澶勭悊
+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);
+ intervalId.value = null;
+ }
+};
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ startPolling();
+ window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+ stopPolling();
+ charts.value.forEach(chart => chart && chart.dispose());
+ window.removeEventListener('resize', handleResize);
+});
</script>
-<style lang="less" scoped>
-.home-contianer {
- padding: 6px;
- background: #eee;
- width: 100%;
- height: 100%;
- // max-width: 800px;
- // position: absolute;
- top: 0;
- right: 0;
- left: 0;
- margin: 0 auto;
- .h-top {
- display: flex;
- .h-top-left {
- height: 100%;
- width: 300px;
- background: white;
- }
- height: 300px;
- }
- .h-top > div {
- border: 1px solid #e8e7e7;
- border-radius: 5px;
- // margin: 6px;
- }
- .h-top-center {
- height: 100%;
- background: white;
- margin: 0 6px;
- display: flex;
- flex-direction: column;
- flex: 1;
- .item1 .num {
- padding-top: 28px;
- }
- .item2 .num {
- padding-bottom: 20px;
- }
-
- .n-item {
- width: 100%;
- height: 100%;
- text-align: center;
- cursor: pointer;
- // display: flex;
- .item {
- border-right: 1px solid #e5e5e5;
- width: 33.3333333%;
- float: left;
- height: 50%;
- border-bottom: 1px solid #e5e5e5;
- padding: 47px 0;
- font-size: 13px;
- }
- .item:hover {
- background: #f9f9f9;
- cursor: pointer;
- }
- .item:last-child {
- border-right: 0;
- }
- .item3,
- .item6 {
- border-right: 0;
- }
- .num {
- word-break: break-all;
- color: #282727;
- font-size: 30px;
- transition: transform 0.8s;
- }
- .num:hover {
- color: #55ce80;
- transform: scale(1.2);
- }
- .text {
- font-size: 13px;
- color: #777;
- }
- }
- }
- .h-top-right {
- // flex: 1;
-
- width: 400px;
- height: 100%;
- background: white;
- }
- .h3 {
- padding: 7px 15px;
- font-weight: 500;
- background: #fff;
- border-bottom: 1px dotted #d4d4d4;
- }
+<style scoped>
+.dashboard-container {
+ padding: 20px;
+ background-color: #f5f6fa;
+ min-height: 100vh;
}
-.task-table {
- table {
- width: 100%;
- .thead {
- font-weight: bold;
- }
- tr {
- cursor: pointer;
- td {
- border-bottom: 1px solid #f3f3f3;
- padding: 9px 8px;
- font-size: 12px;
- }
- }
- tr:hover {
- background: #eee;
- }
- }
-}
-.h-chart {
- height: 340px;
- margin: 6px 0px;
+
+.overview-section {
display: flex;
- .h-left-grid {
- width: 300px;
- height: 100%;
- background: white;
- display: inline-block;
- .name {
- margin-left: 7px;
- }
- .item:hover {
- background: #f9f9f9;
- cursor: pointer;
- }
- .item {
- padding: 22px 14px;
- float: left;
- width: 50%;
- height: 33.33333%;
- border-bottom: 1px solid #eee;
- border-right: 1px solid #eee;
- i {
- font-size: 30px;
- }
- .desc {
- font-size: 12px;
- color: #c3c3c3;
- padding: 5px 0 0 4px;
- line-height: 1.5;
- }
- }
- }
+ gap: 20px;
+ margin-bottom: 20px;
}
-#h-chart2 {
- border-radius: 3px;
- background: white;
- padding-top: 10px;
- height: 100%;
- width: 0;
- flex: 1;
- margin: 0 7px;
-}
-#h-chart3 {
- border-radius: 3px;
- padding: 10px 10px 0 10px;
- background: white;
- // padding-top: 10px;
- height: 100%;
- width: 400px;
+.data-overview {
+ flex: 1;
+ background: white;
+ padding: 16px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
-</style>
+
+.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;
+ 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