From b07472f884708a6bfdf63d999004bbf0bb5f00a8 Mon Sep 17 00:00:00 2001
From: huangxiaoqiang <huangxiaoqiang@hnkhzn.com>
Date: 星期一, 17 十一月 2025 17:12:57 +0800
Subject: [PATCH] 新增分单功能、二维码打印及物料供应商管理页面
---
项目代码/WMS/WIDESEA_WMSClient/src/views/Home.vue | 1685 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 1,596 insertions(+), 89 deletions(-)
diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
index 17c134c..00b689d 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS/WIDESEA_WMSClient/src/views/Home.vue"
@@ -1,126 +1,1633 @@
<template>
- <div class="chart_left" style="margin-left: 20px;">
- <div class="titles">
- <el-icon class="icons" :size="24" color="#409EFF">
- <Checked />
- </el-icon>
- 浠诲姟杩涜涓�
+ <div class="dashboard-container">
+ <!-- 鏁版嵁鎬昏閮ㄥ垎 -->
+ <div class="overview-section">
+ <el-row :gutter="20">
+ <el-col :lg="24">
+ <div class="data-overview">
+ <div class="overview-header">
+ <div class="header-left">
+ <h3 class="title">
+ <i class="el-icon-data-analysis header-icon"></i>
+ 鏁版嵁鎬昏
+ </h3>
+ </div>
+ <div class="header-right">
+ <div class="time-range">鏁版嵁鏇存柊浜�: {{ currentTime }}</div>
+ <div>
+ <el-button type="primary" size="small" :icon="Refresh" @click="handleRefresh" :loading="refreshing"
+ class="refresh-btn">
+ 鍒锋柊鏁版嵁
+ </el-button>
+ </div>
+ </div>
+ </div>
+ <div class="metrics-grid">
+ <div class="metric-card" v-for="(item, index) in dataMetrics" :key="`metric-${index}-${refreshKey}`"
+ :class="getMetricCardClass(item.name)">
+ <div class="metric-content">
+ <div class="metric-icon-wrapper">
+ <i :class="getMetricIcon(item.name)"></i>
+ </div>
+ <div class="metric-info">
+ <div class="metric-name">{{ item.name }}</div>
+ <div class="metric-value">{{ formatNumber(item.value) }}</div>
+ <div class="metric-compare" v-if="item.compare !== undefined">
+ <span v-if="item.name == '搴撳瓨鎬婚噺'">
+
+ </span>
+ <span :class="getCompareClass(item.compare)" v-else>
+ <i :class="getCompareIcon(item.compare)"></i>
+ {{ formatCompareValue(item.compare) }}
+ </span>
+ <!-- <span class="compare-label">杈冩槰鏃�</span> -->
+ <span class="compare-label">{{ text(item.name) }}</span>
+
+ </div>
+ </div>
+ </div>
+ <div class="metric-trend" v-if="item.compare !== undefined">
+ <div class="trend-chart">
+ <div class="trend-bar" :style="{ height: getTrendHeight(item) }"></div>
+ </div>
+ </div>
+ <div class="metric-decoration">
+ <div class="decoration-circle"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
</div>
- <div style="margin-top: 20px;"></div>
- <div class="item_center">
- <Task-List :data="chartData" :options="chartOptions" />
+
+ <!-- 鐢熶骇浠诲姟鐪嬫澘 -->
+ <div class="overview-section">
+ <el-row :gutter="20">
+ <el-col :lg="24">
+ <div class="task-board-container">
+ <div class="board-header">
+ <div class="header-left">
+ <i class="el-icon-s-management board-icon"></i>
+ <span class="board-title">褰撳墠鐢熶骇浠诲姟</span>
+ </div>
+ <div class="header-right">
+ <span class="task-count">鍏� {{ tableData.length }} 涓换鍔�</span>
+ </div>
+ </div>
+ <div class="board-body">
+ <div class="scroll-table-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll">
+ <!-- 琛ㄥご -->
+ <div class="table-header" ref="tableHeader">
+ <table class="header-table">
+ <thead>
+ <tr>
+ <th>搴忓彿</th>
+ <th>鎵樼洏鐮�</th>
+ <th>璧峰鍦板潃</th>
+ <th>鐩爣鍦板潃</th>
+ <th>浠诲姟绫诲瀷</th>
+ <th>浠诲姟鐘舵��</th>
+ <th>鍒涘缓鏃堕棿</th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+
+ <!-- 琛ㄦ牸鍐呭鍖哄煙 -->
+ <div class="table-body-container" ref="tableBody">
+ <div class="table-body-wrapper" :style="{ transform: `translateY(-${scrollPosition}px)` }">
+ <!-- 鍘熷鏁版嵁 -->
+ <table class="body-table">
+ <tbody>
+ <tr v-for="(row, index) in tableData" :key="`task-${index}-${refreshKey}`"
+ :class="index % 2 === 0 ? 'even-row' : 'odd-row'">
+ <td>{{ index + 1 }}</td>
+ <td>{{ row.palletCode || '鏃�' }}</td>
+ <td>{{ row.sourceAddress || '鏃�' }}</td>
+ <td>{{ row.targetAddress || '鏃�' }}</td>
+ <td>{{ row.taskType || '鏃�' }}</td>
+ <td>
+ <el-tag :type="getStatusType(row.taskState)" size="small" class="status-tag">
+ {{ row.taskState || '鏃�' }}
+ </el-tag>
+ </td>
+ <td>{{ formatDateTime(row.createDate) || '鏃�' }}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- 澶嶅埗涓�浠芥暟鎹敤浜庢棤缂濇粴鍔� -->
+ <table class="body-table" v-if="tableData.length > rowNum">
+ <tbody>
+ <tr v-for="(row, index) in tableData" :key="`task-copy-${index}-${refreshKey}`"
+ :class="index % 2 === 0 ? 'even-row' : 'odd-row'">
+ <td>{{ index + tableData.length + 1 }}</td>
+ <td>{{ row.palletCode || '鏃�' }}</td>
+ <td>{{ row.sourceAddress || '鏃�' }}</td>
+ <td>{{ row.targetAddress || '鏃�' }}</td>
+ <td>{{ row.taskType || '鏃�' }}</td>
+ <td>
+ <el-tag :type="getStatusType(row.taskState)" size="small" class="status-tag">
+ {{ row.taskState || '鏃�' }}
+ </el-tag>
+ </td>
+ <td>{{ formatDateTime(row.createDate) || '鏃�' }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div v-if="tableData.length === 0" class="no-data-board">
+ <el-empty description="鏆傛棤鐢熶骇浠诲姟鏁版嵁" :image-size="80" />
+ </div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
</div>
- </div>
- <div class="chart_left" style="margin-left: 20px;">
- <div class="titles">
- <el-icon class="icons" :size="24" color="#409EFF">
- <Checked />
- </el-icon>
- 浠诲姟杩涜涓�
+
+ <!-- 鍥捐〃閮ㄥ垎 -->
+ <div class="charts-section">
+ <el-row :gutter="20">
+ <el-col :lg="12">
+ <div class="chart-card">
+ <div class="chart-header">
+ <h3 class="chart-title">
+ <i class="el-icon-trend-charts chart-icon"></i>
+ 鍑哄簱閲忚秼鍔�
+ </h3>
+ <div class="chart-subtitle">杩�7鏃ュ嚭搴撶粺璁�</div>
+ </div>
+ <div class="chart-content">
+ <div v-if="loading" class="chart-loading">
+ <i class="el-icon-loading"></i>
+ <span>鏁版嵁鍔犺浇涓�...</span>
+ </div>
+ <div v-else-if="error" class="chart-error">
+ <i class="el-icon-warning"></i>
+ <span>鏁版嵁鍔犺浇澶辫触</span>
+ <el-button type="text" @click="fetchData" class="retry-btn">閲嶈瘯</el-button>
+ </div>
+ <div v-else-if="!chartData.outbound.values.length" class="chart-no-data">
+ <i class="el-icon-data-line"></i>
+ <span>鏆傛棤鍑哄簱鏁版嵁</span>
+ </div>
+ <div v-else ref="outboundChart" class="chart" :key="`outbound-${refreshKey}`"></div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :lg="12">
+ <div class="chart-card">
+ <div class="chart-header">
+ <h3 class="chart-title">
+ <i class="el-icon-trend-charts chart-icon"></i>
+ 鍏ュ簱閲忚秼鍔�
+ </h3>
+ <div class="chart-subtitle">杩�7鏃ュ叆搴撶粺璁�</div>
+ </div>
+ <div class="chart-content">
+ <div v-if="loading" class="chart-loading">
+ <i class="el-icon-loading"></i>
+ <span>鏁版嵁鍔犺浇涓�...</span>
+ </div>
+ <div v-else-if="error" class="chart-error">
+ <i class="el-icon-warning"></i>
+ <span>鏁版嵁鍔犺浇澶辫触</span>
+ <el-button type="text" @click="fetchData" class="retry-btn">閲嶈瘯</el-button>
+ </div>
+ <div v-else-if="!chartData.inbound.values.length" class="chart-no-data">
+ <i class="el-icon-data-line"></i>
+ <span>鏆傛棤鍏ュ簱鏁版嵁</span>
+ </div>
+ <div v-else ref="inboundChart" class="chart" :key="`inbound-${refreshKey}`"></div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
</div>
- <div style="margin-top: 20px;"></div>
- <div class="item_center">
- <Task-List :data="chartData" :options="chartOptions" />
+
+ <!-- 鏈堝害鏁版嵁鍥捐〃 -->
+ <div class="charts-section">
+ <el-row :gutter="20">
+ <el-col :lg="24">
+ <div class="chart-card">
+ <div class="chart-header">
+ <h3 class="chart-title">
+ <i class="el-icon-data-line chart-icon"></i>
+ 鏈堝害鍑哄叆搴撳姣�
+ </h3>
+ <div class="chart-subtitle">鏈湀姣忔棩鍑哄叆搴撻噺缁熻</div>
+ </div>
+ <div class="chart-content">
+ <div v-if="loading" class="chart-loading">
+ <i class="el-icon-loading"></i>
+ <span>鏁版嵁鍔犺浇涓�...</span>
+ </div>
+ <div v-else-if="error" class="chart-error">
+ <i class="el-icon-warning"></i>
+ <span>鏁版嵁鍔犺浇澶辫触</span>
+ <el-button type="text" @click="fetchData" class="retry-btn">閲嶈瘯</el-button>
+ </div>
+ <div v-else-if="!chartData.monthData.inValue.length || !chartData.monthData.outValue.length"
+ class="chart-no-data">
+ <i class="el-icon-data-line"></i>
+ <span>鏆傛棤鍑哄叆搴撴暟鎹�</span>
+ </div>
+ <div v-else ref="monthDataChart" class="chart-large" :key="`month-${refreshKey}`"></div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
</div>
</div>
</template>
+
<script setup>
-import TaskList from '../components/index/TaskList.vue';
import http from '../api/http.js';
-import { ref, onMounted, onUnmounted, watch } from 'vue';
-const taskListRef = ref(null);
-// 绀轰緥鏁版嵁
-const chartData = ref([
- // { value: 103, name: '宸插彇娑�', itemStyle: { color: '#FF6B6B' } },
- // { value: 735, name: '宸插畬鎴�', itemStyle: { color: '#4ECDC4' } },
-]);
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
+import * as echarts from 'echarts';
+import { Refresh } from '@element-plus/icons-vue';
-const fetchData = async () => {
+// 鍝嶅簲寮忔暟鎹�
+const dataMetrics = ref([]);
+const tableData = ref([]);
+const chartData = reactive({
+ outbound: { dates: [], values: [] },
+ inbound: { dates: [], values: [] },
+ monthData: { dates: [], inValue: [], outValue: [] }
+});
+const loading = ref(true);
+const error = ref(false);
+const refreshing = ref(false);
+const refreshKey = ref(0); // 鐢ㄤ簬寮哄埗閲嶆柊娓叉煋
+
+// 婊氬姩鐩稿叧
+const tableHeader = ref(null);
+const tableBody = ref(null);
+const scrollPosition = ref(0);
+const isScrolling = ref(true);
+let scrollInterval = null;
+const rowNum = 7; // 鏄剧ず鐨勮鏁�
+const rowHeight = 48; // 姣忚楂樺害
+
+// 褰撳墠鏃堕棿
+const currentTime = ref('');
+
+// 鍥捐〃寮曠敤鍜屽疄渚�
+const outboundChart = ref(null);
+const inboundChart = ref(null);
+const monthDataChart = ref(null);
+const outboundInstance = ref(null);
+const inboundInstance = ref(null);
+const monthDataInstance = ref(null);
+const charts = ref([]);
+
+// 鏍煎紡鍖栨暟瀛�
+const formatNumber = (num) => {
+ if (num === undefined || num === null) return '0';
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+};
+
+const text = (str) => {
+ if (str == "浠婃棩杩涘簱閲�" || str == "浠婃棩鍑哄簱閲�") {
+ return "杈冩槰鏃�"
+ } else if (str == "鏈湀杩涘簱閲�" || str == "鏈湀鍑哄簱閲�") {
+ return "杈冧笂鏈�"
+ } else {
+ return ""
+ }
+}
+
+// 鏍煎紡鍖栨瘮杈冨��
+const formatCompareValue = (value) => {
+ if (value === 0) return '0';
+ return value > 0 ? `+${formatNumber(value)}` : formatNumber(value);
+};
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (dateString) => {
+ if (!dateString) return '';
try {
- const response = await http.post("api/Task/GetTaskData", {});
- chartData.value = response.data;
-
- if (taskListRef.value) taskListRef.value.initChart();
- } catch (error) {
- console.error('API璇锋眰澶辫触:', error);
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } catch {
+ return dateString;
}
};
-const intervalId = ref(null)
-const startPolling = () => {
- fetchData(); // 鍒濆鍔犺浇
- intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5鍒嗛挓
+
+// 鑾峰彇姣旇緝鍊兼牱寮忕被
+const getCompareClass = (compare) => {
+ if (compare > 0) return 'compare-positive';
+ if (compare < 0) return 'compare-negative';
+ return 'compare-zero';
};
-// 娓呯悊瀹氭椂鍣�
+// 鑾峰彇姣旇緝鍊煎浘鏍�
+const getCompareIcon = (compare) => {
+ if (compare > 0) return 'el-icon-top';
+ if (compare < 0) return 'el-icon-bottom';
+ return 'el-icon-minus';
+};
+
+// 鑾峰彇瓒嬪娍楂樺害
+const getTrendHeight = (item) => {
+ if (item.compare === undefined || item.value === 0) return '0%';
+
+ const maxValue = Math.max(Math.abs(item.value), Math.abs(item.compare));
+ if (maxValue === 0) return '0%';
+
+ const percentage = (Math.abs(item.compare) / maxValue) * 100;
+ return `${Math.min(percentage, 100)}%`;
+};
+
+// 鑾峰彇鎸囨爣鍗$墖鏍峰紡绫�
+const getMetricCardClass = (name) => {
+ const classMap = {
+ '浠婃棩杩涘簱閲�': 'metric-inbound-today',
+ '浠婃棩鍑哄簱閲�': 'metric-outbound-today',
+ '鏈湀杩涘簱閲�': 'metric-inbound-month',
+ '鏈湀鍑哄簱閲�': 'metric-outbound-month',
+ '搴撳瓨鎬婚噺': 'metric-total'
+ };
+ return classMap[name] || '';
+};
+
+const getMetricIcon = (type) => {
+ const iconMap = {
+ '浠婃棩杩涘簱閲�': 'el-icon-download',
+ '浠婃棩鍑哄簱閲�': 'el-icon-upload2',
+ '鏈湀杩涘簱閲�': 'el-icon-download',
+ '鏈湀鍑哄簱閲�': 'el-icon-upload2',
+ '搴撳瓨鎬婚噺': 'el-icon-box',
+ };
+ return iconMap[type] || 'el-icon-data-board';
+};
+
+// 鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusMap = {
+ '杩涜涓�': 'primary',
+ '宸插畬鎴�': 'success',
+ '宸插彇娑�': 'info',
+ '寮傚父': 'danger',
+ '寰呮墽琛�': 'warning'
+ };
+ return statusMap[status] || 'primary';
+};
+
+// 鏇存柊褰撳墠鏃堕棿
+const updateCurrentTime = () => {
+ const now = new Date();
+ currentTime.value = now.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+};
+
+// 鍚姩婊氬姩
+const startScrolling = () => {
+ if (tableData.value.length <= rowNum) {
+ isScrolling.value = false;
+ return; // 鏁版嵁灏戞椂涓嶆粴鍔�
+ }
+
+ isScrolling.value = true;
+ const totalHeight = tableData.value.length * rowHeight;
+
+ scrollInterval = setInterval(() => {
+ scrollPosition.value += 1;
+
+ // 褰撴粴鍔ㄥ埌绗竴浠芥暟鎹殑鏈熬鏃讹紝閲嶇疆浣嶇疆瀹炵幇鏃犵紳婊氬姩
+ if (scrollPosition.value >= totalHeight) {
+ scrollPosition.value = 0;
+ }
+ }, 30); // 璋冩暣婊氬姩閫熷害
+};
+
+// 鏆傚仠婊氬姩
+const pauseScroll = () => {
+ if (scrollInterval) {
+ clearInterval(scrollInterval);
+ scrollInterval = null;
+ }
+ isScrolling.value = false;
+};
+
+// 鎭㈠婊氬姩
+const resumeScroll = () => {
+ if (tableData.value.length > rowNum) {
+ startScrolling();
+ }
+};
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+ // 娓呯悊鏃х殑鍥捐〃瀹炰緥
+ if (outboundInstance.value) {
+ outboundInstance.value.dispose();
+ }
+ if (inboundInstance.value) {
+ inboundInstance.value.dispose();
+ }
+ if (monthDataInstance.value) {
+ monthDataInstance.value.dispose();
+ }
+
+ if (!outboundChart.value || !inboundChart.value || !monthDataChart.value) {
+ console.log('鍥捐〃瀹瑰櫒鏈壘鍒帮紝寤惰繜鍒濆鍖�');
+ return;
+ }
+
+ outboundInstance.value = echarts.init(outboundChart.value);
+ inboundInstance.value = echarts.init(inboundChart.value);
+ monthDataInstance.value = echarts.init(monthDataChart.value);
+
+ charts.value = [outboundInstance.value, inboundInstance.value, monthDataInstance.value];
+
+ // 鍑哄簱閲忓浘琛ㄩ厤缃紙鏌辩姸鍥撅級
+ const outboundOption = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ },
+ formatter: function (params) {
+ const data = params[0];
+ return `
+ <div style="font-weight: 600; margin-bottom: 8px; color: #303133;">${data.name}</div>
+ <div style="display: flex; align-items: center; font-size: 14px;">
+ <span style="display: inline-block; width: 8px; height: 8px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span>
+ <span style="color: #606266;">${data.seriesName}: </span>
+ <span style="font-weight: 600; margin-left: 8px; color: #303133;">${data.value}</span>
+ </div>
+ `;
+ },
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
+ borderColor: '#e4e7ed',
+ borderWidth: 1,
+ textStyle: {
+ color: '#303133'
+ },
+ padding: [8, 12],
+ borderRadius: 6,
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
+ shadowBlur: 8
+ },
+ grid: {
+ left: '3%',
+ right: '3%',
+ bottom: '3%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: chartData.outbound.dates,
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266',
+ rotate: 45
+ }
+ },
+ yAxis: {
+ type: 'value',
+ name: '鏁伴噺',
+ nameTextStyle: {
+ color: '#909399'
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266'
+ },
+ splitLine: {
+ lineStyle: {
+ color: '#f0f2f5',
+ type: 'dashed'
+ }
+ }
+ },
+ series: [{
+ name: '鍑哄簱閲�',
+ data: chartData.outbound.values,
+ type: 'bar',
+ itemStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: '#ff9f7f' },
+ { offset: 1, color: '#ff6b6b' }
+ ]),
+ borderRadius: [4, 4, 0, 0]
+ },
+ barWidth: '60%',
+ animation: true,
+ label: {
+ show: true,
+ position: 'top',
+ formatter: '{c}',
+ color: '#ff6b6b',
+ fontWeight: 'bold'
+ }
+ }]
+ };
+
+ // 鍏ュ簱閲忓浘琛ㄩ厤缃紙鎶樼嚎鍥撅級
+ const inboundOption = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'line'
+ },
+ formatter: function (params) {
+ const data = params[0];
+ return `
+ <div style="font-weight: 600; margin-bottom: 8px; color: #303133;">${data.name}</div>
+ <div style="display: flex; align-items: center; font-size: 14px;">
+ <span style="display: inline-block; width: 8px; height: 8px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span>
+ <span style="color: #606266;">${data.seriesName}: </span>
+ <span style="font-weight: 600; margin-left: 8px; color: #303133;">${data.value}</span>
+ </div>
+ `;
+ },
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
+ borderColor: '#e4e7ed',
+ borderWidth: 1,
+ textStyle: {
+ color: '#303133'
+ },
+ padding: [8, 12],
+ borderRadius: 6,
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
+ shadowBlur: 8
+ },
+ grid: {
+ left: '3%',
+ right: '3%',
+ bottom: '3%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: chartData.inbound.dates,
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266',
+ rotate: 45
+ }
+ },
+ yAxis: {
+ type: 'value',
+ name: '鏁伴噺',
+ nameTextStyle: {
+ color: '#909399'
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266'
+ },
+ splitLine: {
+ lineStyle: {
+ color: '#f0f2f5',
+ type: 'dashed'
+ }
+ }
+ },
+ series: [{
+ name: '鍏ュ簱閲�',
+ data: chartData.inbound.values,
+ type: 'line',
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ itemStyle: {
+ color: '#409eff',
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ lineStyle: {
+ width: 3,
+ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+ { offset: 0, color: '#409eff' },
+ { offset: 1, color: '#67c23a' }
+ ])
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
+ { offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
+ ])
+ },
+ animation: true,
+ label: {
+ show: true,
+ position: 'top',
+ formatter: '{c}',
+ color: '#409eff',
+ fontWeight: 'bold'
+ }
+ }]
+ };
+
+ // 鏈堝嚭鍏ュ簱閲忓浘琛ㄩ厤缃紙鍙屾姌绾垮浘锛�
+ const monthDataOption = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ crossStyle: {
+ color: '#999'
+ }
+ },
+ formatter: function (params) {
+ let html = `<div style="font-weight: 600; margin-bottom: 8px; color: #303133;">${params[0].name}</div>`;
+ params.forEach(param => {
+ html += `
+ <div style="display: flex; align-items: center; margin: 4px 0; font-size: 14px;">
+ <span style="display: inline-block; width: 8px; height: 8px; background: ${param.color}; border-radius: 50%; margin-right: 8px;"></span>
+ <span style="color: #606266;">${param.seriesName}: </span>
+ <span style="font-weight: 600; margin-left: 8px; color: #303133;">${param.value}</span>
+ </div>
+ `;
+ });
+ return html;
+ },
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
+ borderColor: '#e4e7ed',
+ borderWidth: 1,
+ textStyle: {
+ color: '#303133'
+ },
+ padding: [12, 16],
+ borderRadius: 6,
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
+ shadowBlur: 8
+ },
+ legend: {
+ data: ['鍏ュ簱閲�', '鍑哄簱閲�'],
+ bottom: 10,
+ textStyle: {
+ color: '#606266'
+ },
+ itemWidth: 12,
+ itemHeight: 12
+ },
+ grid: {
+ left: '3%',
+ right: '3%',
+ bottom: '12%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: chartData.monthData.dates,
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266',
+ rotate: 45
+ },
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ yAxis: {
+ type: 'value',
+ name: '鏁伴噺',
+ nameTextStyle: {
+ color: '#909399'
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#e4e7ed'
+ }
+ },
+ axisLabel: {
+ color: '#606266'
+ },
+ splitLine: {
+ lineStyle: {
+ color: '#f0f2f5',
+ type: 'dashed'
+ }
+ }
+ },
+ series: [
+ {
+ name: '鍏ュ簱閲�',
+ data: chartData.monthData.inValue,
+ type: 'line',
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: 6,
+ itemStyle: {
+ color: '#409eff'
+ },
+ lineStyle: {
+ width: 3
+ },
+ animation: true,
+ label: {
+ show: true,
+ position: 'top',
+ formatter: '{c}',
+ color: '#409eff',
+ fontWeight: 'bold'
+ }
+ },
+ {
+ name: '鍑哄簱閲�',
+ data: chartData.monthData.outValue,
+ type: 'line',
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: 6,
+ itemStyle: {
+ color: '#ff6b6b'
+ },
+ lineStyle: {
+ width: 3
+ },
+ animation: true,
+ label: {
+ show: true,
+ position: 'top',
+ formatter: '{c}',
+ color: '#ff6b6b',
+ fontWeight: 'bold'
+ }
+ }
+ ]
+ };
+
+ outboundInstance.value.setOption(outboundOption);
+ inboundInstance.value.setOption(inboundOption);
+ monthDataInstance.value.setOption(monthDataOption);
+};
+
+// 鏇存柊鍥捐〃鏁版嵁
+const updateCharts = () => {
+ nextTick(() => {
+ // 鍏堥攢姣佹棫鐨勫浘琛ㄥ疄渚�
+ if (outboundInstance.value) {
+ outboundInstance.value.dispose();
+ }
+ if (inboundInstance.value) {
+ inboundInstance.value.dispose();
+ }
+ if (monthDataInstance.value) {
+ monthDataInstance.value.dispose();
+ }
+
+ // 閲嶆柊鍒濆鍖栧浘琛�
+ initCharts();
+ });
+};
+
+// 鍝嶅簲寮忕獥鍙h皟鏁�
+const handleResize = () => {
+ charts.value.forEach(chart => chart && chart.resize());
+};
+
+// 鏁版嵁澶勭悊
+const handleDataUpdate = (data) => {
+ console.log('API鍝嶅簲鏁版嵁:', data);
+
+ // 浣跨敤 Object.assign 纭繚鍝嶅簲寮忔洿鏂�
+ if (data.metrics && Array.isArray(data.metrics)) {
+ dataMetrics.value = data.metrics.map(item => ({
+ name: item.name || item.Name || '鏈煡鎸囨爣',
+ value: item.value != null ? item.value : item.Value || 0,
+ compare: item.compare != null ? item.compare : 0
+ }));
+ }
+
+ // 鏇存柊鍥捐〃鏁版嵁
+ if (data.outbound) {
+ chartData.outbound.dates = data.outbound.dates || [];
+ chartData.outbound.values = data.outbound.values || [];
+ }
+
+ if (data.inbound) {
+ chartData.inbound.dates = data.inbound.dates || [];
+ chartData.inbound.values = data.inbound.values || [];
+ }
+
+ if (data.monthData) {
+ chartData.monthData.dates = data.monthData.dates || [];
+ chartData.monthData.inValue = data.monthData.inValue || [];
+ chartData.monthData.outValue = data.monthData.outValue || [];
+ }
+
+ // 鏇存柊琛ㄦ牸鏁版嵁
+ if (data && data.newTask && Array.isArray(data.newTask)) {
+ tableData.value = data.newTask.map(task => ({
+ palletCode: task.palletCode || '鏃�',
+ roadway: task.roadway || '鏃�',
+ sourceAddress: task.sourceAddress || '鏃�',
+ targetAddress: task.targetAddress || '鏃�',
+ taskType: task.taskType || '鏃�',
+ taskState: task.taskState || '鏃�',
+ errorMessage: task.errorMessage || '鏃�',
+ createDate: task.createDate || '鏃�',
+ }));
+
+ // 鏁版嵁鏇存柊鍚庨噸鏂板惎鍔ㄦ粴鍔�
+ nextTick(() => {
+ pauseScroll();
+ startScrolling();
+ });
+ } else {
+ tableData.value = [];
+ pauseScroll();
+ }
+
+ // 寮哄埗鏇存柊 refreshKey 瑙﹀彂閲嶆柊娓叉煋
+ refreshKey.value++;
+
+ // 寤惰繜鍒濆鍖栧浘琛紝纭繚鏁版嵁宸叉洿鏂�
+ nextTick(() => {
+ updateCharts();
+ });
+};
+
+// 鏁版嵁鑾峰彇
+const fetchData = async () => {
+ try {
+ loading.value = true;
+ error.value = false;
+ refreshing.value = true;
+
+ const response = await http.post("api/StockInfo/GetStockData", {});
+ console.log('API鍝嶅簲:', response);
+
+ if (response.data && response.data.success !== false) {
+ handleDataUpdate(response.data);
+ error.value = false;
+ } else {
+ throw new Error('API杩斿洖鏁版嵁鏍煎紡閿欒');
+ }
+ } catch (err) {
+ console.error('API璇锋眰澶辫触:', err);
+ error.value = true;
+ } finally {
+ loading.value = false;
+ refreshing.value = false;
+ updateCurrentTime();
+ }
+};
+
+// 鍒锋柊澶勭悊
+const handleRefresh = () => {
+ fetchData();
+};
+
+const intervalId = ref(null);
+const timeIntervalId = ref(null);
+
+const startPolling = () => {
+ fetchData();
+
+ // 璁剧疆瀹氭椂鏇存柊褰撳墠鏃堕棿
+ timeIntervalId.value = setInterval(updateCurrentTime, 1000);
+
+ // 璁剧疆瀹氭椂鑾峰彇鏁版嵁
+ intervalId.value = setInterval(fetchData, 5 * 60 * 1000); // 5鍒嗛挓杞
+};
+
const stopPolling = () => {
if (intervalId.value) {
clearInterval(intervalId.value);
- console.log('宸插仠姝㈡暟鎹疆璇�');
+ intervalId.value = null;
}
+ if (timeIntervalId.value) {
+ clearInterval(timeIntervalId.value);
+ timeIntervalId.value = null;
+ }
+ pauseScroll();
};
+
+// 鐢熷懡鍛ㄦ湡
onMounted(() => {
startPolling();
+ window.addEventListener('resize', handleResize);
});
+
onUnmounted(() => {
stopPolling();
-});
-const chartOptions = ref({
- tooltip: {
- formatter: '{b}: {c} ({d}%)'
- },
- legend: {
- itemWidth: 14,
- itemHeight: 14,
- textStyle: { fontSize: 12 }
- }
+ charts.value.forEach(chart => chart && chart.dispose());
+ window.removeEventListener('resize', handleResize);
});
</script>
+
<style scoped>
-.dashboard {
- width: 100%;
- height: 100vh;
+.retry-btn {
+ margin-left: 8px;
+ color: #409eff;
+}
+
+.chart-error {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+}
+
+.chart-error .el-button {
+ margin-top: 8px;
+}
+
+.dashboard-container {
padding: 20px;
- box-sizing: border-box;
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
+ min-height: 100vh;
+ font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
-.m-charts {
- display: grid;
- margin: 10px;
- grid-template-columns: 33% 33% 95%;
+.overview-section {
+ margin-bottom: 24px;
+}
+
+.data-overview {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ padding: 24px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(10px);
+}
+
+.overview-header {
+ display: flex;
justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 24px;
}
-.labelContent {
- padding-top: 3vh;
-}
-
-.chart_left {
- position: relative;
- border-radius: 10px;
- background-color: white;
- box-shadow: 0px 0px 10px 0px #ccc;
- height: 100%;
- width: 550px;
-}
-
-.icons {
- margin-right: 5px;
-}
-
-.indexModel .item_center {
- height: 90vh;
- width: 98%;
- margin: 0vh 20%;
-}
-
-.titles {
- width: 200px;
- height: 5vh;
+.header-left .title {
display: flex;
align-items: center;
- font-size: 1.5rem;
- font-weight: 600;
+ margin: 0 0 8px 0;
+ font-size: 20px;
+ font-weight: 700;
+ color: #303133;
+ line-height: 1.2;
+}
+
+.header-icon {
+ margin-right: 12px;
+ font-size: 24px;
+ color: #409eff;
+ background: linear-gradient(135deg, #409eff, #79bbff);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.subtitle {
+ font-size: 14px;
+ color: #909399;
+ margin: 0;
+}
+
+.header-right {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 12px;
+}
+
+.time-range {
+ font-size: 13px;
+ color: #909399;
+ background: rgba(64, 158, 255, 0.1);
+ padding: 6px 12px;
+ border-radius: 20px;
+ border: 1px solid rgba(64, 158, 255, 0.2);
+}
+
+.refresh-btn {
+ border-radius: 20px;
+ padding: 8px 16px;
+ font-weight: 500;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+ transition: all 0.3s ease;
+}
+
+.refresh-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+.metrics-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 20px;
+}
+
+.metric-card {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 12px;
+ padding: 24px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.metric-card:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
+}
+
+.metric-card::before {
+ content: '';
position: absolute;
- top: 0px;
- left: 15px;
-}</style>
\ No newline at end of file
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ background: linear-gradient(90deg, transparent 0%, currentColor 50%, transparent 100%);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.metric-card:hover::before {
+ opacity: 1;
+}
+
+.metric-inbound-today {
+ border-left: 4px solid #67c23a;
+ color: #67c23a;
+}
+
+.metric-outbound-today {
+ border-left: 4px solid #e6a23c;
+ color: #e6a23c;
+}
+
+.metric-inbound-month {
+ border-left: 4px solid #409eff;
+ color: #409eff;
+}
+
+.metric-outbound-month {
+ border-left: 4px solid #f56c6c;
+ color: #f56c6c;
+}
+
+.metric-total {
+ border-left: 4px solid #909399;
+ color: #909399;
+}
+
+.metric-content {
+ display: flex;
+ align-items: flex-start;
+ flex: 1;
+ z-index: 2;
+}
+
+.metric-icon-wrapper {
+ width: 56px;
+ height: 56px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ font-size: 28px;
+ transition: all 0.3s ease;
+}
+
+.metric-card:hover .metric-icon-wrapper {
+ transform: scale(1.1);
+}
+
+.metric-inbound-today .metric-icon-wrapper {
+ background: linear-gradient(135deg, rgba(103, 194, 58, 0.15), rgba(103, 194, 58, 0.05));
+ color: #67c23a;
+}
+
+.metric-outbound-today .metric-icon-wrapper {
+ background: linear-gradient(135deg, rgba(230, 162, 60, 0.15), rgba(230, 162, 60, 0.05));
+ color: #e6a23c;
+}
+
+.metric-inbound-month .metric-icon-wrapper {
+ background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(64, 158, 255, 0.05));
+ color: #409eff;
+}
+
+.metric-outbound-month .metric-icon-wrapper {
+ background: linear-gradient(135deg, rgba(245, 108, 108, 0.15), rgba(245, 108, 108, 0.05));
+ color: #f56c6c;
+}
+
+.metric-total .metric-icon-wrapper {
+ background: linear-gradient(135deg, rgba(144, 147, 153, 0.15), rgba(144, 147, 153, 0.05));
+ color: #909399;
+}
+
+.metric-info {
+ flex: 1;
+}
+
+.metric-name {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 8px;
+ font-weight: 500;
+}
+
+.metric-value {
+ font-size: 28px;
+ font-weight: 800;
+ color: #303133;
+ margin-bottom: 8px;
+ line-height: 1;
+ background: linear-gradient(135deg, currentColor, #303133);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.metric-compare {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+}
+
+.compare-positive {
+ color: #f56c6c;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ margin-right: 8px;
+}
+
+.compare-positive i {
+ font-size: 12px;
+ margin-right: 4px;
+}
+
+.compare-negative {
+ color: #67c23a;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ margin-right: 8px;
+}
+
+.compare-negative i {
+ font-size: 12px;
+ margin-right: 4px;
+}
+
+.compare-zero {
+ color: #909399;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ margin-right: 8px;
+}
+
+.compare-hidden {
+ display: hidden;
+}
+
+.compare-zero i {
+ font-size: 12px;
+ margin-right: 4px;
+}
+
+.compare-label {
+ color: #909399;
+ font-size: 11px;
+}
+
+.metric-trend {
+ width: 44px;
+ height: 44px;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ z-index: 2;
+}
+
+.trend-chart {
+ width: 6px;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.06);
+ border-radius: 3px;
+ position: relative;
+ overflow: hidden;
+}
+
+.trend-bar {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ border-radius: 3px;
+ transition: height 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.metric-inbound-today .trend-bar {
+ background: linear-gradient(to top, #67c23a, #95d475);
+}
+
+.metric-outbound-today .trend-bar {
+ background: linear-gradient(to top, #e6a23c, #eebe77);
+}
+
+.metric-inbound-month .trend-bar {
+ background: linear-gradient(to top, #409eff, #79bbff);
+}
+
+.metric-outbound-month .trend-bar {
+ background: linear-gradient(to top, #f56c6c, #f89898);
+}
+
+.metric-total .trend-bar {
+ background: linear-gradient(to top, #909399, #b1b3b8);
+}
+
+.metric-decoration {
+ position: absolute;
+ top: -20px;
+ right: -20px;
+ width: 80px;
+ height: 80px;
+ opacity: 0.1;
+ z-index: 1;
+}
+
+.decoration-circle {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background: currentColor;
+}
+
+/* 浠诲姟鐪嬫澘鏍峰紡 */
+.task-board-container {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ overflow: hidden;
+}
+
+.board-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 24px;
+ background: linear-gradient(135deg, #409eff 0%, #79bbff 100%);
+ color: white;
+}
+
+.header-left {
+ display: flex;
+ align-items: center;
+}
+
+.board-icon {
+ font-size: 20px;
+ margin-right: 12px;
+}
+
+.board-title {
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.task-count {
+ font-size: 14px;
+ opacity: 0.9;
+ background: rgba(255, 255, 255, 0.2);
+ padding: 4px 12px;
+ border-radius: 12px;
+}
+
+.board-body {
+ height: 320px;
+ padding: 0;
+}
+
+.scroll-table-container {
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+}
+
+.table-header {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 20;
+ background: #f8f9fa;
+ border-bottom: 2px solid #e4e7ed;
+}
+
+.header-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.header-table th {
+ background: #f8f9fa;
+ color: #606266;
+ font-weight: 600;
+ padding: 16px 12px;
+ text-align: center;
+ border-right: 1px solid #e4e7ed;
+ font-size: 14px;
+ position: relative;
+}
+
+.header-table th:last-child {
+ border-right: none;
+}
+
+.header-table th::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, #409eff, #79bbff);
+}
+
+.table-body-container {
+ position: absolute;
+ top: 56px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+}
+
+.table-body-wrapper {
+ transition: transform 0.3s ease;
+}
+
+.body-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.body-table td {
+ padding: 14px 12px;
+ border-bottom: 1px solid #f0f2f5;
+ font-size: 13px;
+ height: 48px;
+ box-sizing: border-box;
+ text-align: center;
+ color: #606266;
+}
+
+.even-row {
+ background-color: #fafbfc;
+}
+
+.odd-row {
+ background-color: #ffffff;
+}
+
+.body-table tr:hover {
+ background-color: #f0f7ff !important;
+ transform: scale(1.01);
+ transition: all 0.2s ease;
+}
+
+.status-tag {
+ border-radius: 12px;
+ padding: 4px 12px;
+ font-weight: 500;
+ border: none;
+}
+
+.no-data-board {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ background: #f8f9fa;
+}
+
+/* 鍥捐〃鍗$墖鏍峰紡 */
+.charts-section {
+ margin-bottom: 24px;
+}
+
+.chart-card {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ overflow: hidden;
+ height: 400px;
+ display: flex;
+ flex-direction: column;
+}
+
+.chart-header {
+ padding: 20px 24px 0;
+ background: transparent;
+}
+
+.chart-title {
+ display: flex;
+ align-items: center;
+ margin: 0 0 8px 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.chart-icon {
+ margin-right: 8px;
+ color: #409eff;
+ font-size: 18px;
+}
+
+.chart-subtitle {
+ font-size: 13px;
+ color: #909399;
+ margin: 0;
+}
+
+.chart-content {
+ flex: 1;
+ padding: 0 12px 20px;
+ position: relative;
+}
+
+.chart {
+ height: 100%;
+ width: 100%;
+}
+
+.chart-large {
+ height: 100%;
+ width: 100%;
+}
+
+.chart-loading,
+.chart-error,
+.chart-no-data {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ color: #909399;
+ font-size: 14px;
+}
+
+.chart-loading i,
+.chart-error i,
+.chart-no-data i {
+ font-size: 48px;
+ margin-bottom: 16px;
+ opacity: 0.6;
+}
+
+.chart-loading {
+ color: #409eff;
+}
+
+.chart-error {
+ color: #f56c6c;
+}
+
+/* Element Plus 鏍囩鏍峰紡璋冩暣 */
+:deep(.el-tag) {
+ border: none;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+:deep(.el-tag--success) {
+ background: linear-gradient(135deg, #f0f9ff, #e1f3ff);
+ color: #67c23a;
+}
+
+:deep(.el-tag--primary) {
+ background: linear-gradient(135deg, #f0f9ff, #e1f3ff);
+ color: #409eff;
+}
+
+:deep(.el-tag--info) {
+ background: linear-gradient(135deg, #f4f4f5, #e9e9eb);
+ color: #909399;
+}
+
+:deep(.el-tag--warning) {
+ background: linear-gradient(135deg, #fdf6ec, #faecd8);
+ color: #e6a23c;
+}
+
+:deep(.el-tag--danger) {
+ background: linear-gradient(135deg, #fef0f0, #fde2e2);
+ color: #f56c6c;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+ .metrics-grid {
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 16px;
+ }
+
+ .metric-card {
+ padding: 20px;
+ }
+
+ .metric-value {
+ font-size: 24px;
+ }
+}
+
+@media (max-width: 768px) {
+ .dashboard-container {
+ padding: 16px;
+ }
+
+ .overview-header {
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .header-right {
+ align-items: flex-start;
+ }
+
+ .metrics-grid {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+
+ .chart-card {
+ height: 350px;
+ }
+}
+
+/* 婊氬姩鏉℃牱寮忎紭鍖� */
+.scroll-table-container::-webkit-scrollbar {
+ width: 6px;
+}
+
+.scroll-table-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 3px;
+}
+
+.scroll-table-container::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 3px;
+}
+
+.scroll-table-container::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+}
+</style>
\ No newline at end of file
--
Gitblit v1.9.3