|
|
|
|
@ -1,9 +1,17 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<div class="panel-title-left">
|
|
|
|
|
<span class="title-dot"></span>
|
|
|
|
|
<span>事件提醒</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-title-right">
|
|
|
|
|
<el-radio-group v-model="mode" size="small">
|
|
|
|
|
<el-radio-button label="device">设备</el-radio-button>
|
|
|
|
|
<el-radio-button label="mold">模具</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-body body">
|
|
|
|
|
<div class="event-list">
|
|
|
|
|
<div v-for="item in eventItems" :key="item.key" class="event-row">
|
|
|
|
|
@ -24,22 +32,31 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
import { DashboardApi, TaskStatisticsData } from '@/api/dashboard'
|
|
|
|
|
import { colors } from '../utils'
|
|
|
|
|
|
|
|
|
|
interface EventItem {
|
|
|
|
|
key: string
|
|
|
|
|
name: string
|
|
|
|
|
count: number
|
|
|
|
|
percent: number
|
|
|
|
|
color: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 mode = ref<'device' | 'mold'>('device')
|
|
|
|
|
const eventItems = ref<EventItem[]>([])
|
|
|
|
|
const rawStats = ref<TaskStatisticsData | null>(null)
|
|
|
|
|
let switchTimer: number | undefined
|
|
|
|
|
|
|
|
|
|
const render = () => {
|
|
|
|
|
if (!chart) return
|
|
|
|
|
const percentMap = Object.fromEntries(eventItems.map((i) => [i.name, i.percent])) as Record<string, number>
|
|
|
|
|
const items = eventItems.value
|
|
|
|
|
const percentMap = Object.fromEntries(items.map((i) => [i.name, i.percent])) as Record<string, number>
|
|
|
|
|
const seriesData = items.map((i) => ({ value: i.count, name: i.name, itemStyle: { color: i.color } }))
|
|
|
|
|
chart.setOption({
|
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
|
tooltip: { trigger: 'item' },
|
|
|
|
|
@ -51,7 +68,6 @@ const render = () => {
|
|
|
|
|
itemWidth: 10,
|
|
|
|
|
itemHeight: 10,
|
|
|
|
|
itemGap: 14,
|
|
|
|
|
selectedMode: false,
|
|
|
|
|
textStyle: {
|
|
|
|
|
rich: {
|
|
|
|
|
percent: { fontSize: 18, fontWeight: 900, color: '#e5f0ff', lineHeight: 20 },
|
|
|
|
|
@ -71,16 +87,64 @@ const render = () => {
|
|
|
|
|
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 } }
|
|
|
|
|
]
|
|
|
|
|
data: seriesData
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const applyTaskStatistics = (data: TaskStatisticsData) => {
|
|
|
|
|
rawStats.value = data
|
|
|
|
|
const isDevice = mode.value === 'device'
|
|
|
|
|
const inspection = Number(isDevice ? data.deviceInspection : data.moldInspection) || 0
|
|
|
|
|
const maintenance = Number(isDevice ? data.deviceMaintenance : data.moldMaintenance) || 0
|
|
|
|
|
const repair = Number(isDevice ? data.deviceRepair : data.moldRepair) || 0
|
|
|
|
|
const total = inspection + maintenance + repair
|
|
|
|
|
const toPercent = (value: number) => (total > 0 ? Math.round((value / total) * 100) : 0)
|
|
|
|
|
eventItems.value = [
|
|
|
|
|
{
|
|
|
|
|
key: 'check',
|
|
|
|
|
name: '点检',
|
|
|
|
|
count: inspection,
|
|
|
|
|
percent: toPercent(inspection),
|
|
|
|
|
color: colors.cyan
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'maintain',
|
|
|
|
|
name: '保养',
|
|
|
|
|
count: maintenance,
|
|
|
|
|
percent: toPercent(maintenance),
|
|
|
|
|
color: colors.warn
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'repair',
|
|
|
|
|
name: '维修',
|
|
|
|
|
count: repair,
|
|
|
|
|
percent: toPercent(repair),
|
|
|
|
|
color: colors.danger
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
render()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadTaskStatistics = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await DashboardApi.getTaskStatistics()
|
|
|
|
|
const payload = (res && typeof res === 'object' && 'data' in res ? (res as any).data : res) as
|
|
|
|
|
| TaskStatisticsData
|
|
|
|
|
| null
|
|
|
|
|
if (!payload) return
|
|
|
|
|
applyTaskStatistics(payload)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(mode, () => {
|
|
|
|
|
if (!rawStats.value) return
|
|
|
|
|
applyTaskStatistics(rawStats.value)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const resize = () => {
|
|
|
|
|
chart?.resize()
|
|
|
|
|
}
|
|
|
|
|
@ -89,11 +153,20 @@ onMounted(() => {
|
|
|
|
|
if (!chartRef.value) return
|
|
|
|
|
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
|
|
|
|
|
render()
|
|
|
|
|
loadTaskStatistics()
|
|
|
|
|
switchTimer = window.setInterval(() => {
|
|
|
|
|
if (!rawStats.value) return
|
|
|
|
|
mode.value = mode.value === 'device' ? 'mold' : 'device'
|
|
|
|
|
}, 10000)
|
|
|
|
|
window.addEventListener('resize', resize)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
window.removeEventListener('resize', resize)
|
|
|
|
|
if (switchTimer) {
|
|
|
|
|
clearInterval(switchTimer)
|
|
|
|
|
switchTimer = undefined
|
|
|
|
|
}
|
|
|
|
|
chart?.dispose()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
@ -144,6 +217,7 @@ onUnmounted(() => {
|
|
|
|
|
.panel-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
border-bottom: 1px solid rgba(41, 54, 95, 0.9);
|
|
|
|
|
@ -152,6 +226,42 @@ onUnmounted(() => {
|
|
|
|
|
color: #e5f0ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-left {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-right {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
border: 1px solid rgba(56, 189, 248, 0.5);
|
|
|
|
|
background: radial-gradient(circle at 0 0, rgba(56, 189, 248, 0.18), transparent 70%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-right :deep(.el-radio-group) {
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-right :deep(.el-radio-button__inner) {
|
|
|
|
|
border: none;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: rgba(148, 163, 184, 0.95);
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-right :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
|
|
|
|
|
background: rgba(15, 23, 42, 0.9);
|
|
|
|
|
color: #e5f0ff;
|
|
|
|
|
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.85);
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-dot {
|
|
|
|
|
width: 10px;
|
|
|
|
|
height: 10px;
|
|
|
|
|
|