647556386
2026-01-06 39e7948f42ccdf82441041c291c0e7f518bb7c09
Merge branch 'htq20251215' of http://115.159.85.185:8098/r/ZhongRui/ALDbanyunxiangmu into htq20251215
已修改5个文件
1052 ■■■■ 文件已修改
项目代码/Dashboard/src/utils/http.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/views/Dashboard.vue 804 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/Home.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/charts/wms-dashboard.vue 163 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/BigGreenService/BigGreenService.cs 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/Dashboard/src/utils/http.js
@@ -3,7 +3,7 @@
// åˆ›å»ºaxios实例
const request = axios.create({
  baseURL: '/api',
  baseURL: 'http://localhost:9291',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
@@ -34,8 +34,7 @@
    // æ ¹æ®å®žé™…后端返回格式调整
    // å‡è®¾åŽç«¯è¿”回格式为 { code: 200, data: {}, message: '' }
    if (res.code !== undefined && res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      return Promise.reject(new Error(res.message || '请求失败'))
    }
    return res
ÏîÄ¿´úÂë/Dashboard/src/views/Dashboard.vue
@@ -85,28 +85,34 @@
        <div class="table-card">
          <div class="card-title">
            <span>实时作业任务</span>
            <el-button type="primary" size="small" @click="refreshData">
              <el-icon><Refresh /></el-icon>
              åˆ·æ–°
            </el-button>
          </div>
          <el-table :data="taskList" style="width: 100%" height="200">
            <el-table-column prop="taskNo" label="任务编号" width="150" />
            <el-table-column prop="type" label="任务类型" width="100">
          <el-table :data="showTaskList" style="width: 100%" :height="tableHeight">
            <el-table-column prop="taskNum" label="任务号" />
            <el-table-column prop="taskStatus" label="任务状态" width="120">
              <template #default="{ row }">
                <el-tag :type="row.type === '入库' ? 'success' : 'warning'">{{ row.type }}</el-tag>
                <div class="status-container" :class="getStatusClass(row.taskStatus)">
                  <div class="status-dot"></div>
                  <span class="status-text">{{ getTaskStatusText(row.taskStatus) }}</span>
                </div>
              </template>
            </el-table-column>
            <el-table-column prop="material" label="物料名称" />
            <el-table-column prop="quantity" label="数量" width="100" />
            <el-table-column prop="location" label="库位" width="120" />
            <el-table-column prop="status" label="状态" width="100">
            <el-table-column prop="taskType" label="任务类型" width="100">
              <template #default="{ row }">
                <el-tag :type="getStatusType(row.status)">{{ row.status }}</el-tag>
                <div class="type-container" :class="getTypeClass(row.taskType)">
                  <el-icon class="type-icon">
                    <Box v-if="getTypeClass(row.taskType) === 'type-inbound'" />
                    <Upload v-else-if="getTypeClass(row.taskType) === 'type-outbound'" />
                    <Refresh v-else-if="getTypeClass(row.taskType) === 'type-transfer'" />
                    <Operation v-else />
                  </el-icon>
                  <span class="type-text">{{ getTaskTypeText(row.taskType) }}</span>
                </div>
              </template>
            </el-table-column>
            <el-table-column prop="operator" label="操作员" width="100" />
            <el-table-column prop="time" label="时间" width="160" />
            <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>
      </div>
@@ -115,27 +121,231 @@
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import { http } from '@/utils/http'
import { formatDateTime } from '@/utils'
import { ElMessage } from 'element-plus'
// å¯¼å…¥Element Plus图标
import {
  DataBoard, Box, Operation, Warning, Download, Upload, List,
  Top, Bottom, Refresh, ArrowRight, Clock
} from '@element-plus/icons-vue'
export default {
  name: 'Dashboard',
  components: {
    DataBoard, Box, Operation, Warning, Download, Upload, List,
    Top, Bottom, Refresh, ArrowRight, Clock
  },
  setup() {
    const currentTime = ref('')
    const trendChartRef = ref(null)
    const categoryChartRef = ref(null)
    const efficiencyChartRef = ref(null)
    const tableHeight = ref(200)
    // å›¾è¡¨å®žä¾‹å¼•用
    const trendChart = ref(null)
    const categoryChart = ref(null)
    const efficiencyChart = ref(null)
    // åŽç«¯è¿”回数据(响应式)
    const bigscreendata = ref({
      totalStockQuantity: 0,
      unOutBoundOrderCount: 0,
      dailyCompletionRate: 0,
      unhandledExceptionCount: 0,
      locationUtilizationRate: 0,
      inStockPallet: 0,
      freeStockPallet: 0,
      dailyInOutBoundList: [],
      taskList: [],
      inboundCount: 0,
      outboundCount: 0,
      inventoryLocationDist: [],
      completeTask: []
    })
    // ä»»åŠ¡çŠ¶æ€æ˜ å°„
    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 metrics = ref([
      { label: '总库存量', value: '12,580', icon: 'Box', color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', trend: 8.5 },
      { label: '今日入库', value: '1,280', icon: 'Download', color: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', trend: 12.3 },
      { label: '今日出库', value: '965', icon: 'Upload', color: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', trend: -3.2 },
      { label: '待处理任务', value: '48', icon: 'List', color: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', trend: 5.7 }
      {
        label: '总库存量',
        value: 0,
        icon: 'Box',
        color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
        trend: 8.5
      },
      {
        label: '今日入库',
        value: 0,
        icon: 'Download',
        color: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
        trend: 12.3
      },
      {
        label: '今日出库',
        value: 0,
        icon: 'Upload',
        color: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
        trend: -3.2
      },
      {
        label: '待处理任务',
        value: 0,
        icon: 'List',
        color: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
        trend: 5.7
      }
    ])
    // ä»»åŠ¡åˆ—è¡¨ç›¸å…³
    const taskList = ref([])
    const showTaskList = ref([])
    const currentTaskIndex = ref(7) // åˆå§‹ä»Žç¬¬8条开始(前7条默认显示)
    let taskCarouselTimer = null
    // è‡ªåŠ¨åˆ·æ–°ç›¸å…³é…ç½®
    const lastInboundToday = ref(0)  // ä¸Šä¸€æ¬¡å½“天入库量
    const lastOutboundToday = ref(0) // ä¸Šä¸€æ¬¡å½“天出库量
    const refreshInterval = ref(5 * 60 * 1000) // å®šæ—¶åˆ·æ–°é—´éš”(5分钟)
    const minRefreshGap = ref(30 * 1000) // æœ€å°åˆ·æ–°é—´éš”(防抖,30秒)
    let lastRefreshTime = ref(0) // ä¸Šä¸€æ¬¡åˆ·æ–°æ—¶é—´
    let autoRefreshTimer = null // è‡ªåŠ¨åˆ·æ–°å®šæ—¶å™¨
    // èŽ·å–ä»»åŠ¡çŠ¶æ€æ–‡æœ¬
    const getTaskStatusText = (statusNum) => {
      if (statusNum === undefined || statusNum === null || isNaN(statusNum)) {
        return "未知状态";
      }
      return taskStatusMap[statusNum] || "未知状态";
    }
    // å¯åŠ¨ä»»åŠ¡è½®æ’­
    const startTaskCarousel = () => {
      if (taskCarouselTimer) clearInterval(taskCarouselTimer);
      const totalTask = bigscreendata.value.taskList.length;
      if (totalTask <= 7) { // ä»»åŠ¡æ•°<=7时不轮播
        showTaskList.value = [...bigscreendata.value.taskList];
        return;
      }
      taskCarouselTimer = setInterval(() => {
        const tableElement = document.querySelector('.el-table');
        if (tableElement) {
          tableElement.classList.add('flash-effect');
          setTimeout(() => {
            tableElement.classList.remove('flash-effect');
          }, 600);
        }
        // æ–°å¢žä¸‹1条,删除最前1条(保持7条显示)
        showTaskList.value.push(bigscreendata.value.taskList[currentTaskIndex.value]);
        showTaskList.value.shift();
        // å¾ªçŽ¯ç´¢å¼•
        currentTaskIndex.value++;
        if (currentTaskIndex.value >= totalTask) {
          currentTaskIndex.value = 0;
        }
      }, 5000); // 5秒轮播一次
    }
    // èŽ·å–ä»»åŠ¡ç±»åž‹æ–‡æœ¬
    const getTaskTypeText = (taskTypeNum) => {
      if (!taskTypeNum || isNaN(taskTypeNum)) return "未知类型";
      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";
      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;
    updateMetrics();
    taskList.value = bigscreendata.value.taskList || [];
    showTaskList.value = taskList.value.slice(0, 7);
    startTaskCarousel();
    // æ•°æ®åŠ è½½å®ŒæˆåŽåˆå§‹åŒ–å›¾è¡¨
    nextTick(() => {
      initEfficiencyChart();
      initTrendChart();
      initCategoryChart();
    });
  } catch (error) {
    console.error('获取大屏数据失败:', error);
    ElMessage.error('获取数据失败,请稍后重试');
  }
};
    // æ›´æ–°å…³é”®æŒ‡æ ‡
    const updateMetrics = () => {
      metrics.value[0].value = bigscreendata.value.totalStockQuantity || 0
      metrics.value[1].value = bigscreendata.value.inboundCount || 0
      metrics.value[2].value = bigscreendata.value.outboundCount || 0
      metrics.value[3].value = bigscreendata.value.unOutBoundOrderCount || 0
    }
    // æ›´æ–°æ—¶é—´
    let timer
@@ -143,77 +353,103 @@
      currentTime.value = formatDateTime(new Date())
    }
    // èŽ·å–çŠ¶æ€ç±»åž‹
    const getStatusType = (status) => {
      const map = {
        '进行中': 'primary',
        '已完成': 'success',
        '待处理': 'info',
        '异常': 'danger'
      }
      return map[status] || 'info'
    }
    // åˆå§‹åŒ–趋势图
    // åˆå§‹åŒ–趋势图(直接使用后端日期)
    const initTrendChart = () => {
      const chart = echarts.init(trendChartRef.value)
      if (!trendChartRef.value) return
      trendChart.value = echarts.init(trendChartRef.value)
      // ç›´æŽ¥ä»ŽåŽç«¯èŽ·å–æ—¥æœŸå’Œæ•°æ®
      const dates = []
      const inboundData = []
      const outboundData = []
      if (bigscreendata.value.dailyInOutBoundList && bigscreendata.value.dailyInOutBoundList.length > 0) {
        bigscreendata.value.dailyInOutBoundList.forEach(item => {
          dates.push(item.date) // ç›´æŽ¥ä½¿ç”¨åŽç«¯è¿”回的日期
          inboundData.push(item.dailyInboundQuantity || 0)
          outboundData.push(item.dailyOutboundQuantity || 0)
        })
      }
      const hasData = dates.length > 0
      const option = {
        tooltip: { trigger: 'axis' },
        legend: {
          data: ['入库量', '出库量'],
          textStyle: { color: '#fff' }
        },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '15%',
          left: '3%',
          right: '4%',
          bottom: '3%',
          top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
          axisLine: { lineStyle: { color: '#fff' } }
          data: dates,
          axisLine: { lineStyle: { color: '#fff' } },
          axisLabel: { color: '#fff' }
        },
        yAxis: {
          type: 'value',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
          axisLabel: { color: '#fff' },
          min: 0
        },
        series: [
          {
            name: '入库量',
            type: 'line',
            smooth: true,
            data: [120, 200, 450, 680, 520, 780, 650],
            itemStyle: { color: '#43e97b' },
            data: inboundData,
            itemStyle: { color: '#5470c6' },
            lineStyle: { color: '#5470c6' },
            areaStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(67, 233, 123, 0.5)' },
                { offset: 1, color: 'rgba(67, 233, 123, 0)' }
                { offset: 0, color: 'rgba(84, 112, 198, 0.5)' },
                { offset: 1, color: 'rgba(84, 112, 198, 0)' }
              ])
            }
            },
            showSymbol: hasData,
            symbol: 'circle',
            symbolSize: 6
          },
          {
            name: '出库量',
            type: 'line',
            smooth: true,
            data: [80, 150, 380, 520, 420, 650, 580],
            itemStyle: { color: '#4facfe' },
            data: outboundData,
            itemStyle: { color: '#91cc75' },
            lineStyle: { color: '#91cc75' },
            areaStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(79, 172, 254, 0.5)' },
                { offset: 1, color: 'rgba(79, 172, 254, 0)' }
                { offset: 0, color: 'rgba(145, 204, 117, 0.5)' },
                { offset: 1, color: 'rgba(145, 204, 117, 0)' }
              ])
            }
            },
            showSymbol: hasData,
            symbol: 'circle',
            symbolSize: 6
          }
        ]
        ],
      }
      chart.setOption(option)
      return chart
      trendChart.value.setOption(option)
      return trendChart.value
    }
    // åˆå§‹åŒ–分类占比图
    const initCategoryChart = () => {
      const chart = echarts.init(categoryChartRef.value)
      if (!categoryChartRef.value) return
      categoryChart.value = echarts.init(categoryChartRef.value)
      const option = {
        tooltip: { trigger: 'item' },
        legend: {
@@ -238,95 +474,166 @@
            emphasis: {
              label: { show: true, fontSize: 16, fontWeight: 'bold' }
            },
            data: [
              { value: 3580, name: '原材料', itemStyle: { color: '#5470c6' } },
              { value: 2840, name: '半成品', itemStyle: { color: '#91cc75' } },
              { value: 4120, name: '成品', itemStyle: { color: '#fac858' } },
              { value: 2040, name: '辅料', itemStyle: { color: '#ee6666' } }
            ]
            data: bigscreendata.value.inventoryLocationDist.length > 0
              ? bigscreendata.value.inventoryLocationDist
              : [
                  { value: 3580, name: '原材料', itemStyle: { color: '#5470c6' } },
                  { value: 2840, name: '半成品', itemStyle: { color: '#91cc75' } },
                  { value: 4120, name: '成品', itemStyle: { color: '#fac858' } },
                  { value: 2040, name: '辅料', itemStyle: { color: '#ee6666' } }
                ]
          }
        ]
      }
      //chart.setOption(option)
      return chart
      categoryChart.value.setOption(option)
      return categoryChart.value
    }
    // åˆå§‹åŒ–效率统计图
    const initEfficiencyChart = () => {
      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
    // åˆå§‹åŒ–效率统计图
// åˆå§‹åŒ–效率统计图(修复版)
const initEfficiencyChart = () => {
  if (!efficiencyChartRef.value) {
    console.warn('效率图表容器不存在!');
    return;
  }
  // ç¡®ä¿å®¹å™¨æœ‰é«˜åº¦
  efficiencyChartRef.value.style.height = '250px';
  efficiencyChartRef.value.style.width = '100%';
  // é”€æ¯æ—§å®žä¾‹
  if (efficiencyChart.value) {
    efficiencyChart.value.dispose();
  }
  // åˆå§‹åŒ– ECharts
  efficiencyChart.value = echarts.init(efficiencyChartRef.value);
  // æ•°æ®å¤„理
  const taskData = {
    '入库': 0,
    '出库': 0
  };
  bigscreendata.value.completeTask.forEach(item => {
    if (item.taskType && typeof item.count === 'number') {
      taskData[item.taskType] = item.count;
    }
  });
  const option = {
    tooltip: {
      trigger: 'axis',
      axisPointer: { type: 'shadow' },
      formatter: params => {
        return params.map(p => `${p.seriesName}: ${p.value || 0} å•`).join('<br/>');
      }
    },
    grid: {
      left: '5%',
      right: '5%',
      bottom: '10%',
      top: '15%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      data: ['入库作业', '出库作业'],
      axisLine: { lineStyle: { color: '#fff' } },
      axisLabel: { color: '#fff' }
    },
    yAxis: {
      type: 'value',
      name: '完成数量(单)',
      nameTextStyle: { color: '#fff' },
      axisLine: { lineStyle: { color: '#fff' } },
      splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
      axisLabel: { color: '#fff' },
      min: 0
    },
    series: [
      {
        name: '作业数量',
        data: [taskData['入库'], taskData['出库']],
        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]
        },
        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]
            }
        label: {
          show: true,
          position: 'top',
          color: '#fff',
          fontSize: 12
        }
      }
    ]
  };
  efficiencyChart.value.setOption(option, true);
  console.log('效率图表渲染完成,数据:', taskData);
};
    // åˆ·æ–°å›¾è¡¨æ•°æ®ï¼ˆisDataChange:是否是数据变化导致的刷新)
    const refreshCharts = (isDataChange = false) => {
      // æ•°æ®å˜åŒ–时添加闪烁效果
      if (isDataChange) {
        const chartElements = [trendChartRef.value, categoryChartRef.value, efficiencyChartRef.value]
        chartElements.forEach(el => {
          if (el) {
            el.classList.add('flash-effect');
            setTimeout(() => el.classList.remove('flash-effect'), 600);
          }
        ]
        })
      }
      chart.setOption(option)
      return chart
      // é”€æ¯æ—§å›¾è¡¨å®žä¾‹
      if (trendChart.value) trendChart.value.dispose()
      if (categoryChart.value) categoryChart.value.dispose()
      if (efficiencyChart.value) efficiencyChart.value.dispose()
      // é‡æ–°åˆå§‹åŒ–图表
      initTrendChart()
      initCategoryChart()
      initEfficiencyChart()
    }
    // èŽ·å–ä»»åŠ¡åˆ—è¡¨æ•°æ®
    const fetchTaskList = async () => {
      try {
        // æ¨¡æ‹Ÿæ•°æ®ï¼Œå®žé™…使用时调用接口
        // const res = await http.get('/wms/task/list')
        // æ¨¡æ‹Ÿæ•°æ®
        taskList.value = [
          { taskNo: 'RK20241224001', type: '入库', material: '钢板 A型', quantity: 500, location: 'A-01-01', status: '进行中', operator: '张三', time: '2024-12-24 14:25:30' },
          { taskNo: 'CK20241224002', type: '出库', material: '螺丝 M8', quantity: 2000, location: 'B-03-05', status: '已完成', operator: '李四', time: '2024-12-24 14:20:15' },
          { taskNo: 'RK20241224003', type: '入库', material: '铝板 B型', quantity: 300, location: 'A-02-03', status: '待处理', operator: '王五', time: '2024-12-24 14:15:00' },
          { taskNo: 'CK20241224004', type: '出库', material: '螺母 M8', quantity: 1500, location: 'B-02-01', status: '进行中', operator: '赵六', time: '2024-12-24 14:10:45' },
          { taskNo: 'PD20241224001', type: '盘点', material: '垫片', quantity: 5000, location: 'C-01-01', status: '进行中', operator: '孙七', time: '2024-12-24 14:05:20' }
        ]
      } catch (error) {
        console.error('获取任务列表失败:', error)
      }
    }
    // åˆ·æ–°æ•°æ®
    // æ‰‹åŠ¨åˆ·æ–°æ•°æ®
    const refreshData = () => {
      fetchTaskList()
      fetchBigGreenData()
    }
    // çª—口大小改变时重绘图表
    // å¯åŠ¨è‡ªåŠ¨åˆ·æ–°
    const startAutoRefresh = () => {
      if (autoRefreshTimer) clearInterval(autoRefreshTimer)
      autoRefreshTimer = setInterval(() => {
        console.log('定时刷新数据:', new Date().toLocaleString())
        fetchBigGreenData()
      }, refreshInterval.value)
    }
    // çª—口大小改变时重绘图表和调整表格高度
    const handleResize = () => {
      try {
        const refs = [trendChartRef.value, categoryChartRef.value, efficiencyChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.resize()
            }
          }
        const windowHeight = window.innerHeight
        const headerHeight = 60
        const navHeight = 50
        const metricsHeight = 120
        const chartsHeight = 300
        const padding = 80
        const availableHeight = windowHeight - headerHeight - navHeight - metricsHeight - chartsHeight - padding
        tableHeight.value = Math.max(200, Math.min(availableHeight, 400))
        // é‡ç»˜å›¾è¡¨
        const charts = [trendChart.value, categoryChart.value, efficiencyChart.value]
        charts.forEach(chart => {
          if (chart) chart.resize()
        })
      } catch (error) {
        console.warn('图表重绘时出错:', error)
@@ -334,32 +641,39 @@
    }
    onMounted(() => {
      // åˆå§‹åŒ–æ—¶é—´
      updateTime()
      timer = setInterval(updateTime, 1000)
      initTrendChart()
      initCategoryChart()
      initEfficiencyChart()
      fetchTaskList()
      // åˆå§‹åŒ–数据和图表
      fetchBigGreenData()
      nextTick(() => {
        initTrendChart()
        initCategoryChart()
        initEfficiencyChart()
        handleResize()
        startAutoRefresh() // å¯åŠ¨è‡ªåŠ¨åˆ·æ–°
      })
      // ç›‘听窗口大小变化
      window.addEventListener('resize', handleResize)
    })
    onUnmounted(() => {
      // æ¸…除所有定时器
      clearInterval(timer)
      if (taskCarouselTimer) clearInterval(taskCarouselTimer)
      if (autoRefreshTimer) clearInterval(autoRefreshTimer)
      // ç§»é™¤çª—口监听
      window.removeEventListener('resize', handleResize)
      // é”€æ¯å›¾è¡¨å®žä¾‹
      try {
        const refs = [trendChartRef.value, categoryChartRef.value, efficiencyChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.dispose()
            }
          }
        })
        if (trendChart.value) trendChart.value.dispose()
        if (categoryChart.value) categoryChart.value.dispose()
        if (efficiencyChart.value) efficiencyChart.value.dispose()
      } catch (error) {
        console.warn('图表销毁时出错:', error)
      }
