gaoluyang
2025-09-20 6f5bc9367a1bfaa259038268c150b3b91b7c86e4
设备运行管理
已添加1个文件
824 ■■■■■ 文件已修改
src/views/equipmentManagement/operationManagement/index.vue 824 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/operationManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,824 @@
<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>