<template>
|
<div class="screen-container">
|
<div id="index" class="dashboard-screen">
|
<div class="bg">
|
<!-- 顶部标题栏 -->
|
<div class="screen-header">
|
<div class="header-content">
|
<h1 class="main-title">
|
<i class="el-icon-monitor title-icon"></i>
|
明和智能仓储数据大屏
|
</h1>
|
<div class="header-info">
|
<div class="current-time">{{ currentTime }}</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 数据总览部分 -->
|
<div class="screen-section">
|
<div class="section-title">
|
<div class="title-decoration"></div>
|
<h2>数据总览</h2>
|
<div class="title-decoration"></div>
|
</div>
|
<div class="metrics-grid-large">
|
<div class="metric-card-large" v-for="(item, index) in dataMetrics" :key="`metric-${index}-${refreshKey}`"
|
:class="getMetricCardClass(item.name)">
|
<div class="metric-glow"></div>
|
<div class="metric-content">
|
<div class="metric-icon-large">
|
<i :class="getMetricIcon(item.name)"></i>
|
</div>
|
<div class="metric-info-large">
|
<div class="metric-name-large">{{ item.name }}</div>
|
<div class="metric-value-large">{{ formatNumber(item.value) }}</div>
|
<div class="metric-compare-large" v-if="item.compare !== undefined">
|
<span :class="getCompareClass(item.compare)">
|
<i :class="getCompareIcon(item.compare)"></i>
|
{{ formatCompareValue(item.compare) }}
|
</span>
|
<span class="compare-label">较昨日</span>
|
</div>
|
</div>
|
</div>
|
<div class="metric-wave">
|
<div class="wave"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 中间内容区域 -->
|
<div class="screen-main">
|
<!-- 左侧图表 -->
|
<div class="main-left">
|
<div class="chart-container-large">
|
<div class="chart-header-large">
|
<h3 class="chart-title-large">
|
<i class="el-icon-trend-charts"></i>
|
出库量趋势
|
</h3>
|
<div class="chart-subtitle-large">近7日出库统计</div>
|
</div>
|
<div class="chart-content-large">
|
<div v-if="loading" class="chart-loading-large">
|
<i class="el-icon-loading"></i>
|
<span>数据加载中...</span>
|
</div>
|
<div v-else-if="error" class="chart-error-large">
|
<i class="el-icon-warning"></i>
|
<span>数据加载失败</span>
|
</div>
|
<div v-else-if="!chartData.outbound.values.length" class="chart-no-data-large">
|
<i class="el-icon-data-line"></i>
|
<span>暂无出库数据</span>
|
</div>
|
<div v-else ref="outboundChart" class="chart-large"></div>
|
</div>
|
</div>
|
|
<div class="chart-container-large">
|
<div class="chart-header-large">
|
<h3 class="chart-title-large">
|
<i class="el-icon-trend-charts"></i>
|
入库量趋势
|
</h3>
|
<div class="chart-subtitle-large">近7日入库统计</div>
|
</div>
|
<div class="chart-content-large">
|
<div v-if="loading" class="chart-loading-large">
|
<i class="el-icon-loading"></i>
|
<span>数据加载中...</span>
|
</div>
|
<div v-else-if="error" class="chart-error-large">
|
<i class="el-icon-warning"></i>
|
<span>数据加载失败</span>
|
</div>
|
<div v-else-if="!chartData.inbound.values.length" class="chart-no-data-large">
|
<i class="el-icon-data-line"></i>
|
<span>暂无入库数据</span>
|
</div>
|
<div v-else ref="inboundChart" class="chart-large"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 中间任务看板 -->
|
<div class="main-center">
|
<div class="task-board-large">
|
<div class="board-header-large">
|
<div class="header-title">
|
<i class="el-icon-s-management"></i>
|
<span>当前生产任务</span>
|
</div>
|
<div class="task-count-large">
|
<span class="count-number">{{ tableData.length }}</span>
|
<span class="count-label">个任务</span>
|
</div>
|
</div>
|
<div class="board-body-large">
|
<div class="scroll-table-container-large" @mouseenter="pauseScroll" @mouseleave="resumeScroll">
|
<div class="table-body-container-large" ref="tableBody">
|
<div class="table-body-wrapper-large" :style="{ transform: `translateY(-${scrollPosition}px)` }">
|
<table class="body-table-large">
|
<tbody>
|
<tr v-for="(row, index) in tableData" :key="`task-${index}-${refreshKey}`"
|
:class="getRowClass(index)">
|
<td class="cell-index">{{ index + 1 }}</td>
|
<td class="cell-pallet">{{ row.palletCode || '无' }}</td>
|
<td class="cell-address">{{ row.sourceAddress || '无' }}</td>
|
<td class="cell-address">{{ row.targetAddress || '无' }}</td>
|
<td class="cell-type">{{ row.taskType || '无' }}</td>
|
<td class="cell-status">
|
<span class="status-tag-large" :class="getStatusClass(row.taskState)">
|
{{ row.taskState || '无' }}
|
</span>
|
</td>
|
<td class="cell-time">{{ formatDateTime(row.createDate) || '无' }}</td>
|
</tr>
|
</tbody>
|
</table>
|
|
<table class="body-table-large" v-if="tableData.length > rowNum">
|
<tbody>
|
<tr v-for="(row, index) in tableData" :key="`task-copy-${index}-${refreshKey}`"
|
:class="getRowClass(index)">
|
<td class="cell-index">{{ index + tableData.length + 1 }}</td>
|
<td class="cell-pallet">{{ row.palletCode || '无' }}</td>
|
<td class="cell-address">{{ row.sourceAddress || '无' }}</td>
|
<td class="cell-address">{{ row.targetAddress || '无' }}</td>
|
<td class="cell-type">{{ row.taskType || '无' }}</td>
|
<td class="cell-status">
|
<span class="status-tag-large" :class="getStatusClass(row.taskState)">
|
{{ row.taskState || '无' }}
|
</span>
|
</td>
|
<td class="cell-time">{{ formatDateTime(row.createDate) || '无' }}</td>
|
</tr>
|
</tbody>
|
</table>
|
</div>
|
</div>
|
</div>
|
<div v-if="tableData.length === 0" class="no-data-board-large">
|
<i class="el-icon-document"></i>
|
<span>暂无生产任务数据</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 右侧图表 -->
|
<div class="main-right">
|
<div class="chart-container-full">
|
<div class="chart-header-large">
|
<h3 class="chart-title-large">
|
<i class="el-icon-data-line"></i>
|
月度出入库对比
|
</h3>
|
<div class="chart-subtitle-large">本月每日出入库量统计</div>
|
</div>
|
<div class="chart-content-full">
|
<div v-if="loading" class="chart-loading-large">
|
<i class="el-icon-loading"></i>
|
<span>数据加载中...</span>
|
</div>
|
<div v-else-if="error" class="chart-error-large">
|
<i class="el-icon-warning"></i>
|
<span>数据加载失败</span>
|
</div>
|
<div v-else-if="!chartData.monthData.inValue.length || !chartData.monthData.outValue.length"
|
class="chart-no-data-large">
|
<i class="el-icon-data-line"></i>
|
<span>暂无出入库数据</span>
|
</div>
|
<div v-else ref="monthDataChart" class="chart-full"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 背景装饰元素 -->
|
<div class="decoration-corner top-left"></div>
|
<div class="decoration-corner top-right"></div>
|
<div class="decoration-corner bottom-left"></div>
|
<div class="decoration-corner bottom-right"></div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import http from '../api/ajax.js';
|
import * as echarts from 'echarts';
|
|
export default {
|
name: 'DashboardScreen',
|
|
data() {
|
return {
|
// 响应式数据
|
dataMetrics: [],
|
tableData: [],
|
chartData: {
|
outbound: { dates: [], values: [] },
|
inbound: { dates: [], values: [] },
|
monthData: { dates: [], inValue: [], outValue: [] }
|
},
|
loading: true,
|
error: false,
|
refreshing: false,
|
refreshKey: 0,
|
|
// 滚动相关
|
scrollPosition: 0,
|
isScrolling: true,
|
scrollInterval: null,
|
rowNum: 6,
|
rowHeight: 60,
|
|
// 当前时间
|
currentTime: '',
|
|
// 图表实例
|
outboundInstance: null,
|
inboundInstance: null,
|
monthDataInstance: null,
|
|
// 缩放控制
|
scaleRatio: 1,
|
|
// 图标引用
|
refreshIcon: 'el-icon-refresh'
|
};
|
},
|
|
mounted() {
|
this.updateScale();
|
this.fetchData();
|
this.timeInterval = setInterval(this.updateCurrentTime, 1000);
|
this.dataInterval = setInterval(this.fetchData, 300000); // 5分钟
|
|
// 添加窗口调整监听
|
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.updateScale);
|
},
|
|
beforeDestroy() {
|
clearInterval(this.timeInterval);
|
clearInterval(this.dataInterval);
|
this.pauseScroll();
|
|
// 清理图表实例
|
[this.outboundInstance, this.inboundInstance, this.monthDataInstance].forEach(instance => {
|
if (instance) instance.dispose();
|
});
|
|
// 移除窗口调整监听
|
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.updateScale);
|
},
|
|
methods: {
|
// 动态缩放函数
|
updateScale() {
|
const baseWidth = 1920;
|
const baseHeight = 1080;
|
|
const windowWidth = window.innerWidth;
|
const windowHeight = window.innerHeight;
|
|
// 计算宽高比例
|
const widthRatio = windowWidth / baseWidth;
|
const heightRatio = windowHeight / baseHeight;
|
|
// 根据宽高比决定缩放策略
|
let scale;
|
if (windowWidth / windowHeight > baseWidth / baseHeight) {
|
// 宽屏,按高度缩放
|
scale = heightRatio;
|
} else {
|
// 高屏,按宽度缩放
|
scale = widthRatio;
|
}
|
|
// 限制缩放范围
|
scale = Math.max(0.5, Math.min(scale, 2));
|
|
this.scaleRatio = scale;
|
|
// 应用缩放
|
const screenElement = document.getElementById('index');
|
if (screenElement) {
|
screenElement.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
}
|
|
console.log(`窗口尺寸: ${windowWidth}x${windowHeight}, 缩放比例: ${scale.toFixed(3)}`);
|
},
|
|
// 格式化函数
|
formatNumber(num) {
|
if (num === undefined || num === null) return '0';
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
},
|
|
formatCompareValue(value) {
|
if (value === 0) return '0';
|
return value > 0 ? `+${this.formatNumber(value)}` : this.formatNumber(value);
|
},
|
|
formatDateTime(dateString) {
|
if (!dateString) return '';
|
try {
|
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;
|
}
|
},
|
|
// 样式类函数
|
getCompareClass(compare) {
|
if (compare > 0) return 'compare-positive';
|
if (compare < 0) return 'compare-negative';
|
return 'compare-zero';
|
},
|
|
getCompareIcon(compare) {
|
if (compare > 0) return 'el-icon-top';
|
if (compare < 0) return 'el-icon-bottom';
|
return 'el-icon-minus';
|
},
|
|
getMetricCardClass(name) {
|
const classMap = {
|
'今日进库量': 'metric-inbound-today',
|
'今日出库量': 'metric-outbound-today',
|
'本月进库量': 'metric-inbound-month',
|
'本月出库量': 'metric-outbound-month',
|
'库存总量': 'metric-total'
|
};
|
return classMap[name] || '';
|
},
|
|
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';
|
},
|
|
getStatusClass(status) {
|
const statusMap = {
|
'进行中': 'status-primary',
|
'已完成': 'status-success',
|
'已取消': 'status-info',
|
'异常': 'status-danger',
|
'待执行': 'status-warning'
|
};
|
return statusMap[status] || 'status-primary';
|
},
|
|
getRowClass(index) {
|
return index % 2 === 0 ? 'even-row' : 'odd-row';
|
},
|
|
// 时间更新
|
updateCurrentTime() {
|
const now = new Date();
|
this.currentTime = now.toLocaleString('zh-CN', {
|
year: 'numeric',
|
month: '2-digit',
|
day: '2-digit',
|
hour: '2-digit',
|
minute: '2-digit',
|
second: '2-digit'
|
});
|
},
|
|
// 滚动控制
|
startScrolling() {
|
if (this.tableData.length <= this.rowNum) {
|
this.isScrolling = false;
|
return;
|
}
|
|
this.isScrolling = true;
|
const totalHeight = this.tableData.length * this.rowHeight;
|
|
this.scrollInterval = setInterval(() => {
|
this.scrollPosition += 1;
|
if (this.scrollPosition >= totalHeight) {
|
this.scrollPosition = 0;
|
}
|
}, 40);
|
},
|
|
pauseScroll() {
|
if (this.scrollInterval) {
|
clearInterval(this.scrollInterval);
|
this.scrollInterval = null;
|
}
|
this.isScrolling = false;
|
},
|
|
resumeScroll() {
|
if (this.tableData.length > this.rowNum) {
|
this.startScrolling();
|
}
|
},
|
|
// 图表初始化
|
initCharts() {
|
console.log('初始化图表...');
|
console.log('outbound数据:', this.chartData.outbound);
|
console.log('inbound数据:', this.chartData.inbound);
|
console.log('monthData数据:', this.chartData.monthData);
|
|
if (!this.$refs.outboundChart || !this.$refs.inboundChart || !this.$refs.monthDataChart) {
|
console.log('图表容器未找到');
|
return;
|
}
|
|
// 清理旧实例
|
[this.outboundInstance, this.inboundInstance, this.monthDataInstance].forEach(instance => {
|
if (instance) {
|
instance.dispose();
|
}
|
});
|
|
try {
|
this.outboundInstance = echarts.init(this.$refs.outboundChart);
|
this.inboundInstance = echarts.init(this.$refs.inboundChart);
|
this.monthDataInstance = echarts.init(this.$refs.monthDataChart);
|
|
// 大屏图表配置
|
const chartOptions = {
|
backgroundColor: 'transparent',
|
grid: {
|
left: '3%',
|
right: '3%',
|
bottom: '10%',
|
top: '15%',
|
containLabel: true
|
},
|
textStyle: {
|
color: '#fff',
|
fontSize: 14
|
}
|
};
|
|
// 出库量图表配置
|
const outboundOption = {
|
...chartOptions,
|
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;">出库量: </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'
|
}
|
},
|
xAxis: {
|
type: 'category',
|
data: this.chartData.outbound.dates,
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', rotate: 45, fontSize: 12 }
|
},
|
yAxis: {
|
type: 'value',
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)', type: 'dashed' } }
|
},
|
series: [{
|
name: '出库量',
|
data: this.chartData.outbound.values,
|
type: 'bar',
|
itemStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: '#ff7b7b' },
|
{ offset: 1, color: '#ff3b3b' }
|
])
|
},
|
barWidth: '40%'
|
}]
|
};
|
|
// 入库量图表配置
|
const inboundOption = {
|
...chartOptions,
|
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;">入库量: </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'
|
}
|
},
|
xAxis: {
|
type: 'category',
|
data: this.chartData.inbound.dates,
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', rotate: 45, fontSize: 12 }
|
},
|
yAxis: {
|
type: 'value',
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)', type: 'dashed' } }
|
},
|
series: [{
|
name: '入库量',
|
data: this.chartData.inbound.values,
|
type: 'line',
|
smooth: true,
|
lineStyle: { color: '#36a3ff', width: 3 },
|
itemStyle: { color: '#36a3ff' },
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(54, 163, 255, 0.5)' },
|
{ offset: 1, color: 'rgba(54, 163, 255, 0.1)' }
|
])
|
}
|
}]
|
};
|
|
// 月度数据图表配置
|
const monthDataOption = {
|
...chartOptions,
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross'
|
},
|
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'
|
}
|
},
|
legend: {
|
data: ['入库量', '出库量'],
|
bottom: 10,
|
textStyle: { color: 'rgba(255,255,255,0.7)', fontSize: 12 }
|
},
|
xAxis: {
|
type: 'category',
|
data: this.chartData.monthData.dates,
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', rotate: 45, fontSize: 12 }
|
},
|
yAxis: {
|
type: 'value',
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
axisLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
|
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)', type: 'dashed' } }
|
},
|
series: [
|
{
|
name: '入库量',
|
data: this.chartData.monthData.inValue,
|
type: 'line',
|
smooth: true,
|
lineStyle: { color: '#36a3ff', width: 3 },
|
itemStyle: { color: '#36a3ff' }
|
},
|
{
|
name: '出库量',
|
data: this.chartData.monthData.outValue,
|
type: 'line',
|
smooth: true,
|
lineStyle: { color: '#ff6b6b', width: 3 },
|
itemStyle: { color: '#ff6b6b' }
|
}
|
]
|
};
|
|
// 设置图表选项
|
this.outboundInstance.setOption(outboundOption);
|
this.inboundInstance.setOption(inboundOption);
|
this.monthDataInstance.setOption(monthDataOption);
|
|
console.log('图表初始化完成');
|
} catch (error) {
|
console.error('图表初始化失败:', error);
|
}
|
},
|
|
// 窗口调整
|
handleResize() {
|
[this.outboundInstance, this.inboundInstance, this.monthDataInstance].forEach(instance => {
|
if (instance) {
|
try {
|
instance.resize();
|
} catch (error) {
|
console.error('图表调整大小失败:', error);
|
}
|
}
|
});
|
},
|
|
// 数据处理
|
handleDataUpdate(data) {
|
console.log('处理数据更新:', data);
|
|
// 处理指标数据
|
if (data.metrics && Array.isArray(data.metrics)) {
|
this.dataMetrics = data.metrics.map(item => ({
|
name: item.name || '未知指标',
|
value: item.value != null ? item.value : 0,
|
compare: item.compare != null ? item.compare : 0
|
}));
|
}
|
|
// 处理出库数据
|
if (data.outbound) {
|
this.chartData.outbound.dates = data.outbound.dates || [];
|
this.chartData.outbound.values = data.outbound.values || [];
|
}
|
|
// 处理入库数据
|
if (data.inbound) {
|
this.chartData.inbound.dates = data.inbound.dates || [];
|
this.chartData.inbound.values = data.inbound.values || [];
|
}
|
|
// 处理月度数据
|
if (data.monthData) {
|
this.chartData.monthData.dates = data.monthData.dates || [];
|
this.chartData.monthData.inValue = data.monthData.inValue || [];
|
this.chartData.monthData.outValue = data.monthData.outValue || [];
|
}
|
|
// 处理任务数据
|
if (data.newTask && Array.isArray(data.newTask)) {
|
this.tableData = data.newTask.map(task => ({
|
palletCode: task.palletCode || '无',
|
sourceAddress: task.sourceAddress || '无',
|
targetAddress: task.targetAddress || '无',
|
taskType: task.taskType || '无',
|
taskState: task.taskState || '无',
|
createDate: task.createDate || '无',
|
}));
|
|
this.$nextTick(() => {
|
this.pauseScroll();
|
this.startScrolling();
|
});
|
} else {
|
this.tableData = [];
|
this.pauseScroll();
|
}
|
|
console.log('处理后的图表数据:', this.chartData);
|
|
this.refreshKey++;
|
|
// 延迟初始化图表,确保DOM已更新
|
this.$nextTick(() => {
|
setTimeout(() => {
|
this.initCharts();
|
}, 100);
|
});
|
},
|
|
// 数据获取
|
async fetchData() {
|
try {
|
this.loading = true;
|
this.refreshing = true;
|
|
console.log('开始获取数据...');
|
const response = await http.post("api/StockInfo/GetStockData", {});
|
console.log('API响应:', response);
|
|
if (response.data && response.data.data) {
|
this.handleDataUpdate(response.data.data);
|
this.error = false;
|
} else {
|
throw new Error('API返回数据格式错误');
|
}
|
} catch (err) {
|
console.error('API请求失败:', err);
|
this.error = true;
|
} finally {
|
this.loading = false;
|
this.refreshing = false;
|
this.updateCurrentTime();
|
}
|
},
|
|
handleRefresh() {
|
this.fetchData();
|
}
|
}
|
};
|
</script>
|
|
<style scoped lang="css">
|
/* 全局样式重置 */
|
:global(html),
|
:global(body) {
|
margin: 0;
|
padding: 0;
|
overflow: hidden;
|
width: 100%;
|
height: 100%;
|
}
|
|
/* 外层容器 - 全屏控制 */
|
.screen-container {
|
width: 100vw;
|
height: 100vh;
|
overflow: hidden;
|
background: #000;
|
position: relative;
|
}
|
|
/* 大屏内容 - 固定1920x1080尺寸 */
|
#index {
|
color: #d3d6dd;
|
width: 1920px;
|
height: 1080px;
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
transform-origin: center center;
|
overflow: hidden;
|
min-width: 1920px;
|
min-height: 1080px;
|
transition: transform 0.2s ease;
|
/* 防止缩放模糊 */
|
-webkit-font-smoothing: antialiased;
|
-moz-osx-font-smoothing: grayscale;
|
}
|
|
.bg {
|
width: 100%;
|
height: 100%;
|
padding: 25px 16px 0 16px;
|
background-image: url("../assets/bg5.jpg");
|
background-size: cover;
|
background-position: center center;
|
background-repeat: no-repeat;
|
}
|
|
.dashboard-screen {
|
width: 100%;
|
height: 100%;
|
color: #fff;
|
position: relative;
|
overflow: hidden;
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
}
|
|
/* 顶部标题栏 */
|
.screen-header {
|
background: rgba(16, 36, 71, 0.8);
|
border-bottom: 1px solid rgba(64, 158, 255, 0.3);
|
padding: 15px 30px;
|
backdrop-filter: blur(10px);
|
border-radius: 10px;
|
margin-bottom: 20px;
|
}
|
|
.header-content {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.main-title {
|
font-size: 32px;
|
font-weight: 700;
|
background: linear-gradient(135deg, #409eff, #79bbff);
|
background-clip: text;
|
margin: 0;
|
display: flex;
|
align-items: center;
|
}
|
|
.title-icon {
|
font-size: 36px;
|
margin-right: 15px;
|
}
|
|
.header-info {
|
display: flex;
|
align-items: center;
|
gap: 20px;
|
}
|
|
.current-time {
|
font-size: 18px;
|
color: rgba(255, 255, 255, 0.8);
|
}
|
|
.refresh-btn {
|
background: linear-gradient(135deg, #409eff, #79bbff);
|
border: none;
|
border-radius: 25px;
|
padding: 12px 24px;
|
font-size: 16px;
|
font-weight: 600;
|
}
|
|
/* 区域标题 */
|
.screen-section {
|
padding: 0 16px 20px;
|
}
|
|
.section-title {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 20px;
|
margin-bottom: 25px;
|
}
|
|
.section-title h2 {
|
font-size: 24px;
|
font-weight: 600;
|
color: #fff;
|
margin: 0;
|
}
|
|
.title-decoration {
|
flex: 1;
|
height: 2px;
|
background: linear-gradient(90deg, transparent, #409eff, transparent);
|
}
|
|
/* 数据卡片网格 */
|
.metrics-grid-large {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.metric-card-large {
|
background: rgba(16, 36, 71, 0.6);
|
border: 1px solid rgba(64, 158, 255, 0.3);
|
border-radius: 15px;
|
padding: 25px;
|
position: relative;
|
overflow: hidden;
|
transition: all 0.3s ease;
|
backdrop-filter: blur(10px);
|
}
|
|
.metric-card-large:hover {
|
transform: translateY(-5px);
|
box-shadow: 0 10px 30px rgba(64, 158, 255, 0.2);
|
border-color: rgba(64, 158, 255, 0.6);
|
}
|
|
.metric-glow {
|
position: absolute;
|
top: 0;
|
left: 0;
|
right: 0;
|
height: 3px;
|
background: linear-gradient(90deg, #409eff, #79bbff);
|
opacity: 0.7;
|
}
|
|
.metric-content {
|
display: flex;
|
align-items: center;
|
gap: 20px;
|
}
|
|
.metric-icon-large {
|
width: 70px;
|
height: 70px;
|
border-radius: 15px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 32px;
|
background: rgba(255, 255, 255, 0.1);
|
}
|
|
.metric-inbound-today .metric-icon-large {
|
color: #67c23a;
|
}
|
|
.metric-outbound-today .metric-icon-large {
|
color: #e6a23c;
|
}
|
|
.metric-inbound-month .metric-icon-large {
|
color: #409eff;
|
}
|
|
.metric-outbound-month .metric-icon-large {
|
color: #f56c6c;
|
}
|
|
.metric-total .metric-icon-large {
|
color: #909399;
|
}
|
|
.metric-info-large {
|
flex: 1;
|
}
|
|
.metric-name-large {
|
font-size: 16px;
|
color: rgba(255, 255, 255, 0.8);
|
margin-bottom: 10px;
|
}
|
|
.metric-value-large {
|
font-size: 36px;
|
font-weight: 800;
|
color: #fff;
|
margin-bottom: 8px;
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
}
|
|
.metric-compare-large {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
font-size: 14px;
|
}
|
|
.compare-positive {
|
color: #f56c6c;
|
}
|
|
.compare-negative {
|
color: #67c23a;
|
}
|
|
.compare-zero {
|
color: rgba(255, 255, 255, 0.6);
|
}
|
|
.metric-wave {
|
position: absolute;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
height: 20px;
|
overflow: hidden;
|
}
|
|
.wave {
|
width: 200%;
|
height: 100%;
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
animation: wave 3s linear infinite;
|
}
|
|
@keyframes wave {
|
0% {
|
transform: translateX(-50%);
|
}
|
|
100% {
|
transform: translateX(0);
|
}
|
}
|
|
/* 主内容区域 */
|
.screen-main {
|
display: grid;
|
grid-template-columns: 1fr 1.2fr 1fr;
|
gap: 20px;
|
padding: 0 16px 20px;
|
height: 450px;
|
}
|
|
/* 图表容器 */
|
.chart-container-large,
|
.chart-container-full {
|
background: rgba(16, 36, 71, 0.6);
|
border: 1px solid rgba(64, 158, 255, 0.3);
|
border-radius: 15px;
|
padding: 20px;
|
backdrop-filter: blur(10px);
|
}
|
|
.chart-container-large {
|
height: 350px;
|
}
|
|
.chart-container-full {
|
height: 100%;
|
}
|
|
.chart-header-large {
|
margin-bottom: 15px;
|
}
|
|
.chart-title-large {
|
font-size: 18px;
|
font-weight: 600;
|
color: #fff;
|
margin: 0 0 5px 0;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.chart-subtitle-large {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.6);
|
}
|
|
.chart-content-large,
|
.chart-content-full {
|
height: calc(100% - 60px);
|
position: relative;
|
}
|
|
.chart-large,
|
.chart-full {
|
height: 100%;
|
width: 100%;
|
min-height: 300px;
|
}
|
|
/* 任务看板 */
|
.task-board-large {
|
background: rgba(16, 36, 71, 0.6);
|
border: 1px solid rgba(64, 158, 255, 0.3);
|
border-radius: 15px;
|
height: 100%;
|
backdrop-filter: blur(10px);
|
}
|
|
.board-header-large {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 20px;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
}
|
|
.header-title {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
font-size: 18px;
|
font-weight: 600;
|
}
|
|
.task-count-large {
|
display: flex;
|
align-items: baseline;
|
gap: 5px;
|
}
|
|
.count-number {
|
font-size: 28px;
|
font-weight: 800;
|
color: #409eff;
|
}
|
|
.count-label {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.6);
|
}
|
|
.board-body-large {
|
height: calc(100% - 80px);
|
position: relative;
|
}
|
|
.scroll-table-container-large {
|
height: 100%;
|
overflow: hidden;
|
position: relative;
|
}
|
|
.table-body-container-large {
|
height: 100%;
|
overflow: hidden;
|
}
|
|
.table-body-wrapper-large {
|
transition: transform 0.5s ease;
|
}
|
|
.body-table-large {
|
width: 100%;
|
border-collapse: collapse;
|
}
|
|
.body-table-large td {
|
padding: 15px 10px;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
font-size: 14px;
|
height: 60px;
|
box-sizing: border-box;
|
}
|
|
.cell-index {
|
width: 60px;
|
text-align: center;
|
}
|
|
.cell-pallet {
|
width: 100px;
|
text-align: center;
|
}
|
|
.cell-address {
|
width: 120px;
|
text-align: center;
|
}
|
|
.cell-type {
|
width: 100px;
|
text-align: center;
|
}
|
|
.cell-status {
|
width: 100px;
|
text-align: center;
|
}
|
|
.cell-time {
|
width: 140px;
|
text-align: center;
|
}
|
|
.even-row {
|
background: rgba(255, 255, 255, 0.05);
|
}
|
|
.odd-row {
|
background: rgba(255, 255, 255, 0.02);
|
}
|
|
.body-table-large tr:hover {
|
background: rgba(64, 158, 255, 0.1) !important;
|
}
|
|
.status-tag-large {
|
padding: 4px 12px;
|
border-radius: 12px;
|
font-size: 12px;
|
font-weight: 600;
|
}
|
|
.status-primary {
|
background: rgba(64, 158, 255, 0.2);
|
color: #409eff;
|
}
|
|
.status-success {
|
background: rgba(103, 194, 58, 0.2);
|
color: #67c23a;
|
}
|
|
.status-warning {
|
background: rgba(230, 162, 60, 0.2);
|
color: #e6a23c;
|
}
|
|
.status-danger {
|
background: rgba(245, 108, 108, 0.2);
|
color: #f56c6c;
|
}
|
|
.status-info {
|
background: rgba(144, 147, 153, 0.2);
|
color: #909399;
|
}
|
|
.no-data-board-large {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
height: 100%;
|
color: rgba(255, 255, 255, 0.6);
|
font-size: 16px;
|
gap: 10px;
|
}
|
|
.no-data-board-large i {
|
font-size: 48px;
|
}
|
|
/* 装饰角 */
|
.decoration-corner {
|
position: absolute;
|
width: 80px;
|
height: 80px;
|
border: 2px solid rgba(64, 158, 255, 0.5);
|
}
|
|
.top-left {
|
top: 0;
|
left: 0;
|
border-right: none;
|
border-bottom: none;
|
}
|
|
.top-right {
|
top: 0;
|
right: 0;
|
border-left: none;
|
border-bottom: none;
|
}
|
|
.bottom-left {
|
bottom: 0;
|
left: 0;
|
border-right: none;
|
border-top: none;
|
}
|
|
.bottom-right {
|
bottom: 0;
|
right: 0;
|
border-left: none;
|
border-top: none;
|
}
|
|
/* 加载和错误状态 */
|
.chart-loading-large,
|
.chart-error-large,
|
.chart-no-data-large {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
height: 100%;
|
color: rgba(255, 255, 255, 0.6);
|
font-size: 16px;
|
gap: 10px;
|
}
|
|
.chart-loading-large i,
|
.chart-error-large i,
|
.chart-no-data-large i {
|
font-size: 48px;
|
}
|
|
.chart-loading-large {
|
color: #409eff;
|
}
|
|
.chart-error-large {
|
color: #f56c6c;
|
}
|
|
/* 响应式设计 - 移除原有的媒体查询,由JS动态缩放处理 */
|
</style>
|