@@ -369,10 +683,15 @@
      currentTime,
      metrics,
      taskList,
      showTaskList,
      trendChartRef,
      categoryChartRef,
      efficiencyChartRef,
      getStatusType,
      tableHeight,
      getTaskTypeText,
      getTaskStatusText,
      getStatusClass,
      getTypeClass,
      refreshData
    }
  }
@@ -382,12 +701,13 @@
<style scoped>
.dashboard-container {
  width: 100%;
  height: 100%;
  height: 100vh;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  overflow-y: auto;
  background-color: #0a0e27;
}
.header {
@@ -490,6 +810,7 @@
.metric-value {
  font-size: 28px;
  font-weight: bold;
  color: #fff;
  margin-bottom: 5px;
}
@@ -554,6 +875,7 @@
  border-radius: 10px;
}
/* Element Plus Table æ ·å¼è¦†ç›– */
:deep(.el-table) {
  background: transparent !important;
}
@@ -566,6 +888,7 @@
:deep(.el-table td) {
  border-color: rgba(255, 255, 255, 0.1) !important;
  color: rgba(255, 255, 255, 0.9) !important;
}
:deep(.el-table tr) {
@@ -578,6 +901,201 @@
:deep(.el-table--enable-row-hover .el-table__body tr:hover > td) {
  background: rgba(255, 255, 255, 0.05) !important;
}
/* ä»»åŠ¡çŠ¶æ€æ ·å¼ */
.status-container {
  display: flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 20px;
  position: relative;
  overflow: hidden;
  font-size: 12px;
  font-weight: 500;
}
.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
  position: relative;
}
.status-dot::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  animation: pulse 2s infinite;
}
.status-text {
  white-space: nowrap;
}
/* ä¸åŒçŠ¶æ€çš„é¢œè‰² */
.status-completed {
  background: rgba(103, 194, 58, 0.15);
  color: #67c23a;
}
.status-completed .status-dot {
  background: #67c23a;
}
.status-completed .status-dot::after {
  background: rgba(103, 194, 58, 0.5);
}
.status-processing {
  background: rgba(64, 158, 255, 0.15);
  color: #409eff;
}
.status-processing .status-dot {
  background: #409eff;
}
.status-processing .status-dot::after {
  background: rgba(64, 158, 255, 0.5);
}
.status-pending {
  background: rgba(144, 147, 153, 0.15);
  color: #909399;
}
.status-pending .status-dot {
  background: #909399;
}
.status-pending .status-dot::after {
  background: rgba(144, 147, 153, 0.5);
}
.status-suspended {
  background: rgba(230, 162, 60, 0.15);
  color: #e6a23c;
}
.status-suspended .status-dot {
  background: #e6a23c;
}
.status-suspended .status-dot::after {
  background: rgba(230, 162, 60, 0.5);
}
.status-canceled {
  background: rgba(144, 147, 153, 0.15);
  color: #909399;
}
.status-canceled .status-dot {
  background: #909399;
}
.status-canceled .status-dot::after {
  background: rgba(144, 147, 153, 0.5);
}
.status-error {
  background: rgba(245, 108, 108, 0.15);
  color: #f56c6c;
}
.status-error .status-dot {
  background: #f56c6c;
}
.status-error .status-dot::after {
  background: rgba(245, 108, 108, 0.5);
}
/* ä»»åŠ¡ç±»åž‹æ ·å¼ */
.type-container {
  display: flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 20px;
  position: relative;
  font-size: 12px;
  font-weight: 500;
}
.type-icon {
  margin-right: 6px;
  font-size: 14px;
}
.type-text {
  white-space: nowrap;
}
/* ä¸åŒç±»åž‹çš„颜色 */
.type-inbound {
  background: rgba(103, 194, 58, 0.15);
  color: #67c23a;
}
.type-outbound {
  background: rgba(230, 162, 60, 0.15);
  color: #e6a23c;
}
.type-transfer {
  background: rgba(64, 158, 255, 0.15);
  color: #409eff;
}
.type-other {
  background: rgba(144, 147, 153, 0.15);
  color: #909399;
}
/* åŠ¨ç”»æ•ˆæžœ */
@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.5);
    opacity: 0.3;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}
