| | |
| | | <template> |
| | | <div class="dashboard-container"> |
| | | <!-- 顶部:本月出入库趋势 (全宽) --> |
| | | <!-- 各仓库月度出入库对比图 --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">每月出入库趋势</div> |
| | | <div id="chart-monthly-trend" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第二行:每日出入库趋势 (全宽) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">每日出入库趋势</div> |
| | | <div id="chart-daily" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第四行:仓库分布 --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">各仓库库存分布</div> |
| | | <div id="chart-warehouse" class="chart-content"></div> |
| | | <div class="card-title">各仓库月度出入库对比</div> |
| | | <div id="chart-warehouse-monthly" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | dailyData: [], |
| | | monthlyData: [], |
| | | warehouseData: [] |
| | | warehouseNames: ['FJSC1', 'ZJSC1', 'GWSC1', 'CWSC1', 'HCSC1'] |
| | | }; |
| | | }, |
| | | mounted() { |
| | |
| | | }, |
| | | |
| | | initCharts() { |
| | | this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend")); |
| | | this.charts.daily = echarts.init(document.getElementById("chart-daily")); |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | this.charts.warehouseMonthly = echarts.init(document.getElementById("chart-warehouse-monthly")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadMonthlyStats(); |
| | | await this.loadDailyStats(); |
| | | await this.loadStockByWarehouse(); |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 }); |
| | | if (res.status && res.data) { |
| | | console.log("每月统计数据:", res.data); |
| | | this.monthlyData = res.data; |
| | | this.updateMonthlyTrendChart(); |
| | | } |
| | | const promises = this.warehouseNames.map(warehouse => |
| | | this.http.get("/api/Dashboard/MonthlyStats", { |
| | | months: 6, |
| | | Roadway: warehouse |
| | | }) |
| | | ); |
| | | |
| | | const results = await Promise.all(promises); |
| | | |
| | | this.monthlyData = results.map((res, index) => ({ |
| | | warehouse: this.warehouseNames[index], |
| | | warehouseName: this.getWarehouseName(this.warehouseNames[index]), |
| | | data: res.data || [] |
| | | })); |
| | | |
| | | this.updateWarehouseMonthlyChart(); |
| | | } catch (e) { |
| | | console.error("加载每月统计失败", e); |
| | | } |
| | | }, |
| | | |
| | | async loadDailyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/DailyStats", { days: 30 }); |
| | | if (res.status && res.data) { |
| | | console.log("每日统计数据:", res.data); |
| | | this.dailyData = res.data; |
| | | this.updateDailyChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载每日统计失败", e); |
| | | } |
| | | }, |
| | | |
| | | async loadStockByWarehouse() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | if (res.status && res.data) { |
| | | console.log("仓库分布数据:", res.data); |
| | | this.warehouseData = res.data.data || res.data; |
| | | this.updateWarehouseChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载仓库分布失败", e); |
| | | } |
| | | }, |
| | | |
| | | updateMonthlyTrendChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.monthlyData.map(m => m.month), |
| | | axisLabel: { color: "#fff", rotate: 45 } |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: "value", |
| | | name: "数量", |
| | | axisLabel: { color: "#fff" } |
| | | } |
| | | ], |
| | | series: [ |
| | | { name: "入库", type: "bar", data: this.monthlyData.map(m => m.inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "出库", type: "line", data: this.monthlyData.map(m => m.outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | getWarehouseName(code) { |
| | | const nameMap = { |
| | | 'FJSC1': '负极卷1号仓库', |
| | | 'ZJSC1': '正极卷1号仓库', |
| | | 'GWSC1': '高温1号仓库', |
| | | 'CWSC1': '常温1号仓库', |
| | | 'HCSC1': '分容1号仓库' |
| | | }; |
| | | this.charts.monthlyTrend.setOption(option, true); |
| | | return nameMap[code] || code; |
| | | }, |
| | | |
| | | updateDailyChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.dailyData.map(d => d.date), |
| | | axisLabel: { |
| | | color: "#fff", |
| | | interval: 0, |
| | | rotate: 45, |
| | | fontSize: 12, |
| | | margin: 10 |
| | | }, |
| | | axisTick: { |
| | | alignWithLabel: true |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '10%', |
| | | containLabel: true |
| | | }, |
| | | series: [ |
| | | { name: "入库", type: "bar", data: this.dailyData.map(d => d.inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "出库", type: "bar", data: this.dailyData.map(d => d.outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.daily.setOption(option, true); |
| | | }, |
| | | |
| | | updateWarehouseChart() { |
| | | const warehouseNames = this.warehouseData.map(w => w.warehouse); |
| | | const totalStocks = this.warehouseData.map(w => w.total); |
| | | const hasStocks = this.warehouseData.map(w => w.hasStock); |
| | | const noStocks = this.warehouseData.map(w => w.noStock); |
| | | const hasStockPercentages = this.warehouseData.map(w => w.hasStockPercentage); |
| | | const noStockPercentages = this.warehouseData.map(w => w.noStockPercentage); |
| | | updateWarehouseMonthlyChart() { |
| | | // 获取所有月份 |
| | | const months = this.monthlyData[0]?.data.map(d => `${d.month}月`) || []; |
| | | |
| | | // 为每个仓库生成系列数据 |
| | | const series = []; |
| | | |
| | | this.monthlyData.forEach((warehouseData, index) => { |
| | | const data = warehouseData.data; |
| | | |
| | | series.push({ |
| | | name: warehouseData.warehouseName, |
| | | type: 'bar', |
| | | data: data.map(d => ({ |
| | | value: (d.inbound || 0) + (d.outbound || 0), |
| | | inbound: d.inbound || 0, |
| | | outbound: d.outbound || 0, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: function(params) { |
| | | return `入:${params.data.inbound}\n出:${params.data.outbound}`; |
| | | }, |
| | | fontSize: 10, |
| | | color: '#fff', |
| | | lineHeight: 15 |
| | | } |
| | | })), |
| | | barWidth: '15%', |
| | | barGap: '10%', |
| | | itemStyle: { |
| | | color: this.getBarColor(index), |
| | | borderRadius: [3, 3, 0, 0] |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '各仓库月度出入库对比', |
| | | textStyle: { |
| | | color: '#00ffff', |
| | | fontSize: 16 |
| | | }, |
| | | left: 'center', |
| | | top: 10 |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: function(params) { |
| | | let tip = params[0].name + '<br/>'; |
| | | let tip = `<strong>${params[0].axisValue}</strong><br/>`; |
| | | params.forEach(param => { |
| | | const dataIndex = param.dataIndex; |
| | | const warehouse = window.homeComponent.warehouseData[dataIndex]; |
| | | |
| | | if (param.seriesName === '已用容量') { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.hasStockPercentage})<br/>`; |
| | | tip += `有库存: ${warehouse.hasStock}<br/>`; |
| | | tip += `无库存: ${warehouse.noStock}<br/>`; |
| | | tip += `总容量: ${warehouse.total}`; |
| | | } else if (param.seriesName === '剩余容量') { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.noStockPercentage})<br/>`; |
| | | tip += `有库存: ${warehouse.hasStock}<br/>`; |
| | | tip += `无库存: ${warehouse.noStock}<br/>`; |
| | | tip += `总容量: ${warehouse.total}`; |
| | | } |
| | | tip += `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:5px;"></span>`; |
| | | tip += `${param.seriesName}: `; |
| | | tip += `入库:${param.data.inbound} | 出库:${param.data.outbound} | 总计:${param.value}<br/>`; |
| | | }); |
| | | return tip; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['已用容量', '剩余容量'], |
| | | textStyle: { color: '#fff' } |
| | | data: this.monthlyData.map(d => d.warehouseName), |
| | | textStyle: { color: '#fff', fontSize: 11 }, |
| | | top: 45, |
| | | left: 'center', |
| | | type: 'scroll' |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '10%', |
| | | top: '20%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: warehouseNames, |
| | | axisLabel: { color: '#fff', rotate: 30 } |
| | | data: months, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | fontSize: 11 |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { color: 'rgba(255,255,255,0.3)' } |
| | | }, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { color: '#fff' } |
| | | name: '数量', |
| | | nameTextStyle: { color: '#fff' }, |
| | | axisLabel: { color: '#fff' }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | series: [ |
| | | dataZoom: [ |
| | | { |
| | | name: '已用容量', |
| | | type: 'bar', |
| | | data: hasStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c} {a|' + hasStockPercentages[index] + '}', |
| | | rich: { |
| | | a: { |
| | | lineHeight: 20, |
| | | borderColor: '#91cc75', |
| | | color: '#91cc75' |
| | | } |
| | | } |
| | | } |
| | | })), |
| | | itemStyle: { color: '#91cc75' } |
| | | type: 'inside', |
| | | start: 0, |
| | | end: 100 |
| | | }, |
| | | { |
| | | name: '剩余容量', |
| | | type: 'bar', |
| | | data: noStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c} {a|' + noStockPercentages[index] + '}', |
| | | rich: { |
| | | a: { |
| | | lineHeight: 20, |
| | | borderColor: '#fac858', |
| | | color: '#fac858' |
| | | } |
| | | } |
| | | } |
| | | })), |
| | | itemStyle: { color: '#fac858' } |
| | | start: 0, |
| | | end: 100, |
| | | height: 20, |
| | | bottom: 0, |
| | | borderColor: 'rgba(255,255,255,0.3)', |
| | | fillerColor: 'rgba(0,255,255,0.1)', |
| | | handleStyle: { |
| | | color: '#00ffff', |
| | | borderColor: '#00ffff' |
| | | }, |
| | | textStyle: { |
| | | color: '#fff' |
| | | } |
| | | } |
| | | ] |
| | | ], |
| | | series: series |
| | | }; |
| | | |
| | | window.homeComponent = this; |
| | | |
| | | this.charts.warehouse.setOption(option, true); |
| | | |
| | | this.charts.warehouseMonthly.setOption(option, true); |
| | | }, |
| | | |
| | | getBarColor(index) { |
| | | const colors = [ |
| | | '#5470c6', // 蓝 |
| | | '#fac858', // 黄 |
| | | '#73c0de', // 天蓝 |
| | | '#fc8452', // 橙 |
| | | '#ea7ccc' // 粉 |
| | | ]; |
| | | return colors[index] || '#5470c6'; |
| | | } |
| | | } |
| | | }; |
| | |
| | | box-shadow: 2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .chart-card::before, |
| | | .chart-card::after { |
| | | animation: neon-flicker 2s infinite alternate; |
| | | } |
| | | |
| | | @keyframes neon-flicker { |
| | | 0%, 100% { |
| | | opacity: 1; |
| | | box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | 50% { |
| | | opacity: 0.8; |
| | | box-shadow: -2px -2px 5px #00ffff, 0 0 5px rgba(0, 255, 255, 0.5); |
| | | } |
| | | } |
| | | |
| | | .card-title { |
| | | color: #00ffff; |
| | | font-size: 16px; |
| | |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 280px; |
| | | height: 500px; |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 全宽图表 */ |
| | | .full-width .chart-card { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 350px; |
| | | height: 500px; |
| | | } |
| | | |
| | | /* 添加网格线效果 */ |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |