初始化:静态菜单版 数据大屏管理系统,对接KLPL3数据库
This commit is contained in:
380
src/modules/dashboardBig/views/cost.vue
Normal file
380
src/modules/dashboardBig/views/cost.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="big-screen">
|
||||
<header class="screen-header">
|
||||
<h1 class="title">成本数据大屏</h1>
|
||||
<span class="time">{{ currentTime }}</span>
|
||||
</header>
|
||||
|
||||
<main class="screen-body">
|
||||
<div class="card-grid">
|
||||
<div class="data-card" v-for="card in cards" :key="card.title">
|
||||
<div class="card-title">{{ card.title }}</div>
|
||||
<div class="card-value" :style="{ color: card.color }">{{ card.value }}</div>
|
||||
<div class="card-unit">{{ card.unit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-area">
|
||||
<div class="chart-box">
|
||||
<div class="box-title">成本趋势</div>
|
||||
<div ref="trendChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">成本构成</div>
|
||||
<div ref="pieChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">成本对比</div>
|
||||
<div class="compare-list">
|
||||
<div class="compare-item" v-for="item in compareList" :key="item.name">
|
||||
<span class="name">{{ item.name }}</span>
|
||||
<div class="bar-container">
|
||||
<div class="bar-fill" :style="{ width: item.percent + '%', background: item.color }"></div>
|
||||
</div>
|
||||
<span class="value">{{ item.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-area">
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">成本类型对比</div>
|
||||
<div class="cost-grid">
|
||||
<div class="cost-item" v-for="item in costItems" :key="item.name">
|
||||
<div class="cost-header">
|
||||
<span class="cost-name">{{ item.name }}</span>
|
||||
<span class="cost-percent">{{ item.percent }}%</span>
|
||||
</div>
|
||||
<div class="cost-bar">
|
||||
<div class="cost-fill" :style="{ width: item.percent + '%', background: item.color }"></div>
|
||||
</div>
|
||||
<div class="cost-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">月度成本对比</div>
|
||||
<div ref="barChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">成本分析</div>
|
||||
<div class="analysis-list">
|
||||
<div class="analysis-item" v-for="item in analysisList" :key="item.label">
|
||||
<span class="label">{{ item.label }}</span>
|
||||
<span class="value" :style="{ color: item.color }">{{ item.value }}</span>
|
||||
<span class="change" :class="item.change > 0 ? 'up' : 'down'">{{ item.change > 0 ? '↑' : '↓' }} {{ Math.abs(item.change) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const currentTime = ref('')
|
||||
const trendChartRef = ref(null)
|
||||
const pieChartRef = ref(null)
|
||||
const barChartRef = ref(null)
|
||||
let trendChart = null
|
||||
let pieChart = null
|
||||
let barChart = null
|
||||
let timeInterval = null
|
||||
|
||||
const cards = ref([
|
||||
{ title: '总成本', value: '156.8', unit: '万', color: '#f56c6c' },
|
||||
{ title: '材料成本', value: '98.5', unit: '万', color: '#1A5CD7' },
|
||||
{ title: '人工成本', value: '23.4', unit: '万', color: '#5cd9e8' },
|
||||
{ title: '能源成本', value: '18.9', unit: '万', color: '#ff9800' }
|
||||
])
|
||||
|
||||
const compareList = ref([
|
||||
{ name: '材料成本', percent: 62.8, value: '98.5万', color: 'linear-gradient(90deg, #1A5CD7, #5cd9e8)' },
|
||||
{ name: '人工成本', percent: 14.9, value: '23.4万', color: 'linear-gradient(90deg, #5cd9e8, #33cea0)' },
|
||||
{ name: '能源成本', percent: 12.1, value: '18.9万', color: 'linear-gradient(90deg, #ff9800, #ffa726)' },
|
||||
{ name: '其他成本', percent: 10.2, value: '16.0万', color: 'linear-gradient(90deg, #f56c6c, #ff5252)' }
|
||||
])
|
||||
|
||||
const costItems = ref([
|
||||
{ name: '材料成本', percent: 62.8, value: '¥98.5万', color: '#1A5CD7' },
|
||||
{ name: '人工成本', percent: 14.9, value: '¥23.4万', color: '#5cd9e8' },
|
||||
{ name: '能源成本', percent: 12.1, value: '¥18.9万', color: '#ff9800' },
|
||||
{ name: '其他成本', percent: 10.2, value: '¥16.0万', color: '#f56c6c' }
|
||||
])
|
||||
|
||||
const analysisList = ref([
|
||||
{ label: '材料成本占比', value: '62.8%', change: 2.5, color: '#1A5CD7' },
|
||||
{ label: '人工成本占比', value: '14.9%', change: -1.2, color: '#5cd9e8' },
|
||||
{ label: '能源成本占比', value: '12.1%', change: 3.8, color: '#ff9800' },
|
||||
{ label: '成本同比增长', value: '8.5%', change: 5.2, color: '#67c23a' },
|
||||
{ label: '预算执行率', value: '92.3%', change: -1.5, color: '#9c27b0' },
|
||||
{ label: '成本节约率', value: '5.6%', change: 2.1, color: '#33cea0' }
|
||||
])
|
||||
|
||||
const updateTime = () => {
|
||||
currentTime.value = new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const initCharts = () => {
|
||||
if (trendChartRef.value) {
|
||||
trendChart = echarts.init(trendChartRef.value)
|
||||
trendChart.setOption({
|
||||
grid: { top: 30, right: 30, bottom: 30, left: 60 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999', formatter: (v) => (v / 10000).toFixed(0) + '万' }
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [1420000, 1380000, 1520000, 1490000, 1568000, 1568000],
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(26,92,215,0.5)' },
|
||||
{ offset: 1, color: 'rgba(26,92,215,0.1)' }
|
||||
])
|
||||
},
|
||||
lineStyle: { color: '#1A5CD7', width: 3 },
|
||||
itemStyle: { color: '#1A5CD7' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (pieChartRef.value) {
|
||||
pieChart = echarts.init(pieChartRef.value)
|
||||
pieChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 10, textStyle: { color: '#999' } },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
data: [
|
||||
{ value: 62.8, name: '材料成本', itemStyle: { color: '#1A5CD7' } },
|
||||
{ value: 14.9, name: '人工成本', itemStyle: { color: '#5cd9e8' } },
|
||||
{ value: 12.1, name: '能源成本', itemStyle: { color: '#ff9800' } },
|
||||
{ value: 10.2, name: '其他成本', itemStyle: { color: '#f56c6c' } }
|
||||
],
|
||||
label: { show: false }
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (barChartRef.value) {
|
||||
barChart = echarts.init(barChartRef.value)
|
||||
barChart.setOption({
|
||||
grid: { top: 20, right: 20, bottom: 30, left: 50 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999', formatter: (v) => (v / 10000).toFixed(0) + '万' }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: [142, 138, 152, 149, 156.8],
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1A5CD7' },
|
||||
{ offset: 1, color: '#5cd9e8' }
|
||||
]),
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
initCharts()
|
||||
window.addEventListener('resize', () => {
|
||||
trendChart?.resize()
|
||||
pieChart?.resize()
|
||||
barChart?.resize()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
trendChart?.dispose()
|
||||
pieChart?.dispose()
|
||||
barChart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.big-screen {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1f4e 100%);
|
||||
color: #d3d6dd;
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
|
||||
.title { font-size: 32px; font-weight: bold; color: #fff; text-shadow: 0 0 20px rgba(26, 92, 215, 0.8); letter-spacing: 4px; }
|
||||
.time { font-size: 18px; color: #5cd9e8; font-family: 'Courier New', monospace; }
|
||||
}
|
||||
|
||||
.screen-body { padding: 0 20px; }
|
||||
|
||||
.card-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px 0;
|
||||
|
||||
.data-card {
|
||||
width: 22%;
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
.card-title { font-size: 16px; color: #999; margin-bottom: 10px; }
|
||||
.card-value { font-size: 36px; font-weight: bold; margin-bottom: 5px; }
|
||||
.card-unit { font-size: 14px; color: #666; }
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.chart-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
.chart { height: 280px; }
|
||||
|
||||
.compare-list {
|
||||
height: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
.compare-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.name { width: 80px; font-size: 14px; color: #d3d6dd; }
|
||||
.bar-container { flex: 1; height: 12px; background: rgba(255,255,255,0.1); border-radius: 6px; overflow: hidden; }
|
||||
.bar-fill { height: 100%; border-radius: 6px; transition: width 0.5s ease; }
|
||||
.value { width: 60px; text-align: right; font-size: 14px; color: #5cd9e8; font-weight: 600; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.bottom-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
.chart { height: 220px; }
|
||||
}
|
||||
|
||||
.cost-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
|
||||
.cost-item {
|
||||
.cost-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.cost-name { font-size: 14px; color: #d3d6dd; }
|
||||
.cost-percent { font-size: 14px; color: #5cd9e8; font-weight: 600; }
|
||||
}
|
||||
|
||||
.cost-bar {
|
||||
height: 8px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.cost-fill { height: 100%; border-radius: 4px; transition: width 0.5s ease; }
|
||||
}
|
||||
|
||||
.cost-value { font-size: 14px; color: #999; }
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.analysis-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background: rgba(26, 92, 215, 0.1);
|
||||
border-radius: 6px;
|
||||
|
||||
.label { font-size: 14px; color: #999; }
|
||||
.value { font-size: 16px; font-weight: bold; }
|
||||
.change {
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
&.up { background: rgba(103,194,58,0.2); color: #67c23a; }
|
||||
&.down { background: rgba(245,108,108,0.2); color: #f56c6c; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
435
src/modules/dashboardBig/views/energy.vue
Normal file
435
src/modules/dashboardBig/views/energy.vue
Normal file
@@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<div class="big-screen">
|
||||
<header class="screen-header">
|
||||
<h1 class="title">能源数据大屏</h1>
|
||||
<span class="time">{{ currentTime }}</span>
|
||||
</header>
|
||||
|
||||
<main class="screen-body">
|
||||
<div class="card-grid">
|
||||
<div class="data-card" v-for="card in cards" :key="card.title">
|
||||
<div class="card-icon" :style="{ background: card.iconBg }">{{ card.icon }}</div>
|
||||
<div class="card-title">{{ card.title }}</div>
|
||||
<div class="card-value" :style="{ color: card.color }">{{ card.value }}</div>
|
||||
<div class="card-unit">{{ card.unit }}</div>
|
||||
<div class="card-trend" :class="card.trend > 0 ? 'up' : 'down'">
|
||||
{{ card.trend > 0 ? '↑' : '↓' }} {{ Math.abs(card.trend) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-area">
|
||||
<div class="chart-box">
|
||||
<div class="box-title">能耗趋势</div>
|
||||
<div ref="trendChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">能源构成</div>
|
||||
<div ref="pieChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">实时能耗</div>
|
||||
<div class="realtime-container">
|
||||
<div class="gauge">
|
||||
<svg viewBox="0 0 100 60">
|
||||
<path d="M 10 55 A 40 40 0 0 1 90 55" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="8" stroke-linecap="round"/>
|
||||
<path d="M 10 55 A 40 40 0 0 1 90 55" fill="none" :stroke="gaugeColor" stroke-width="8" stroke-linecap="round"
|
||||
:stroke-dasharray="circumference" :stroke-dashoffset="dashOffset"/>
|
||||
</svg>
|
||||
<div class="gauge-value">{{ gaugeValue }}<span class="unit">kW</span></div>
|
||||
<div class="gauge-label">当前功率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-area">
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">分项能耗</div>
|
||||
<div class="energy-grid">
|
||||
<div class="energy-item" v-for="item in energyItems" :key="item.name">
|
||||
<div class="energy-header">
|
||||
<span class="energy-icon">{{ item.icon }}</span>
|
||||
<span class="energy-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="energy-value" :style="{ color: item.color }">{{ item.value }}</div>
|
||||
<div class="energy-unit">{{ item.unit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">能耗对比</div>
|
||||
<div ref="barChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">能耗告警</div>
|
||||
<div class="alarm-list">
|
||||
<div class="alarm-item" v-for="alarm in alarmList" :key="alarm.time">
|
||||
<span class="alarm-icon">{{ alarm.icon }}</span>
|
||||
<div class="alarm-content">
|
||||
<div class="alarm-title">{{ alarm.title }}</div>
|
||||
<div class="alarm-value">{{ alarm.value }}</div>
|
||||
</div>
|
||||
<span class="alarm-time">{{ alarm.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const currentTime = ref('')
|
||||
const trendChartRef = ref(null)
|
||||
const pieChartRef = ref(null)
|
||||
const barChartRef = ref(null)
|
||||
let trendChart = null
|
||||
let pieChart = null
|
||||
let barChart = null
|
||||
let timeInterval = null
|
||||
|
||||
const gaugeValue = ref(856)
|
||||
const circumference = 2 * Math.PI * 40
|
||||
const dashOffset = computed(() => circumference * (1 - gaugeValue.value / 1000))
|
||||
const gaugeColor = computed(() => {
|
||||
if (gaugeValue.value > 800) return '#f56c6c'
|
||||
if (gaugeValue.value > 600) return '#ff9800'
|
||||
return '#67c23a'
|
||||
})
|
||||
|
||||
const cards = ref([
|
||||
{ title: '总能耗', value: '2,560', unit: 'kWh', icon: '⚡', iconBg: 'rgba(245,108,108,0.2)', color: '#f56c6c', trend: 3.5 },
|
||||
{ title: '电能', value: '1,820', unit: 'kWh', icon: '🔌', iconBg: 'rgba(26,92,215,0.2)', color: '#1A5CD7', trend: 2.1 },
|
||||
{ title: '天然气', value: '456', unit: 'm³', icon: '🔥', iconBg: 'rgba(255,152,0,0.2)', color: '#ff9800', trend: -1.2 },
|
||||
{ title: '水耗', value: '284', unit: 'm³', icon: '💧', iconBg: 'rgba(64,158,255,0.2)', color: '#409eff', trend: 0.8 }
|
||||
])
|
||||
|
||||
const energyItems = ref([
|
||||
{ name: '电能', value: '1,820', unit: 'kWh', icon: '⚡', color: '#1A5CD7' },
|
||||
{ name: '天然气', value: '456', unit: 'm³', icon: '🔥', color: '#ff9800' },
|
||||
{ name: '水', value: '284', unit: 'm³', icon: '💧', color: '#409eff' },
|
||||
{ name: '压缩空气', value: '156', unit: 'm³', icon: '💨', color: '#9c27b0' }
|
||||
])
|
||||
|
||||
const alarmList = ref([
|
||||
{ icon: '⚠️', title: '电能消耗偏高', value: '当前: 856kW / 阈值: 800kW', time: '14:30' },
|
||||
{ icon: '🔴', title: '天然气压力异常', value: '当前: 0.35MPa / 正常: 0.4-0.6MPa', time: '10:15' },
|
||||
{ icon: '⚠️', title: '水耗超出阈值', value: '当前: 284m³ / 阈值: 250m³', time: '09:00' }
|
||||
])
|
||||
|
||||
const updateTime = () => {
|
||||
currentTime.value = new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const updateGauge = () => {
|
||||
gaugeValue.value = 700 + Math.floor(Math.random() * 300)
|
||||
}
|
||||
|
||||
const initCharts = () => {
|
||||
if (trendChartRef.value) {
|
||||
trendChart = echarts.init(trendChartRef.value)
|
||||
trendChart.setOption({
|
||||
grid: { top: 30, right: 30, bottom: 30, left: 60 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [450, 380, 620, 850, 720, 680, 550],
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(245,108,108,0.5)' },
|
||||
{ offset: 1, color: 'rgba(245,108,108,0.1)' }
|
||||
])
|
||||
},
|
||||
lineStyle: { color: '#f56c6c', width: 3 },
|
||||
itemStyle: { color: '#f56c6c' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (pieChartRef.value) {
|
||||
pieChart = echarts.init(pieChartRef.value)
|
||||
pieChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 10, textStyle: { color: '#999' } },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
data: [
|
||||
{ value: 71.1, name: '电能', itemStyle: { color: '#1A5CD7' } },
|
||||
{ value: 17.8, name: '天然气', itemStyle: { color: '#ff9800' } },
|
||||
{ value: 11.1, name: '水', itemStyle: { color: '#409eff' } },
|
||||
{ value: 6.0, name: '压缩空气', itemStyle: { color: '#9c27b0' } }
|
||||
],
|
||||
label: { show: false }
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (barChartRef.value) {
|
||||
barChart = echarts.init(barChartRef.value)
|
||||
barChart.setOption({
|
||||
grid: { top: 20, right: 20, bottom: 30, left: 50 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: [2200, 2450, 2380, 2620, 2580, 1890, 1650],
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#f56c6c' },
|
||||
{ offset: 1, color: '#ff5252' }
|
||||
]),
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(() => {
|
||||
updateTime()
|
||||
updateGauge()
|
||||
}, 1000)
|
||||
initCharts()
|
||||
window.addEventListener('resize', () => {
|
||||
trendChart?.resize()
|
||||
pieChart?.resize()
|
||||
barChart?.resize()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
trendChart?.dispose()
|
||||
pieChart?.dispose()
|
||||
barChart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.big-screen {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1f4e 100%);
|
||||
color: #d3d6dd;
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
|
||||
.title { font-size: 32px; font-weight: bold; color: #fff; text-shadow: 0 0 20px rgba(26, 92, 215, 0.8); letter-spacing: 4px; }
|
||||
.time { font-size: 18px; color: #5cd9e8; font-family: 'Courier New', monospace; }
|
||||
}
|
||||
|
||||
.screen-body { padding: 0 20px; }
|
||||
|
||||
.card-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px 0;
|
||||
|
||||
.data-card {
|
||||
width: 22%;
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
.card-title { font-size: 14px; color: #999; margin-bottom: 8px; }
|
||||
.card-value { font-size: 32px; font-weight: bold; margin-bottom: 5px; }
|
||||
.card-unit { font-size: 13px; color: #666; margin-bottom: 8px; }
|
||||
.card-trend {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
&.up { background: rgba(103,194,58,0.2); color: #67c23a; }
|
||||
&.down { background: rgba(245,108,108,0.2); color: #f56c6c; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.chart-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
.chart { height: 280px; }
|
||||
|
||||
.realtime-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 280px;
|
||||
|
||||
.gauge {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 120px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
.gauge-value {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -30%);
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #5cd9e8;
|
||||
|
||||
.unit {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.gauge-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 40%);
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.bottom-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
.chart { height: 220px; }
|
||||
}
|
||||
|
||||
.energy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
|
||||
.energy-item {
|
||||
background: rgba(26, 92, 215, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
|
||||
.energy-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.energy-icon { font-size: 18px; }
|
||||
.energy-name { font-size: 14px; color: #999; }
|
||||
}
|
||||
|
||||
.energy-value { font-size: 24px; font-weight: bold; margin-bottom: 3px; }
|
||||
.energy-unit { font-size: 12px; color: #666; }
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.alarm-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: rgba(230, 162, 60, 0.15);
|
||||
border-left: 4px solid #e6a23c;
|
||||
border-radius: 0 6px 6px 0;
|
||||
|
||||
.alarm-icon { font-size: 18px; margin-right: 12px; }
|
||||
.alarm-content {
|
||||
flex: 1;
|
||||
|
||||
.alarm-title { font-size: 14px; color: #fff; margin-bottom: 3px; }
|
||||
.alarm-value { font-size: 12px; color: #999; }
|
||||
}
|
||||
.alarm-time { font-size: 12px; color: #5cd9e8; }
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
554
src/modules/dashboardBig/views/index.vue
Normal file
554
src/modules/dashboardBig/views/index.vue
Normal file
@@ -0,0 +1,554 @@
|
||||
<template>
|
||||
<div class="big-screen">
|
||||
<!-- 顶部标题 -->
|
||||
<header class="screen-header">
|
||||
<h1 class="title">大数据可视化平台</h1>
|
||||
<span class="time">{{ currentTime }}</span>
|
||||
</header>
|
||||
|
||||
<!-- 主体区域 -->
|
||||
<main class="screen-body">
|
||||
<!-- 数据卡片区域 -->
|
||||
<div class="card-grid">
|
||||
<div class="data-card" v-for="card in cards" :key="card.title">
|
||||
<div class="card-title">{{ card.title }}</div>
|
||||
<div class="card-value" :style="{ color: card.color }">{{ card.value }}</div>
|
||||
<div class="card-unit">{{ card.unit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="chart-area">
|
||||
<div class="chart-box">
|
||||
<div class="box-title">产量趋势</div>
|
||||
<div ref="lineChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">运行状态</div>
|
||||
<div ref="pieChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">班组排名</div>
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-item" v-for="(item, index) in rankingList" :key="item.name">
|
||||
<span class="rank" :class="'rank-' + (index + 1)">{{ index + 1 }}</span>
|
||||
<span class="name">{{ item.name }}</span>
|
||||
<span class="value">{{ item.value }}</span>
|
||||
<span class="unit">吨</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部区域 -->
|
||||
<div class="bottom-area">
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">工艺参数</div>
|
||||
<div class="params-grid">
|
||||
<div class="param-item" v-for="param in paramsList" :key="param.label">
|
||||
<div class="param-label">{{ param.label }}</div>
|
||||
<div class="param-value" :style="{ color: param.color }">{{ param.value }}</div>
|
||||
<div class="param-unit">{{ param.unit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">生产进度</div>
|
||||
<div class="progress-container">
|
||||
<div class="progress-ring">
|
||||
<svg class="ring-svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="45" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="6"/>
|
||||
<circle cx="50" cy="50" r="45" fill="none" stroke="#1A5CD7" stroke-width="6"
|
||||
:stroke-dasharray="circumference"
|
||||
:stroke-dashoffset="dashOffset"
|
||||
stroke-linecap="round"
|
||||
transform="rotate(-90 50 50)"/>
|
||||
</svg>
|
||||
<div class="progress-text">
|
||||
<div class="progress-value">{{ progressValue }}%</div>
|
||||
<div class="progress-label">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-box">
|
||||
<div class="box-title">实时告警</div>
|
||||
<div class="alarm-list">
|
||||
<div class="alarm-item" v-for="alarm in alarmList" :key="alarm.time">
|
||||
<span class="alarm-icon">{{ alarm.icon }}</span>
|
||||
<div class="alarm-content">
|
||||
<div class="alarm-title">{{ alarm.title }}</div>
|
||||
<div class="alarm-time">{{ alarm.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const currentTime = ref('')
|
||||
const lineChartRef = ref(null)
|
||||
const pieChartRef = ref(null)
|
||||
let lineChart = null
|
||||
let pieChart = null
|
||||
let timeInterval = null
|
||||
|
||||
const circumference = 2 * Math.PI * 45
|
||||
const progressValue = ref(82.3)
|
||||
const dashOffset = computed(() => circumference * (1 - progressValue.value / 100))
|
||||
|
||||
const cards = ref([
|
||||
{ title: '今日产量', value: '12,580', unit: '吨', color: '#5cd9e8' },
|
||||
{ title: '设备利用率', value: '94.6', unit: '%', color: '#33cea0' },
|
||||
{ title: '产品良品率', value: '98.2', unit: '%', color: '#1A5CD7' },
|
||||
{ title: '今日能耗', value: '2,560', unit: 'kWh', color: '#ff9800' }
|
||||
])
|
||||
|
||||
const rankingList = ref([
|
||||
{ name: '班组A', value: 1250 },
|
||||
{ name: '班组B', value: 1180 },
|
||||
{ name: '班组C', value: 1050 },
|
||||
{ name: '班组D', value: 980 },
|
||||
{ name: '班组E', value: 850 }
|
||||
])
|
||||
|
||||
const paramsList = ref([
|
||||
{ label: '温度', value: '850', unit: '℃', color: '#ff9800' },
|
||||
{ label: '速度', value: '120', unit: 'm/min', color: '#5cd9e8' },
|
||||
{ label: '厚度', value: '2.0', unit: 'mm', color: '#33cea0' },
|
||||
{ label: '宽度', value: '1250', unit: 'mm', color: '#1A5CD7' },
|
||||
{ label: '张力', value: '150', unit: 'kN', color: '#ff5252' },
|
||||
{ label: '压力', value: '2.5', unit: 'MPa', color: '#9c27b0' }
|
||||
])
|
||||
|
||||
const alarmList = ref([
|
||||
{ icon: '⚠️', title: '电能消耗偏高', time: '14:30:00' },
|
||||
{ icon: '🔴', title: '天然气压力异常', time: '10:15:00' },
|
||||
{ icon: '⚠️', title: '水耗超出阈值', time: '09:00:00' }
|
||||
])
|
||||
|
||||
const updateTime = () => {
|
||||
currentTime.value = new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const initCharts = () => {
|
||||
if (lineChartRef.value) {
|
||||
lineChart = echarts.init(lineChartRef.value)
|
||||
lineChart.setOption({
|
||||
grid: { top: 30, right: 20, bottom: 30, left: 50 },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'line', lineStyle: { color: '#1A5CD7' } } },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [8500, 9200, 8800, 10500, 11200, 12580],
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(26,92,215,0.5)' },
|
||||
{ offset: 1, color: 'rgba(26,92,215,0.1)' }
|
||||
])
|
||||
},
|
||||
lineStyle: { color: '#1A5CD7', width: 3 },
|
||||
itemStyle: { color: '#1A5CD7' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (pieChartRef.value) {
|
||||
pieChart = echarts.init(pieChartRef.value)
|
||||
pieChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 10, textStyle: { color: '#999' } },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
data: [
|
||||
{ value: 45, name: '运行中', itemStyle: { color: '#67c23a' } },
|
||||
{ value: 35, name: '待机', itemStyle: { color: '#409eff' } },
|
||||
{ value: 15, name: '维护', itemStyle: { color: '#ff9800' } },
|
||||
{ value: 5, name: '故障', itemStyle: { color: '#f56c6c' } }
|
||||
],
|
||||
label: { show: false }
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
initCharts()
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
lineChart?.resize()
|
||||
pieChart?.resize()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
lineChart?.dispose()
|
||||
pieChart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.big-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: calc(100vh - 180px);
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1f4e 100%);
|
||||
color: #d3d6dd;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 30px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 20px rgba(26, 92, 215, 0.8);
|
||||
letter-spacing: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 16px;
|
||||
color: #5cd9e8;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.screen-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 15px 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
padding: 15px 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
.data-card {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-unit {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
padding: 15px 0;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
.chart-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.box-title {
|
||||
font-size: 14px;
|
||||
color: #5cd9e8;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #1A5CD7;
|
||||
}
|
||||
|
||||
.chart {
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
min-height: 200px;
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: rgba(26, 92, 215, 0.1);
|
||||
border-radius: 6px;
|
||||
|
||||
.rank {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
&.rank-1 { background: linear-gradient(135deg, #ffd700, #ffb700); color: #fff; }
|
||||
&.rank-2 { background: linear-gradient(135deg, #c0c0c0, #a0a0a0); color: #fff; }
|
||||
&.rank-3 { background: linear-gradient(135deg, #cd7f32, #b87333); color: #fff; }
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #d3d6dd;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #5cd9e8;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-area {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
padding: 15px 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
.bottom-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.box-title {
|
||||
font-size: 14px;
|
||||
color: #5cd9e8;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #1A5CD7;
|
||||
}
|
||||
}
|
||||
|
||||
.params-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
|
||||
.param-item {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
background: rgba(26, 92, 215, 0.1);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.param-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.param-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.param-unit {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-height: 180px;
|
||||
|
||||
.progress-ring {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
|
||||
.ring-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
|
||||
.progress-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #5cd9e8;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
min-height: 180px;
|
||||
|
||||
.alarm-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background: rgba(230, 162, 60, 0.15);
|
||||
border-left: 4px solid #e6a23c;
|
||||
border-radius: 0 6px 6px 0;
|
||||
|
||||
.alarm-icon {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.alarm-content {
|
||||
flex: 1;
|
||||
|
||||
.alarm-title {
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.alarm-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.card-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.chart-area,
|
||||
.bottom-area {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.chart-area .chart-box:last-child,
|
||||
.bottom-area .bottom-box:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.card-grid,
|
||||
.chart-area,
|
||||
.bottom-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-area .chart-box:last-child,
|
||||
.bottom-area .bottom-box:last-child {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.screen-header .title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
310
src/modules/dashboardBig/views/order.vue
Normal file
310
src/modules/dashboardBig/views/order.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div class="big-screen">
|
||||
<header class="screen-header">
|
||||
<h1 class="title">订单数据大屏</h1>
|
||||
<span class="time">{{ currentTime }}</span>
|
||||
</header>
|
||||
|
||||
<main class="screen-body">
|
||||
<div class="card-grid">
|
||||
<div class="data-card" v-for="card in cards" :key="card.title">
|
||||
<div class="card-title">{{ card.title }}</div>
|
||||
<div class="card-value" :style="{ color: card.color }">{{ card.value }}</div>
|
||||
<div class="card-unit">{{ card.unit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-area">
|
||||
<div class="chart-box">
|
||||
<div class="box-title">订单趋势</div>
|
||||
<div ref="trendChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">订单状态分布</div>
|
||||
<div ref="pieChartRef" class="chart"></div>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="box-title">客户订单排行</div>
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-item" v-for="(item, index) in rankingList" :key="item.name">
|
||||
<span class="rank" :class="'rank-' + (index + 1)">{{ index + 1 }}</span>
|
||||
<span class="name">{{ item.name }}</span>
|
||||
<span class="value">{{ item.value }}</span>
|
||||
<span class="unit">单</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="order-list-box">
|
||||
<div class="box-title">订单列表</div>
|
||||
<div class="order-table">
|
||||
<div class="table-header">
|
||||
<span>订单号</span>
|
||||
<span>客户</span>
|
||||
<span>金额</span>
|
||||
<span>状态</span>
|
||||
<span>时间</span>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<div class="table-row" v-for="order in orderList" :key="order.orderNo">
|
||||
<span class="order-no">{{ order.orderNo }}</span>
|
||||
<span>{{ order.customer }}</span>
|
||||
<span class="amount">{{ formatAmount(order.amount) }}</span>
|
||||
<span :class="['status', order.status]">{{ order.status }}</span>
|
||||
<span class="time">{{ order.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const currentTime = ref('')
|
||||
const trendChartRef = ref(null)
|
||||
const pieChartRef = ref(null)
|
||||
let trendChart = null
|
||||
let pieChart = null
|
||||
let timeInterval = null
|
||||
|
||||
const cards = ref([
|
||||
{ title: '今日订单', value: '45', unit: '单', color: '#5cd9e8' },
|
||||
{ title: '待处理订单', value: '12', unit: '单', color: '#ff9800' },
|
||||
{ title: '已完成订单', value: '156', unit: '单', color: '#67c23a' },
|
||||
{ title: '订单金额', value: '258', unit: '万元', color: '#1A5CD7' }
|
||||
])
|
||||
|
||||
const rankingList = ref([
|
||||
{ name: '周口钢铁', value: 125 },
|
||||
{ name: '南阳重工', value: 98 },
|
||||
{ name: '洛阳机械', value: 86 },
|
||||
{ name: '开封汽配', value: 72 },
|
||||
{ name: '商丘金属', value: 65 }
|
||||
])
|
||||
|
||||
const orderList = ref([
|
||||
{ orderNo: 'ORD20260515001', customer: '周口钢铁', amount: 125000, status: '生产中', time: '10:30' },
|
||||
{ orderNo: 'ORD20260515002', customer: '南阳重工', amount: 89000, status: '已完成', time: '09:45' },
|
||||
{ orderNo: 'ORD20260515003', customer: '洛阳机械', amount: 156000, status: '待生产', time: '11:20' },
|
||||
{ orderNo: 'ORD20260515004', customer: '开封汽配', amount: 67000, status: '生产中', time: '08:15' },
|
||||
{ orderNo: 'ORD20260515005', customer: '商丘金属', amount: 45000, status: '已完成', time: '07:30' }
|
||||
])
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
return '¥' + (amount / 10000).toFixed(2) + '万'
|
||||
}
|
||||
|
||||
const updateTime = () => {
|
||||
currentTime.value = new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const initCharts = () => {
|
||||
if (trendChartRef.value) {
|
||||
trendChart = echarts.init(trendChartRef.value)
|
||||
trendChart.setOption({
|
||||
grid: { top: 30, right: 30, bottom: 30, left: 60 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
axisLine: { lineStyle: { color: '#2a3f5c' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: [35, 42, 38, 45, 40, 48],
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1A5CD7' },
|
||||
{ offset: 1, color: '#5cd9e8' }
|
||||
]),
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
if (pieChartRef.value) {
|
||||
pieChart = echarts.init(pieChartRef.value)
|
||||
pieChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 10, textStyle: { color: '#999' } },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
data: [
|
||||
{ value: 45, name: '生产中', itemStyle: { color: '#409eff' } },
|
||||
{ value: 12, name: '待生产', itemStyle: { color: '#e6a23c' } },
|
||||
{ value: 156, name: '已完成', itemStyle: { color: '#67c23a' } }
|
||||
],
|
||||
label: { show: false }
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
initCharts()
|
||||
window.addEventListener('resize', () => {
|
||||
trendChart?.resize()
|
||||
pieChart?.resize()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
trendChart?.dispose()
|
||||
pieChart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.big-screen {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1f4e 100%);
|
||||
color: #d3d6dd;
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
|
||||
.title { font-size: 32px; font-weight: bold; color: #fff; text-shadow: 0 0 20px rgba(26, 92, 215, 0.8); letter-spacing: 4px; }
|
||||
.time { font-size: 18px; color: #5cd9e8; font-family: 'Courier New', monospace; }
|
||||
}
|
||||
|
||||
.screen-body { padding: 0 20px; }
|
||||
|
||||
.card-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px 0;
|
||||
|
||||
.data-card {
|
||||
width: 22%;
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
.card-title { font-size: 16px; color: #999; margin-bottom: 10px; }
|
||||
.card-value { font-size: 36px; font-weight: bold; margin-bottom: 5px; }
|
||||
.card-unit { font-size: 14px; color: #666; }
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.chart-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
.chart { height: 280px; }
|
||||
|
||||
.ranking-list {
|
||||
height: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background: rgba(26, 92, 215, 0.1);
|
||||
border-radius: 6px;
|
||||
|
||||
.rank {
|
||||
width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 12px; font-weight: bold; margin-right: 12px; background: rgba(255,255,255,0.1);
|
||||
&.rank-1 { background: linear-gradient(135deg, #ffd700, #ffb700); color: #fff; }
|
||||
&.rank-2 { background: linear-gradient(135deg, #c0c0c0, #a0a0a0); color: #fff; }
|
||||
&.rank-3 { background: linear-gradient(135deg, #cd7f32, #b87333); color: #fff; }
|
||||
}
|
||||
|
||||
.name { flex: 1; font-size: 14px; color: #d3d6dd; }
|
||||
.value { font-size: 16px; font-weight: bold; color: #5cd9e8; margin-right: 5px; }
|
||||
.unit { font-size: 12px; color: #666; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-list-box {
|
||||
background: rgba(19, 25, 47, 0.8);
|
||||
border: 1px solid rgba(26, 92, 215, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 20px;
|
||||
|
||||
.box-title { font-size: 16px; color: #5cd9e8; margin-bottom: 15px; padding-left: 10px; border-left: 3px solid #1A5CD7; }
|
||||
|
||||
.order-table {
|
||||
.table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr 1.5fr 1fr 1fr;
|
||||
gap: 15px;
|
||||
padding: 10px 15px;
|
||||
background: rgba(26, 92, 215, 0.2);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr 1.5fr 1fr 1fr;
|
||||
gap: 15px;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
font-size: 14px;
|
||||
|
||||
.order-no { color: #5cd9e8; font-family: 'Courier New', monospace; }
|
||||
.amount { color: #ff9800; font-weight: 600; }
|
||||
.status {
|
||||
padding: 2px 8px; border-radius: 4px; font-size: 12px; text-align: center;
|
||||
&.已完成 { background: rgba(103,194,58,0.2); color: #67c23a; }
|
||||
&.生产中 { background: rgba(64,158,255,0.2); color: #409eff; }
|
||||
&.待生产 { background: rgba(230,162,60,0.2); color: #e6a23c; }
|
||||
}
|
||||
.time { color: #999; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user