1
heshaofeng
2026-01-20 5dfd83bd540c2e43af2e0449c246c79a22cb1296
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/charts/wms-dashboard.vue
@@ -1,154 +1,124 @@
<template>
  <div class="wms-dashboard">
    <!-- é¡¶éƒ¨æ ‡é¢˜æ  -->
    <div class="header">
      <h1>WMS仓储可视化图表看板</h1>
      <div class="header-right">
        <span>2025-12-24 16:11:44</span>
        <el-select v-model="month" placeholder="本月" style="margin-left: 10px; width: 80px;">
          <el-option label="本月" value="month"></el-option>
          <el-option label="上月" value="lastMonth"></el-option>
        </el-select>
        <el-button type="primary" style="margin-left: 10px;" @click="refreshCharts">刷新</el-button>
      </div>
    </div>
    <!-- ç»Ÿè®¡å¡ç‰‡åŒºåŸŸ -->
    <!-- ç»Ÿè®¡å¡ç‰‡åŒºåŸŸ - ç»‘定响应式数据 -->
    <el-row :gutter="20" class="stats-card-row">
      <el-col :span="6">
        <el-card class="stats-card">
          <div class="card-title">总库存(件)</div>
          <div class="card-value">269,225</div>
          <div class="card-change"><el-tag type="success">↑ 2.1% è¾ƒæ˜¨æ—¥</el-tag></div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="stats-card">
          <div class="card-title">待出库订单</div>
          <div class="card-value">425</div>
          <div class="card-change"><el-tag type="warning">↑ 5.3% è¾ƒ1小时前</el-tag></div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="stats-card">
          <div class="card-title">今日作业完成率</div>
          <div class="card-value">93.9%</div>
          <div class="card-change"><el-tag type="success">↑ 1.8% è¾ƒæ˜¨æ—¥</el-tag></div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="stats-card">
          <div class="card-title">未处理异常</div>
          <div class="card-value">7</div>
          <div class="card-change"><el-tag type="danger">↑ 1 è¾ƒ30分钟前</el-tag></div>
        </el-card>
      </el-col>
    </el-row>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Box />
        </el-icon>
      </div>
      <div class="card-title">总库存</div>
      <div class="card-value">{{ formatNumber(bigscreendata.totalStockQuantity) }}</div>
    </div>
  </el-col>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Document />
        </el-icon>
      </div>
      <div class="card-title">待出库订单</div>
      <div class="card-value">{{ bigscreendata.unOutBoundOrderCount }}</div>
    </div>
  </el-col>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Download />
        </el-icon>
      </div>
      <div class="card-title">今日入库完成数</div>
      <div class="card-value">{{ bigscreendata.inboundCount }}</div>
    </div>
  </el-col>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Upload />
        </el-icon>
      </div>
      <div class="card-title">今日出库完成数</div>
      <div class="card-value">{{ bigscreendata.outboundCount }}</div>
    </div>
  </el-col>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Box />
        </el-icon>
      </div>
      <div class="card-title">有货料箱</div>
      <div class="card-value">{{ formatNumber(bigscreendata.inStockPallet) }}</div>
    </div>
  </el-col>
  <el-col :span="4">
    <div class="stats-card">
      <div class="metric-icon">
        <el-icon :size="32">
          <Box />
        </el-icon>
      </div>
      <div class="card-title">空箱数量</div>
      <div class="card-value">{{ formatNumber(bigscreendata.freeStockPallet) }}</div>
    </div>
  </el-col>