/* é—ªçƒæ•ˆæžœï¼ˆè¡¨æ ¼å’Œå›¾è¡¨åˆ·æ–°æ—¶ï¼‰ */
.flash-effect {
  animation: flash 0.6s ease-in-out;
}
@keyframes flash {
  0% {
    box-shadow: 0 0 0 rgba(79, 172, 254, 0);
  }
  20% {
    box-shadow: 0 0 15px rgba(79, 172, 254, 0.7);
  }
  40% {
    box-shadow: 0 0 0 rgba(79, 172, 254, 0);
  }
  60% {
    box-shadow: 0 0 15px rgba(79, 172, 254, 0.7);
  }
  80% {
    box-shadow: 0 0 0 rgba(79, 172, 254, 0);
  }
  100% {
    box-shadow: 0 0 0 rgba(79, 172, 254, 0);
  }
}
/* å“åº”式适配 */
@@ -662,7 +1180,7 @@
@media screen and (max-width: 1024px) {
  .metrics-row {
    grid-template-columns: 1fr 1fr;
  }
  }
  .nav-menu {
    flex-wrap: wrap;
@@ -726,4 +1244,4 @@
    height: 150px;
  }
}
</style>
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/Home.vue
@@ -363,7 +363,6 @@
// åˆå§‹åŒ–è¿‘7日出入库趋势图(关联后端数据)
const initStockTrend = () => {
  debugger
  if (!stockTrendRef.value) return;
  if (stockTrendChart) {
@@ -374,6 +373,12 @@
  // ä¼˜å…ˆä½¿ç”¨åŽç«¯æ•°æ®
  const trendData = bigscreendata.value.dailyInOutBoundList;
  console.log('出入库趋势数据', trendData);
  // è®¡ç®—数据中的最大值,用于设置Y轴范围
  const maxInbound = Math.max(...trendData.map(item => item.dailyInboundQuantity || 0));
  const maxOutbound = Math.max(...trendData.map(item => item.dailyOutboundQuantity || 0));
  const maxValue = Math.max(maxInbound, maxOutbound);
  console.log('最大值计算结果:', { maxInbound, maxOutbound, maxValue });
  const option = {
    tooltip: {
@@ -400,23 +405,31 @@
    },
    yAxis: {
      type: 'value',
      name: '数量(千件)',
      max: 25
      name: '数量',
      min: 0,
      // æ ¹æ®æ•°æ®åŠ¨æ€è®¾ç½®æœ€å¤§å€¼ï¼Œç•™å‡ºä¸€äº›ç©ºé—´
      max: maxValue > 0 ? Math.ceil(maxValue * 1.2) : 10
    },
    series: [
      {
        name: '入库量',
        type: 'bar',
        barWidth: '30%',
        data: trendData.map(item => item.inNum),
        itemStyle: { color: '#52c41a' }
        data: trendData.map(item => item.dailyInboundQuantity),
        itemStyle: {
          color: '#52c41a',
          borderRadius: [4, 4, 0, 0]
        }
      },
      {
        name: '出库量',
        type: 'bar',
        barWidth: '30%',
        data: trendData.map(item => item.outNum),
        itemStyle: { color: '#1890ff' }
        data: trendData.map(item => item.dailyOutboundQuantity),
        itemStyle: {
          color: '#1890ff',
          borderRadius: [4, 4, 0, 0]
        }
      }
    ]
  };
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/charts/wms-dashboard.vue
@@ -258,7 +258,6 @@
// èŽ·å–åŽç«¯å¤§å±æ•°æ®
const fetchBigGreenData = async () => {
  try {
    debugger
    const res = await http.get('/api/BigScreen/GetBigGreenData');
    console.log('大屏数据', res);
    
@@ -271,10 +270,13 @@
        ? [...bigscreendata.value.taskList.slice(0,5)] 
        : [...bigscreendata.value.taskList];
      startTaskCarousel(); // å¯åŠ¨ä»»åŠ¡è½®æ’­
      refreshCharts();
      // refreshCharts(); // ç§»é™¤è¿™é‡Œçš„调用,改为在数据获取后统一初始化
    });
    // è¿”回Promise,以便在数据获取成功后初始化图表
    return Promise.resolve();
  } catch (error) {
    ElMessage.error('数据获取失败,请检查后端接口是否正常');
    return Promise.reject(error);
  }
};
@@ -362,68 +364,45 @@
};
// åˆå§‹åŒ–è¿‘7日出入库趋势图(关联后端数据)
const initStockTrend = () => {
  debugger
  if (!stockTrendRef.value) return;
  if (stockTrendChart) {
    stockTrendChart.dispose();
  }
  stockTrendChart = echarts.init(stockTrendRef.value);
  // ä¼˜å…ˆä½¿ç”¨åŽç«¯æ•°æ®
  const trendData = bigscreendata.value.dailyInOutBoundList;
  console.log('出入库趋势数据', trendData);
  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: trendData.map(item => item.date)
    },
    yAxis: {
      type: 'value',
      name: '数量(千件)',
      max: 25
    },
    series: [
      {
        name: '入库量',
        type: 'bar',
        barWidth: '30%',
        data: trendData.map(item => item.inNum),
        itemStyle: { color: '#52c41a' }
      },
      {
        name: '出库量',
        type: 'bar',
        barWidth: '30%',
        data: trendData.map(item => item.outNum),
        itemStyle: { color: '#1890ff' }
      }
    ]
  };
  stockTrendChart.setOption(option);
  return stockTrendChart;
};
// 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
// };
// åˆå§‹åŒ–库位利用率环形图(修正:统一实例管理,关联后端数据)
const initLocationRate = () => {
@@ -589,11 +568,13 @@
  return exceptionTrendChart;
};
// åˆ·æ–°æ‰€æœ‰å›¾è¡¨ï¼ˆç§»é™¤æ— æ•ˆé›·è¾¾å›¾é€»è¾‘)
// åˆ·æ–°æ‰€æœ‰å›¾è¡¨
const refreshCharts = () => {
  const charts = [
    initInventoryPie,
    initStockTrend,
    initLocationRate,
    initExceptionTrend,
  ];
  charts.forEach(initFunc => {
@@ -622,15 +603,44 @@
// ç»„件挂载时:先请求数据,再初始化图表
onMounted(() => {
  console.log('组件已挂载,开始初始化');
  // å…ˆèŽ·å–åŽç«¯æ•°æ®
  fetchBigGreenData();
  // åˆå§‹åŒ–图表(确保DOM已渲染)
  nextTick(() => {
    initInventoryPie();
    initStockTrend();
    initLocationRate();
    initExceptionTrend();
    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);
  });
});
@@ -767,6 +777,7 @@
  transition: all 0.3s ease;
  overflow: hidden;
  position: relative;
  min-height: 400px;
}
.chart-card:hover {
@@ -781,9 +792,11 @@
  flex: 1;
  position: relative;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(5px);
  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;
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/BigGreenService/BigGreenService.cs
@@ -69,6 +69,8 @@
            // 4. èŽ·å–è¿‘7日每日出入库明细(核心修改:调用上面的方法)
            var dailyInOutBoundList = Get7DaysDailyInOutBound();
            //获取作业统计
            var completeTask = SimpleStatistics();
            //任务
            List<Dt_Task> tasks = _taskRepository.QueryData();
@@ -82,7 +84,8 @@
                InboundCount = inboundCount,
                OutboundCount = outboundCount,
                InStockPallet = inStockPallet,
                FreeStockPallet = freeStockPallet
                FreeStockPallet = freeStockPallet,
                CompleteTask = completeTask
            };
            return WebResponseContent.Instance.OK(data: bigGreenData);
        }
