feat:添加第二种样式的大屏页面
parent
0ec8df976c
commit
8fd545671c
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<div class="bg-grid"></div>
|
||||
<div class="bg-scan"><div class="scan-line"></div></div>
|
||||
|
||||
<DashboardHeader />
|
||||
|
||||
<main>
|
||||
<div class="layout">
|
||||
<el-row :gutter="10" class="main-row">
|
||||
<el-col :span="5" class="col">
|
||||
<div class="col-item col-item-overview">
|
||||
<!-- 设备概况 -->
|
||||
<DeviceOverview />
|
||||
</div>
|
||||
<div class="col-item col-item-payment">
|
||||
<!-- Payment method -->
|
||||
<PaymentMethod />
|
||||
</div>
|
||||
<div class="col-item col-item-extra">
|
||||
<!-- 产量趋势 -->
|
||||
<ProductionTrend />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="14" class="col">
|
||||
<div class="center-shell">
|
||||
<img class="dashboard-center-image" src="@/assets/imgs/dashboard_img.png" alt="dashboard" />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="5" class="col">
|
||||
<div class="col-item col-item-event">
|
||||
<!-- 事件提醒 -->
|
||||
<EventReminder />
|
||||
</div>
|
||||
<div class="col-item col-item-task">
|
||||
<!-- 任务列表 -->
|
||||
<TaskList />
|
||||
</div>
|
||||
<div class="col-item col-item-energy">
|
||||
<!-- 能耗监测 -->
|
||||
<EnergyMonitor />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DashboardHeader from './components/DashboardHeader.vue'
|
||||
import DeviceOverview from './components/DeviceOverview.vue'
|
||||
import PaymentMethod from './components/PaymentMethod.vue'
|
||||
import EventReminder from './components/EventReminder.vue'
|
||||
import TaskList from './components/TaskList.vue'
|
||||
import EnergyMonitor from './components/EnergyMonitor.vue'
|
||||
import ProductionTrend from './components/ProductionTrend.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Define CSS Variables locally for this dashboard */
|
||||
.dashboard-container {
|
||||
--bg: #050816;
|
||||
--bg-deep: #020617;
|
||||
--card-bg: rgba(15, 23, 42, 0.86);
|
||||
--border: rgba(56, 189, 248, 0.35);
|
||||
--text: #e5f0ff;
|
||||
--muted: #94a3b8;
|
||||
--primary: #38bdf8;
|
||||
--accent: #22d3ee;
|
||||
--blue: #1e90ff;
|
||||
--green: #22c55e;
|
||||
--purple: #8b5cf6;
|
||||
--warn: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--gap: 10px;
|
||||
--header-h: 86px;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft Yahei", Arial, sans-serif;
|
||||
color: var(--text);
|
||||
background-color: var(--bg-deep);
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 0, rgba(56, 189, 248, 0.26) 0, transparent 48%),
|
||||
radial-gradient(circle at 80% 110%, rgba(129, 140, 248, 0.22) 0, transparent 52%),
|
||||
linear-gradient(135deg, #020617 0%, #020617 45%, #020617 100%);
|
||||
}
|
||||
|
||||
.bg-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image: linear-gradient(rgba(15,23,42,0.8) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(15,23,42,0.8) 1px, transparent 1px);
|
||||
background-size: 70px 70px;
|
||||
opacity: 0.55;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-scan {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.scan-line {
|
||||
position: absolute;
|
||||
top: -40%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
background: radial-gradient(circle at 50% 0, rgba(56, 189, 248, 0.38), transparent 70%);
|
||||
opacity: 0.5;
|
||||
filter: blur(32px);
|
||||
animation: scanDown 16s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scanDown {
|
||||
0% { transform: translateY(-100%); }
|
||||
100% { transform: translateY(260%); }
|
||||
}
|
||||
|
||||
main {
|
||||
height: calc(100vh - var(--header-h));
|
||||
padding: var(--gap);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.layout {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.main-row {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.col {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
.col-item {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.col-item :deep(> *) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.center-shell {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.55);
|
||||
background: rgba(2,6,23,0.18);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.dashboard-center-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<div class="header-left">
|
||||
<div class="time">{{ timeStr }}</div>
|
||||
<div class="date">{{ dateStr }}</div>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<div class="title-wrap">
|
||||
<svg class="title-frame" viewBox="0 0 1200 120" preserveAspectRatio="none" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="frameStroke" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="rgba(34,211,238,0.0)" />
|
||||
<stop offset="0.18" stop-color="rgba(34,211,238,0.85)" />
|
||||
<stop offset="0.5" stop-color="rgba(96,165,250,0.95)" />
|
||||
<stop offset="0.82" stop-color="rgba(34,211,238,0.85)" />
|
||||
<stop offset="1" stop-color="rgba(34,211,238,0.0)" />
|
||||
</linearGradient>
|
||||
<linearGradient id="frameFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="rgba(30,144,255,0.20)" />
|
||||
<stop offset="1" stop-color="rgba(2,6,23,0.0)" />
|
||||
</linearGradient>
|
||||
<filter id="frameGlow" x="-30%" y="-60%" width="160%" height="220%">
|
||||
<feGaussianBlur stdDeviation="3.2" result="blur" />
|
||||
<feColorMatrix
|
||||
in="blur"
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
|
||||
result="colored"
|
||||
/>
|
||||
<feMerge>
|
||||
<feMergeNode in="colored" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<path d="M10 5 L150 115 L1050 115 L1190 5 Z" fill="url(#frameFill)" />
|
||||
<path
|
||||
d="M10 5 L150 115 L1050 115 L1190 5"
|
||||
stroke="url(#frameStroke)"
|
||||
stroke-width="4"
|
||||
fill="none"
|
||||
filter="url(#frameGlow)"
|
||||
/>
|
||||
</svg>
|
||||
<div class="title">产线运行看板</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="back-btn" @click="goBack">
|
||||
<Icon icon="ep:back" />
|
||||
<span>返回</span>
|
||||
</div>
|
||||
<div class="weather">
|
||||
<Icon icon="fa-solid:cloud-sun" class="weather-icon" />
|
||||
<div class="weather-meta">
|
||||
<div class="temp">{{ weather.temp }}</div>
|
||||
<div class="desc">{{ weather.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const timeStr = ref('')
|
||||
const dateStr = ref('')
|
||||
const weather = ref({ temp: '27°C', desc: 'Cloudy to clear' })
|
||||
let timer: number | undefined
|
||||
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const updateTime = () => {
|
||||
const d = new Date()
|
||||
const h = String(d.getHours()).padStart(2, '0')
|
||||
const mi = String(d.getMinutes()).padStart(2, '0')
|
||||
const s = String(d.getSeconds()).padStart(2, '0')
|
||||
timeStr.value = `${h}:${mi}:${s}`
|
||||
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const weekMap = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
||||
dateStr.value = `${weekMap[d.getDay()]}, ${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timer = window.setInterval(updateTime, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
height: var(--header-h);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 18px;
|
||||
background:
|
||||
linear-gradient(to bottom, rgba(15, 23, 42, 0.95), rgba(15, 23, 42, 0.85)),
|
||||
radial-gradient(circle at 50% 0, rgba(56, 189, 248, 0.22), transparent 60%);
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.35);
|
||||
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.9);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr 320px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
justify-self: start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #e0f2fe;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.header-center {
|
||||
justify-self: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
position: relative;
|
||||
width: min(980px, 100%);
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 3px;
|
||||
background: linear-gradient(to bottom, #e0f2fe, #60a5fa);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 0 0 20px rgba(56, 189, 248, 0.65);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
background: rgba(30, 64, 175, 0.3);
|
||||
border: 1px solid rgba(56, 189, 248, 0.3);
|
||||
color: #e0f2fe;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: rgba(30, 64, 175, 0.6);
|
||||
border-color: rgba(56, 189, 248, 0.8);
|
||||
box-shadow: 0 0 10px rgba(56, 189, 248, 0.4);
|
||||
}
|
||||
|
||||
.weather {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.weather-icon {
|
||||
font-size: 28px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.weather-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.temp {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #e0f2fe;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
grid-template-columns: 280px 1fr 280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.title {
|
||||
font-size: 20px;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title">
|
||||
<span class="title-dot"></span>
|
||||
<span>设备概况</span>
|
||||
</div>
|
||||
<div class="panel-body overview-body">
|
||||
<div v-for="item in overviewItems" :key="item.key" class="gauge-item">
|
||||
<div class="gauge" :style="getGaugeStyle(item.percent, item.color)">
|
||||
<div class="gauge-inner">
|
||||
<div class="gauge-value">{{ item.value }}</div>
|
||||
<div class="gauge-label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { colors } from '../utils'
|
||||
|
||||
const overviewItems = [
|
||||
{ key: 'device', label: '设备数量', value: '987,965', percent: 78, color: colors.cyan },
|
||||
{ key: 'running', label: '运行数量', value: '30', percent: 66, color: colors.blue },
|
||||
{ key: 'idle', label: '待机数量', value: '2', percent: 42, color: colors.warn },
|
||||
{ key: 'alarm', label: '报警数量', value: '10', percent: 58, color: colors.danger }
|
||||
]
|
||||
|
||||
const getGaugeStyle = (percent: number, color: string) => {
|
||||
const p = Math.max(0, Math.min(100, percent))
|
||||
return {
|
||||
background: `conic-gradient(${color} ${p * 3.6}deg, rgba(148,163,184,0.18) 0deg)`
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.overview-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gauge-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gauge {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border-radius: 50%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gauge-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: rgba(2,6,23,0.92);
|
||||
border: 1px solid rgba(148,163,184,0.25);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gauge-value {
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.gauge-label {
|
||||
font-size: 11px;
|
||||
color: rgba(148,163,184,0.95);
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.gauge {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title panel-title-between">
|
||||
<div class="panel-title-left">
|
||||
<span class="title-dot"></span>
|
||||
<span>能耗监测</span>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<span class="tab" :class="{ active: energyTab === 'first' }" @click="energyTab = 'first'">First aid</span>
|
||||
<span class="tab" :class="{ active: energyTab === 'after' }" @click="energyTab = 'after'">Aftermarket</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { colors, style } from '../utils'
|
||||
|
||||
const energyTab = ref<'first' | 'after'>('first')
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
|
||||
const render = () => {
|
||||
if (!chart) return
|
||||
const x = ['9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00']
|
||||
const y =
|
||||
energyTab.value === 'first'
|
||||
? [820, 1650, 980, 1240, 1560, 1320, 1680]
|
||||
: [680, 1420, 880, 1100, 1320, 1180, 1480]
|
||||
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { top: '18%', left: '6%', right: '4%', bottom: '12%', containLabel: true },
|
||||
xAxis: { type: 'category', data: x, axisLine: style.axisLine, axisLabel: { ...style.axisLabel, fontSize: 10 } },
|
||||
yAxis: { type: 'value', axisLine: { show: false }, axisLabel: { ...style.axisLabel, fontSize: 10 }, splitLine: style.splitLine },
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 12,
|
||||
itemStyle: {
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colors.blue },
|
||||
{ offset: 1, color: 'rgba(30,144,255,0.10)' }
|
||||
])
|
||||
},
|
||||
data: y
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!chartRef.value) return
|
||||
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
||||
render()
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
})
|
||||
|
||||
watch(energyTab, () => {
|
||||
render()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.panel-title-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.panel-title-left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(148,163,184,0.95);
|
||||
}
|
||||
|
||||
.tab {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(148,163,184,0.4);
|
||||
background: rgba(2,6,23,0.2);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-color: rgba(56,189,248,0.85);
|
||||
color: #e0f2fe;
|
||||
box-shadow: 0 0 14px rgba(56,189,248,0.35);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 160px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title">
|
||||
<span class="title-dot"></span>
|
||||
<span>事件提醒</span>
|
||||
</div>
|
||||
<div class="panel-body body">
|
||||
<div class="event-list">
|
||||
<div v-for="item in eventItems" :key="item.key" class="event-row">
|
||||
<div class="event-name">
|
||||
<span class="event-bullet" :style="{ borderColor: item.color }"></span>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="event-count" :style="{ color: item.color }">{{ item.count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-chart">
|
||||
<div class="chart-container">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { colors } from '../utils'
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
|
||||
const eventItems = [
|
||||
{ key: 'check', name: '点检', count: 1, percent: 30, color: colors.cyan },
|
||||
{ key: 'maintain', name: '保养', count: 1, percent: 42, color: colors.warn },
|
||||
{ key: 'repair', name: '维修', count: 1, percent: 20, color: colors.danger }
|
||||
]
|
||||
|
||||
const render = () => {
|
||||
if (!chart) return
|
||||
const percentMap = Object.fromEntries(eventItems.map((i) => [i.name, i.percent])) as Record<string, number>
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
icon: 'circle',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 14,
|
||||
selectedMode: false,
|
||||
textStyle: {
|
||||
rich: {
|
||||
percent: { fontSize: 18, fontWeight: 900, color: '#e5f0ff', lineHeight: 20 },
|
||||
name: { fontSize: 12, fontWeight: 700, color: 'rgba(148,163,184,0.95)', lineHeight: 16 }
|
||||
}
|
||||
},
|
||||
formatter: (name: string) => `{percent|${percentMap[name] ?? 0}%}\n{name|${name}}`
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['48%', '70%'],
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
padAngle: 2,
|
||||
itemStyle: { borderRadius: 8, borderWidth: 6, borderColor: 'rgba(2,6,23,0.9)' },
|
||||
emphasis: { scale: false },
|
||||
data: [
|
||||
{ value: 30, name: '点检', itemStyle: { color: colors.cyan } },
|
||||
{ value: 42, name: '保养', itemStyle: { color: colors.warn } },
|
||||
{ value: 20, name: '维修', itemStyle: { color: colors.danger } }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!chartRef.value) return
|
||||
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
||||
render()
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: grid;
|
||||
grid-template-columns: 0.7fr 1.3fr;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.event-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.event-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(30,64,175,0.7);
|
||||
background: rgba(15,23,42,0.7);
|
||||
}
|
||||
|
||||
.event-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.event-bullet {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.event-count {
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.event-chart {
|
||||
height: 100%;
|
||||
min-height: 170px;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.body {
|
||||
grid-template-columns: 0.8fr 1.2fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title">
|
||||
<span class="title-dot"></span>
|
||||
<span>Payment method</span>
|
||||
<div class="date-filter">
|
||||
<el-date-picker
|
||||
v-model="pickedDate"
|
||||
type="date"
|
||||
format="YYYY MM/DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:clearable="false"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { colors, style } from '../utils'
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
const pickedDate = ref('2023-08-31')
|
||||
|
||||
const render = () => {
|
||||
if (!chart) return
|
||||
const x = ['9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00']
|
||||
const y = [60, 120, 165, 140, 185, 150, 190]
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
grid: { top: '14%', left: '6%', right: '4%', bottom: '12%', containLabel: true },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: x, axisLine: style.axisLine, axisLabel: { ...style.axisLabel, fontSize: 10 } },
|
||||
yAxis: { type: 'value', axisLine: { show: false }, axisLabel: { ...style.axisLabel, fontSize: 10 }, splitLine: style.splitLine },
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: true,
|
||||
symbolSize: 6,
|
||||
lineStyle: { width: 2, color: colors.cyan },
|
||||
itemStyle: { color: colors.cyan },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(34,211,238,0.38)' },
|
||||
{ offset: 1, color: 'rgba(34,211,238,0.06)' }
|
||||
])
|
||||
},
|
||||
data: y
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!chartRef.value) return
|
||||
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
||||
render()
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.date-filter {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-filter :deep(.el-input__wrapper) {
|
||||
background: rgba(2,6,23,0.35);
|
||||
box-shadow: none;
|
||||
border: 1px solid rgba(148,163,184,0.35);
|
||||
}
|
||||
|
||||
.date-filter :deep(.el-input__inner) {
|
||||
color: rgba(224,242,254,0.95);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 160px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title">
|
||||
<span class="title-dot"></span>
|
||||
<span>产量趋势</span>
|
||||
<span class="tag">今日</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { colors, style } from '../utils'
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
|
||||
const render = () => {
|
||||
if (!chart) return
|
||||
const x = ['9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00']
|
||||
const output = [320, 460, 520, 610, 720, 690, 780]
|
||||
const passRate = [98.5, 99.2, 98.1, 98.9, 99.0, 98.6, 99.3]
|
||||
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { top: '18%', left: '6%', right: '6%', bottom: '12%', containLabel: true },
|
||||
xAxis: { type: 'category', data: x, axisLine: style.axisLine, axisLabel: { ...style.axisLabel, fontSize: 10 } },
|
||||
yAxis: [
|
||||
{ type: 'value', axisLine: { show: false }, axisLabel: { ...style.axisLabel, fontSize: 10 }, splitLine: style.splitLine },
|
||||
{ type: 'value', min: 96, max: 100, axisLine: { show: false }, axisLabel: { ...style.axisLabel, fontSize: 10 }, splitLine: { show: false } }
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '产量',
|
||||
type: 'bar',
|
||||
barWidth: 12,
|
||||
itemStyle: {
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colors.purple },
|
||||
{ offset: 1, color: 'rgba(139,92,246,0.10)' }
|
||||
])
|
||||
},
|
||||
data: output
|
||||
},
|
||||
{
|
||||
name: '良率',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: { width: 2, color: colors.cyan },
|
||||
data: passRate
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!chartRef.value) return
|
||||
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
||||
render()
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-left: auto;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
border: 1px solid rgba(148,163,184,0.4);
|
||||
color: rgba(148,163,184,0.95);
|
||||
background: rgba(2,6,23,0.2);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 160px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="panel-title">
|
||||
<span class="title-dot"></span>
|
||||
<span>任务列表</span>
|
||||
</div>
|
||||
<div class="panel-body table-body">
|
||||
<table class="task-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>类型</th>
|
||||
<th>设备</th>
|
||||
<th>责任人</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in taskRows" :key="row.id" :class="row.status">
|
||||
<td>{{ row.time }}</td>
|
||||
<td>{{ row.type }}</td>
|
||||
<td>{{ row.device }}</td>
|
||||
<td>{{ row.owner }}</td>
|
||||
<td class="status-cell" :style="{ color: row.color }">{{ row.statusLabel }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { colors } from '../utils'
|
||||
|
||||
const taskRows = [
|
||||
{ id: 1, time: '10:20', type: '点检', device: '1号装配机', owner: 'XXX', status: 'ok', statusLabel: '正常', color: colors.cyan },
|
||||
{ id: 2, time: '10:25', type: '点检', device: '1号装配机', owner: 'XXX', status: 'ok', statusLabel: '正常', color: colors.cyan },
|
||||
{ id: 3, time: '10:30', type: '维修', device: '1号装配机', owner: 'XXX', status: 'danger', statusLabel: '维修', color: colors.danger },
|
||||
{ id: 4, time: '10:35', type: '保养', device: '1号装配机', owner: 'XXX', status: 'ok', statusLabel: '保养', color: colors.warn },
|
||||
{ id: 5, time: '10:40', type: '点检', device: '1号装配机', owner: 'XXX', status: 'ok', statusLabel: '正常', color: colors.cyan },
|
||||
{ id: 6, time: '10:45', type: '点检', device: '1号装配机', owner: 'XXX', status: 'ok', statusLabel: '正常', color: colors.cyan }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(30,64,175,0.85);
|
||||
box-shadow:
|
||||
0 18px 45px rgba(15,23,42,0.95),
|
||||
0 0 0 1px rgba(15,23,42,1),
|
||||
inset 0 0 0 1px rgba(56,189,248,0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card::before,
|
||||
.card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(56,189,248,0.75);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #e5f0ff;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56,189,248,0.95);
|
||||
box-shadow: 0 0 12px rgba(56,189,248,0.45);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.task-table thead {
|
||||
background: radial-gradient(circle at 0 0, rgba(56,189,248,0.18), transparent 70%);
|
||||
}
|
||||
|
||||
.task-table th {
|
||||
padding: 10px 8px;
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(51,65,85,0.9);
|
||||
}
|
||||
|
||||
.task-table td {
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid rgba(30,64,175,0.3);
|
||||
color: #e5f0ff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-table tbody tr:hover td {
|
||||
background: rgba(56,189,248,0.08);
|
||||
}
|
||||
|
||||
.status-cell {
|
||||
font-weight: 800;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,62 @@
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export const colors = {
|
||||
blue: '#1e90ff',
|
||||
cyan: '#22d3ee',
|
||||
green: '#22c55e',
|
||||
purple: '#8b5cf6',
|
||||
warn: '#f59e0b',
|
||||
danger: '#ef4444'
|
||||
}
|
||||
|
||||
export const style = {
|
||||
axisLine: { lineStyle: { color: 'rgba(148,163,184,0.45)' } },
|
||||
axisLabel: { color: '#a8b7d8', fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: 'rgba(30,64,175,0.55)', type: 'dashed' } },
|
||||
legendText: { color: '#e5f0ff', fontSize: 12 }
|
||||
}
|
||||
|
||||
export function animateCount(el: HTMLElement | null, target: number, suffix: string, duration: number) {
|
||||
if (!el) return
|
||||
const start = 0
|
||||
const t0 = performance.now()
|
||||
const step = (t: number) => {
|
||||
const p = Math.min(1, (t - t0) / duration)
|
||||
const v = start + (target - start) * p
|
||||
const out = Number.isInteger(target) ? Math.round(v) : Math.round(v * 10) / 10
|
||||
el.textContent = suffix ? `${out}${suffix}` : `${out}`
|
||||
if (p < 1) requestAnimationFrame(step)
|
||||
}
|
||||
requestAnimationFrame(step)
|
||||
}
|
||||
|
||||
export function useChart(domId: string) {
|
||||
let chart: echarts.ECharts | null = null
|
||||
|
||||
const init = () => {
|
||||
const el = document.getElementById(domId)
|
||||
if (!el) return null
|
||||
chart = echarts.init(el, 'dark', { renderer: 'canvas' })
|
||||
return chart
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
})
|
||||
|
||||
return {
|
||||
init,
|
||||
resize,
|
||||
instance: () => chart
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue