1
xiazhengtongxue
3 天以前 c906272c0905b1309503de92affbdb06ec9d4268
Code/WMS/WIDESEA_WMSClient/src/views/Home.vue
@@ -1,10 +1,95 @@
<template>
  <div class="dashboard-container">
    <!-- 各仓库月度出入库对比图 -->
    <!-- 顶部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>
      </div>
      <div class="kpi-card">
        <div class="kpi-icon">📦</div>
        <div class="kpi-info">
          <div class="kpi-label">总库存量</div>
          <div class="kpi-value">{{ totalStock.toLocaleString() }}</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">{{ monthlyInboundTotal.toLocaleString() }}</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">{{ monthlyOutboundTotal.toLocaleString() }}</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>
        <!-- 仓库数字显示区域 -->
        <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 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-warehouse-monthly" class="chart-content"></div>
        <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>
    </div>
  </div>
@@ -18,8 +103,40 @@
  data() {
    return {
      charts: {},
      monthlyData: [],
      warehouseNames: ['FJSC1', 'ZJSC1', 'GWSC1', 'CWSC1', 'HCSC1']
      // 五个仓库定义 - 上3个
      topWarehouses: [
        { code: "GWSC1", name: "高温1号仓库" },
        { code: "CWSC1", name: "常温1号仓库" },
        { code: "HCSC1", name: "分容1号仓库" }
      ],
      // 下2个
      bottomWarehouses: [
        { code: "FJSC1", name: "负极卷1号仓库" },
        { code: "ZJSC1", name: "正极卷1号仓库" }
      ],
      dailyData: [],
      // 存储每个仓库的月度数据
      monthlyData: {
        GWSC1: [],
        CWSC1: [],
        HCSC1: [],
        FJSC1: [],
        ZJSC1: []
      },
      // 存储每个仓库的当前库存
      warehouseStocks: {
        GWSC1: 0,
        CWSC1: 0,
        HCSC1: 0,
        FJSC1: 0,
        ZJSC1: 0
      },
      warehouseData: [],
      // KPI 汇总数据
      totalWarehouses: 5,
      totalStock: 0,
      monthlyInboundTotal: 0,
      monthlyOutboundTotal: 0
    };
  },
  mounted() {
@@ -29,198 +146,412 @@
  },
  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.warehouseMonthly = echarts.init(document.getElementById("chart-warehouse-monthly"));
      // 初始化所有仓库图表
      const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses];
      allWarehouses.forEach(warehouse => {
        const chartId = `chart-${warehouse.code}`;
        const el = document.getElementById(chartId);
        if (el) {
          this.charts[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() {
      await this.loadMonthlyStats();
      // 并行加载所有仓库的月度数据(分别传入不同的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 loadMonthlyStats() {
    async loadMonthlyStatsForWarehouse(roadway) {
      console.log(`正在加载${roadway}的每月统计数据...`);
      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();
        // 关键修复:分别传入不同的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("加载每月统计失败", e);
        console.error(`加载${roadway}每月统计失败`, e);
        this.monthlyData[roadway] = [];
      }
    },
    getWarehouseName(code) {
      const nameMap = {
        'FJSC1': '负极卷1号仓库',
        'ZJSC1': '正极卷1号仓库',
        'GWSC1': '高温1号仓库',
        'CWSC1': '常温1号仓库',
        'HCSC1': '分容1号仓库'
      };
      return nameMap[code] || code;
    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);
      }
    },
    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
    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);
            }
          })),
          barWidth: '15%',
          barGap: '10%',
          itemStyle: {
            color: this.getBarColor(index),
            borderRadius: [3, 3, 0, 0]
          } catch (e) {
            // 使用模拟数据
            const mockStocks = {
              GWSC1: 12580,
              CWSC1: 8920,
              HCSC1: 15600,
              FJSC1: 4300,
              ZJSC1: 7200
            };
            this.warehouseStocks[warehouse.code] = mockStocks[warehouse.code] || 0;
          }
        });
        }
      } catch (e) {
        console.error("加载仓库库存失败", e);
      }
    },
    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;
    },
    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;
    },
    getWarehouseStock(warehouseCode) {
      return this.warehouseStocks[warehouseCode] || 0;
    },
    calculateKPIs() {
      // 计算总库存
      let totalStock = 0;
      for (const code in this.warehouseStocks) {
        totalStock += this.warehouseStocks[code];
      }
      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;
    },
    // 更新所有仓库的月度趋势图表
    updateAllMonthlyTrendCharts() {
      const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses];
      allWarehouses.forEach(warehouse => {
        this.updateMonthlyTrendChartForWarehouse(warehouse.code);
      });
    },
    updateMonthlyTrendChartForWarehouse(roadway) {
      const chart = this.charts[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 option = {
        title: {
          text: '各仓库月度出入库对比',
          textStyle: {
            color: '#00ffff',
            fontSize: 16
          },
          left: 'center',
          top: 10
        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: monthLabels,
          axisLabel: {
            color: "#ccc",
            rotate: 45,
            fontSize: 10,
            interval: 0,
            margin: 8
          },
          axisLine: { lineStyle: { color: "#4a5b6e" } }
        },
        yAxis: {
          type: "value",
          name: "任务数量",
          nameTextStyle: { color: "#ccc", fontSize: 11 },
          axisLabel: { color: "#ccc" },
          splitLine: { lineStyle: { color: "#2a3a4a", type: "dashed" } }
        },
        series: [
          {
            name: "入库",
            type: "bar",
            data: inboundData,
            itemStyle: {
              color: "#5470c6",
              borderRadius: [4, 4, 0, 0]
            },
            barWidth: "35%",
            label: {
              show: inboundData.length <= 8,
              position: "top",
              color: "#5470c6",
              fontSize: 10
            }
          },
          {
            name: "出库",
            type: "line",
            data: outboundData,
            symbol: "circle",
            symbolSize: 6,
            itemStyle: { color: "#91cc75" },
            lineStyle: { width: 2, type: "solid" },
            smooth: false,
            label: {
              show: outboundData.length <= 8,
              position: "top",
              color: "#91cc75",
              fontSize: 10
            }
          }
        ]
      };
      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;
      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',
          trigger: "axis",
          axisPointer: {
            type: 'shadow'
            type: "shadow"
          },
          formatter: function(params) {
            let tip = `<strong>${params[0].axisValue}</strong><br/>`;
            let tip = params[0].name + "<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/>`;
              const dataIndex = param.dataIndex;
              const warehouse = window.homeComponent?.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: 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
          data: ["已用容量", "剩余容量"],
          textStyle: { color: "#fff" }
        },
        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'
            }
          }
          type: "category",
          data: warehouseNames,
          axisLabel: { color: "#fff", rotate: 30, interval: 0 }
        },
        yAxis: {
          type: 'value',
          name: '数量',
          nameTextStyle: { color: '#fff' },
          axisLabel: { color: '#fff' },
          splitLine: {
            lineStyle: {
              color: 'rgba(255,255,255,0.1)',
              type: 'dashed'
            }
          }
          type: "value",
          name: "容量",
          axisLabel: { color: "#fff" }
        },
        dataZoom: [
        series: [
          {
            type: 'inside',
            start: 0,
            end: 100
            name: "已用容量",
            type: "bar",
            data: hasStocks.map((value, index) => ({
              value: value,
              label: {
                show: true,
                position: "top",
                formatter: (params) => {
                  const pct = hasStockPercentages[params.dataIndex];
                  return `${params.value} (${pct})`;
                },
                color: "#91cc75",
                fontSize: 11
              }
            })),
            itemStyle: { color: "#91cc75" }
          },
          {
            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'
            }
            name: "剩余容量",
            type: "bar",
            data: noStocks.map((value, index) => ({
              value: value,
              label: {
                show: true,
                position: "top",
                formatter: (params) => {
                  const pct = noStockPercentages[params.dataIndex];
                  return `${params.value} (${pct})`;
                },
                color: "#fac858",
                fontSize: 11
              }
            })),
            itemStyle: { color: "#fac858" }
          }
        ],
        series: series
        ]
      };
      this.charts.warehouseMonthly.setOption(option, true);
    },
    getBarColor(index) {
      const colors = [
        '#5470c6', // 蓝
        '#fac858', // 黄
        '#73c0de', // 天蓝
        '#fc8452', // 橙
        '#ea7ccc'  // 粉
      ];
      return colors[index] || '#5470c6';
      window.homeComponent = this;
      this.charts.warehouse.setOption(option, true);
    }
  }
};
@@ -235,36 +566,93 @@
  background-attachment: fixed;
}
.chart-row {
/* KPI 卡片样式 */
.kpi-cards {
  display: grid;
  grid-template-columns: repeat(4, 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;
}
/* 上3个图表布局 */
.chart-row.top-three {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-bottom: 20px;
}
/* 下2个图表布局 */
.chart-row.bottom-two {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-bottom: 20px;
}
.chart-row.full-width {
  width: 100%;
  margin-bottom: 20px;
}
.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);
  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);
  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);
}
@@ -292,29 +680,129 @@
  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;
  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;
}
.number-item {
  text-align: center;
  flex: 1;
}
.number-label {
  display: block;
  font-size: 11px;
  color: #8ba0b5;
  margin-bottom: 4px;
}
.number-value {
  display: block;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 1px;
}
.number-item.inbound .number-value {
  color: #5470c6;
}
.number-item.outbound .number-value {
  color: #91cc75;
}
.number-item.stock .number-value {
  color: #fac858;
}
.chart-content {
  height: 500px;
  height: 240px;
  width: 100%;
}
/* 全宽图表 */
.full-width .chart-card {
  flex: none;
  width: 100%;
}
.full-width .chart-content {
  height: 500px;
  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;
  }
  .chart-row.top-three {
    grid-template-columns: 1fr;
  }
  .chart-row.bottom-two {
    grid-template-columns: 1fr;
  }
  .chart-content {
    height: 220px;
  }
  .full-width .chart-content {
    height: 280px;
  }
  .card-title {
    font-size: 13px;
    white-space: normal;
  }
  .number-value {
    font-size: 16px;
  }
}
/* 添加网格线效果 */
.dashboard-container::before {
  content: "";
  position: fixed;
@@ -322,8 +810,7 @@
  left: 0;
  right: 0;
  bottom: 0;
  background-image:
    linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px),
  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;