@@ -107,14 +110,15 @@
                .Queryable<Dt_OutboundOrderDetail>()
                .Where(x => x.CreateDate >= startDate
                         && x.CreateDate < endDate.AddDays(1))
                .GroupBy(x => x.CreateDate) // æŒ‰æ—¥æœŸæ ¼å¼åŒ–分组
                .Select(x => new
                {
                    Date = x.CreateDate.ToString( "MM-dd"),
                    DailyOutbound = SqlFunc.AggregateSum((decimal?)x.OverOutQuantity) ?? 0
                    Date = x.CreateDate.ToString("MM-dd"),
                    x.OverOutQuantity
                })
                .ToList()
                .ToDictionary(k => k.Date, v => v.DailyOutbound); // è½¬ä¸ºå­—典方便匹配
                .GroupBy(x => x.Date)
                .ToDictionary(k => k.Key , g => g.Sum(x => (decimal?)x.OverOutQuantity) ?? 0); // è½¬ä¸ºå­—典方便匹配
            // 3. æŸ¥è¯¢æ¯æ—¥å…¥åº“明细(按日期分组)
            var dailyInboundList = _inboundOrderDetailRepository.Db
@@ -122,14 +126,14 @@
                .Where(x => x.CreateDate != null // è¿‡æ»¤ç©ºæ—¥æœŸ
                         && x.CreateDate >= startDate
                         && x.CreateDate < endDate.AddDays(1))
                .GroupBy(x => x.CreateDate).Distinct() // æŒ‰æ—¥æœŸæ ¼å¼åŒ–分组
                .Select(x => new
                {
                    Date = x.CreateDate.ToString("MM-dd"),
                    DailyInbound = SqlFunc.AggregateSum((decimal?)x.OverInQuantity) ?? 0
                    x.OverInQuantity
                })
                .ToList()
                .ToDictionary(k => k.Date, v => v.DailyInbound); // è½¬ä¸ºå­—典方便匹配
                .GroupBy(x=>x.Date)
                .ToDictionary(k => k.Key, g => g.Sum(x => (decimal?)x.OverInQuantity) ?? 0); // è½¬ä¸ºå­—典方便匹配
            // 4. åˆå¹¶æ¯æ—¥æ•°æ®ï¼ˆç¡®ä¿7天日期完整,无数据补0)
            var dailyInOutBoundList = all7Days.Select(date => new DailyInOutBoundDto
@@ -143,7 +147,26 @@
            return dailyInOutBoundList;
        }
        public List<SimpleStatisticsDTO> SimpleStatistics()
        {
            DateTime sevenDaysAgo = DateTime.Now.AddDays(-7);
            var stats = _taskHtyRepository
                .QueryData(x => x.TaskStatus == (int)TaskStatusEnum.Finish && x.CreateDate >= sevenDaysAgo)
                .GroupBy(t =>
                    (int)t.TaskType >= 100 && (int)t.TaskType <= 299 ? "出库" :
                    (int)t.TaskType >= 500 && (int)t.TaskType <= 699 ? "入库" : "其他"
                )
                .Where(g => g.Key == "出库" || g.Key == "入库")
                .Select(g => new SimpleStatisticsDTO
                {
                    TaskType = g.Key,
                    Count = g.Count()
                })
                .ToList();
            return stats;
        }
        /// <summary>
        /// å¤§å±/汇总数据返回DTO(大绿数据汇总)
@@ -196,7 +219,7 @@
            /// <summary>
            /// è¿‘7日净入库量(入库-出库)
            /// </summary>
            public decimal totalStockChangeRate { get; set; }
            public decimal TotalStockChangeRate { get; set; }
            /// <summary>
            /// ä»»åŠ¡åˆ—è¡¨
@@ -206,6 +229,8 @@
            public int InStockPallet { get; set; }
            public int FreeStockPallet { get; set; }
            public List<SimpleStatisticsDTO> CompleteTask { get; set; }
        }
        /// <summary>
@@ -230,6 +255,12 @@
        }
        public class SimpleStatisticsDTO
        {
            public string TaskType { get; set; }
            public int Count { get; set; }
        }
    }
}