<template>
|
<div class="app-container">
|
|
<!-- 统计概览卡片 -->
|
<div class="stats-overview">
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="stats-content">
|
<div class="stats-icon running">
|
<el-icon><VideoPlay /></el-icon>
|
</div>
|
<div class="stats-info">
|
<div class="stats-value">{{ overviewData.runningDevices }}</div>
|
<div class="stats-label">运行设备</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="stats-content">
|
<div class="stats-icon stopped">
|
<el-icon><VideoPause /></el-icon>
|
</div>
|
<div class="stats-info">
|
<div class="stats-value">{{ overviewData.stoppedDevices }}</div>
|
<div class="stats-label">停机设备</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="stats-content">
|
<div class="stats-icon alarm">
|
<el-icon><Warning /></el-icon>
|
</div>
|
<div class="stats-info">
|
<div class="stats-value">{{ overviewData.alarmCount }}</div>
|
<div class="stats-label">报警数量</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="stats-content">
|
<div class="stats-icon maintenance">
|
<el-icon><Tools /></el-icon>
|
</div>
|
<div class="stats-info">
|
<div class="stats-value">{{ overviewData.maintenanceCount }}</div>
|
<div class="stats-label">维护中</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 主要内容区域 -->
|
<el-row :gutter="20">
|
<!-- 左侧:设备启停记录 -->
|
<el-col :span="12">
|
<el-card class="main-card">
|
<template #header>
|
<div class="card-header">
|
<span>设备启停记录</span>
|
<el-button type="primary" size="small" @click="refreshDeviceRecords">
|
<el-icon><Refresh /></el-icon>
|
刷新
|
</el-button>
|
</div>
|
</template>
|
|
<!-- 设备状态筛选 -->
|
<div class="filter-section">
|
<el-radio-group v-model="deviceFilter" @change="filterDeviceRecords">
|
<el-radio-button label="all">全部</el-radio-button>
|
<el-radio-button label="start">启动</el-radio-button>
|
<el-radio-button label="stop">停机</el-radio-button>
|
</el-radio-group>
|
</div>
|
|
<!-- 设备记录列表 -->
|
<div class="device-records">
|
<div
|
v-for="record in filteredDeviceRecords"
|
:key="record.id"
|
class="device-record"
|
:class="record.type"
|
>
|
<div class="record-icon">
|
<el-icon v-if="record.type === 'start'"><VideoPlay /></el-icon>
|
<el-icon v-else><VideoPause /></el-icon>
|
</div>
|
<div class="record-content">
|
<div class="device-name">{{ record.deviceName }}</div>
|
<div class="record-time">{{ record.time }}</div>
|
<div class="record-status" :class="record.type">
|
{{ record.type === 'start' ? '设备启动' : '设备停机' }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
|
<!-- 右侧:装置开停工信息 -->
|
<el-col :span="12">
|
<el-card class="main-card">
|
<template #header>
|
<div class="card-header">
|
<span>装置开停工信息</span>
|
<el-button type="success" size="small" @click="refreshUnitInfo">
|
<el-icon><Refresh /></el-icon>
|
刷新
|
</el-button>
|
</div>
|
</template>
|
|
<!-- 装置状态筛选 -->
|
<div class="filter-section">
|
<el-radio-group v-model="unitFilter" @change="filterUnitInfo">
|
<el-radio-button label="all">全部</el-radio-button>
|
<el-radio-button label="startup">开工</el-radio-button>
|
<el-radio-button label="shutdown">停工</el-radio-button>
|
<el-radio-button label="unplanned">非计划停工</el-radio-button>
|
</el-radio-group>
|
</div>
|
|
<!-- 装置信息列表 -->
|
<div class="unit-info">
|
<div
|
v-for="unit in filteredUnitInfo"
|
:key="unit.id"
|
class="unit-item"
|
:class="unit.status"
|
>
|
<div class="unit-header">
|
<div class="unit-name">{{ unit.unitName }}</div>
|
<div class="unit-status" :class="unit.status">
|
{{ getUnitStatusText(unit.status) }}
|
</div>
|
</div>
|
<div class="unit-details">
|
<div class="detail-item">
|
<span class="label">开始时间:</span>
|
<span class="value">{{ unit.startTime }}</span>
|
</div>
|
<div class="detail-item" v-if="unit.endTime">
|
<span class="label">结束时间:</span>
|
<span class="value">{{ unit.endTime }}</span>
|
</div>
|
<div class="detail-item" v-if="unit.reason">
|
<span class="label">原因:</span>
|
<span class="value">{{ unit.reason }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 报警信息集中展示 -->
|
<el-card class="alarm-card">
|
<template #header>
|
<div class="card-header">
|
<span>报警信息集中展示</span>
|
<div class="alarm-actions">
|
<el-button type="warning" size="small" @click="refreshAlarms">
|
<el-icon><Refresh /></el-icon>
|
刷新
|
</el-button>
|
<el-button type="danger" size="small" @click="clearAlarms">
|
<el-icon><Delete /></el-icon>
|
清除已处理
|
</el-button>
|
</div>
|
</div>
|
</template>
|
|
<!-- 报警级别筛选 -->
|
<div class="filter-section">
|
<el-radio-group v-model="alarmFilter" @change="filterAlarms">
|
<el-radio-button label="all">全部</el-radio-button>
|
<el-radio-button label="critical">严重</el-radio-button>
|
<el-radio-button label="warning">警告</el-radio-button>
|
<el-radio-button label="info">信息</el-radio-button>
|
</el-radio-group>
|
</div>
|
|
<!-- 报警信息表格 -->
|
<el-table
|
:data="filteredAlarms"
|
style="width: 100%"
|
:header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
|
max-height="400"
|
>
|
<el-table-column
|
align="center"
|
label="序号"
|
type="index"
|
width="60"
|
/>
|
<el-table-column
|
label="报警时间"
|
prop="alarmTime"
|
width="150"
|
align="center"
|
/>
|
<el-table-column
|
label="设备名称"
|
prop="deviceName"
|
width="150"
|
show-overflow-tooltip
|
/>
|
<el-table-column
|
label="报警级别"
|
prop="level"
|
width="100"
|
align="center"
|
>
|
<template #default="scope">
|
<el-tag
|
:type="getAlarmTagType(scope.row.level)"
|
size="small"
|
>
|
{{ scope.row.level }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column
|
label="报警内容"
|
prop="content"
|
min-width="200"
|
show-overflow-tooltip
|
/>
|
<el-table-column
|
label="处理状态"
|
prop="status"
|
width="100"
|
align="center"
|
>
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.status === '已处理' ? 'success' : 'danger'"
|
size="small"
|
>
|
{{ scope.row.status }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column
|
label="处理人"
|
prop="handler"
|
width="100"
|
show-overflow-tooltip
|
/>
|
<el-table-column
|
label="操作"
|
width="120"
|
align="center"
|
>
|
<template #default="scope">
|
<el-button
|
v-if="scope.row.status === '未处理'"
|
type="primary"
|
size="small"
|
@click="handleAlarm(scope.row)"
|
>
|
处理
|
</el-button>
|
<el-button
|
v-else
|
type="info"
|
size="small"
|
disabled
|
>
|
已处理
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import {
|
VideoPlay,
|
VideoPause,
|
Warning,
|
Tools,
|
Refresh,
|
Delete
|
} from '@element-plus/icons-vue'
|
|
// 响应式数据
|
const deviceFilter = ref('all')
|
const unitFilter = ref('all')
|
const alarmFilter = ref('all')
|
|
// 概览数据
|
const overviewData = reactive({
|
runningDevices: 15,
|
stoppedDevices: 3,
|
alarmCount: 8,
|
maintenanceCount: 2
|
})
|
|
// 设备启停记录数据
|
const deviceRecords = ref([
|
{
|
id: 1,
|
deviceName: '压缩机A-001',
|
type: 'start',
|
time: '2024-01-15 08:30:25'
|
},
|
{
|
id: 2,
|
deviceName: '泵B-002',
|
type: 'stop',
|
time: '2024-01-15 08:25:10'
|
},
|
{
|
id: 3,
|
deviceName: '风机C-003',
|
type: 'start',
|
time: '2024-01-15 08:20:15'
|
},
|
{
|
id: 4,
|
deviceName: '搅拌器D-004',
|
type: 'start',
|
time: '2024-01-15 08:15:30'
|
},
|
{
|
id: 5,
|
deviceName: '加热器E-005',
|
type: 'stop',
|
time: '2024-01-15 08:10:45'
|
},
|
{
|
id: 6,
|
deviceName: '冷却器F-006',
|
type: 'start',
|
time: '2024-01-15 08:05:20'
|
}
|
])
|
|
// 装置开停工信息数据
|
const unitInfo = ref([
|
{
|
id: 1,
|
unitName: '反应装置A',
|
status: 'startup',
|
startTime: '2024-01-15 08:00:00',
|
endTime: null,
|
reason: null
|
},
|
{
|
id: 2,
|
unitName: '分离装置B',
|
status: 'shutdown',
|
startTime: '2024-01-15 07:30:00',
|
endTime: '2024-01-15 08:00:00',
|
reason: '计划维护'
|
},
|
{
|
id: 3,
|
unitName: '精制装置C',
|
status: 'unplanned',
|
startTime: '2024-01-15 07:45:00',
|
endTime: null,
|
reason: '设备故障'
|
},
|
{
|
id: 4,
|
unitName: '包装装置D',
|
status: 'startup',
|
startTime: '2024-01-15 08:15:00',
|
endTime: null,
|
reason: null
|
}
|
])
|
|
// 报警信息数据
|
const alarms = ref([
|
{
|
id: 1,
|
alarmTime: '2024-01-15 08:30:00',
|
deviceName: '压缩机A-001',
|
level: '严重',
|
content: '温度过高报警',
|
status: '未处理',
|
handler: ''
|
},
|
{
|
id: 2,
|
alarmTime: '2024-01-15 08:25:00',
|
deviceName: '泵B-002',
|
level: '警告',
|
content: '压力异常',
|
status: '已处理',
|
handler: '张三'
|
},
|
{
|
id: 3,
|
alarmTime: '2024-01-15 08:20:00',
|
deviceName: '风机C-003',
|
level: '信息',
|
content: '运行时间达到维护周期',
|
status: '未处理',
|
handler: ''
|
},
|
{
|
id: 4,
|
alarmTime: '2024-01-15 08:15:00',
|
deviceName: '搅拌器D-004',
|
level: '严重',
|
content: '振动异常',
|
status: '未处理',
|
handler: ''
|
},
|
{
|
id: 5,
|
alarmTime: '2024-01-15 08:10:00',
|
deviceName: '加热器E-005',
|
level: '警告',
|
content: '加热效率下降',
|
status: '已处理',
|
handler: '李四'
|
}
|
])
|
|
// 计算属性 - 过滤后的设备记录
|
const filteredDeviceRecords = computed(() => {
|
if (deviceFilter.value === 'all') {
|
return deviceRecords.value
|
}
|
return deviceRecords.value.filter(record => record.type === deviceFilter.value)
|
})
|
|
// 计算属性 - 过滤后的装置信息
|
const filteredUnitInfo = computed(() => {
|
if (unitFilter.value === 'all') {
|
return unitInfo.value
|
}
|
return unitInfo.value.filter(unit => unit.status === unitFilter.value)
|
})
|
|
// 计算属性 - 过滤后的报警信息
|
const filteredAlarms = computed(() => {
|
if (alarmFilter.value === 'all') {
|
return alarms.value
|
}
|
return alarms.value.filter(alarm => alarm.level === alarmFilter.value)
|
})
|
|
// 方法
|
const refreshDeviceRecords = () => {
|
ElMessage.success('设备记录已刷新')
|
// 这里可以调用API获取最新数据
|
}
|
|
const refreshUnitInfo = () => {
|
ElMessage.success('装置信息已刷新')
|
// 这里可以调用API获取最新数据
|
}
|
|
const refreshAlarms = () => {
|
ElMessage.success('报警信息已刷新')
|
// 这里可以调用API获取最新数据
|
}
|
|
const clearAlarms = async () => {
|
try {
|
await ElMessageBox.confirm('确定要清除所有已处理的报警信息吗?', '确认清除', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
|
alarms.value = alarms.value.filter(alarm => alarm.status === '未处理')
|
ElMessage.success('已清除所有已处理的报警信息')
|
} catch {
|
// 用户取消操作
|
}
|
}
|
|
const filterDeviceRecords = () => {
|
// 过滤逻辑已在计算属性中处理
|
}
|
|
const filterUnitInfo = () => {
|
// 过滤逻辑已在计算属性中处理
|
}
|
|
const filterAlarms = () => {
|
// 过滤逻辑已在计算属性中处理
|
}
|
|
const getUnitStatusText = (status) => {
|
const statusMap = {
|
startup: '开工中',
|
shutdown: '已停工',
|
unplanned: '非计划停工'
|
}
|
return statusMap[status] || status
|
}
|
|
const getAlarmTagType = (level) => {
|
const typeMap = {
|
'严重': 'danger',
|
'警告': 'warning',
|
'信息': 'info'
|
}
|
return typeMap[level] || 'info'
|
}
|
|
const handleAlarm = (alarm) => {
|
ElMessageBox.prompt('请输入处理说明', '处理报警', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
inputPlaceholder: '请输入处理说明'
|
}).then(({ value }) => {
|
alarm.status = '已处理'
|
alarm.handler = '当前用户' // 这里应该获取当前登录用户
|
ElMessage.success('报警处理完成')
|
}).catch(() => {
|
// 用户取消操作
|
})
|
}
|
|
// 组件挂载时初始化数据
|
onMounted(() => {
|
// 这里可以调用API获取初始数据
|
console.log('运行管理页面已加载')
|
})
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
background: #f5f7fa;
|
min-height: 100vh;
|
}
|
|
|
.stats-overview {
|
margin-bottom: 20px;
|
}
|
|
.stats-card {
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
.stats-content {
|
display: flex;
|
align-items: center;
|
padding: 10px 0;
|
}
|
|
.stats-icon {
|
width: 50px;
|
height: 50px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 15px;
|
font-size: 24px;
|
color: #fff;
|
}
|
|
.stats-icon.running {
|
background: linear-gradient(135deg, #67C23A, #85CE61);
|
}
|
|
.stats-icon.stopped {
|
background: linear-gradient(135deg, #F56C6C, #F78989);
|
}
|
|
.stats-icon.alarm {
|
background: linear-gradient(135deg, #E6A23C, #EEBE77);
|
}
|
|
.stats-icon.maintenance {
|
background: linear-gradient(135deg, #409EFF, #66B1FF);
|
}
|
|
.stats-info {
|
flex: 1;
|
}
|
|
.stats-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #333;
|
line-height: 1;
|
margin-bottom: 5px;
|
}
|
|
.stats-label {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.main-card {
|
margin-bottom: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.filter-section {
|
margin-bottom: 15px;
|
}
|
|
.device-records {
|
max-height: 400px;
|
overflow-y: auto;
|
}
|
|
.device-record {
|
display: flex;
|
align-items: center;
|
padding: 12px;
|
margin-bottom: 8px;
|
border-radius: 6px;
|
background: #f8f9fa;
|
border-left: 4px solid #ddd;
|
}
|
|
.device-record.start {
|
border-left-color: #67C23A;
|
background: #f0f9ff;
|
}
|
|
.device-record.stop {
|
border-left-color: #F56C6C;
|
background: #fef0f0;
|
}
|
|
.record-icon {
|
width: 32px;
|
height: 32px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 12px;
|
font-size: 16px;
|
color: #fff;
|
}
|
|
.device-record.start .record-icon {
|
background: #67C23A;
|
}
|
|
.device-record.stop .record-icon {
|
background: #F56C6C;
|
}
|
|
.record-content {
|
flex: 1;
|
}
|
|
.device-name {
|
font-weight: 500;
|
color: #333;
|
margin-bottom: 4px;
|
}
|
|
.record-time {
|
font-size: 12px;
|
color: #666;
|
margin-bottom: 4px;
|
}
|
|
.record-status {
|
font-size: 12px;
|
padding: 2px 8px;
|
border-radius: 12px;
|
display: inline-block;
|
}
|
|
.record-status.start {
|
background: #e1f3d8;
|
color: #67C23A;
|
}
|
|
.record-status.stop {
|
background: #fde2e2;
|
color: #F56C6C;
|
}
|
|
.unit-info {
|
max-height: 400px;
|
overflow-y: auto;
|
}
|
|
.unit-item {
|
padding: 15px;
|
margin-bottom: 12px;
|
border-radius: 6px;
|
background: #f8f9fa;
|
border-left: 4px solid #ddd;
|
}
|
|
.unit-item.startup {
|
border-left-color: #67C23A;
|
background: #f0f9ff;
|
}
|
|
.unit-item.shutdown {
|
border-left-color: #409EFF;
|
background: #f0f9ff;
|
}
|
|
.unit-item.unplanned {
|
border-left-color: #E6A23C;
|
background: #fdf6ec;
|
}
|
|
.unit-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 8px;
|
}
|
|
.unit-name {
|
font-weight: 500;
|
color: #333;
|
font-size: 14px;
|
}
|
|
.unit-status {
|
font-size: 12px;
|
padding: 4px 8px;
|
border-radius: 12px;
|
display: inline-block;
|
}
|
|
.unit-status.startup {
|
background: #e1f3d8;
|
color: #67C23A;
|
}
|
|
.unit-status.shutdown {
|
background: #e1f3ff;
|
color: #409EFF;
|
}
|
|
.unit-status.unplanned {
|
background: #fdf6ec;
|
color: #E6A23C;
|
}
|
|
.unit-details {
|
font-size: 12px;
|
color: #666;
|
}
|
|
.detail-item {
|
margin-bottom: 4px;
|
}
|
|
.detail-item .label {
|
font-weight: 500;
|
margin-right: 4px;
|
}
|
|
.alarm-card {
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
.alarm-actions {
|
display: flex;
|
gap: 8px;
|
}
|
|
:deep(.el-card__header) {
|
background: #f8f9fa;
|
border-bottom: 1px solid #e9ecef;
|
font-weight: 500;
|
}
|
|
:deep(.el-table .el-table__header-wrapper th) {
|
background-color: #F0F1F5 !important;
|
color: #333333;
|
font-weight: 600;
|
}
|
|
:deep(.el-table .el-table__body-wrapper td) {
|
padding: 8px 0;
|
}
|
|
:deep(.el-radio-button__inner) {
|
border-radius: 4px;
|
}
|
|
:deep(.el-radio-button:first-child .el-radio-button__inner) {
|
border-left: 1px solid #dcdfe6;
|
border-radius: 4px;
|
}
|
|
:deep(.el-radio-button:last-child .el-radio-button__inner) {
|
border-radius: 4px;
|
}
|
</style>
|