@@ -1,11 +1,894 @@
< script setup lang = "ts" >
< / script >
< template >
< div class = "min-h-screen bg-slate-900 text-slate-100 flex flex-col overflow-hidden relative" >
<!-- 背景动态效果 -- >
< div class = "absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(59,130,246,0.15)_0,rgba(15,23,42,0)_70%)] pointer-events-none" > < / div >
< div class = "absolute inset-0 bg-grid pattern-grid pointer-events-none" > < / div >
<!-- 顶部标题区域 -- >
< header class = "bg-slate-800/80 backdrop-blur-sm border-b border-slate-700 py-3 px-6 flex justify-between items-center z-10" >
< div class = "flex items-center gap-3" >
< div class = "bg-blue-600 rounded-lg w-10 h-10 flex items-center justify-center shadow-lg shadow-blue-600/20" >
< i class = "fa fa-shield text-xl" > < / i >
< / div >
< h1 class = "text-2xl font-bold tracking-wide text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-cyan-300" >
预警监控指挥系统
< / h1 >
< / div >
< div class = "flex items-center gap-6" >
< div class = "flex items-center gap-2 text-slate-300" >
< i class = "fa fa-clock-o" > < / i >
< span > { { currentTime } } < / span >
< / div >
< div class = "flex items-center gap-2 text-slate-300" >
< i class = "fa fa-refresh" > < / i >
< span > 数据更新于 : { { currentTime } } < / span >
< / div >
<!-- < button class = "bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 px-4 py-2 rounded-lg transition-all duration-300 flex items-center gap-2 transform hover:scale-105" > -- >
<!-- < i class = "fa fa-download" > < / i > -- >
<!-- < span > 导出报告 < / span > -- >
<!-- < / button > -- >
< / div >
< / header >
<!-- 主内容区域 -- >
< main class = "flex-1 flex overflow-hidden p-4 gap-4" >
<!-- 左侧预警列表区域 -- >
< section class = "w-1/4 flex flex-col gap-4" >
<!-- 预警分类统计 -- >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< h2 class = "text-lg font-semibold mb-3 flex items-center" >
< i class = "fa fa-tags text-blue-400 mr-2" > < / i >
预警分类统计
< / h2 >
< div class = "grid grid-cols-2 gap-3" >
< div class = "bg-red-600/10 border border-red-500/30 rounded-lg p-3 transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-red-500/10" >
< div class = "text-sm text-red-300" > 紧急预警 < / div >
< div class = "text-2xl font-bold text-red-400 mt-1 counter-animation" > { { stats . emergency } } < / div >
< / div >
< div class = "bg-orange-600/10 border border-orange-500/30 rounded-lg p-3 transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-orange-500/10" >
< div class = "text-sm text-orange-300" > 重要预警 < / div >
< div class = "text-2xl font-bold text-orange-400 mt-1 counter-animation" > { { stats . important } } < / div >
< / div >
< div class = "bg-yellow-600/10 border border-yellow-500/30 rounded-lg p-3 transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-yellow-500/10" >
< div class = "text-sm text-yellow-300" > 一般预警 < / div >
< div class = "text-2xl font-bold text-yellow-400 mt-1 counter-animation" > { { stats . normal } } < / div >
< / div >
< div class = "bg-green-600/10 border border-green-500/30 rounded-lg p-3 transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-green-500/10" >
< div class = "text-sm text-green-300" > 已处理 < / div >
< div class = "text-2xl font-bold text-green-400 mt-1 counter-animation" > { { stats . resolved } } < / div >
< / div >
< / div >
<!-- 预警类型饼图 -- >
<!-- < div class = "mt-4 h-40" > -- >
<!-- < canvas ref = "eventTypePieChart" > < / canvas > -- >
<!-- < / div > -- >
< / div >
<!-- 预警列表 -- >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg flex-1 overflow-hidden flex flex-col transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< div class = "flex justify-between items-center mb-3" >
< h2 class = "text-lg font-semibold flex items-center" >
< i class = "fa fa-list-alt text-blue-400 mr-2" > < / i >
预警列表
< / h2 >
< div class = "relative" >
< input
type = "text"
placeholder = "搜索预警..."
class = "bg-slate-700/50 text-sm rounded-lg px-3 py-1.5 w-40 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-all duration-300 focus:w-48"
>
< i class = "fa fa-search absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm" > < / i >
< / div >
< / div >
< div class = "overflow-y-auto flex-1 scrollbar-thin scrollbar-thumb-slate-700 scrollbar-track-transparent" >
< div
v-for = "event in events"
:key = "event.id"
@click ="selectEvent(event)"
class = "p-3 rounded-lg border border-slate-700 mb-2 cursor-pointer transition-all duration-200 hover:border-blue-500/50 hover:bg-slate-700/30 flex items-center gap-3 transform hover:translate-x-1"
: class = "{ 'bg-blue-600/20 border-blue-500/50': selectedEvent?.id === event.id }"
>
< div class = "w-2 h-2 rounded-full" :class = "eventStatusColor(event.status)" > < / div >
< div class = "flex-1 min-w-0" >
< div class = "font-medium text-sm truncate" > { { event . title } } < / div >
< div class = "text-xs text-slate-400 mt-0.5" > { { event . location } } · { { formatTime ( event . time ) } } < / div >
< / div >
< span class = "text-xs px-2 py-0.5 rounded-full" :class = "eventStatusBadgeClass(event.status)" >
{ { eventStatusText ( event . status ) } }
< / span >
< / div >
< / div >
< / div >
< / section >
<!-- 中间地图区域 -- >
< section class = "flex-1 flex flex-col gap-4" >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg flex-1 relative overflow-hidden transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< h2 class = "text-lg font-semibold mb-3 flex items-center relative z-10" >
< i class = "fa fa-map-marker text-blue-400 mr-2" > < / i >
预警分布地图
< / h2 >
<!-- 模拟地图 -- >
< div class = "absolute inset-0 bg-slate-900/50 rounded-lg overflow-hidden" >
< div class = "w-full h-full bg-[url('https://picsum.photos/id/1015/1200/800')] opacity-20 bg-cover bg-center" > < / div >
<!-- 网格线 -- >
< div class = "absolute inset-0 grid grid-cols-8 grid-rows-6" >
< div v-for = "i in 48" :key="i" class="border border-slate-700/30" > < / div >
< / div >
< ! - - 预警标记点 - - >
< div
v-for = "event in events"
:key = "event.id"
: style = "{ left: `${event.position.x}%`, top: `${event.position.y}%` }"
class = "absolute transform -translate-x-1/2 -translate-y-1/2 cursor-pointer group"
@click ="selectEvent(event)"
>
< div
class = "w-3 h-3 rounded-full"
:class = "eventStatusColor(event.status)"
> < / div >
< div class = "absolute -top-10 left-1/2 transform -translate-x-1/2 bg-slate-800 px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-10" >
{ { event . title } }
< / div >
< div
class = "absolute w-6 h-6 rounded-full animate-ping"
:class = "eventStatusPingClass(event.status)"
style = "animation-duration: 2s"
> < / div >
< / div >
<!-- 选中预警的高亮 -- >
< div
v-if = "selectedEvent"
: style = "{ left: `${selectedEvent.position.x}%`, top: `${selectedEvent.position.y}%` }"
class = "absolute transform -translate-x-1/2 -translate-y-1/2"
>
< div class = "w-4 h-4 rounded-full bg-blue-500 border-2 border-white shadow-lg" > < / div >
< div class = "absolute w-8 h-8 rounded-full border-2 border-blue-500 animate-ping opacity-75" style = "animation-duration: 1.5s" > < / div >
< / div >
< / div >
<!-- 地图控制 -- >
< div class = "absolute bottom-4 right-4 flex flex-col gap-2 z-10" >
< button class = "bg-slate-800/80 hover:bg-slate-700 w-8 h-8 rounded-lg flex items-center justify-center transition-all duration-300 hover:scale-110" >
< i class = "fa fa-plus text-sm" > < / i >
< / button >
< button class = "bg-slate-800/80 hover:bg-slate-700 w-8 h-8 rounded-lg flex items-center justify-center transition-all duration-300 hover:scale-110" >
< i class = "fa fa-minus text-sm" > < / i >
< / button >
< button class = "bg-slate-800/80 hover:bg-slate-700 w-8 h-8 rounded-lg flex items-center justify-center transition-all duration-300 hover:scale-110" >
< i class = "fa fa-location-arrow text-sm" > < / i >
< / button >
< / div >
< / div >
<!-- 处理时间和图表区域 -- >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg h-64 transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< h2 class = "text-lg font-semibold mb-3 flex items-center" >
< i class = "fa fa-line-chart text-blue-400 mr-2" > < / i >
预警处理数据分析
< / h2 >
< div class = "grid grid-cols-2 gap-4 h-48" >
<!-- 处理时间趋势折线图 -- >
< div >
< h3 class = "text-sm text-slate-300 mb-2" > 平均处理时间 ( 分钟 ) < / h3 >
< div class = "h-36" >
< canvas ref = "processingTimeChart" > < / canvas >
< / div >
< / div >
<!-- 每日预警数量柱状图 -- >
< div >
< h3 class = "text-sm text-slate-300 mb-2" > 每日预警数量 < / h3 >
< div class = "h-36" >
< canvas ref = "dailyEventsChart" > < / canvas >
< / div >
< / div >
< / div >
< / div >
< / section >
<!-- 右侧处理状态和操作 -- >
< section class = "w-1/4 flex flex-col gap-4" >
<!-- 预警详情 -- >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< h2 class = "text-lg font-semibold mb-3 flex items-center" >
< i class = "fa fa-info-circle text-blue-400 mr-2" > < / i >
预警详情
< / h2 >
< div v-if = "selectedEvent" class="space-y-3 animate-fadeIn" >
< div >
< div class = "text-xs text-slate-400" > 预警标题 < / div >
< div class = "font-medium" > { { selectedEvent . title } } < / div >
< / div >
< div >
< div class = "text-xs text-slate-400" > 预警类型 < / div >
< div class = "font-medium" > { { selectedEvent . type } } < / div >
< / div >
< div >
< div class = "text-xs text-slate-400" > 发生时间 < / div >
< div class = "font-medium" > { { formatDateTime ( selectedEvent . time ) } } < / div >
< / div >
< div >
< div class = "text-xs text-slate-400" > 位置信息 < / div >
< div class = "font-medium" > { { selectedEvent . location } } < / div >
< / div >
< div >
< div class = "text-xs text-slate-400" > 预警描述 < / div >
< div class = "text-sm text-slate-300 bg-slate-700/30 p-2 rounded-lg mt-1 hover:bg-slate-700/50 transition-colors duration-300" >
{ { selectedEvent . description } }
< / div >
< / div >
< div >
< div class = "text-xs text-slate-400" > 当前状态 < / div >
< div class = "flex items-center mt-1" >
< span class = "text-sm px-2 py-0.5 rounded-full" :class = "eventStatusBadgeClass(selectedEvent.status)" >
{ { eventStatusText ( selectedEvent . status ) } }
< / span >
< span class = "ml-2 text-xs text-slate-400" >
更新于 : { { formatTime ( selectedEvent . updateTime ) } }
< / span >
< / div >
< / div >
< / div >
< div v-else class = "flex items-center justify-center h-48 text-slate-500" >
< div class = "text-center" >
< i class = "fa fa-hand-pointer-o text-2xl mb-2 animate-pulse" > < / i >
< p > 请从左侧列表或地图中选择一个预警 < / p >
< / div >
< / div >
< / div >
<!-- 处理状态和操作 -- >
< div class = "bg-slate-800/60 backdrop-blur-sm rounded-xl p-4 border border-slate-700/50 shadow-lg flex-1 flex flex-col transform transition-all duration-300 hover:shadow-blue-500/10 hover:border-blue-500/20" >
< h2 class = "text-lg font-semibold mb-3 flex items-center" >
< i class = "fa fa-cogs text-blue-400 mr-2" > < / i >
处理操作
< / h2 >
< div v-if = "selectedEvent" class="flex-1 flex flex-col animate-fadeIn" >
< ! - - 处理进度 - - >
< div class = "mb-4" >
< div class = "text-sm font-medium mb-2" > 处理进度 < / div >
< div class = "relative pt-1" >
< div class = "flex mb-2 items-center justify-between" >
< div >
< span class = "text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-600 bg-blue-200/10" >
处理进度
< / span >
< / div >
< div class = "text-right" >
< span class = "text-xs font-semibold inline-block text-blue-400" >
{ { processingProgress } } %
< / span >
< / div >
< / div >
< div class = "overflow-hidden h-2 mb-4 text-xs flex rounded bg-slate-700/50" >
< div
: style = "{ width: `${processingProgress}%` }"
class = "shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-gradient-to-r from-blue-500 to-cyan-400 transition-all duration-1000 ease-out"
> < / div >
< / div >
< / div >
< / div >
<!-- 处理记录 -- >
< div class = "mb-4 flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-slate-700" >
< div class = "text-sm font-medium mb-2" > 处理记录 < / div >
< div class = "space-y-3" >
< div v-for = "record in selectedEvent.processingRecords" :key="record.id" class="flex gap-2 transform transition-all duration-300 hover:translate-x-1" >
< div class = "mt-0.5 w-6 h-6 rounded-full bg-slate-700 flex items-center justify-center flex-shrink-0" >
< i class = "fa fa-check text-xs text-slate-300" > < / i >
< / div >
< div >
< div class = "text-sm" > { { record . action } } < / div >
< div class = "text-xs text-slate-400 mt-0.5" >
{ { record . user } } · { { formatTime ( record . time ) } }
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- 操作按钮 -- >
< div class = "space-y-2 pt-2 border-t border-slate-700/50" >
< div class = "flex gap-2" >
< button
class = "flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-all duration-300 flex items-center justify-center gap-1 text-sm transform hover:scale-[1.02] active:scale-[0.98]"
: disabled = "selectedEvent.status === 'resolved'"
@click ="markAsResolved"
>
< i class = "fa fa-check" > < / i >
< span > 标记为已处理 < / span >
< / button >
< button class = "bg-slate-700 hover:bg-slate-600 text-white p-2 rounded-lg transition-all duration-300 transform hover:scale-110 active:scale-90" >
< i class = "fa fa-comments" > < / i >
< / button >
< / div >
< div class = "flex gap-2" >
< button class = "flex-1 bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg transition-all duration-300 flex items-center justify-center gap-1 text-sm transform hover:scale-[1.02] active:scale-[0.98]" >
< i class = "fa fa-user-plus" > < / i >
< span > 指派处理人 < / span >
< / button >
< button class = "flex-1 bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg transition-all duration-300 flex items-center justify-center gap-1 text-sm transform hover:scale-[1.02] active:scale-[0.98]" >
< i class = "fa fa-file-text-o" > < / i >
< span > 生成报告 < / span >
< / button >
< / div >
< div >
< textarea
placeholder = "输入处理备注..."
class = "w-full bg-slate-700/50 border border-slate-600 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none h-16 transition-all duration-300 focus:border-blue-500/50"
v-model = "processingNote"
> < / textarea >
< / div >
< / div >
< / div >
< div v-else class = "flex-1 flex items-center justify-center text-slate-500" >
< div class = "text-center" >
< i class = "fa fa-wrench text-2xl mb-2 animate-pulse" > < / i >
< p > 选择预警后显示处理操作 < / p >
< / div >
< / div >
< / div >
< / section >
< / main >
< / div >
< / template >
< style scoped lang = "scss" >
< script setup >
import { ref , onMounted , computed } from 'vue' ;
import { useNotifyStore } from '#/store' ;
// 处理备注
const processingNote = ref ( '' ) ;
< / style >
const list = useNotifyStore ( ) . sseList ;
list . map ( item => {
} )
// 模拟预警数据
const events = ref ( [
{
id : 1 ,
title : "设备故障报警" ,
type : "设备故障" ,
status : "emergency" ,
time : "2023-10-15T08:23:45" ,
updateTime : "2023-10-15T08:45:12" ,
location : "一号厂房A区" ,
description : "流水线三号设备突然停机, 显示电机故障代码E109, 需要紧急处理以避免生产线中断。" ,
position : { x : 35 , y : 40 } ,
processingRecords : [
{ id : 1 , action : "接收到报警信息" , user : "系统自动" , time : "2023-10-15T08:23:45" } ,
{ id : 2 , action : "指派给维修组" , user : "张经理" , time : "2023-10-15T08:25:10" }
]
} ,
{
id : 2 ,
title : "安全门异常开启" ,
type : "安全预警" ,
status : "important" ,
time : "2023-10-15T07:15:30" ,
updateTime : "2023-10-15T07:30:22" ,
location : "二号仓库入口" ,
description : "非工作时间安全门被异常开启,系统已自动记录并触发警报,需检查是否有异常进入。" ,
position : { x : 65 , y : 30 } ,
processingRecords : [
{ id : 1 , action : "接收到报警信息" , user : "系统自动" , time : "2023-10-15T07:15:30" } ,
{ id : 2 , action : "安保人员已前往查看" , user : "李主管" , time : "2023-10-15T07:17:05" } ,
{ id : 3 , action : "初步检查未发现异常" , user : "王保安" , time : "2023-10-15T07:30:22" }
]
} ,
{
id : 3 ,
title : "温湿度超标" ,
type : "环境异常" ,
status : "normal" ,
time : "2023-10-15T09:40:12" ,
updateTime : "2023-10-15T09:40:12" ,
location : "实验室B区" ,
description : "实验室B区温湿度超出正常范围, 当前温度26℃, 湿度65%,需调整空调系统。" ,
position : { x : 45 , y : 60 } ,
processingRecords : [
{ id : 1 , action : "接收到报警信息" , user : "系统自动" , time : "2023-10-15T09:40:12" }
]
} ,
{
id : 4 ,
title : "物料短缺预警" ,
type : "物料管理" ,
status : "normal" ,
time : "2023-10-15T10:15:22" ,
updateTime : "2023-10-15T10:15:22" ,
location : "原料仓库" ,
description : "A类原材料库存低于警戒线, 剩余数量约可维持2天生产, 请及时采购补充。" ,
position : { x : 25 , y : 70 } ,
processingRecords : [
{ id : 1 , action : "系统自动发出预警" , user : "系统自动" , time : "2023-10-15T10:15:22" }
]
} ,
{
id : 5 ,
title : "网络中断恢复" ,
type : "网络问题" ,
status : "resolved" ,
time : "2023-10-15T06:30:15" ,
updateTime : "2023-10-15T07:05:33" ,
location : "三号车间" ,
description : "三号车间网络中断,影响设备数据上传,技术人员已修复,网络恢复正常。" ,
position : { x : 75 , y : 55 } ,
processingRecords : [
{ id : 1 , action : "检测到网络中断" , user : "系统自动" , time : "2023-10-15T06:30:15" } ,
{ id : 2 , action : "技术人员前往处理" , user : "赵主管" , time : "2023-10-15T06:35:40" } ,
{ id : 3 , action : "网络已恢复正常" , user : "孙工" , time : "2023-10-15T07:05:33" }
]
}
] ) ;
// 选中的预警
const selectedEvent = ref ( null ) ;
// 统计数据
const stats = ref ( {
emergency : 1 ,
important : 1 ,
normal : 2 ,
resolved : 1
} ) ;
// 当前时间和最后更新时间
const currentTime = ref ( "" ) ;
const lastUpdateTime = ref ( "2023-10-15 10:30:45" ) ;
// 图表引用
const eventTypePieChart = ref ( null ) ;
const processingTimeChart = ref ( null ) ;
const dailyEventsChart = ref ( null ) ;
// 处理进度(根据预警状态计算)
const processingProgress = computed ( ( ) => {
if ( ! selectedEvent . value ) return 0 ;
switch ( selectedEvent . value . status ) {
case 'emergency' : return 30 ;
case 'important' : return 50 ;
case 'normal' : return 20 ;
case 'resolved' : return 100 ;
default : return 0 ;
}
} ) ;
// 选择预警
const selectEvent = ( event ) => {
selectedEvent . value = event ;
} ;
// 标记为已处理
const markAsResolved = ( ) => {
if ( selectedEvent . value ) {
// 更新预警状态
selectedEvent . value . status = 'resolved' ;
selectedEvent . value . updateTime = new Date ( ) . toISOString ( ) ;
// 添加处理记录
if ( processingNote . value . trim ( ) ) {
selectedEvent . value . processingRecords . push ( {
id : selectedEvent . value . processingRecords . length + 1 ,
action : ` 标记为已处理: ${ processingNote . value . trim ( ) } ` ,
user : "当前操作员" ,
time : new Date ( ) . toISOString ( )
} ) ;
processingNote . value = '' ;
} else {
selectedEvent . value . processingRecords . push ( {
id : selectedEvent . value . processingRecords . length + 1 ,
action : "标记为已处理" ,
user : "当前操作员" ,
time : new Date ( ) . toISOString ( )
} ) ;
}
// 更新统计数据
stats . value . resolved ++ ;
if ( stats . value . emergency > 0 ) stats . value . emergency -- ;
else if ( stats . value . important > 0 ) stats . value . important -- ;
else if ( stats . value . normal > 0 ) stats . value . normal -- ;
// 重新绘制图表
drawAllCharts ( ) ;
}
} ;
// 格式化时间
const formatTime = ( timeString ) => {
const date = new Date ( timeString ) ;
return date . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
} ;
// 格式化日期时间
const formatDateTime = ( timeString ) => {
const date = new Date ( timeString ) ;
return date . toLocaleString ( ) ;
} ;
// 预警状态文本
const eventStatusText = ( status ) => {
const statusMap = {
'emergency' : '紧急' ,
'important' : '重要' ,
'normal' : '一般' ,
'resolved' : '已处理'
} ;
return statusMap [ status ] || '未知' ;
} ;
// 预警状态颜色
const eventStatusColor = ( status ) => {
const colorMap = {
'emergency' : 'bg-red-500' ,
'important' : 'bg-orange-500' ,
'normal' : 'bg-yellow-500' ,
'resolved' : 'bg-green-500'
} ;
return colorMap [ status ] || 'bg-slate-500' ;
} ;
// 预警状态标记颜色(脉冲效果)
const eventStatusPingClass = ( status ) => {
const colorMap = {
'emergency' : 'bg-red-500/30' ,
'important' : 'bg-orange-500/30' ,
'normal' : 'bg-yellow-500/30' ,
'resolved' : 'bg-green-500/30'
} ;
return colorMap [ status ] || 'bg-slate-500/30' ;
} ;
// 预警状态徽章样式
const eventStatusBadgeClass = ( status ) => {
const classMap = {
'emergency' : 'bg-red-500/20 text-red-400 border border-red-500/30' ,
'important' : 'bg-orange-500/20 text-orange-400 border border-orange-500/30' ,
'normal' : 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/30' ,
'resolved' : 'bg-green-500/20 text-green-400 border border-green-500/30'
} ;
return classMap [ status ] || 'bg-slate-500/20 text-slate-400 border border-slate-500/30' ;
} ;
// 更新当前时间
const updateCurrentTime = ( ) => {
const now = new Date ( ) ;
currentTime . value = now . toLocaleString ( ) ;
} ;
// 绘制饼图 - 使用原生Canvas API
const drawPieChart = ( canvas , data ) => {
if ( ! canvas ) return ;
const ctx = canvas . getContext ( '2d' ) ;
const width = canvas . width ;
const height = canvas . height ;
// 清除画布
ctx . clearRect ( 0 , 0 , width , height ) ;
const centerX = width / 2 ;
const centerY = height / 2 ;
const radius = Math . min ( width , height ) / 3 ;
const total = data . values . reduce ( ( sum , value ) => sum + value , 0 ) ;
let startAngle = 0 ;
data . values . forEach ( ( value , index ) => {
const sliceAngle = 2 * Math . PI * ( value / total ) ;
const color = data . colors [ index ] ;
// 绘制扇形
ctx . beginPath ( ) ;
ctx . moveTo ( centerX , centerY ) ;
ctx . arc ( centerX , centerY , radius , startAngle , startAngle + sliceAngle ) ;
ctx . closePath ( ) ;
ctx . fillStyle = color ;
ctx . fill ( ) ;
// 添加边框
ctx . strokeStyle = 'rgba(30, 41, 59, 0.8)' ;
ctx . lineWidth = 1 ;
ctx . stroke ( ) ;
// 计算标签位置
const labelAngle = startAngle + sliceAngle / 2 ;
const labelRadius = radius + 15 ;
const labelX = centerX + Math . cos ( labelAngle ) * labelRadius ;
const labelY = centerY + Math . sin ( labelAngle ) * labelRadius ;
// 绘制标签
ctx . fillStyle = '#e2e8f0' ;
ctx . font = '10px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'middle' ;
ctx . fillText ( ` ${ data . labels [ index ] } : ${ value } ` , labelX , labelY ) ;
startAngle += sliceAngle ;
} ) ;
} ;
// 绘制折线图 - 使用原生Canvas API
const drawLineChart = ( canvas , data ) => {
if ( ! canvas ) return ;
const ctx = canvas . getContext ( '2d' ) ;
const width = canvas . width ;
const height = canvas . height ;
// 清除画布
ctx . clearRect ( 0 , 0 , width , height ) ;
// 边距
const margin = { top : 10 , right : 10 , bottom : 20 , left : 30 } ;
const chartWidth = width - margin . left - margin . right ;
const chartHeight = height - margin . top - margin . bottom ;
// 找到数据范围
const maxValue = Math . max ( ... data . values ) ;
const minValue = 0 ;
const valueRange = maxValue - minValue ;
// 计算X轴和Y轴的比例
const xScale = chartWidth / ( data . values . length - 1 ) ;
const yScale = chartHeight / valueRange ;
// 绘制坐标轴
ctx . beginPath ( ) ;
ctx . strokeStyle = 'rgba(148, 163, 184, 0.5)' ;
ctx . lineWidth = 1 ;
// X轴
ctx . moveTo ( margin . left , margin . top + chartHeight ) ;
ctx . lineTo ( margin . left + chartWidth , margin . top + chartHeight ) ;
ctx . stroke ( ) ;
// Y轴
ctx . moveTo ( margin . left , margin . top ) ;
ctx . lineTo ( margin . left , margin . top + chartHeight ) ;
ctx . stroke ( ) ;
// 绘制网格线
ctx . strokeStyle = 'rgba(148, 163, 184, 0.1)' ;
// 水平网格线
const yGridCount = 5 ;
for ( let i = 0 ; i <= yGridCount ; i ++ ) {
const y = margin . top + chartHeight - ( i * chartHeight / yGridCount ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( margin . left , y ) ;
ctx . lineTo ( margin . left + chartWidth , y ) ;
ctx . stroke ( ) ;
// Y轴刻度
ctx . fillStyle = 'rgba(148, 163, 184, 0.7)' ;
ctx . font = '8px sans-serif' ;
ctx . textAlign = 'right' ;
ctx . textBaseline = 'middle' ;
ctx . fillText ( Math . round ( i * valueRange / yGridCount ) , margin . left - 5 , y ) ;
}
// 绘制数据线
ctx . beginPath ( ) ;
data . values . forEach ( ( value , index ) => {
const x = margin . left + index * xScale ;
const y = margin . top + chartHeight - ( value - minValue ) * yScale ;
if ( index === 0 ) {
ctx . moveTo ( x , y ) ;
} else {
ctx . lineTo ( x , y ) ;
}
// 绘制数据点
ctx . fillStyle = data . lineColor ;
ctx . beginPath ( ) ;
ctx . arc ( x , y , 3 , 0 , 2 * Math . PI ) ;
ctx . fill ( ) ;
// 绘制白色边框
ctx . strokeStyle = '#fff' ;
ctx . lineWidth = 1 ;
ctx . stroke ( ) ;
} ) ;
// 绘制线条
ctx . strokeStyle = data . lineColor ;
ctx . lineWidth = 2 ;
ctx . stroke ( ) ;
// 填充区域
ctx . lineTo ( margin . left + ( data . values . length - 1 ) * xScale , margin . top + chartHeight ) ;
ctx . lineTo ( margin . left , margin . top + chartHeight ) ;
ctx . closePath ( ) ;
ctx . fillStyle = data . areaColor ;
ctx . fill ( ) ;
// 绘制X轴标签
data . labels . forEach ( ( label , index ) => {
const x = margin . left + index * xScale ;
ctx . fillStyle = 'rgba(148, 163, 184, 0.7)' ;
ctx . font = '8px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'top' ;
ctx . fillText ( label , x , margin . top + chartHeight + 5 ) ;
} ) ;
} ;
// 绘制柱状图 - 使用原生Canvas API
const drawBarChart = ( canvas , data ) => {
if ( ! canvas ) return ;
const ctx = canvas . getContext ( '2d' ) ;
const width = canvas . width ;
const height = canvas . height ;
// 清除画布
ctx . clearRect ( 0 , 0 , width , height ) ;
// 边距
const margin = { top : 10 , right : 10 , bottom : 20 , left : 30 } ;
const chartWidth = width - margin . left - margin . right ;
const chartHeight = height - margin . top - margin . bottom ;
// 找到数据范围
const maxValue = Math . max ( ... data . values ) * 1.1 ; // 留10%的余量
const minValue = 0 ;
const valueRange = maxValue - minValue ;
// 计算X轴和Y轴的比例
const barWidth = chartWidth / ( data . values . length * 2 ) ;
const xScale = chartWidth / ( data . values . length ) ;
const yScale = chartHeight / valueRange ;
// 绘制坐标轴
ctx . beginPath ( ) ;
ctx . strokeStyle = 'rgba(148, 163, 184, 0.5)' ;
ctx . lineWidth = 1 ;
// X轴
ctx . moveTo ( margin . left , margin . top + chartHeight ) ;
ctx . lineTo ( margin . left + chartWidth , margin . top + chartHeight ) ;
ctx . stroke ( ) ;
// Y轴
ctx . moveTo ( margin . left , margin . top ) ;
ctx . lineTo ( margin . left , margin . top + chartHeight ) ;
ctx . stroke ( ) ;
// 绘制网格线
ctx . strokeStyle = 'rgba(148, 163, 184, 0.1)' ;
// 水平网格线
const yGridCount = 5 ;
for ( let i = 0 ; i <= yGridCount ; i ++ ) {
const y = margin . top + chartHeight - ( i * chartHeight / yGridCount ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( margin . left , y ) ;
ctx . lineTo ( margin . left + chartWidth , y ) ;
ctx . stroke ( ) ;
// Y轴刻度
ctx . fillStyle = 'rgba(148, 163, 184, 0.7)' ;
ctx . font = '8px sans-serif' ;
ctx . textAlign = 'right' ;
ctx . textBaseline = 'middle' ;
ctx . fillText ( Math . round ( i * valueRange / yGridCount ) , margin . left - 5 , y ) ;
}
// 绘制柱子
data . values . forEach ( ( value , index ) => {
const x = margin . left + index * xScale + ( xScale - barWidth ) / 2 ;
const barHeight = ( value - minValue ) * yScale ;
const y = margin . top + chartHeight - barHeight ;
// 绘制柱子
ctx . fillStyle = data . barColor ;
ctx . fillRect ( x , y , barWidth , barHeight ) ;
// 绘制柱子顶部的值
ctx . fillStyle = 'rgba(226, 232, 240, 0.9)' ;
ctx . font = '8px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'bottom' ;
ctx . fillText ( value , x + barWidth / 2 , y - 3 ) ;
} ) ;
// 绘制X轴标签
data . labels . forEach ( ( label , index ) => {
const x = margin . left + index * xScale + xScale / 2 ;
ctx . fillStyle = 'rgba(148, 163, 184, 0.7)' ;
ctx . font = '8px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'top' ;
ctx . fillText ( label , x , margin . top + chartHeight + 5 ) ;
} ) ;
} ;
// 绘制所有图表
const drawAllCharts = ( ) => {
// 设置Canvas尺寸( 考虑高DPI屏幕)
const setupCanvas = ( canvas ) => {
if ( ! canvas ) return ;
const dpr = window . devicePixelRatio || 1 ;
const rect = canvas . getBoundingClientRect ( ) ;
canvas . width = rect . width * dpr ;
canvas . height = rect . height * dpr ;
const ctx = canvas . getContext ( '2d' ) ;
ctx . scale ( dpr , dpr ) ;
canvas . style . width = ` ${ rect . width } px ` ;
canvas . style . height = ` ${ rect . height } px ` ;
} ;
// 预警类型饼图数据
const pieData = {
labels : [ '设备故障' , '安全预警' , '环境异常' , '物料管理' , '网络问题' ] ,
values : [ 1 , 1 , 1 , 1 , 1 ] ,
colors : [
'#3b82f6' , // 蓝色
'#f97316' , // 橙色
'#eab308' , // 黄色
'#10b981' , // 绿色
'#8b5cf6' // 紫色
]
} ;
// 处理时间折线图数据
const lineData = {
labels : [ '1日' , '2日' , '3日' , '4日' , '5日' , '6日' , '7日' ] ,
values : [ 25 , 32 , 28 , 45 , 36 , 22 , 30 ] ,
lineColor : '#3b82f6' ,
areaColor : 'rgba(59, 130, 246, 0.1)'
} ;
// 每日预警数量柱状图数据
const barData = {
labels : [ '1日' , '2日' , '3日' , '4日' , '5日' , '6日' , '7日' ] ,
values : [ 8 , 12 , 5 , 15 , 7 , 10 , 5 ] ,
barColor : '#06b6d4'
} ;
// 设置并绘制图表
// setupCanvas(eventTypePieChart.value);
setupCanvas ( processingTimeChart . value ) ;
setupCanvas ( dailyEventsChart . value ) ;
// drawPieChart(eventTypePieChart.value, pieData);
drawLineChart ( processingTimeChart . value , lineData ) ;
drawBarChart ( dailyEventsChart . value , barData ) ;
} ;
// 页面加载时初始化
onMounted ( ( ) => {
// 默认选择第一个预警
if ( events . value . length > 0 ) {
selectedEvent . value = events . value [ 0 ] ;
}
// 初始化时间
updateCurrentTime ( ) ;
setInterval ( updateCurrentTime , 1000 ) ;
// 初始化图表
drawAllCharts ( ) ;
// 监听窗口大小变化,重新绘制图表
window . addEventListener ( 'resize' , drawAllCharts ) ;
} ) ;
< / script >