| | |
| | | <template> |
| | | <div class="title"></div> |
| | | <div class="dashboard-container"> |
| | | <!-- 各仓库月度出入库对比图 --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">各仓库月度出入库对比</div> |
| | | <div id="chart-warehouse-monthly" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { ref, reactive } from 'vue' |
| | | import * as echarts from "echarts"; |
| | | |
| | | export default { |
| | | setup() { |
| | | name: "Home", |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | monthlyData: [], |
| | | warehouseNames: ['FJSC1', 'ZJSC1', 'GWSC1', 'CWSC1', 'HCSC1'] |
| | | }; |
| | | }, |
| | | mounted() { |
| | | this.initCharts(); |
| | | this.loadData(); |
| | | window.addEventListener("resize", this.handleResize); |
| | | }, |
| | | beforeUnmount() { |
| | | window.removeEventListener("resize", this.handleResize); |
| | | Object.values(this.charts).forEach(chart => chart.dispose()); |
| | | }, |
| | | methods: { |
| | | handleResize() { |
| | | Object.values(this.charts).forEach(chart => chart.resize()); |
| | | }, |
| | | |
| | | initCharts() { |
| | | this.charts.warehouseMonthly = echarts.init(document.getElementById("chart-warehouse-monthly")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadMonthlyStats(); |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | try { |
| | | 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); |
| | | } |
| | | }, |
| | | |
| | | getWarehouseName(code) { |
| | | const nameMap = { |
| | | 'FJSC1': '负极卷1号仓库', |
| | | 'ZJSC1': '正极卷1号仓库', |
| | | 'GWSC1': '高温1号仓库', |
| | | 'CWSC1': '常温1号仓库', |
| | | 'HCSC1': '分容1号仓库' |
| | | }; |
| | | return nameMap[code] || code; |
| | | }, |
| | | |
| | | 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 = `<strong>${params[0].axisValue}</strong><br/>`; |
| | | params.forEach(param => { |
| | | 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: 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: 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', |
| | | name: '数量', |
| | | nameTextStyle: { color: '#fff' }, |
| | | axisLabel: { color: '#fff' }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | dataZoom: [ |
| | | { |
| | | type: 'inside', |
| | | start: 0, |
| | | end: 100 |
| | | }, |
| | | { |
| | | 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 |
| | | }; |
| | | |
| | | this.charts.warehouseMonthly.setOption(option, true); |
| | | }, |
| | | |
| | | getBarColor(index) { |
| | | const colors = [ |
| | | '#5470c6', // 蓝 |
| | | '#fac858', // 黄 |
| | | '#73c0de', // 天蓝 |
| | | '#fc8452', // 橙 |
| | | '#ea7ccc' // 粉 |
| | | ]; |
| | | return colors[index] || '#5470c6'; |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .title { |
| | | line-height: 70vh; |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | color: #e0e0e0; |
| | | min-height: calc(100vh - 60px); |
| | | background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| | | background-attachment: fixed; |
| | | } |
| | | |
| | | .chart-row { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-row.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart-card { |
| | | flex: 1; |
| | | background: rgba(10, 16, 35, 0.6); |
| | | backdrop-filter: blur(10px); |
| | | border: 1px solid rgba(64, 224, 208, 0.3); |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | position: relative; |
| | | box-shadow: |
| | | 0 0 15px rgba(0, 255, 255, 0.1), |
| | | inset 0 0 10px rgba(64, 224, 208, 0.1); |
| | | transition: all 0.3s ease; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .chart-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: |
| | | 0 0 25px rgba(0, 255, 255, 0.3), |
| | | inset 0 0 15px rgba(64, 224, 208, 0.2); |
| | | border: 1px solid rgba(64, 224, 208, 0.6); |
| | | } |
| | | |
| | | .chart-card::before { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #00ffff; |
| | | border-left: 2px solid #00ffff; |
| | | box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .chart-card::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #00ffff; |
| | | border-right: 2px solid #00ffff; |
| | | box-shadow: 2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .card-title { |
| | | color: #00ffff; |
| | | font-size: 16px; |
| | | text-align: center; |
| | | font-size: 28px; |
| | | color: orange; |
| | | margin-bottom: 10px; |
| | | text-shadow: 0 0 10px rgba(0, 255, 255, 0.7); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 500px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-card { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 500px; |
| | | } |
| | | |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-image: |
| | | linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(64, 224, 208, 0.05) 1px, transparent 1px); |
| | | background-size: 30px 30px; |
| | | pointer-events: none; |
| | | z-index: -1; |
| | | } |
| | | </style> |