<template>
|
<div class="dashboard-container">
|
<!-- 顶部标题 -->
|
<div class="header">
|
<h1 class="title">WMS仓库管理系统监控看板</h1>
|
<div class="datetime">{{ currentTime }}</div>
|
</div>
|
|
<!-- 导航菜单 -->
|
<div class="nav-menu">
|
<router-link to="/dashboard" class="nav-item" active-class="active">
|
<el-icon><DataBoard /></el-icon>
|
<span>综合看板</span>
|
</router-link>
|
<router-link to="/warehouse" class="nav-item" active-class="active">
|
<el-icon><Box /></el-icon>
|
<span>仓库监控</span>
|
</router-link>
|
<router-link to="/production" class="nav-item" active-class="active">
|
<el-icon><Operation /></el-icon>
|
<span>生产监控</span>
|
</router-link>
|
<router-link to="/inventory" class="nav-item" active-class="active">
|
<el-icon><Warning /></el-icon>
|
<span>库存预警</span>
|
</router-link>
|
</div>
|
|
<!-- 主要内容区域 -->
|
<div class="main-content">
|
<!-- 第一行:关键指标卡片 -->
|
<div class="metrics-row">
|
<div class="metric-card" v-for="(item, index) in metrics" :key="index">
|
<div class="metric-icon" :style="{ background: item.color }">
|
<el-icon :size="32">
|
<Box v-if="index === 0" />
|
<Download v-else-if="index === 1" />
|
<Upload v-else-if="index === 2" />
|
<List v-else />
|
</el-icon>
|
</div>
|
<div class="metric-content">
|
<div class="metric-value">{{ item.value }}</div>
|
<div class="metric-label">{{ item.label }}</div>
|
<div class="metric-trend" :class="item.trend > 0 ? 'up' : 'down'">
|
<el-icon>
|
<Top v-if="item.trend > 0" />
|
<Bottom v-else />
|
</el-icon>
|
<span>{{ Math.abs(item.trend) }}%</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 第二行:图表区域 -->
|
<div class="charts-row">
|
<!-- 入库出库趋势 -->
|
<div class="chart-card">
|
<div class="card-title">
|
<span>出入库趋势</span>
|
</div>
|
<div ref="trendChartRef" class="chart-container"></div>
|
</div>
|
|
<!-- 库存分类占比 -->
|
<div class="chart-card">
|
<div class="card-title">
|
<span>库存分类占比</span>
|
</div>
|
<div ref="categoryChartRef" class="chart-container"></div>
|
</div>
|
|
<!-- 作业效率 -->
|
<div class="chart-card">
|
<div class="card-title">
|
<span>作业效率统计</span>
|
</div>
|
<div ref="efficiencyChartRef" class="chart-container"></div>
|
</div>
|
</div>
|
|
<!-- 第三行:数据表格 -->
|
<div class="table-row">
|
<div class="table-card">
|
<div class="card-title">
|
<span>实时作业任务</span>
|
</div>
|
<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 }">
|
<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="taskType" label="任务类型" width="100">
|
<template #default="{ row }">
|
<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="palletCode" label="托盘编号" />
|
<el-table-column prop="sourceAddress" label="起点位置"/>
|
<el-table-column prop="targetAddress" label="终点位置"/>
|
<el-table-column prop="createDate" label="创建时间"/>
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
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: 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
|
const updateTime = () => {
|
currentTime.value = formatDateTime(new Date())
|
}
|
|
// 初始化趋势图(直接使用后端日期)
|
const initTrendChart = () => {
|
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 = {
|
|
legend: {
|
data: ['入库量', '出库量'],
|
textStyle: { color: '#fff' }
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
top: '15%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
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)' } },
|
axisLabel: { color: '#fff' },
|
min: 0
|
},
|
series: [
|
{
|
name: '入库量',
|
type: 'line',
|
smooth: true,
|
data: inboundData,
|
itemStyle: { color: '#5470c6' },
|
lineStyle: { color: '#5470c6' },
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ 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: outboundData,
|
itemStyle: { color: '#91cc75' },
|
lineStyle: { color: '#91cc75' },
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(145, 204, 117, 0.5)' },
|
{ offset: 1, color: 'rgba(145, 204, 117, 0)' }
|
])
|
},
|
showSymbol: hasData,
|
symbol: 'circle',
|
symbolSize: 6
|
}
|
],
|
|
}
|
|
trendChart.value.setOption(option)
|
return trendChart.value
|
}
|
|
// 初始化分类占比图
|
const initCategoryChart = () => {
|
if (!categoryChartRef.value) return
|
|
categoryChart.value = echarts.init(categoryChartRef.value)
|
|
const option = {
|
tooltip: { trigger: 'item' },
|
legend: {
|
orient: 'vertical',
|
right: '10%',
|
top: 'center',
|
textStyle: { color: '#fff' }
|
},
|
series: [
|
{
|
name: '库存分类',
|
type: 'pie',
|
radius: ['40%', '70%'],
|
center: ['35%', '50%'],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderRadius: 10,
|
borderColor: '#0a0e27',
|
borderWidth: 2
|
},
|
label: { show: false },
|
emphasis: {
|
label: { show: true, fontSize: 16, fontWeight: 'bold' }
|
},
|
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' } }
|
]
|
}
|
]
|
}
|
|
categoryChart.value.setOption(option)
|
return categoryChart.value
|
}
|
|
// 初始化效率统计图
|
// 初始化效率统计图
|
// 初始化效率统计图(修复版)
|
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]
|
},
|
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);
|
}
|
})
|
}
|
|
// 销毁旧图表实例
|
if (trendChart.value) trendChart.value.dispose()
|
if (categoryChart.value) categoryChart.value.dispose()
|
if (efficiencyChart.value) efficiencyChart.value.dispose()
|
|
// 重新初始化图表
|
initTrendChart()
|
initCategoryChart()
|
initEfficiencyChart()
|
}
|
|
// 手动刷新数据
|
const refreshData = () => {
|
fetchBigGreenData()
|
}
|
|
// 启动自动刷新
|
const startAutoRefresh = () => {
|
if (autoRefreshTimer) clearInterval(autoRefreshTimer)
|
autoRefreshTimer = setInterval(() => {
|
console.log('定时刷新数据:', new Date().toLocaleString())
|
fetchBigGreenData()
|
}, refreshInterval.value)
|
}
|
|
// 窗口大小改变时重绘图表和调整表格高度
|
const handleResize = () => {
|
try {
|
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)
|
}
|
}
|
|
onMounted(() => {
|
// 初始化时间
|
updateTime()
|
timer = setInterval(updateTime, 1000)
|
|
// 初始化数据和图表
|
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 {
|
if (trendChart.value) trendChart.value.dispose()
|
if (categoryChart.value) categoryChart.value.dispose()
|
if (efficiencyChart.value) efficiencyChart.value.dispose()
|
} catch (error) {
|
console.warn('图表销毁时出错:', error)
|
}
|
})
|
|
return {
|
currentTime,
|
metrics,
|
taskList,
|
showTaskList,
|
trendChartRef,
|
categoryChartRef,
|
efficiencyChartRef,
|
tableHeight,
|
getTaskTypeText,
|
getTaskStatusText,
|
getStatusClass,
|
getTypeClass,
|
refreshData
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.dashboard-container {
|
width: 100%;
|
height: 100vh;
|
padding: 20px;
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
overflow-y: auto;
|
background-color: #0a0e27;
|
}
|
|
.header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 0 20px;
|
height: 60px;
|
background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
|
border-radius: 10px;
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
}
|
|
.title {
|
font-size: 24px;
|
font-weight: bold;
|
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
}
|
|
.datetime {
|
font-size: 16px;
|
color: #4facfe;
|
}
|
|
.nav-menu {
|
display: flex;
|
gap: 15px;
|
padding: 0 20px;
|
}
|
|
.nav-item {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
padding: 10px 25px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 8px;
|
color: #fff;
|
text-decoration: none;
|
transition: all 0.3s;
|
cursor: pointer;
|
}
|
|
.nav-item:hover {
|
background: rgba(79, 172, 254, 0.2);
|
border-color: #4facfe;
|
}
|
|
.nav-item.active {
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
border-color: transparent;
|
}
|
|
.main-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.metrics-row {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 15px;
|
}
|
|
.metric-card {
|
display: flex;
|
align-items: center;
|
gap: 20px;
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
transition: all 0.3s;
|
}
|
|
.metric-card:hover {
|
transform: translateY(-5px);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
}
|
|
.metric-icon {
|
width: 60px;
|
height: 60px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 12px;
|
color: #fff;
|
}
|
|
.metric-content {
|
flex: 1;
|
}
|
|
.metric-value {
|
font-size: 28px;
|
font-weight: bold;
|
color: #fff;
|
margin-bottom: 5px;
|
}
|
|
.metric-label {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.6);
|
margin-bottom: 5px;
|
}
|
|
.metric-trend {
|
display: flex;
|
align-items: center;
|
gap: 5px;
|
font-size: 12px;
|
}
|
|
.metric-trend.up {
|
color: #67c23a;
|
}
|
|
.metric-trend.down {
|
color: #f56c6c;
|
}
|
|
.charts-row {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 15px;
|
}
|
|
.chart-card {
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
}
|
|
.card-title {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
font-size: 16px;
|
font-weight: bold;
|
color: #4facfe;
|
}
|
|
.chart-container {
|
width: 100%;
|
height: 250px;
|
}
|
|
.table-row {
|
flex: 1;
|
}
|
|
.table-card {
|
height: 100%;
|
padding: 20px;
|
background: rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
border-radius: 10px;
|
}
|
|
/* Element Plus Table 样式覆盖 */
|
:deep(.el-table) {
|
background: transparent !important;
|
}
|
|
:deep(.el-table th) {
|
background: rgba(255, 255, 255, 0.1) !important;
|
color: #fff !important;
|
border-color: rgba(255, 255, 255, 0.1) !important;
|
}
|
|
: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) {
|
background: transparent !important;
|
}
|
|
:deep(.el-table__row:hover td) {
|
background: rgba(255, 255, 255, 0.05) !important;
|
}
|
|
: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);
|
}
|
}
|
|
/* 响应式适配 */
|
@media screen and (max-width: 1920px) {
|
.metric-value {
|
font-size: 24px;
|
}
|
|
.chart-container {
|
height: 220px;
|
}
|
}
|
|
@media screen and (max-width: 1600px) {
|
.metrics-row {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.charts-row {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.title {
|
font-size: 20px;
|
}
|
|
.metric-value {
|
font-size: 20px;
|
}
|
|
.chart-container {
|
height: 200px;
|
}
|
}
|
|
@media screen and (max-width: 1366px) {
|
.dashboard-container {
|
padding: 15px;
|
}
|
|
.header {
|
height: 50px;
|
padding: 0 15px;
|
}
|
|
.title {
|
font-size: 18px;
|
}
|
|
.nav-item {
|
padding: 8px 20px;
|
font-size: 14px;
|
}
|
|
.metrics-row {
|
grid-template-columns: repeat(2, 1fr);
|
gap: 10px;
|
}
|
|
.metric-card {
|
padding: 15px;
|
}
|
|
.metric-value {
|
font-size: 18px;
|
}
|
|
.charts-row {
|
grid-template-columns: 1fr;
|
gap: 10px;
|
}
|
|
.chart-card {
|
padding: 15px;
|
}
|
|
.chart-container {
|
height: 180px;
|
}
|
}
|
|
@media screen and (max-width: 1024px) {
|
.metrics-row {
|
grid-template-columns: 1fr 1fr;
|
}
|
|
.nav-menu {
|
flex-wrap: wrap;
|
}
|
|
.nav-item {
|
flex: 1;
|
min-width: 100px;
|
justify-content: center;
|
}
|
}
|
|
@media screen and (max-width: 768px) {
|
.dashboard-container {
|
padding: 10px;
|
}
|
|
.header {
|
flex-direction: column;
|
height: auto;
|
padding: 10px;
|
gap: 10px;
|
}
|
|
.title {
|
font-size: 16px;
|
}
|
|
.datetime {
|
font-size: 14px;
|
}
|
|
.nav-menu {
|
gap: 8px;
|
padding: 0;
|
}
|
|
.nav-item {
|
padding: 6px 12px;
|
font-size: 12px;
|
flex-direction: column;
|
gap: 4px;
|
}
|
|
.metrics-row {
|
grid-template-columns: 1fr;
|
}
|
|
.metric-card {
|
padding: 12px;
|
flex-direction: column;
|
text-align: center;
|
}
|
|
.metric-icon {
|
width: 45px;
|
height: 45px;
|
}
|
|
.chart-container {
|
height: 150px;
|
}
|
}
|
</style>
|