xiazhengtongxue
3 天以前 51922d7093b9c8f52417bfdd0fe9aa087d1fb5be
Code/WMS/WIDESEA_WMSClient/src/views/Home.vue
@@ -1,46 +1,79 @@
<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>
    <!-- 顶部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">{{ totalLocation }}</div>
        </div>
      </div>
    </div>
    <!-- 第二行:今日/本周出入库对比 -->
    <div class="chart-row">
      <div class="chart-card">
        <div class="card-title">今日出入库对比</div>
        <div id="chart-today" class="chart-content"></div>
      <div class="kpi-card">
        <div class="kpi-icon">🔥</div>
        <div class="kpi-info">
          <div class="kpi-label">化成库</div>
          <div class="kpi-value">{{ warehouseLocations.hc }}</div>
        </div>
      </div>
      <div class="chart-card">
        <div class="card-title">本周出入库对比</div>
        <div id="chart-week" class="chart-content"></div>
      <div class="kpi-card">
        <div class="kpi-icon">🌡️</div>
        <div class="kpi-info">
          <div class="kpi-label">高温库</div>
          <div class="kpi-value">{{ warehouseLocations.gw }}</div>
        </div>
      </div>
    </div>
    <!-- 第三行:本月对比/库存总量 -->
    <div class="chart-row">
      <div class="chart-card">
        <div class="card-title">本月出入库对比</div>
        <div id="chart-month" class="chart-content"></div>
      <div class="kpi-card">
        <div class="kpi-icon">❄️</div>
        <div class="kpi-info">
          <div class="kpi-label">常温库</div>
          <div class="kpi-value">{{ warehouseLocations.cw }}</div>
        </div>
      </div>
      <div class="chart-card">
        <div class="card-title">当前库存总量</div>
        <div class="stock-total">
          <div class="total-number">{{ overviewData.TotalStock || 0 }}</div>
          <div class="total-label">托盘</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>
    <!-- 第四行:库龄分布/仓库分布 -->
    <div class="chart-row">
      <div class="chart-card">
        <div class="card-title">库存库龄分布</div>
        <div id="chart-stock-age" class="chart-content"></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">
          <!-- 极卷库显示有货托盘(电池数量)和空托盘 -->
          <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="`daily-chart-${warehouse.code}`" 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>
