| | |
| | | <template> |
| | | <div class="dashboard-container"> |
| | | <!-- 顶部KPI卡片:显示仓库总数和总库存量 --> |
| | | <!-- 顶部KPI卡片:显示总货位及各仓库货位 --> |
| | | <div class="kpi-cards"> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">🏚️</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">仓库总数</div> |
| | | <div class="kpi-value">{{ totalWarehouses }}</div> |
| | | <div class="kpi-label">总货位</div> |
| | | <div class="kpi-value">{{ totalLocation }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">📦</div> |
| | | <div class="kpi-icon">🔥</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">总库存量</div> |
| | | <div class="kpi-value">{{ totalStock.toLocaleString() }}</div> |
| | | <div class="kpi-label">化成库</div> |
| | | <div class="kpi-value">{{ warehouseLocations.hc }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">📊</div> |
| | | <div class="kpi-icon">🌡️</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">本月总入库</div> |
| | | <div class="kpi-value">{{ monthlyInboundTotal.toLocaleString() }}</div> |
| | | <div class="kpi-label">高温库</div> |
| | | <div class="kpi-value">{{ warehouseLocations.gw }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">📤</div> |
| | | <div class="kpi-icon">❄️</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">本月总出库</div> |
| | | <div class="kpi-value">{{ monthlyOutboundTotal.toLocaleString() }}</div> |
| | | <div class="kpi-label">常温库</div> |
| | | <div class="kpi-value">{{ warehouseLocations.cw }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">📜</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">极卷库</div> |
| | | <div class="kpi-value">{{ warehouseLocations.jj }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 顶部:本月出入库趋势 - 上3下2布局,每个卡片直接显示仓库数字 --> |
| | | <div class="chart-row top-three"> |
| | | <div v-for="warehouse in topWarehouses" :key="warehouse.code" class="chart-card"> |
| | | <div class="card-title">{{ warehouse.name }}</div> |
| | | <!-- 仓库数字显示区域 --> |
| | | <!-- 第一行:4个每日出入库趋势图(每行2个) --> |
| | | <div class="chart-row daily-grid"> |
| | | <div class="chart-card" v-for="warehouse in dailyWarehouses" :key="warehouse.code"> |
| | | <div class="card-title">{{ warehouse.name }} - 每日趋势</div> |
| | | <!-- 仓库数字显示区域:显示每日总量和空托盘数量 --> |
| | | <div class="warehouse-numbers"> |
| | | <div class="number-item inbound"> |
| | | <span class="number-label">入库</span> |
| | | <span class="number-value">{{ getMonthlyInbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item outbound"> |
| | | <span class="number-label">出库</span> |
| | | <span class="number-value">{{ getMonthlyOutbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item stock"> |
| | | <span class="number-label">库存</span> |
| | | <span class="number-value">{{ getWarehouseStock(warehouse.code) }}</span> |
| | | </div> |
| | | <!-- 极卷库显示有货托盘(电池数量)和空托盘 --> |
| | | <template v-if="warehouse.code === 'ROLL'"> |
| | | <div class="number-item battery"> |
| | | <span class="number-label">电池数量</span> |
| | | <span class="number-value">{{ getBatteryCount(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item empty-tray"> |
| | | <span class="number-label">空托盘数量</span> |
| | | <span class="number-value">{{ getEmptyTrayCount(warehouse.code) }}</span> |
| | | </div> |
| | | </template> |
| | | <!-- 其他仓库显示电池数量和空托盘数量 --> |
| | | <template v-else> |
| | | <div class="number-item inbound"> |
| | | <span class="number-label">电池数量</span> |
| | | <span class="number-value">{{ getBatteryCount(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item empty-tray"> |
| | | <span class="number-label">空托盘数量</span> |
| | | <span class="number-value">{{ getEmptyTrayCount(warehouse.code) }}</span> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | <div :id="`chart-${warehouse.code}`" class="chart-content"></div> |
| | | <div :id="`daily-chart-${warehouse.code}`" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="chart-row bottom-two"> |
| | | <div v-for="warehouse in bottomWarehouses" :key="warehouse.code" class="chart-card"> |
| | | <div class="card-title">{{ warehouse.name }}</div> |
| | | <!-- 仓库数字显示区域 --> |
| | | <div class="warehouse-numbers"> |
| | | <div class="number-item inbound"> |
| | | <span class="number-label">入库</span> |
| | | <span class="number-value">{{ getMonthlyInbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item outbound"> |
| | | <span class="number-label">出库</span> |
| | | <span class="number-value">{{ getMonthlyOutbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item stock"> |
| | | <span class="number-label">库存</span> |
| | | <span class="number-value">{{ getWarehouseStock(warehouse.code) }}</span> |
| | | </div> |
| | | </div> |
| | | <div :id="`chart-${warehouse.code}`" 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> |
| | |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | // 五个仓库定义 - 上3个 |
| | | topWarehouses: [ |
| | | { code: "GWSC1", name: "高温1号仓库" }, |
| | | { code: "CWSC1", name: "常温1号仓库" }, |
| | | { code: "HCSC1", name: "分容1号仓库" } |
| | | // 四个核心仓库(合并了正负极卷库为极卷库) |
| | | dailyWarehouses: [ |
| | | { code: "HCSC1", name: "化成库", type: "hc" }, |
| | | { code: "GWSC1", name: "高温库", type: "gw" }, |
| | | { code: "CWSC1", name: "常温库", type: "cw" }, |
| | | { code: "ROLL", name: "极卷库", type: "jj" } |
| | | ], |
| | | // 下2个 |
| | | bottomWarehouses: [ |
| | | { code: "FJSC1", name: "负极卷1号仓库" }, |
| | | { code: "ZJSC1", name: "正极卷1号仓库" } |
| | | ], |
| | | dailyData: [], |
| | | // 存储每个仓库的月度数据 |
| | | monthlyData: { |
| | | // 原始每日数据存储 (其中ROLL为合并后的极卷库数据) |
| | | dailyDataMap: { |
| | | GWSC1: [], |
| | | CWSC1: [], |
| | | HCSC1: [], |
| | | FJSC1: [], |
| | | ZJSC1: [] |
| | | ROLL: [] |
| | | }, |
| | | // 存储每个仓库的当前库存 |
| | | // 存储每个仓库的电池数量 |
| | | warehouseStocks: { |
| | | GWSC1: 0, |
| | | CWSC1: 0, |
| | | HCSC1: 0, |
| | | FJSC1: 0, |
| | | ZJSC1: 0 |
| | | ROLL: 0 |
| | | }, |
| | | warehouseData: [], |
| | | // KPI 汇总数据 |
| | | totalWarehouses: 5, |
| | | totalStock: 0, |
| | | monthlyInboundTotal: 0, |
| | | monthlyOutboundTotal: 0 |
| | | // 极卷库特殊数据 |
| | | rollData: { |
| | | batteryCount: 0, // 电池数量 |
| | | emptyTrayCount: 0 // 空托盘数量 |
| | | }, |
| | | // 其他仓库的空托盘数量 |
| | | emptyTrayCounts: { |
| | | GWSC1: 0, |
| | | CWSC1: 0, |
| | | HCSC1: 0 |
| | | }, |
| | | warehouseData: [], // 仓库分布图数据 |
| | | // 仓库货位数据(固定配置) |
| | | warehouseLocations: { |
| | | hc: 35, // 化成库 |
| | | gw: 324, // 高温库 |
| | | cw: 140, // 常温库 |
| | | jj: 104 // 极卷库 |
| | | } |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 总货位计算 |
| | | totalLocation() { |
| | | return this.warehouseLocations.hc + this.warehouseLocations.gw + |
| | | this.warehouseLocations.cw + this.warehouseLocations.jj; |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.initCharts(); |
| | |
| | | }, |
| | | |
| | | initCharts() { |
| | | // 初始化所有仓库图表 |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | const chartId = `chart-${warehouse.code}`; |
| | | // 初始化每日图表 |
| | | this.dailyWarehouses.forEach(warehouse => { |
| | | const chartId = `daily-chart-${warehouse.code}`; |
| | | const el = document.getElementById(chartId); |
| | | if (el) { |
| | | this.charts[warehouse.code] = echarts.init(el); |
| | | this.charts[`daily-${warehouse.code}`] = echarts.init(el); |
| | | } |
| | | }); |
| | | // 初始化每日图表和仓库分布图表 |
| | | this.charts.daily = echarts.init(document.getElementById("chart-daily")); |
| | | // 初始化仓库分布图表 |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | // 并行加载所有仓库的月度数据(分别传入不同的Roadway参数) |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | const monthlyPromises = allWarehouses.map(warehouse => |
| | | this.loadMonthlyStatsForWarehouse(warehouse.code) |
| | | ); |
| | | await Promise.all(monthlyPromises); |
| | | // 更新所有仓库的月度图表 |
| | | this.updateAllMonthlyTrendCharts(); |
| | | |
| | | await this.loadDailyStats(); |
| | | await this.loadStockByWarehouse(); |
| | | await this.loadWarehouseStocks(); |
| | | this.calculateKPIs(); |
| | | }, |
| | | |
| | | async loadMonthlyStatsForWarehouse(roadway) { |
| | | console.log(`正在加载${roadway}的每月统计数据...`); |
| | | try { |
| | | // 关键修复:分别传入不同的Roadway参数 |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats?monthly=12&roadway=" + roadway); |
| | | if (res.status && res.data) { |
| | | console.log(`${roadway} 每月统计数据:`, res.data); |
| | | this.monthlyData[roadway] = res.data; |
| | | } else { |
| | | this.monthlyData[roadway] = []; |
| | | } |
| | | } catch (e) { |
| | | console.error(`加载${roadway}每月统计失败`, e); |
| | | this.monthlyData[roadway] = []; |
| | | // 并行加载所有数据 |
| | | await Promise.all([ |
| | | this.loadAllDailyStats(), |
| | | this.loadStockAndTrayCount(), |
| | | this.loadStockByWarehouse() |
| | | ]); |
| | | |
| | | // 更新所有图表 |
| | | this.updateAllDailyCharts(); |
| | | } catch (error) { |
| | | console.error("加载数据失败:", error); |
| | | this.$message?.error("数据加载失败,请稍后重试"); |
| | | } |
| | | }, |
| | | |
| | | 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 loadAllDailyStats() { |
| | | console.log("正在加载所有仓库的每日统计数据..."); |
| | | const res = await this.http.get("/api/Dashboard/DailyStats?days=10"); |
| | | if (res.status && res.data) { |
| | | console.log("所有仓库每日统计数据:", res.data); |
| | | |
| | | const dataArray = res.data; |
| | | |
| | | // 按仓库分类数据 |
| | | dataArray.forEach(item => { |
| | | const roadway = item.roadway; |
| | | const dailyStats = item.dailyStats || []; |
| | | |
| | | switch(roadway) { |
| | | case "GWSC1": |
| | | this.dailyDataMap.GWSC1 = dailyStats; |
| | | break; |
| | | case "CWSC1": |
| | | this.dailyDataMap.CWSC1 = dailyStats; |
| | | break; |
| | | case "HCSC1": |
| | | this.dailyDataMap.HCSC1 = dailyStats; |
| | | break; |
| | | case "ZJSC1": |
| | | case "FJSC1": |
| | | // 极卷库数据合并处理 |
| | | this.mergeRollDailyStats(dailyStats); |
| | | break; |
| | | } |
| | | }); |
| | | |
| | | console.log("GWSC1数据:", this.dailyDataMap.GWSC1); |
| | | console.log("CWSC1数据:", this.dailyDataMap.CWSC1); |
| | | console.log("HCSC1数据:", this.dailyDataMap.HCSC1); |
| | | console.log("极卷库合并数据:", this.dailyDataMap.ROLL); |
| | | } else { |
| | | console.error("获取每日数据失败"); |
| | | } |
| | | }, |
| | | |
| | | mergeRollDailyStats(newData) { |
| | | // 合并正极卷库和负极卷库的每日数据 |
| | | if (!this.dailyDataMap.ROLL.length) { |
| | | this.dailyDataMap.ROLL = [...newData]; |
| | | } else { |
| | | const dateMap = new Map(); |
| | | [...this.dailyDataMap.ROLL, ...newData].forEach(item => { |
| | | const date = item.date; |
| | | if (date) { |
| | | if (!dateMap.has(date)) { |
| | | dateMap.set(date, { date, inbound: 0, outbound: 0 }); |
| | | } |
| | | const existing = dateMap.get(date); |
| | | existing.inbound += item.inbound || 0; |
| | | existing.outbound += item.outbound || 0; |
| | | } |
| | | }); |
| | | |
| | | const sortedDates = Array.from(dateMap.keys()).sort(); |
| | | const mergedData = sortedDates.map(date => dateMap.get(date)); |
| | | this.dailyDataMap["ROLL"] = mergedData; |
| | | } |
| | | }, |
| | | |
| | | async loadStockAndTrayCount() { |
| | | // 加载电池数量和空托盘数量 |
| | | console.log("正在加载电池数量和空托盘数据..."); |
| | | const res = await this.http.get("/api/Dashboard/StockAndTrayCount"); |
| | | |
| | | if (res.status && res.data) { |
| | | console.log("电池和空托盘数据:", res.data); |
| | | |
| | | // 重置数据 |
| | | this.rollData.batteryCount = 0; |
| | | this.rollData.emptyTrayCount = 0; |
| | | |
| | | // 根据返回的数据结构解析 |
| | | res.data.forEach(item => { |
| | | const warehouseName = item.warehouseName; |
| | | const batteryCount = item.batteryCount || 0; |
| | | const emptyTrayCount = item.emptyTrayCount || 0; |
| | | |
| | | // 根据仓库名称映射到对应的代码 |
| | | if (warehouseName === "高温库") { |
| | | this.emptyTrayCounts.GWSC1 = emptyTrayCount; |
| | | this.warehouseStocks.GWSC1 = batteryCount; |
| | | } else if (warehouseName === "常温库") { |
| | | this.emptyTrayCounts.CWSC1 = emptyTrayCount; |
| | | this.warehouseStocks.CWSC1 = batteryCount; |
| | | } else if (warehouseName === "化成库") { |
| | | this.emptyTrayCounts.HCSC1 = emptyTrayCount; |
| | | this.warehouseStocks.HCSC1 = batteryCount; |
| | | } else if (warehouseName === "极卷库") { |
| | | // 极卷库需要合并两个极卷库的数据 |
| | | this.rollData.batteryCount += batteryCount; |
| | | this.rollData.emptyTrayCount += emptyTrayCount; |
| | | } |
| | | }); |
| | | |
| | | // 设置极卷库总电池数量 |
| | | this.warehouseStocks.ROLL = this.rollData.batteryCount; |
| | | |
| | | console.log("特殊数据加载完成:", { |
| | | rollData: this.rollData, |
| | | emptyTrayCounts: this.emptyTrayCounts, |
| | | warehouseStocks: this.warehouseStocks |
| | | }); |
| | | } else { |
| | | console.error("获取电池和空托盘数据失败"); |
| | | throw new Error("获取电池和空托盘数据失败"); |
| | | } |
| | | }, |
| | | |
| | | 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); |
| | | } |
| | | }, |
| | | |
| | | async loadWarehouseStocks() { |
| | | // 模拟加载每个仓库的当前库存量 |
| | | // 如果后端有接口,可以替换为真实API调用 |
| | | try { |
| | | // 尝试加载库存数据,如果接口不存在则使用模拟数据 |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | for (const warehouse of allWarehouses) { |
| | | try { |
| | | const res = await this.http.get(`/api/Dashboard/WarehouseStock?warehouse=${warehouse.code}`); |
| | | if (res.status && res.data) { |
| | | this.warehouseStocks[warehouse.code] = res.data.stock || 0; |
| | | } else { |
| | | // 从月度数据中计算模拟库存(最近月份累计入库-出库) |
| | | const monthlyData = this.monthlyData[warehouse.code] || []; |
| | | let totalInbound = 0; |
| | | let totalOutbound = 0; |
| | | monthlyData.forEach(m => { |
| | | totalInbound += (m.inbound ?? m.Inbound) || 0; |
| | | totalOutbound += (m.outbound ?? m.Outbound) || 0; |
| | | }); |
| | | this.warehouseStocks[warehouse.code] = Math.max(0, totalInbound - totalOutbound); |
| | | } |
| | | } catch (e) { |
| | | // 使用模拟数据 |
| | | const mockStocks = { |
| | | GWSC1: 12580, |
| | | CWSC1: 8920, |
| | | HCSC1: 15600, |
| | | FJSC1: 4300, |
| | | ZJSC1: 7200 |
| | | }; |
| | | this.warehouseStocks[warehouse.code] = mockStocks[warehouse.code] || 0; |
| | | console.log("正在加载仓库分布数据..."); |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | |
| | | if (res.status && res.data) { |
| | | console.log("仓库分布数据:", res.data); |
| | | const rawData = res.data.data || res.data; |
| | | |
| | | // 处理极卷库合并 |
| | | let rollHasStock = 0; |
| | | let rollNoStock = 0; |
| | | let rollTotal = 0; |
| | | const otherWarehouses = []; |
| | | |
| | | rawData.forEach(item => { |
| | | if (item.warehouse === "极卷库" || item.warehouse.includes("极卷库")) { |
| | | // 合并极卷库数据 |
| | | rollHasStock += item.hasStock || 0; |
| | | rollNoStock += item.noStock || 0; |
| | | rollTotal += item.total || 0; |
| | | } else { |
| | | otherWarehouses.push(item); |
| | | } |
| | | }); |
| | | |
| | | // 添加合并后的极卷库 |
| | | if (rollTotal > 0) { |
| | | const hasStockPercentage = ((rollHasStock / rollTotal) * 100).toFixed(1) + "%"; |
| | | const noStockPercentage = ((rollNoStock / rollTotal) * 100).toFixed(1) + "%"; |
| | | |
| | | otherWarehouses.push({ |
| | | warehouse: "极卷库", |
| | | hasStock: rollHasStock, |
| | | noStock: rollNoStock, |
| | | total: rollTotal, |
| | | hasStockPercentage: hasStockPercentage, |
| | | noStockPercentage: noStockPercentage |
| | | }); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载仓库库存失败", e); |
| | | |
| | | this.warehouseData = otherWarehouses; |
| | | this.updateWarehouseChart(); |
| | | } else { |
| | | console.error("获取仓库分布数据失败"); |
| | | throw new Error("获取仓库分布数据失败"); |
| | | } |
| | | }, |
| | | |
| | | getMonthlyInbound(warehouseCode) { |
| | | const data = this.monthlyData[warehouseCode] || []; |
| | | if (data.length === 0) return 0; |
| | | // 获取最近一个月(最后一条)的入库数 |
| | | const latest = data[data.length - 1]; |
| | | return (latest.inbound ?? latest.Inbound) || 0; |
| | | getDailyTotalInbound(warehouseCode) { |
| | | const data = this.dailyDataMap[warehouseCode] || []; |
| | | return data.reduce((sum, item) => sum + (item.inbound || 0), 0); |
| | | }, |
| | | |
| | | getMonthlyOutbound(warehouseCode) { |
| | | const data = this.monthlyData[warehouseCode] || []; |
| | | if (data.length === 0) return 0; |
| | | // 获取最近一个月(最后一条)的出库数 |
| | | const latest = data[data.length - 1]; |
| | | return (latest.outbound ?? latest.Outbound) || 0; |
| | | getDailyTotalOutbound(warehouseCode) { |
| | | const data = this.dailyDataMap[warehouseCode] || []; |
| | | return data.reduce((sum, item) => sum + (item.outbound || 0), 0); |
| | | }, |
| | | |
| | | getWarehouseStock(warehouseCode) { |
| | | return this.warehouseStocks[warehouseCode] || 0; |
| | | }, |
| | | |
| | | calculateKPIs() { |
| | | // 计算总库存 |
| | | let totalStock = 0; |
| | | for (const code in this.warehouseStocks) { |
| | | totalStock += this.warehouseStocks[code]; |
| | | getBatteryCount(warehouseCode) { |
| | | if (warehouseCode === 'ROLL') { |
| | | return this.rollData.batteryCount; |
| | | } else if (warehouseCode === 'GWSC1') { |
| | | return this.warehouseStocks.GWSC1; |
| | | } else if (warehouseCode === 'CWSC1') { |
| | | return this.warehouseStocks.CWSC1; |
| | | } else if (warehouseCode === 'HCSC1') { |
| | | return this.warehouseStocks.HCSC1; |
| | | } |
| | | this.totalStock = totalStock; |
| | | |
| | | // 计算本月总入库和总出库(所有仓库最近一个月的合计) |
| | | let totalInbound = 0; |
| | | let totalOutbound = 0; |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | totalInbound += this.getMonthlyInbound(warehouse.code); |
| | | totalOutbound += this.getMonthlyOutbound(warehouse.code); |
| | | }); |
| | | this.monthlyInboundTotal = totalInbound; |
| | | this.monthlyOutboundTotal = totalOutbound; |
| | | return 0; |
| | | }, |
| | | |
| | | // 更新所有仓库的月度趋势图表 |
| | | updateAllMonthlyTrendCharts() { |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | this.updateMonthlyTrendChartForWarehouse(warehouse.code); |
| | | getEmptyTrayCount(warehouseCode) { |
| | | if (warehouseCode === 'ROLL') { |
| | | return this.rollData.emptyTrayCount; |
| | | } else if (warehouseCode === 'GWSC1') { |
| | | return this.emptyTrayCounts.GWSC1; |
| | | } else if (warehouseCode === 'CWSC1') { |
| | | return this.emptyTrayCounts.CWSC1; |
| | | } else if (warehouseCode === 'HCSC1') { |
| | | return this.emptyTrayCounts.HCSC1; |
| | | } |
| | | return 0; |
| | | }, |
| | | |
| | | updateAllDailyCharts() { |
| | | this.dailyWarehouses.forEach(warehouse => { |
| | | this.updateDailyChartForWarehouse(warehouse.code); |
| | | }); |
| | | }, |
| | | |
| | | updateMonthlyTrendChartForWarehouse(roadway) { |
| | | const chart = this.charts[roadway]; |
| | | updateDailyChartForWarehouse(roadway) { |
| | | const chart = this.charts[`daily-${roadway}`]; |
| | | if (!chart) return; |
| | | |
| | | const data = this.monthlyData[roadway] || []; |
| | | // 兼容大小写字段名 |
| | | const monthLabels = data.map(m => m.month || m.Month || ""); |
| | | const inboundData = data.map(m => { |
| | | const val = m.inbound ?? m.Inbound; |
| | | return val !== undefined && val !== null ? Number(val) : 0; |
| | | }); |
| | | const outboundData = data.map(m => { |
| | | const val = m.outbound ?? m.Outbound; |
| | | return val !== undefined && val !== null ? Number(val) : 0; |
| | | }); |
| | | const data = this.dailyDataMap[roadway] || []; |
| | | |
| | | // 如果没有数据,显示空图表提示 |
| | | if (!data.length) { |
| | | chart.setOption({ |
| | | title: { |
| | | show: true, |
| | | text: '暂无数据', |
| | | left: 'center', |
| | | top: 'center', |
| | | textStyle: { color: '#ccc', fontSize: 14 } |
| | | } |
| | | }, true); |
| | | return; |
| | | } |
| | | |
| | | const dates = data.map(d => d.date); |
| | | const inboundData = data.map(d => d.inbound || 0); |
| | | const outboundData = data.map(d => d.outbound || 0); |
| | | |
| | | const option = { |
| | | tooltip: { |
| | |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: monthLabels, |
| | | data: dates, |
| | | axisLabel: { |
| | | color: "#ccc", |
| | | rotate: 45, |
| | |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "任务数量", |
| | | name: "数量", |
| | | nameTextStyle: { color: "#ccc", fontSize: 11 }, |
| | | axisLabel: { color: "#ccc" }, |
| | | splitLine: { lineStyle: { color: "#2a3a4a", type: "dashed" } } |
| | |
| | | color: "#5470c6", |
| | | borderRadius: [4, 4, 0, 0] |
| | | }, |
| | | barWidth: "35%", |
| | | barWidth: "40%", |
| | | label: { |
| | | show: inboundData.length <= 8, |
| | | show: true, |
| | | position: "top", |
| | | color: "#5470c6", |
| | | fontSize: 10 |
| | | fontSize: 10, |
| | | formatter: (params) => params.value |
| | | } |
| | | }, |
| | | { |
| | |
| | | lineStyle: { width: 2, type: "solid" }, |
| | | smooth: false, |
| | | label: { |
| | | show: outboundData.length <= 8, |
| | | show: true, |
| | | position: "top", |
| | | color: "#91cc75", |
| | | fontSize: 10 |
| | | fontSize: 10, |
| | | formatter: (params) => params.value |
| | | } |
| | | } |
| | | ] |
| | |
| | | chart.setOption(option, true); |
| | | }, |
| | | |
| | | updateDailyChart() { |
| | | if (!this.charts.daily) return; |
| | | 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() { |
| | | if (!this.charts.warehouse) return; |
| | | |
| | | if (!this.warehouseData.length) { |
| | | this.charts.warehouse.setOption({ |
| | | title: { |
| | | show: true, |
| | | text: '暂无仓库数据', |
| | | left: 'center', |
| | | top: 'center', |
| | | textStyle: { color: '#ccc' } |
| | | } |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const warehouseNames = this.warehouseData.map(w => w.warehouse); |
| | | const hasStocks = this.warehouseData.map(w => w.hasStock); |
| | | const noStocks = this.warehouseData.map(w => w.noStock); |
| | |
| | | const option = { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: "shadow" |
| | | }, |
| | | formatter: function(params) { |
| | | axisPointer: { type: "shadow" }, |
| | | formatter: (params) => { |
| | | let tip = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | const dataIndex = param.dataIndex; |
| | | const warehouse = window.homeComponent?.warehouseData[dataIndex]; |
| | | const warehouse = this.warehouseData[dataIndex]; |
| | | if (warehouse) { |
| | | if (param.seriesName === "已用容量") { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.hasStockPercentage})<br/>`; |
| | |
| | | label: { |
| | | show: true, |
| | | position: "top", |
| | | formatter: (params) => { |
| | | const pct = hasStockPercentages[params.dataIndex]; |
| | | return `${params.value} (${pct})`; |
| | | formatter: () => { |
| | | const pct = hasStockPercentages[index]; |
| | | return `${value} (${pct})`; |
| | | }, |
| | | color: "#91cc75", |
| | | fontSize: 11 |
| | |
| | | label: { |
| | | show: true, |
| | | position: "top", |
| | | formatter: (params) => { |
| | | const pct = noStockPercentages[params.dataIndex]; |
| | | return `${params.value} (${pct})`; |
| | | formatter: () => { |
| | | const pct = noStockPercentages[index]; |
| | | return `${value} (${pct})`; |
| | | }, |
| | | color: "#fac858", |
| | | fontSize: 11 |
| | |
| | | ] |
| | | }; |
| | | |
| | | window.homeComponent = this; |
| | | this.charts.warehouse.setOption(option, true); |
| | | } |
| | | } |
| | |
| | | background-attachment: fixed; |
| | | } |
| | | |
| | | /* KPI 卡片样式 */ |
| | | /* KPI 卡片样式 - 5列布局 */ |
| | | .kpi-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | grid-template-columns: repeat(5, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 24px; |
| | | } |
| | |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | /* 上3个图表布局 */ |
| | | .chart-row.top-three { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | /* 下2个图表布局 */ |
| | | .chart-row.bottom-two { |
| | | /* 每日图表布局 - 每行2个 */ |
| | | .chart-row.daily-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-row.full-width { |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | |
| | | 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: 15px; |
| | |
| | | padding: 8px 0; |
| | | background: rgba(0, 0, 0, 0.3); |
| | | border-radius: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .number-item { |
| | | text-align: center; |
| | | flex: 1; |
| | | min-width: 80px; |
| | | } |
| | | |
| | | .number-label { |
| | |
| | | |
| | | .number-value { |
| | | display: block; |
| | | font-size: 20px; |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | letter-spacing: 1px; |
| | | } |
| | |
| | | color: #5470c6; |
| | | } |
| | | |
| | | .number-item.outbound .number-value { |
| | | color: #91cc75; |
| | | .number-item.battery .number-value { |
| | | color: #ee6666; |
| | | } |
| | | |
| | | .number-item.stock .number-value { |
| | | color: #fac858; |
| | | .number-item.empty-tray .number-value { |
| | | color: #fc8452; |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 240px; |
| | | height: 280px; |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 全宽图表 */ |
| | | .full-width .chart-card { |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 350px; |
| | | } |
| | | |
| | | /* 响应式调整 */ |
| | | @media (max-width: 1024px) { |
| | | .kpi-cards { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | .chart-row.top-three { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | .chart-row.bottom-two { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .kpi-cards { |
| | | grid-template-columns: 1fr; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | .chart-row.top-three { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | .chart-row.bottom-two { |
| | | .chart-row.daily-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | .chart-content { |
| | | height: 220px; |
| | | } |
| | | .full-width .chart-content { |
| | | height: 280px; |
| | | height: 240px; |
| | | } |
| | | .card-title { |
| | | font-size: 13px; |
| | | white-space: normal; |
| | | } |
| | | .number-value { |
| | | font-size: 16px; |
| | | font-size: 14px; |
| | | } |
| | | .number-item { |
| | | min-width: 60px; |
| | | } |
| | | } |
| | | |
| | | /* 添加网格线效果 */ |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |