初始化:静态菜单版 数据大屏管理系统,对接KLPL3数据库

This commit is contained in:
zuqijia
2026-05-15 18:18:51 +08:00
commit 39fed2c08c
58 changed files with 12751 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>