</el-row>
    <!-- å›¾è¡¨åŒºåŸŸï¼ˆç¬¬ä¸€è¡Œï¼‰ -->
    <el-row :gutter="20" class="chart-row">
      <el-col :span="12">
        <el-card class="chart-card">
          <div class="chart-title">库存库位分布(图像化占比)<el-button type="text" class="view-btn">切换视图</el-button></div>
          <div ref="inventoryPieRef" class="chart-container"></div>
        </el-card>
        <!-- æ›¿æ¢el-card为div,保留chart-card类名 -->
        <div class="chart-card">
          <div class="chart-title">库位利用率</div>
          <div ref="locationRateRef" class="chart-container"></div>
        </div>
      </el-col>
      <el-col :span="12">
        <el-card class="chart-card">
          <div class="chart-title">近7日出入库趋势(图像化走势)
            <el-button-group class="btn-group">
              <el-button type="primary" size="small">全部</el-button>
              <el-button type="default" size="small">入库</el-button>
              <el-button type="default" size="small">出库</el-button>
            </el-button-group>
          </div>
        <div class="chart-card">
          <div class="chart-title">近7日出入库趋势(图像化走势)</div>
          <div ref="stockTrendRef" class="chart-container"></div>
        </el-card>
        </div>
      </el-col>
    </el-row>
    <!-- å›¾è¡¨åŒºåŸŸï¼ˆç¬¬äºŒè¡Œï¼‰ -->
    <el-row :gutter="20" class="chart-row">
      <el-col :span="8">
        <el-card class="chart-card">
          <div class="chart-title">库位利用率</div>
          <div ref="locationRateRef" class="chart-container"></div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="chart-card">
          <div class="chart-title">作业类型分布<el-button type="text" class="view-btn">查看详情</el-button></div>
          <div ref="operationRadarRef" class="chart-container"></div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card class="chart-card">
          <div class="chart-title">异常类型统计趋势<el-button type="text" class="view-btn">筛选</el-button></div>
          <div ref="exceptionTrendRef" class="chart-container"></div>
        </el-card>
      </el-col>
    </el-row>
    <!-- è¡¨æ ¼åŒºåŸŸ -->
    <el-row :gutter="20" class="table-row">
      <el-col :span="12">
        <el-card class="table-card">
          <div class="table-title">订单处理进度<el-select v-model="orderType" placeholder="退货入库"
              style="width: 100px; margin-left: 10px;">
              <el-option label="退货入库" value="return"></el-option>
              <el-option label="普通出库" value="normal"></el-option>
            </el-select></div>
          <el-table :data="orderList" border style="width: 100%;">
            <el-table-column prop="orderNo" label="订单号" />
            <el-table-column prop="type" label="类型" />
            <el-table-column prop="priority" label="优先级">
              <template #default="scope">
                <el-tag
                  :type="scope.row.priority === '紧急' ? 'danger' : scope.row.priority === '加急' ? 'warning' : 'success'">
                  {{ scope.row.priority }}
                </el-tag>
    <!-- è¡¨æ ¼åŒºåŸŸ - ç»‘定后端作业数据 -->
    <el-row :gutter="20" class="table-row" width="100%">
      <el-col :span="24">
        <!-- æ›¿æ¢el-card为div,保留table-card类名 -->
        <div class="table-card">
          <div class="table-title">实时作业监控</div>
          <el-table :data="showTaskList" border style="width: 100%;">
            <el-table-column prop="taskNum" label="任务号" />
            <el-table-column prop="taskStatus" label="任务状态" >
              <template #default="{ row }">
                <span class="task-status" :class="getStatusClass(row.taskStatus)">{{ getTaskStatusText(row.taskStatus) }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="progress" label="进度">
              <template #default="scope">
                <el-progress :percentage="scope.row.progress" :color="scope.row.progressColor" />
            <el-table-column prop="taskType" label="任务类型" >
              <template #default="{ row }">
                <span class="task-type" :class="getTypeClass(row.taskType)">{{ getTaskTypeText(row.taskType) }}</span>
              </template>
            </el-table-column>
          </el-table>
          <div class="table-pagination">
            <el-button type="text">加载更多订单</el-button>
            <el-pagination layout="prev, pager, next, jumper" :current-page="1" :total="50" />
          </div>
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card class="table-card">
          <div class="table-title">实时作业监控
            <el-button-group class="btn-group">
              <el-button type="primary" size="small">全部作业</el-button>
              <el-button type="default" size="small">入库</el-button>
              <el-button type="default" size="small">出库</el-button>
              <el-button type="default" size="small">盘点</el-button>
            </el-button-group>
          </div>
          <el-table :data="operationList" border style="width: 100%;">
            <el-table-column prop="opNo" label="作业单号" />
            <el-table-column prop="opType" label="作业类型" />
            <el-table-column prop="operator" label="操作人员" />
            <el-table-column prop="startTime" label="开始时间" />
            <el-table-column prop="status" label="当前状态">
              <template #default="scope">
                <el-tag :type="scope.row.status === '处理中' ? 'info' : scope.row.status === '已完成' ? 'success' : 'danger'">
                  {{ scope.row.status }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作"><el-button type="text">详情</el-button></el-table-column>
            <el-table-column prop="palletCode" label="托盘编号" />
            <el-table-column prop="sourceAddress" label="起点位置"/>
            <el-table-column prop="targetAddress" label="终点位置"/>
            <el-table-column prop="createDate" label="创建时间"/>
          </el-table>
          <div class="table-pagination">
            <el-pagination layout="prev, pager, next, jumper" :current-page="1" :total="50" />
          </div>
        </el-card>
        </div>
      </el-col>
    </el-row>
  </div>
@@ -157,18 +127,160 @@
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import http from "@/api/http.js";
// å“åº”式数据
const month = ref('month');
const orderType = ref('return');
// æ¨¡æ‹Ÿæ•°æ®
const orderList = ref([
  { orderNo: 'OD20251224001', type: '普通出库', priority: '紧急', progress: 60, progressColor: '#ff4d4f' },
  { orderNo: 'OD20251224002', type: '退货入库', priority: '普通', progress: 80, progressColor: '#1890ff' },
  { orderNo: 'OD20251224003', type: '调拨订单', priority: '加急', progress: 40, progressColor: '#faad14' },
  { orderNo: 'OD20251224004', type: '普通出库', priority: '常规', progress: 100, progressColor: '#52c41a' }
]);
// åŽç«¯è¿”回数据(响应式)
const bigscreendata = ref({
  totalStockQuantity: 0,
  unOutBoundOrderCount: 0,
  dailyCompletionRate: 0,
  unhandledExceptionCount: 0,
  locationUtilizationRate: 0,
  inStockPallet: 0,
  freeStockPallet: 0,
  dailyInOutBoundList: [],
  taskList: [],
  inboundCount: 0,
  outboundCount: 0,
  inventoryLocationDist: [], // åº“存库位分布数据
  exceptionTypeTrend: []     // å¼‚常类型趋势数据
});
const taskStatusMap = {
  100: "新建",
  105: "已发送",
  200: "堆垛机待执行",
  210: "堆垛机执行中",
  220: "堆垛机完成",
  400: "输送线待执行",
  410: "输送线执行中",
  420: "输送线完成",
  300: "AGV待执行",
  310: "AGV执行中",
  315: "AGV取货中",
  320: "AGV待继续执行",
  325: "AGV放货中",
  330: "AGV完成",
  900: "任务完成",
  970: "任务挂起",
  980: "任务取消",
  990: "任务异常",
  110: "提升机执行中"
};
const getTaskStatusText = (statusNum) => {
  // å¤„理空值、无效值,兜底显示“未知状态”
  if (statusNum === undefined || statusNum === null || isNaN(statusNum)) {
    return "未知状态";
  }
  // ç²¾å‡†åŒ¹é…æ˜ å°„值,无匹配则返回“未知状态”
  return taskStatusMap[statusNum] || "未知状态";
};
const showTaskList = ref([]); // è¡¨æ ¼æ˜¾ç¤ºçš„5条任务(轮播用)
const currentTaskIndex = ref(5); // ä¸‹ä¸€ä¸ªè¦æ˜¾ç¤ºçš„任务索引(初始从第6条开始,前5条默认显示)
let taskCarouselTimer = null; // è½®æ’­å®šæ—¶å™¨
// æ•°å­—格式化(千分位分隔)
const formatNumber = (num) => {
  if (!num) return '0';
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const startTaskCarousel = () => {
  // æ¸…除旧定时器,防止重复启动
  if (taskCarouselTimer) clearInterval(taskCarouselTimer);
  const totalTask = bigscreendata.value.taskList.length;
  // ä»»åŠ¡æ•°<=5时,不轮播(没有更多任务可换)
  if (totalTask <= 5) {
    showTaskList.value = [...bigscreendata.value.taskList];
    return;
  }
  // å¯åЍ5秒定时器
  taskCarouselTimer = setInterval(() => {
    // 1. æ–°å¢žä¸‹1条任务到显示列表末尾
    showTaskList.value.push(bigscreendata.value.taskList[currentTaskIndex.value]);
    // 2. åˆ é™¤åˆ—表最前面1条旧任务(始终保持5条)
    showTaskList.value.shift();
    // 3. æ›´æ–°ä¸‹ä¸€ä¸ªä»»åŠ¡ç´¢å¼•ï¼Œè¶…å‡ºæ€»æ•°åˆ™é‡ç½®ï¼ˆå¾ªçŽ¯è½®æ’­ï¼‰
    currentTaskIndex.value++;
    if (currentTaskIndex.value >= totalTask) {
      currentTaskIndex.value = 0; // é‡ç½®ä¸º0,循环播放
    }
  }, 5000); // 5秒间隔
};
const getTaskTypeText = (taskTypeNum) => {
  // å¤„理空值/无效值
  if (!taskTypeNum || isNaN(taskTypeNum)) return "未知类型";
  // å¯¹åº”后端TaskEnumHelper的分组规则
  if (taskTypeNum >= 500 && taskTypeNum < 900) return "入库";
  if (taskTypeNum >= 100 && taskTypeNum < 500) return "出库";
  if (taskTypeNum >= 900 && taskTypeNum < 1000) return "移库";
  return "其他作业"; // å…œåº•
};
// èŽ·å–ä»»åŠ¡çŠ¶æ€æ ·å¼ç±»
const getStatusClass = (statusNum) => {
  if (statusNum === undefined || statusNum === null || isNaN(statusNum)) {
    return "status-unknown";
  }
  // æ ¹æ®çŠ¶æ€èŒƒå›´è¿”å›žä¸åŒæ ·å¼
  if (statusNum >= 900) return "status-completed"; // å®Œæˆ
  if (statusNum >= 400) return "status-processing"; // è¾“送线执行中
  if (statusNum >= 300) return "status-processing"; // AGV执行中
  if (statusNum >= 200) return "status-processing"; // å †åž›æœºæ‰§è¡Œä¸­
  if (statusNum >= 100) return "status-pending"; // æ–°å»ºã€å·²å‘送
  if (statusNum === 970) return "status-suspended"; // æŒ‚èµ·
  if (statusNum === 980) return "status-canceled"; // å–消
  if (statusNum === 990) return "status-error"; // å¼‚常
  return "status-unknown";
};
// èŽ·å–ä»»åŠ¡ç±»åž‹æ ·å¼ç±»
const getTypeClass = (taskTypeNum) => {
  if (!taskTypeNum || isNaN(taskTypeNum)) return "type-unknown";
  // å¯¹åº”后端TaskEnumHelper的分组规则
  if (taskTypeNum >= 500 && taskTypeNum < 900) return "type-inbound"; // å…¥åº“
  if (taskTypeNum >= 100 && taskTypeNum < 500) return "type-outbound"; // å‡ºåº“
  if (taskTypeNum >= 900 && taskTypeNum < 1000) return "type-transfer"; // ç§»åº“
  return "type-other"; // å…¶ä»–作业
};
// èŽ·å–åŽç«¯å¤§å±æ•°æ®
const fetchBigGreenData = async () => {
  try {
    const res = await http.get('/api/BigScreen/GetBigGreenData');
    console.log('大屏数据', res);
    bigscreendata.value = res.data || res;
    // æ•°æ®æ›´æ–°åŽåˆ·æ–°å›¾è¡¨
    nextTick(() => {
      const total = bigscreendata.value.taskList.length;
      // åˆå§‹åŒ–显示前5条(不足5条则显示全部)
      showTaskList.value = total >=5
        ? [...bigscreendata.value.taskList.slice(0,5)]
        : [...bigscreendata.value.taskList];
      startTaskCarousel(); // å¯åŠ¨ä»»åŠ¡è½®æ’­
      // refreshCharts(); // ç§»é™¤è¿™é‡Œçš„调用,改为在数据获取后统一初始化
    });
    // è¿”回Promise,以便在数据获取成功后初始化图表
    return Promise.resolve();
  } catch (error) {
    ElMessage.error('数据获取失败,请检查后端接口是否正常');
    return Promise.reject(error);
  }
};
// å¤‡ç”¨æ¨¡æ‹Ÿæ•°æ®
const operationList = ref([
  { opNo: 'JW251224001', opType: '入库', operator: '张三', startTime: '15:30:22', status: '处理中' },
  { opNo: 'CK251224002', opType: '出库', operator: '李四', startTime: '15:25:10', status: '已完成' },
@@ -176,29 +288,38 @@
  { opNo: 'SC251224005', opType: '上架', operator: '孙七', startTime: '15:10:18', status: '异常' }
]);
// å›¾è¡¨å®¹å™¨
// å›¾è¡¨å®¹å™¨Ref
const inventoryPieRef = ref(null);
const stockTrendRef = ref(null);
const locationRateRef = ref(null);
const operationRadarRef = ref(null);
const exceptionTrendRef = ref(null);
// å›¾è¡¨å®žä¾‹
// å›¾è¡¨å®žä¾‹ï¼ˆå…¨å±€ç®¡ç†ï¼Œç”¨äºŽé”€æ¯å’Œresize)
let inventoryPieChart = null;
let stockTrendChart = null;
let locationRateChart = null;
let operationRadarChart = null;
let exceptionTrendChart = null;
// åˆå§‹åŒ–库存库位分布饼图
const initInventoryPie = () => {
// åˆå§‹åŒ–库存库位分布饼图(关联后端数据)
const initInventoryPie= () => {
  if (!inventoryPieRef.value) return;
  // é”€æ¯æ—§å®žä¾‹ï¼Œé˜²æ­¢å†…存泄漏
  if (inventoryPieChart) {
    inventoryPieChart.dispose();
  }
  inventoryPieChart = echarts.init(inventoryPieRef.value);
  // ä¼˜å…ˆä½¿ç”¨åŽç«¯æ•°æ®ï¼Œæ— æ•°æ®åˆ™ç”¨é»˜è®¤å€¼
  const locationData = bigscreendata.value.inventoryLocationDist.length
    ? bigscreendata.value.inventoryLocationDist
    : [
        { value: 48.7, name: '常温区A区', itemStyle: { color: '#5470c6' } },
        { value: 29.2, name: '冷藏区B区', itemStyle: { color: '#91cc75' } },
        { value: 21.9, name: '保税区C区', itemStyle: { color: '#fac858' } },
        { value: 2.2, name: '残次品区D区', itemStyle: { color: '#ee6666' } }
      ];
  const option = {
    tooltip: {
      trigger: 'item',
@@ -207,7 +328,7 @@
    legend: {
      bottom: 0,
      left: 'center',
      data: ['常温区A区', '冷藏区B区', '保税区C区', '残次品区D区']
      data: locationData.map(item => item.name)
    },
    series: [{
      name: '库存库位分布',
@@ -234,12 +355,7 @@
      labelLine: {
        show: false
      },
      data: [
        { value: 48.7, name: '常温区A区', itemStyle: { color: '#5470c6' } },
        { value: 29.2, name: '冷藏区B区', itemStyle: { color: '#91cc75' } },
        { value: 21.9, name: '保税区C区', itemStyle: { color: '#fac858' } },
        { value: 2.2, name: '残次品区D区', itemStyle: { color: '#ee6666' } }
      ]
      data: locationData
    }]
  };
@@ -247,220 +363,117 @@
  return inventoryPieChart;
};
// åˆå§‹åŒ–è¿‘7日出入库趋势图
const initStockTrend = () => {
  if (!stockTrendRef.value) return;
// åˆå§‹åŒ–è¿‘7日出入库趋势图(关联后端数据)
// const initStockTrend = () => {
//   const chart = echarts.init(efficiencyChartRef.value)
//       const option = {
//         tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
//         grid: {
//           left: '3%', right: '4%', bottom: '3%', top: '10%',
//           containLabel: true
//         },
//         xAxis: {
//           type: 'category',
//           data: ['入库作业', '出库作业', '盘点作业', '调拨作业', '补货作业'],
//           axisLine: { lineStyle: { color: '#fff' } },
//           axisLabel: { color: '#fff' }
//         },
//         yAxis: {
//           type: 'value',
//           name: '效率(单位/小时)',
//           axisLine: { lineStyle: { color: '#fff' } },
//           splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
//           axisLabel: { color: '#fff' }
//         },
//         series: [
//           {
//             data: [180, 156, 95, 78, 120],
//             type: 'bar',
//             barWidth: '40%',
//             itemStyle: {
//               color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
//                 { offset: 0, color: '#83bff6' },
//                 { offset: 1, color: '#188df0' }
//               ]),
//               borderRadius: [5, 5, 0, 0]
//             }
//           }
//         ]
//       }
//       chart.setOption(option)
//       return chart
// };
  if (stockTrendChart) {
    stockTrendChart.dispose();
  }
  stockTrendChart = echarts.init(stockTrendRef.value);
  const option = {
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'cross'
      }
    },
    legend: {
      data: ['入库量', '出库量'],
      top: 10
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      top: '15%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      boundaryGap: true,
      data: ['12-18', '12-19', '12-20', '12-21', '12-22', '12-23', '12-24']
    },
    yAxis: {
      type: 'value',
      name: '数量(千件)',
      max: 25
    },
    series: [
      {
        name: '入库量',
        type: 'bar',
        barWidth: '30%',
        data: [10, 12, 10, 12, 10, 12, 12],
        itemStyle: { color: '#52c41a' }
      },
      {
        name: '出库量',
        type: 'bar',
        barWidth: '30%',
        data: [16, 18, 14, 18, 16, 18, 20],
        itemStyle: { color: '#1890ff' }
      }
    ]
  };
  stockTrendChart.setOption(option);
  return stockTrendChart;
};
// åˆå§‹åŒ–库位利用率环形图
// åˆå§‹åŒ–库位利用率环形图(修正:统一实例管理,关联后端数据)
const initLocationRate = () => {
  if (!locationRateRef.value) return;
  // é”€æ¯æ—§å®žä¾‹
  if (locationRateChart) {
    locationRateChart.dispose();
  }
  locationRateChart = echarts.init(locationRateRef.value);
  // ä¼˜å…ˆä½¿ç”¨åŽç«¯æ•°æ®ï¼Œæ— æ•°æ®åˆ™ç”¨é»˜è®¤å€¼
  console.log('库位利用率数据', bigscreendata.value.locationUtilizationRate);
  const utilizationRate = bigscreendata.value.locationUtilizationRate || 0;
  const freeRate = 100 - utilizationRate;
  const option = {
    tooltip: {
      formatter: '{a} <br/>{b} : {c}%'
      trigger: 'item',
      formatter: '{b}: {c}%'
    },
    series: [{
      name: '库位利用率',
      type: 'gauge',
      min: 0,
      max: 100,
      splitNumber: 10,
      radius: '90%',
      center: ['50%', '55%'],
      startAngle: 180,
      endAngle: 0,
      progress: {
        show: true,
        width: 20,
        itemStyle: {
          color: '#1890ff'
    legend: {
      bottom: 0,
      left: 'center',
      data: ['已占用库位', '空闲库位'],
      textStyle: { fontSize: 12, color: '#666' }
    },
    graphic: [
      {
        type: 'text',
        left: 'right',
        top: '10%',
        style: {
          text: `${utilizationRate}%`,
          fontSize: 24,
          fontWeight: 'bold',
          fill: '#333'
        }
      },
      axisLine: {
        lineStyle: {
          width: 20,
          color: [[1, 'rgba(200,200,200,0.3)']]
      {
        type: 'text',
        left: 'right',
        top: '25%',
        style: {
          text: '库位利用率',
          fontSize: 14,
          fill: '#666'
        }
      },
      axisTick: {
        distance: -30,
        splitNumber: 5,
        lineStyle: {
          width: 2,
          color: '#999'
        }
      },
      splitLine: {
        distance: -30,
        length: 14,
        lineStyle: {
          width: 3,
          color: '#999'
        }
      },
      axisLabel: {
        distance: -20,
        color: '#999',
        fontSize: 12
      },
      anchor: {
        show: false
      },
      title: {
        show: true,
        offsetCenter: [0, '30%'],
        fontSize: 16,
        fontWeight: 'bold'
      },
      detail: {
        valueAnimation: true,
        formatter: '{value}%',
        fontSize: 20,
        fontWeight: 'bold',
        offsetCenter: [0, '70%']
      },
      data: [{
        value: 86.2,
        name: '库位利用率'
      }]
    }]
      }
    ],
    series: [
      {
        type: 'pie',
        radius: ['50%', '70%'],
        center: ['40%', '50%'],
        avoidLabelOverlap: false,
        label: { show: false },
        labelLine: { show: false },
        data: [
          { value: utilizationRate, name: '已占用库位', itemStyle: { color: '#1890ff' } },
          { value: freeRate, name: '空闲库位', itemStyle: { color: '#e5e9f2' } }
        ]
      }
    ]
  };
  locationRateChart.setOption(option);
  return locationRateChart;
};
// åˆå§‹åŒ–作业类型雷达图
const initOperationRadar = () => {
  if (!operationRadarRef.value) return;
  if (operationRadarChart) {
    operationRadarChart.dispose();
  }
  operationRadarChart = echarts.init(operationRadarRef.value);
  const option = {
    tooltip: {
      trigger: 'item'
    },
    legend: {
      show: false
    },
    radar: {
      indicator: [
        { name: '出库作业', max: 100 },
        { name: '入库作业', max: 100 },
        { name: '调拨作业', max: 100 },
        { name: '盘点作业', max: 100 }
      ],
      shape: 'circle',
      splitNumber: 5,
      axisName: {
        color: '#666'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(0,0,0,0.1)'
        }
      },
      splitArea: {
        show: true,
        areaStyle: {
          color: ['rgba(255,255,255,0.8)', 'rgba(200,200,200,0.2)']
        }
      }
    },
    series: [{
      name: '作业类型分布',
      type: 'radar',
      symbolSize: 8,
      areaStyle: {
        color: 'rgba(24,144,255,0.3)'
      },
      lineStyle: {
        width: 2
      },
      itemStyle: {
        color: '#1890ff'
      },
      data: [{
        value: [45, 30, 15, 10],
        name: '作业占比',
        label: {
          show: true,
          formatter: function (params) {
            return params.value + '%';
          }
        }
      }]
    }]
  };
  operationRadarChart.setOption(option);
  return operationRadarChart;
};
// åˆå§‹åŒ–异常趋势折线图
// åˆå§‹åŒ–异常类型统计趋势图(关联后端数据)
const initExceptionTrend = () => {
  if (!exceptionTrendRef.value) return;
@@ -469,6 +482,17 @@
  }
  exceptionTrendChart = echarts.init(exceptionTrendRef.value);
  // ä¼˜å…ˆä½¿ç”¨åŽç«¯æ•°æ®
  const exceptionData = bigscreendata.value.exceptionTypeTrend.length
    ? bigscreendata.value.exceptionTypeTrend
    : {
        dates: ['12-18', '12-19', '12-20', '12-21', '12-22', '12-23', '12-24'],
        stockShort: [10, 11, 9, 12, 10, 13, 12],
        orderTimeout: [8, 9, 7, 8, 7, 9, 8],
        locationException: [4, 5, 2, 4, 3, 5, 4],
        checkDiff: [2, 3, 1, 2, 1, 3, 2]
      };
  const option = {
    tooltip: {
      trigger: 'axis',
@@ -490,7 +514,7 @@
    xAxis: {
      type: 'category',
      boundaryGap: false,
      data: ['12-18', '12-19', '12-20', '12-21', '12-22', '12-23', '12-24']
      data: exceptionData.dates
    },
    yAxis: {
      type: 'value',
@@ -503,11 +527,9 @@
        smooth: true,
        symbol: 'circle',
        symbolSize: 8,
        data: [10, 11, 9, 12, 10, 13, 12],
        data: exceptionData.stockShort,
        itemStyle: { color: '#ff4d4f' },
        lineStyle: {
          width: 3
        }
        lineStyle: { width: 3 }
      },
      {
        name: '订单超时',
@@ -515,11 +537,9 @@
        smooth: true,
        symbol: 'circle',
        symbolSize: 8,
        data: [8, 9, 7, 8, 7, 9, 8],
        data: exceptionData.orderTimeout,
        itemStyle: { color: '#faad14' },
        lineStyle: {
          width: 3
        }
        lineStyle: { width: 3 }
      },
      {
        name: '库位异常',
@@ -527,11 +547,9 @@
        smooth: true,
        symbol: 'circle',
        symbolSize: 8,
        data: [4, 5, 2, 4, 3, 5, 4],
        data: exceptionData.locationException,
        itemStyle: { color: '#722ed1' },
        lineStyle: {
          width: 3
        }
        lineStyle: { width: 3 }
      },
      {
        name: '盘点差异',
@@ -539,11 +557,9 @@
        smooth: true,
        symbol: 'circle',
        symbolSize: 8,
        data: [2, 3, 1, 2, 1, 3, 2],
        data: exceptionData.checkDiff,
        itemStyle: { color: '#13c2c2' },
        lineStyle: {
          width: 3
        }
        lineStyle: { width: 3 }
      }
    ]
  };
@@ -558,8 +574,7 @@
    initInventoryPie,
    initStockTrend,
    initLocationRate,
    initOperationRadar,
    initExceptionTrend
    initExceptionTrend,
  ];
  charts.forEach(initFunc => {
@@ -570,13 +585,12 @@
  });
};
// ç›‘听窗口大小变化
// çª—口大小变化监听
const handleResize = () => {
  const charts = [
    inventoryPieChart,
    stockTrendChart,
    locationRateChart,
    operationRadarChart,
    exceptionTrendChart
  ];
@@ -587,26 +601,55 @@
  });
};
// æŒ‚载后初始化图表
// ç»„件挂载时:先请求数据,再初始化图表
onMounted(() => {
  nextTick(() => {
    initInventoryPie();
    initStockTrend();
    initLocationRate();
    initOperationRadar();
    initExceptionTrend();
  console.log('组件已挂载,开始初始化');
    window.addEventListener('resize', handleResize);
  // å…ˆèŽ·å–åŽç«¯æ•°æ®
  fetchBigGreenData().then(() => {
    console.log('数据获取成功,开始初始化图表');
    // æ•°æ®èŽ·å–æˆåŠŸåŽå†åˆå§‹åŒ–å›¾è¡¨
    nextTick(() => {
      console.log('DOM已更新,开始初始化图表');
      try {
        // ç«‹å³åˆå§‹åŒ–图表
        initInventoryPie();
        console.log('库存分布饼图初始化完成');
        // ç‰¹åˆ«å¤„理出入库趋势图
        if (stockTrendRef.value) {
          console.log('出入库趋势图容器存在,开始初始化');
          initStockTrend();
          console.log('出入库趋势图初始化完成');
        } else {
          console.error('出入库趋势图容器不存在');
        }
        initLocationRate();
        console.log('库位利用率图初始化完成');
        initExceptionTrend();
        console.log('异常趋势图初始化完成');
        window.addEventListener('resize', handleResize);
        console.log('所有图表初始化完成');
      } catch (error) {
        console.error('图表初始化过程中出错:', error);
      }
    });
  }).catch(error => {
    console.error('获取数据失败:', error);
  });
});
// ç»„件卸载时清理
// ç»„件卸载时:销毁图表实例,移除事件监听
onUnmounted(() => {
  const charts = [
    inventoryPieChart,
    stockTrendChart,
    locationRateChart,
    operationRadarChart,
    exceptionTrendChart
  ];
@@ -615,17 +658,18 @@
      chart.dispose();
    }
  });
  clearInterval(taskCarouselTimer); // æ¸…除轮播定时器
  window.removeEventListener('resize', handleResize);
});
</script>
<style scoped>
.wms-dashboard {
  padding: 20px;
  background: #f5f7fa;
  padding: 24px;
  background: linear-gradient(135deg, #f0f2f5 0%, #e6e9f0 100%);
  min-height: 100vh;
  box-sizing: border-box;
  font-family: "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.header {
@@ -645,81 +689,411 @@
  margin-bottom: 20px;
}
/* æ ¸å¿ƒä¿®æ”¹ï¼šè¡¥å……div版card的基础样式,模拟el-card的视觉效果 */
.stats-card, .chart-card, .table-card {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
  border: 1px solid #ebeef5;
}
.stats-card {
  height: 120px;
  height: 140px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 20px;
  align-items: center;
  padding: 20px 15px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
  background: linear-gradient(145deg, #ffffff 0%, #f9fafc 100%);
}
.stats-card:hover {
  transform: translateY(-6px) scale(1.02);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
  border-color: #409eff;
  background: linear-gradient(145deg, #ffffff 0%, #f0f2f5 100%);
}
.metric-icon {
  width: 56px;
  height: 56px;
  border-radius: 16px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  margin-bottom: 12px;
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  transition: all 0.3s ease;
}
.stats-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-title {
  font-size: 14px;
  color: #666;
  margin-bottom: 8px;
  font-size: 15px;
  color: #606266;
  margin-bottom: 10px;
  font-weight: 500;
  letter-spacing: 0.5px;
}
.card-value {
  font-size: 28px;
  font-weight: bold;
  font-size: 26px;
  font-weight: 600;
  margin: 4px 0;
  background: linear-gradient(to right, #409eff, #36cfc9);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.card-value {
  font-size: 32px;
  font-weight: 700;
  margin: 8px 0 4px;
  color: #333;
  color: #2c3e50;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  line-height: 1.2;
}
.card-change {
  margin-top: 8px;
  margin-top: 3px;
}
.chart-card {
  height: 320px;
  padding: 15px;
  height: 400px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);
  border: none;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
  border-radius: 12px;
  transition: all 0.3s ease;
  overflow: hidden;
  position: relative;
  min-height: 400px;
}
.chart-card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  transform: translateY(-2px);
}
.chart-container {
  width: 100%;
  height: 100%;
  min-height: 250px;
  min-height: 300px;
  flex: 1;
  position: relative;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.9);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.03);
  overflow: hidden;
  z-index: 1;
  display: block;
}
.chart-title,
.table-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  font-size: 16px;
  font-weight: bold;
  color: #333;
  margin-bottom: 24px;
  font-size: 18px;
  font-weight: 600;
  color: #2c3e50;
  padding-left: 12px;
  border-left: 4px solid #409eff;
  position: relative;
  letter-spacing: 0.5px;
}
.view-btn {
  font-size: 12px;
}
.table-card {
  padding: 24px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
  border: 1px solid rgba(0, 0, 0, 0.04);
  overflow: hidden;
  transition: all 0.3s ease;
}
.table-card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  transform: translateY(-2px);
}
.table-pagination {
  display: flex;
  justify-content: space-between;
  justify-content: flex-end;
  align-items: center;
  margin-top: 15px;
  margin-top: 20px;
  padding-top: 15px;
  border-top: 1px solid #ebeef5;
}
/* è¡¨æ ¼æ ·å¼ä¼˜åŒ– */
:deep(.el-table) {
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
:deep(.el-table th) {
  background-color: #f5f7fa;
  color: #606266;
  font-weight: 600;
  padding: 12px 0;
}
:deep(.el-table td) {
  padding: 12px 0;
}
:deep(.el-table--border) {
  border-radius: 6px;
}
:deep(.el-table--border::after) {
  display: none;
}
:deep(.el-table--group::after) {
  display: none;
}
:deep(.el-table::before) {
  display: none;
}
:deep(.el-table__fixed-right::before) {
  display: none;
}
:deep(.el-table__fixed::before) {
  display: none;
}
/* åˆ†é¡µå™¨æ ·å¼ä¼˜åŒ– */
:deep(.el-pagination) {
  margin-top: 10px;
}
:deep(.el-pagination .btn-prev),
:deep(.el-pagination .btn-next),
:deep(.el-pagination .el-pager li) {
  border-radius: 4px;
  margin: 0 2px;
  transition: all 0.3s;
}
:deep(.el-pagination .btn-prev:hover),
:deep(.el-pagination .btn-next:hover),
:deep(.el-pagination .el-pager li:hover) {
  transform: translateY(-2px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
:deep(.el-pagination .el-pager li.active) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: #fff;
}
/* ä»»åŠ¡çŠ¶æ€æ ·å¼ */
.task-status {
  display: inline-block;
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 500;
  text-align: center;
  min-width: 80px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  transition: all 0.2s ease;
  letter-spacing: 0.5px;
}
.status-pending {
  background-color: rgba(64, 158, 255, 0.1);
  color: #409eff;
  border: 1px solid rgba(64, 158, 255, 0.2);
}
.status-processing {
  background-color: rgba(103, 194, 58, 0.1);
  color: #67c23a;
  border: 1px solid rgba(103, 194, 58, 0.2);
}
.status-completed {
  background-color: rgba(103, 194, 58, 0.1);
  color: #67c23a;
  border: 1px solid rgba(103, 194, 58, 0.2);
}
.status-suspended {
  background-color: rgba(230, 162, 60, 0.1);
  color: #e6a23c;
  border: 1px solid rgba(230, 162, 60, 0.2);
}
.status-canceled {
  background-color: rgba(144, 147, 153, 0.1);
  color: #909399;
  border: 1px solid rgba(144, 147, 153, 0.2);
}
.status-error {
  background-color: rgba(245, 108, 108, 0.1);
  color: #f56c6c;
  border: 1px solid rgba(245, 108, 108, 0.2);
}
.status-unknown {
  background-color: rgba(144, 147, 153, 0.1);
  color: #909399;
  border: 1px solid rgba(144, 147, 153, 0.2);
}
/* ä»»åŠ¡ç±»åž‹æ ·å¼ */
.task-type {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px 12px;
  border-radius: 16px;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  min-width: 70px;
  position: relative;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  letter-spacing: 0.5px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
@keyframes pulse {
  0% {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  }
  50% {
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
  }
  100% {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  }
}
.task-type::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  transform: translateX(-100%);
  transition: transform 0.8s cubic-bezier(0.19, 1, 0.22, 1);
}
.task-type:hover::before {
  transform: translateX(0);
}
.task-type:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
.task-type::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
.task-type:hover::after {
  transform: scaleX(1);
}
.type-inbound:hover {
  box-shadow: 0 5px 15px rgba(19, 194, 194, 0.4);
}
.type-outbound:hover {
  box-shadow: 0 5px 15px rgba(47, 84, 235, 0.4);
}
.type-transfer:hover {
  box-shadow: 0 5px 15px rgba(250, 140, 22, 0.4);
}
.type-other:hover {
  box-shadow: 0 5px 15px rgba(114, 46, 209, 0.4);
}
.type-unknown:hover {
  box-shadow: 0 5px 15px rgba(89, 89, 89, 0.4);
}
.type-inbound {
  background: linear-gradient(135deg, #13c2c2 0%, #36cfc9 100%);
  color: white;
  box-shadow: 0 3px 5px rgba(19, 194, 194, 0.3);
  animation: pulse 2s infinite;
}
.type-outbound {
  background: linear-gradient(135deg, #2f54eb 0%, #597ef7 100%);
  color: white;
  box-shadow: 0 3px 5px rgba(47, 84, 235, 0.3);
  animation: pulse 2.5s infinite;
}
.type-transfer {
  background: linear-gradient(135deg, #fa8c16 0%, #ffc53d 100%);
  color: white;
  box-shadow: 0 3px 5px rgba(250, 140, 22, 0.3);
  animation: pulse 3s infinite;
}
.type-other {
  background: linear-gradient(135deg, #722ed1 0%, #b37feb 100%);
  color: white;
  box-shadow: 0 3px 5px rgba(114, 46, 209, 0.3);
  animation: pulse 2.2s infinite;
}
.type-unknown {
  background: linear-gradient(135deg, #595959 0%, #8c8c8c 100%);
  color: white;
  box-shadow: 0 3px 5px rgba(89, 89, 89, 0.3);
  animation: pulse 2.8s infinite;
}
.btn-group {
  margin-left: 10px;
}
/* ç¡®ä¿å›¾è¡¨å®¹å™¨æœ‰æ˜Žç¡®å°ºå¯¸ */
:deep(.el-card__body) {
  height: 100%;
  display: flex;
  flex-direction: column;
}
/* ç§»é™¤åŽŸæœ‰çš„el-card__body样式穿透,因为已经替换为纯div */
</style>