@@ -57,18 +90,54 @@
  data() {
    return {
      charts: {},
      overviewData: {
        TodayInbound: 0,
        TodayOutbound: 0,
        MonthInbound: 0,
        MonthOutbound: 0,
        TotalStock: 0
      // 四个核心仓库(合并了正负极卷库为极卷库)
      dailyWarehouses: [
        { code: "HCSC1", name: "化成库", type: "hc" },
        { code: "GWSC1", name: "高温库", type: "gw" },
        { code: "CWSC1", name: "常温库", type: "cw" },
        { code: "ROLL", name: "极卷库", type: "jj" }
      ],
      // 原始每日数据存储 (其中ROLL为合并后的极卷库数据)
      dailyDataMap: {
        GWSC1: [],
        CWSC1: [],
        HCSC1: [],
        ROLL: []
      },
      weeklyData: [],
      monthlyData: [],
      stockAgeData: [],
      warehouseData: []
      // 存储每个仓库的电池数量
      warehouseStocks: {
        GWSC1: 0,
        CWSC1: 0,
        HCSC1: 0,
        ROLL: 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();
@@ -77,242 +146,460 @@
  },
  beforeUnmount() {
    window.removeEventListener("resize", this.handleResize);
    Object.values(this.charts).forEach(chart => chart.dispose());
    Object.values(this.charts).forEach(chart => chart && chart.dispose());
  },
  methods: {
    handleResize() {
      Object.values(this.charts).forEach(chart => chart.resize());
      Object.values(this.charts).forEach(chart => chart && chart.resize());
    },
    initCharts() {
      this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend"));
      this.charts.today = echarts.init(document.getElementById("chart-today"));
      this.charts.week = echarts.init(document.getElementById("chart-week"));
      this.charts.month = echarts.init(document.getElementById("chart-month"));
      this.charts.stockAge = echarts.init(document.getElementById("chart-stock-age"));
      // 初始化每日图表
      this.dailyWarehouses.forEach(warehouse => {
        const chartId = `daily-chart-${warehouse.code}`;
        const el = document.getElementById(chartId);
        if (el) {
          this.charts[`daily-${warehouse.code}`] = echarts.init(el);
        }
      });
      // 初始化仓库分布图表
      this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse"));
    },
    async loadData() {
      await this.loadOverview();
      await this.loadWeeklyStats();
      await this.loadMonthlyStats();
      await this.loadStockAgeDistribution();
      await this.loadStockByWarehouse();
    },
    async loadOverview() {
      try {
        const res = await this.http.get("/api/Dashboard/Overview");
        console.log("总览数据", res.Data);
        if (res.Status && res.Data) {
          this.overviewData = res.Data;
          this.updateTodayChart();
          this.updateWeekChart();
          this.updateMonthChart();
        }
      } catch (e) {
        console.error("加载总览数据失败", e);
        // 并行加载所有数据
        await Promise.all([
          this.loadAllDailyStats(),
          this.loadStockAndTrayCount(),
          this.loadStockByWarehouse()
        ]);
        // 更新所有图表
        this.updateAllDailyCharts();
      } catch (error) {
        console.error("加载数据失败:", error);
        this.$message?.error("数据加载失败,请稍后重试");
      }
    },
    async loadWeeklyStats() {
      try {
        const res = await this.http.get("/api/Dashboard/WeeklyStats", { weeks: 12 });
        if (res.Status && res.Data) {
          this.weeklyData = res.Data;
          this.updateWeekChart();
        }
      } 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("获取每日数据失败");
      }
    },
    async loadMonthlyStats() {
      try {
        const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 });
        if (res.Status && res.Data) {
          this.monthlyData = res.Data;
          this.updateMonthlyTrendChart();
        }
      } catch (e) {
        console.error("加载每月统计失败", e);
    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 loadStockAgeDistribution() {
      try {
        const res = await this.http.get("/api/Dashboard/StockAgeDistribution");
        if (res.Status && res.Data) {
          this.stockAgeData = res.Data;
          this.updateStockAgeChart();
        }
      } catch (e) {
        console.error("加载库龄分布失败", e);
    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) {
          this.warehouseData = res.Data;
          this.updateWarehouseChart();
      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("获取仓库分布数据失败");
      }
    },
    updateTodayChart() {
      const option = {
        tooltip: { trigger: "axis" },
        legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } },
        xAxis: {
          type: "category",
          data: ["今日"],
          axisLabel: { color: "#fff" }
        },
        yAxis: {
          type: "value",
          axisLabel: { color: "#fff" }
        },
        series: [
          { name: "入库", type: "bar", data: [this.overviewData.TodayInbound], itemStyle: { color: "#5470c6" } },
          { name: "出库", type: "bar", data: [this.overviewData.TodayOutbound], itemStyle: { color: "#91cc75" } }
        ]
      };
      this.charts.today.setOption(option, true);
    getDailyTotalInbound(warehouseCode) {
      const data = this.dailyDataMap[warehouseCode] || [];
      return data.reduce((sum, item) => sum + (item.inbound || 0), 0);
    },
    updateWeekChart() {
      const thisWeek = this.getThisWeekData(this.weeklyData);
      const option = {
        tooltip: { trigger: "axis" },
        legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } },
        xAxis: {
          type: "category",
          data: ["本周"],
          axisLabel: { color: "#fff" }
        },
        yAxis: {
          type: "value",
          axisLabel: { color: "#fff" }
        },
        series: [
          { name: "入库", type: "bar", data: [thisWeek.Inbound], itemStyle: { color: "#5470c6" } },
          { name: "出库", type: "bar", data: [thisWeek.Outbound], itemStyle: { color: "#91cc75" } }
        ]
      };
      this.charts.week.setOption(option, true);
    getDailyTotalOutbound(warehouseCode) {
      const data = this.dailyDataMap[warehouseCode] || [];
      return data.reduce((sum, item) => sum + (item.outbound || 0), 0);
    },
    getThisWeekData(weeklyData) {
      if (!weeklyData || weeklyData.length === 0) return { Inbound: 0, Outbound: 0 };
      const thisWeekKey = this.getCurrentWeekKey();
      const thisWeek = weeklyData.find(w => w.Week === thisWeekKey);
      return thisWeek || { Inbound: 0, Outbound: 0 };
    getWarehouseStock(warehouseCode) {
      return this.warehouseStocks[warehouseCode] || 0;
    },
    getCurrentWeekKey() {
      const now = new Date();
      const diff = (7 + (now.getDay() - 1)) % 7;
      const monday = new Date(now);
      monday.setDate(now.getDate() - diff);
      const year = monday.getFullYear();
      const jan1 = new Date(year, 0, 1);
      const weekNum = Math.ceil(((monday - jan1) / 86400000 + jan1.getDay() + 1) / 7);
      return `${year}-W${String(weekNum).padStart(2, "0")}`;
    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;
      }
      return 0;
    },
    updateMonthChart() {
      const option = {
        tooltip: { trigger: "axis" },
        legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } },
        xAxis: {
          type: "category",
          data: ["本月"],
          axisLabel: { color: "#fff" }
        },
        yAxis: {
          type: "value",
          axisLabel: { color: "#fff" }
        },
        series: [
          { name: "入库", type: "bar", data: [this.overviewData.MonthInbound], itemStyle: { color: "#5470c6" } },
          { name: "出库", type: "bar", data: [this.overviewData.MonthOutbound], itemStyle: { color: "#91cc75" } }
        ]
      };
      this.charts.month.setOption(option, true);
    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;
    },
    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" }
    updateAllDailyCharts() {
      this.dailyWarehouses.forEach(warehouse => {
        this.updateDailyChartForWarehouse(warehouse.code);
      });
    },
    updateDailyChartForWarehouse(roadway) {
      const chart = this.charts[`daily-${roadway}`];
      if (!chart) return;
      const data = this.dailyDataMap[roadway] || [];
      // 如果没有数据,显示空图表提示
      if (!data.length) {
        chart.setOption({
          title: {
            show: true,
            text: '暂无数据',
            left: 'center',
            top: 'center',
            textStyle: { color: '#ccc', fontSize: 14 }
          }
        ],
        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" } }
        ]
      };
      this.charts.monthlyTrend.setOption(option, true);
    },
        }, true);
        return;
      }
    updateStockAgeChart() {
      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: { trigger: "axis" },
        tooltip: {
          trigger: "axis",
          formatter: function(params) {
            let result = params[0].axisValue + "<br/>";
            params.forEach(p => {
              result += `${p.marker}${p.seriesName}: ${p.value}<br/>`;
            });
            return result;
          }
        },
        legend: {
          data: ["入库", "出库"],
          textStyle: { color: "#fff" },
          top: 0,
          right: 10,
          itemWidth: 20,
          itemHeight: 12
        },
        grid: {
          left: "8%",
          right: "8%",
          top: "18%",
          bottom: "12%",
          containLabel: true
        },
        xAxis: {
          type: "category",
          data: this.stockAgeData.map(s => s.Range),
          axisLabel: { color: "#fff" }
          data: dates,
          axisLabel: {
            color: "#ccc",
            rotate: 45,
            fontSize: 10,
            interval: 0,
            margin: 8
          },
          axisLine: { lineStyle: { color: "#4a5b6e" } }
        },
        yAxis: {
          type: "value",
          axisLabel: { color: "#fff" }
          name: "数量",
          nameTextStyle: { color: "#ccc", fontSize: 11 },
          axisLabel: { color: "#ccc" },
          splitLine: { lineStyle: { color: "#2a3a4a", type: "dashed" } }
        },
        series: [
          {
            name: "入库",
            type: "bar",
            data: this.stockAgeData.map(s => s.Count),
            itemStyle: { color: "#5470c6" }
            data: inboundData,
            itemStyle: {
              color: "#5470c6",
              borderRadius: [4, 4, 0, 0]
            },
            barWidth: "40%",
            label: {
              show: true,
              position: "top",
              color: "#5470c6",
              fontSize: 10,
              formatter: (params) => params.value
            }
          },
          {
            name: "出库",
            type: "line",
            data: outboundData,
            symbol: "circle",
            symbolSize: 6,
            itemStyle: { color: "#91cc75" },
            lineStyle: { width: 2, type: "solid" },
            smooth: false,
            label: {
              show: true,
              position: "top",
              color: "#91cc75",
              fontSize: 10,
              formatter: (params) => params.value
            }
          }
        ]
      };
      this.charts.stockAge.setOption(option, true);
      chart.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 hasStockPercentages = this.warehouseData.map(w => w.hasStockPercentage);
      const noStockPercentages = this.warehouseData.map(w => w.noStockPercentage);
      const option = {
        tooltip: { trigger: "axis" },
        tooltip: {
          trigger: "axis",
          axisPointer: { type: "shadow" },
          formatter: (params) => {
            let tip = params[0].name + "<br/>";
            params.forEach(param => {
              const dataIndex = param.dataIndex;
              const warehouse = this.warehouseData[dataIndex];
              if (warehouse) {
                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/>`;
                }
              } else {
                tip += `${param.marker}${param.seriesName}: ${param.value}<br/>`;
              }
            });
            return tip;
          }
        },
        legend: {
          data: ["已用容量", "剩余容量"],
          textStyle: { color: "#fff" }
        },
        xAxis: {
          type: "category",
          data: this.warehouseData.map(w => w.Warehouse),
          axisLabel: { color: "#fff", rotate: 30 }
          data: warehouseNames,
          axisLabel: { color: "#fff", rotate: 30, interval: 0 }
        },
        yAxis: {
          type: "value",
          name: "容量",
          axisLabel: { color: "#fff" }
        },
        series: [
          {
            name: "已用容量",
            type: "bar",
            data: this.warehouseData.map(w => w.Count),
            itemStyle: { color: "#5470c6" }
            data: hasStocks.map((value, index) => ({
              value: value,
              label: {
                show: true,
                position: "top",
                formatter: () => {
                  const pct = hasStockPercentages[index];
                  return `${value} (${pct})`;
                },
                color: "#91cc75",
                fontSize: 11
              }
            })),
            itemStyle: { color: "#91cc75" }
          },
          {
            name: "剩余容量",
            type: "bar",
            data: noStocks.map((value, index) => ({
              value: value,
              label: {
                show: true,
                position: "top",
                formatter: () => {
                  const pct = noStockPercentages[index];
                  return `${value} (${pct})`;
                },
                color: "#fac858",
                fontSize: 11
              }
            })),
            itemStyle: { color: "#fac858" }
          }
        ]
      };
      this.charts.warehouse.setOption(option, true);
    }
  }
@@ -322,27 +609,87 @@
<style scoped>
.dashboard-container {
  padding: 20px;
  background-color: #0e1a2b;
  color: #e0e0e0;
  min-height: calc(100vh - 60px);
  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
  background-attachment: fixed;
}
.chart-row {
/* KPI 卡片样式 - 5列布局 */
.kpi-cards {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 20px;
  margin-bottom: 24px;
}
.kpi-card {
  background: rgba(10, 16, 35, 0.7);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(64, 224, 208, 0.3);
  border-radius: 16px;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  transition: all 0.3s ease;
  box-shadow: 0 0 15px rgba(0, 255, 255, 0.1);
}
.kpi-card:hover {
  transform: translateY(-3px);
  border-color: rgba(64, 224, 208, 0.6);
  box-shadow: 0 0 25px rgba(0, 255, 255, 0.2);
}
.kpi-icon {
  font-size: 32px;
  opacity: 0.9;
}
.kpi-info {
  flex: 1;
}
.kpi-label {
  font-size: 13px;
  color: #8ba0b5;
  margin-bottom: 6px;
  letter-spacing: 1px;
}
.kpi-value {
  font-size: 28px;
  font-weight: 700;
  color: #00ffff;
  text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
  line-height: 1.2;
}
/* 每日图表布局 - 每行2个 */
.chart-row.daily-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-bottom: 20px;
}
.chart-row.full-width {
  width: 100%;
}
.chart-card {
  flex: 1;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(25, 186, 139, 0.17);
  border-radius: 4px;
  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 {
@@ -352,8 +699,9 @@
  left: 0;
  width: 10px;
  height: 10px;
  border-top: 2px solid #02a6b5;
  border-left: 2px solid #02a6b5;
  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 {
@@ -363,15 +711,64 @@
  right: 0;
  width: 10px;
  height: 10px;
  border-top: 2px solid #02a6b5;
  border-right: 2px solid #02a6b5;
  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: #fff;
  font-size: 16px;
  color: #00ffff;
  font-size: 15px;
  text-align: center;
  margin-bottom: 10px;
  margin-bottom: 12px;
  text-shadow: 0 0 10px rgba(0, 255, 255, 0.7);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 仓库数字显示区域 */
.warehouse-numbers {
  display: flex;
  justify-content: space-around;
  margin-bottom: 12px;
  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 {
  display: block;
  font-size: 11px;
  color: #8ba0b5;
  margin-bottom: 4px;
}
.number-value {
  display: block;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 1px;
}
.number-item.inbound .number-value {
  color: #5470c6;
}
.number-item.battery .number-value {
  color: #ee6666;
}
.number-item.empty-tray .number-value {
  color: #fc8452;
}
.chart-content {
@@ -379,34 +776,39 @@
  width: 100%;
}
.stock-total {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 280px;
@media (max-width: 768px) {
  .kpi-cards {
    grid-template-columns: repeat(2, 1fr);
  }
  .chart-row.daily-grid {
    grid-template-columns: 1fr;
  }
  .chart-content {
    height: 240px;
  }
  .card-title {
    font-size: 13px;
    white-space: normal;
  }
  .number-value {
    font-size: 14px;
  }
  .number-item {
    min-width: 60px;
  }
}
.total-number {
  font-size: 64px;
  font-weight: bold;
  color: #67caca;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
.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;
}
.total-label {
  font-size: 18px;
  color: #fcf0d8;
  margin-top: 10px;
}
/* 全宽图表 */
.full-width .chart-card {
  flex: none;
  width: 100%;
}
.full-width .chart-content {
  height: 350px;
}
</style>
</style>