zouyu
6 天以前 09f49269f00a1fa70bbf9d52476b01bf917c948e
代码调整5
已添加5个文件
已修改5个文件
已删除3个文件
5083 ■■■■ 文件已修改
.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/contractor/assets/index.vue 637 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/contractor/compliance/index.vue 664 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/contractor/list/index.vue 572 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/Dashboard.vue 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/device/DeviceManagement.vue 513 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/diagnosis/FaultDiagnosis.vue 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 934 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/maintenance/MaintenanceManagement.vue 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitoring/RealTimeMonitoring.vue 515 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,5 +1,5 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æ‰¿åŒ…商管理系统
VITE_APP_TITLE = è®¾å¤‡åœ¨çº¿ç›‘测诊断及维修系统
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
.env.production
@@ -1,5 +1,5 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æ‰¿åŒ…商管理系统
VITE_APP_TITLE = è®¾å¤‡åœ¨çº¿ç›‘测诊断及维修系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
src/main.js
@@ -76,7 +76,7 @@
app.config.globalProperties.addDateRange = addDateRange;
app.config.globalProperties.selectDictLabel = selectDictLabel;
app.config.globalProperties.selectDictLabels = selectDictLabels;
app.config.globalProperties.javaApi = "http://10.136.12.71:8018";
app.config.globalProperties.javaApi = "http://10.136.12.71:8020";
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
src/views/contractor/assets/index.vue
ÎļþÒÑɾ³ý
src/views/contractor/compliance/index.vue
ÎļþÒÑɾ³ý
src/views/contractor/list/index.vue
ÎļþÒÑɾ³ý
src/views/dashboard/Dashboard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,301 @@
<template>
  <div class="dashboard-container">
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <el-row :gutter="20">
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">设备在线率</div>
            <div class="card-value">{{ onlineRate }}%</div>
            <div class="card-desc">当前在线设备数:{{ onlineCount }} / {{ totalDevices }}</div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">故障预警数</div>
            <div class="card-value">{{ warningCount }}</div>
            <div class="card-desc">高风险:{{ highRiskCount }} | ä¸­é£Žé™©ï¼š{{ mediumRiskCount }} | ä½Žé£Žé™©ï¼š{{ lowRiskCount }}</div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">待处理维修单</div>
            <div class="card-value">{{ pendingOrders }}</div>
            <div class="card-desc">处理中:{{ processingOrders }} | å·²å®Œæˆï¼š{{ completedOrders }}</div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">重点设备运行状态</div>
            <div class="card-value">{{ normalDevices }} æ­£å¸¸</div>
            <div class="card-desc">异常:{{ abnormalDevices }} | æ•…障:{{ faultDevices }}</div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <el-row :gutter="20" style="margin-top: 20px;">
      <!-- è®¾å¤‡å¥åº·åº¦è¶‹åŠ¿å›¾ -->
      <el-col :span="12">
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>设备健康度趋势图</span>
            </div>
          </template>
          <div ref="healthChartRef" class="chart-container"></div>
        </el-card>
      </el-col>
      <!-- è¿‘7日故障类型统计 -->
      <el-col :span="12">
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>近7日故障类型统计</span>
            </div>
          </template>
          <div ref="faultChartRef" class="chart-container"></div>
        </el-card>
      </el-col>
    </el-row>
    <!-- é‡ç‚¹è®¾å¤‡è¿è¡ŒçŠ¶æ€å¡ç‰‡ -->
    <el-card shadow="hover" style="margin-top: 20px;">
      <template #header>
        <div class="card-header">
          <span>重点设备运行状态</span>
        </div>
      </template>
      <el-table :data="keyDevices" stripe style="width: 100%">
        <el-table-column prop="name" label="设备名称" width="180"></el-table-column>
        <el-table-column prop="model" label="型号" width="120"></el-table-column>
        <el-table-column prop="ip" label="IP地址" width="150"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'online' ? 'success' : scope.row.status === 'warning' ? 'warning' : 'danger'">
              {{ scope.row.status === 'online' ? '在线' : scope.row.status === 'warning' ? '预警' : '故障' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="temperature" label="温度(℃)" width="100"></el-table-column>
        <el-table-column prop="pressure" label="压力(MPa)" width="100"></el-table-column>
        <el-table-column prop="speed" label="转速(rpm)" width="100"></el-table-column>
        <el-table-column prop="health" label="健康度" width="120">
          <template #default="scope">
            <el-progress :percentage="scope.row.health" :stroke-width="10"></el-progress>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
// ç»Ÿè®¡æ•°æ®
const onlineRate = ref(85)
const onlineCount = ref(170)
const totalDevices = ref(200)
const warningCount = ref(23)
const highRiskCount = ref(5)
const mediumRiskCount = ref(12)
const lowRiskCount = ref(6)
const pendingOrders = ref(15)
const processingOrders = ref(8)
const completedOrders = ref(45)
const normalDevices = ref(162)
const abnormalDevices = ref(18)
const faultDevices = ref(10)
// é‡ç‚¹è®¾å¤‡åˆ—表
const keyDevices = ref([
  { name: '空压机A-001', model: 'KA-200', ip: '192.168.1.101', status: 'online', temperature: 42, pressure: 0.8, speed: 1450, health: 92 },
  { name: '冷却塔B-002', model: 'CT-300', ip: '192.168.1.102', status: 'warning', temperature: 58, pressure: 0.6, speed: 980, health: 75 },
  { name: 'æ°´æ³µC-003', model: 'WP-150', ip: '192.168.1.103', status: 'online', temperature: 38, pressure: 1.2, speed: 1200, health: 88 },
  { name: '发电机D-004', model: 'GE-500', ip: '192.168.1.104', status: 'danger', temperature: 75, pressure: 0.5, speed: 1500, health: 60 },
  { name: '变压器E-005', model: 'TR-1000', ip: '192.168.1.105', status: 'online', temperature: 45, pressure: 0, speed: 0, health: 95 }
])
// å›¾è¡¨å¼•用
const healthChartRef = ref(null)
const faultChartRef = ref(null)
let healthChart = null
let faultChart = null
// å¥åº·åº¦è¶‹åŠ¿æ•°æ®
const healthTrendData = {
  dates: ['12-10', '12-11', '12-12', '12-13', '12-14', '12-15', '12-16'],
  values: [88, 90, 85, 87, 92, 91, 93]
}
// æ•…障类型统计数据
const faultTypeData = {
  types: ['温度异常', '压力超标', '转速异常', '振动过大', '其他'],
  values: [15, 8, 12, 6, 3]
}
// åˆå§‹åŒ–健康度趋势图
const initHealthChart = () => {
  if (healthChartRef.value) {
    healthChart = echarts.init(healthChartRef.value)
    const option = {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross',
          label: {
            backgroundColor: '#6a7985'
          }
        }
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: healthTrendData.dates
      },
      yAxis: {
        type: 'value',
        min: 70,
        max: 100,
        name: '健康度(%)'
      },
      series: [
        {
          name: '健康度',
          type: 'line',
          stack: '总量',
          areaStyle: {},
          emphasis: {
            focus: 'series'
          },
          data: healthTrendData.values,
          itemStyle: {
            color: '#67c23a'
          },
          lineStyle: {
            width: 3
          }
        }
      ]
    }
    healthChart.setOption(option)
  }
}
// åˆå§‹åŒ–故障类型统计饼图
const initFaultChart = () => {
  if (faultChartRef.value) {
    faultChart = echarts.init(faultChartRef.value)
    const option = {
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '故障类型',
          type: 'pie',
          radius: '50%',
          data: faultTypeData.types.map((type, index) => ({
            name: type,
            value: faultTypeData.values[index]
          })),
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    }
    faultChart.setOption(option)
  }
}
// ç›‘听窗口大小变化,调整图表大小
const handleResize = () => {
  healthChart?.resize()
  faultChart?.resize()
}
onMounted(() => {
  initHealthChart()
  initFaultChart()
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
  healthChart?.dispose()
  faultChart?.dispose()
})
</script>
<style scoped>
.dashboard-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.statistics-card {
  height: 180px;
}
.card-content {
  display: flex;
  flex-direction: column;
  height: 100%;
  justify-content: center;
  align-items: center;
}
.card-title {
  font-size: 16px;
  color: #606266;
  margin-bottom: 10px;
}
.card-value {
  font-size: 32px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 8px;
}
.card-desc {
  font-size: 14px;
  color: #909399;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-container {
  width: 100%;
  height: 350px;
}
</style>
src/views/device/DeviceManagement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,513 @@
<template>
  <div class="device-management-container">
    <el-card shadow="hover">
      <template #header>
        <div class="card-header">
          <span>设备管理</span>
          <div class="header-buttons">
            <el-button type="primary" @click="showAddDeviceDialog">
              <el-icon-plus /> æ·»åŠ è®¾å¤‡
            </el-button>
            <el-button @click="exportDevices">
              <el-icon-download /> å¯¼å‡º
            </el-button>
            <el-button @click="showImportDialog">
              <el-icon-upload /> å¯¼å…¥
            </el-button>
          </div>
        </div>
      </template>
      <!-- ç­›é€‰æ¡ä»¶ -->
      <el-form :inline="true" :model="filterForm" class="device-filter-form">
        <el-form-item label="设备名称">
          <el-input v-model="filterForm.name" placeholder="请输入设备名称" clearable></el-input>
        </el-form-item>
        <el-form-item label="型号">
          <el-input v-model="filterForm.model" placeholder="请输入型号" clearable></el-input>
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="filterForm.status" placeholder="请选择状态" clearable>
            <el-option label="在线" value="online"></el-option>
            <el-option label="离线" value="offline"></el-option>
            <el-option label="故障" value="fault"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleFilter">查询</el-button>
          <el-button @click="resetFilter">重置</el-button>
        </el-form-item>
      </el-form>
      <!-- è®¾å¤‡åˆ—表 -->
      <el-table :data="filteredDevices" stripe style="width: 100%" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="id" label="设备ID" width="100"></el-table-column>
        <el-table-column prop="name" label="设备名称" width="180"></el-table-column>
        <el-table-column prop="model" label="型号" width="120"></el-table-column>
        <el-table-column prop="ip" label="IP地址" width="150"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'online' ? 'success' : scope.row.status === 'offline' ? 'info' : 'danger'">
              {{ scope.row.status === 'online' ? '在线' : scope.row.status === 'offline' ? '离线' : '故障' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="location" label="安装位置" width="180"></el-table-column>
        <el-table-column prop="installDate" label="安装日期" width="150"></el-table-column>
        <el-table-column prop="manufacturer" label="制造商" width="150"></el-table-column>
        <el-table-column label="操作" width="220" fixed="right">
          <template #default="scope">
            <el-button type="text" size="small" @click="showDeviceDetail(scope.row)">
              è¯¦æƒ…
            </el-button>
            <el-button type="text" size="small" @click="showEditDeviceDialog(scope.row)">
              ç¼–辑
            </el-button>
            <el-button type="text" size="small" @click="handleDelete(scope.row)">
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <div class="pagination-container">
        <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          :total="filteredDevices.length"
          :current-page="currentPage"
          :page-sizes="[10, 20, 50, 100]"
          :page-size="pageSize"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        ></el-pagination>
      </div>
    </el-card>
    <!-- æ·»åŠ è®¾å¤‡å¯¹è¯æ¡† -->
    <el-dialog v-model="addDeviceDialogVisible" title="添加设备" width="600px">
      <el-form :model="deviceForm" :rules="deviceRules" ref="deviceFormRef" label-width="100px">
        <el-form-item label="设备名称" prop="name">
          <el-input v-model="deviceForm.name" placeholder="请输入设备名称"></el-input>
        </el-form-item>
        <el-form-item label="型号" prop="model">
          <el-input v-model="deviceForm.model" placeholder="请输入型号"></el-input>
        </el-form-item>
        <el-form-item label="IP地址" prop="ip">
          <el-input v-model="deviceForm.ip" placeholder="请输入IP地址"></el-input>
        </el-form-item>
        <el-form-item label="安装位置" prop="location">
          <el-input v-model="deviceForm.location" placeholder="请输入安装位置"></el-input>
        </el-form-item>
        <el-form-item label="制造商" prop="manufacturer">
          <el-input v-model="deviceForm.manufacturer" placeholder="请输入制造商"></el-input>
        </el-form-item>
        <el-form-item label="安装日期" prop="installDate">
          <el-date-picker v-model="deviceForm.installDate" type="date" placeholder="选择安装日期" style="width: 100%"></el-date-picker>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="deviceForm.status" placeholder="请选择状态">
            <el-option label="在线" value="online"></el-option>
            <el-option label="离线" value="offline"></el-option>
            <el-option label="故障" value="fault"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="addDeviceDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleAddDevice">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç¼–辑设备对话框 -->
    <el-dialog v-model="editDeviceDialogVisible" title="编辑设备" width="600px">
      <el-form :model="deviceForm" :rules="deviceRules" ref="deviceFormRef" label-width="100px">
        <el-form-item label="设备名称" prop="name">
          <el-input v-model="deviceForm.name" placeholder="请输入设备名称"></el-input>
        </el-form-item>
        <el-form-item label="型号" prop="model">
          <el-input v-model="deviceForm.model" placeholder="请输入型号"></el-input>
        </el-form-item>
        <el-form-item label="IP地址" prop="ip">
          <el-input v-model="deviceForm.ip" placeholder="请输入IP地址"></el-input>
        </el-form-item>
        <el-form-item label="安装位置" prop="location">
          <el-input v-model="deviceForm.location" placeholder="请输入安装位置"></el-input>
        </el-form-item>
        <el-form-item label="制造商" prop="manufacturer">
          <el-input v-model="deviceForm.manufacturer" placeholder="请输入制造商"></el-input>
        </el-form-item>
        <el-form-item label="安装日期" prop="installDate">
          <el-date-picker v-model="deviceForm.installDate" type="date" placeholder="选择安装日期" style="width: 100%"></el-date-picker>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="deviceForm.status" placeholder="请选择状态">
            <el-option label="在线" value="online"></el-option>
            <el-option label="离线" value="offline"></el-option>
            <el-option label="故障" value="fault"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="editDeviceDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleEditDevice">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- è®¾å¤‡è¯¦æƒ…对话框 -->
    <el-dialog v-model="deviceDetailDialogVisible" title="设备详情" width="600px">
      <el-descriptions :column="1" border>
        <el-descriptions-item label="设备名称">{{ selectedDevice.name }}</el-descriptions-item>
        <el-descriptions-item label="设备ID">{{ selectedDevice.id }}</el-descriptions-item>
        <el-descriptions-item label="型号">{{ selectedDevice.model }}</el-descriptions-item>
        <el-descriptions-item label="IP地址">{{ selectedDevice.ip }}</el-descriptions-item>
        <el-descriptions-item label="状态">
          <el-tag :type="selectedDevice.status === 'online' ? 'success' : selectedDevice.status === 'offline' ? 'info' : 'danger'">
            {{ selectedDevice.status === 'online' ? '在线' : selectedDevice.status === 'offline' ? '离线' : '故障' }}
          </el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="安装位置">{{ selectedDevice.location }}</el-descriptions-item>
        <el-descriptions-item label="制造商">{{ selectedDevice.manufacturer }}</el-descriptions-item>
        <el-descriptions-item label="安装日期">{{ selectedDevice.installDate }}</el-descriptions-item>
        <el-descriptions-item label="创建时间">{{ selectedDevice.createTime }}</el-descriptions-item>
      </el-descriptions>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="deviceDetailDialogVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog v-model="importDialogVisible" title="导入设备" width="400px">
      <el-upload
        class="upload-demo"
        action="#"
        :on-change="handleFileChange"
        :auto-upload="false"
        accept=".xlsx,.xls"
      >
        <el-button type="primary">选择文件</el-button>
        <template #tip>
          <div class="el-upload__tip">
            åªèƒ½ä¸Šä¼  xlsx/xls æ–‡ä»¶ï¼Œä¸”不超过 2MB
          </div>
        </template>
      </el-upload>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="importDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleImport">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
// è®¾å¤‡åˆ—表数据
const devices = ref([
  {
    id: 'D001',
    name: '空压机A-001',
    model: 'KA-200',
    ip: '192.168.1.101',
    status: 'online',
    location: '车间A-1区',
    manufacturer: '康普斯',
    installDate: '2023-05-10',
    createTime: '2023-05-10 10:30:00'
  },
  {
    id: 'D002',
    name: '冷却塔B-002',
    model: 'CT-300',
    ip: '192.168.1.102',
    status: 'warning',
    location: '车间B-2区',
    manufacturer: '良机',
    installDate: '2023-06-15',
    createTime: '2023-06-15 14:20:00'
  },
  {
    id: 'D003',
    name: 'æ°´æ³µC-003',
    model: 'WP-150',
    ip: '192.168.1.103',
    status: 'online',
    location: '车间C-3区',
    manufacturer: '格兰富',
    installDate: '2023-07-20',
    createTime: '2023-07-20 09:15:00'
  },
  {
    id: 'D004',
    name: '发电机D-004',
    model: 'GE-500',
    ip: '192.168.1.104',
    status: 'fault',
    location: '机房',
    manufacturer: '卡特彼勒',
    installDate: '2023-08-25',
    createTime: '2023-08-25 16:45:00'
  },
  {
    id: 'D005',
    name: '变压器E-005',
    model: 'TR-1000',
    ip: '192.168.1.105',
    status: 'online',
    location: '配电房',
    manufacturer: 'ABB',
    installDate: '2023-09-30',
    createTime: '2023-09-30 11:20:00'
  }
])
// ç­›é€‰è¡¨å•
const filterForm = ref({
  name: '',
  model: '',
  status: ''
})
// åˆ†é¡µæ•°æ®
const currentPage = ref(1)
const pageSize = ref(10)
// å¯¹è¯æ¡†çŠ¶æ€
const addDeviceDialogVisible = ref(false)
const editDeviceDialogVisible = ref(false)
const deviceDetailDialogVisible = ref(false)
const importDialogVisible = ref(false)
// è®¾å¤‡è¡¨å•数据
const deviceForm = ref({
  id: '',
  name: '',
  model: '',
  ip: '',
  status: 'online',
  location: '',
  manufacturer: '',
  installDate: ''
})
// è¡¨å•验证规则
const deviceRules = ref({
  name: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
  model: [{ required: true, message: '请输入型号', trigger: 'blur' }],
  ip: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
  location: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
  manufacturer: [{ required: true, message: '请输入制造商', trigger: 'blur' }],
  installDate: [{ required: true, message: '请选择安装日期', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
})
// è¡¨å•引用
const deviceFormRef = ref(null)
// é€‰ä¸­çš„设备
const selectedDevice = ref({})
// é€‰ä¸­çš„设备列表(用于批量操作)
const selectedDevices = ref([])
// å¯¼å…¥çš„æ–‡ä»¶
const importFile = ref(null)
// è¿‡æ»¤åŽçš„设备列表
const filteredDevices = computed(() => {
  let result = [...devices.value]
  // æŒ‰åç§°ç­›é€‰
  if (filterForm.value.name) {
    result = result.filter(device => device.name.includes(filterForm.value.name))
  }
  // æŒ‰åž‹å·ç­›é€‰
  if (filterForm.value.model) {
    result = result.filter(device => device.model.includes(filterForm.value.model))
  }
  // æŒ‰çŠ¶æ€ç­›é€‰
  if (filterForm.value.status) {
    result = result.filter(device => device.status === filterForm.value.status)
  }
  return result
})
// æ˜¾ç¤ºæ·»åŠ è®¾å¤‡å¯¹è¯æ¡†
const showAddDeviceDialog = () => {
  // é‡ç½®è¡¨å•
  deviceForm.value = {
    id: '',
    name: '',
    model: '',
    ip: '',
    status: 'online',
    location: '',
    manufacturer: '',
    installDate: ''
  }
  addDeviceDialogVisible.value = true
}
// æ˜¾ç¤ºç¼–辑设备对话框
const showEditDeviceDialog = (device) => {
  deviceForm.value = { ...device }
  editDeviceDialogVisible.value = true
}
// æ˜¾ç¤ºè®¾å¤‡è¯¦æƒ…
const showDeviceDetail = (device) => {
  selectedDevice.value = { ...device }
  deviceDetailDialogVisible.value = true
}
// æ˜¾ç¤ºå¯¼å…¥å¯¹è¯æ¡†
const showImportDialog = () => {
  importDialogVisible.value = true
}
// å¤„理添加设备
const handleAddDevice = () => {
  // æ¨¡æ‹Ÿæ·»åŠ è®¾å¤‡
  const newDevice = {
    ...deviceForm.value,
    id: `D${String(devices.value.length + 1).padStart(3, '0')}`,
    createTime: new Date().toLocaleString()
  }
  devices.value.push(newDevice)
  addDeviceDialogVisible.value = false
  ElMessage.success('设备添加成功')
}
// å¤„理编辑设备
const handleEditDevice = () => {
  // æ¨¡æ‹Ÿç¼–辑设备
  const index = devices.value.findIndex(device => device.id === deviceForm.value.id)
  if (index !== -1) {
    devices.value[index] = { ...deviceForm.value }
    editDeviceDialogVisible.value = false
    ElMessage.success('设备编辑成功')
  }
}
// å¤„理删除设备
const handleDelete = (device) => {
  ElMessageBox.confirm('确定要删除该设备吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    // æ¨¡æ‹Ÿåˆ é™¤è®¾å¤‡
    const index = devices.value.findIndex(item => item.id === device.id)
    if (index !== -1) {
      devices.value.splice(index, 1)
      ElMessage.success('设备删除成功')
    }
  }).catch(() => {
    // å–消删除
  })
}
// å¤„理筛选
const handleFilter = () => {
  // ç­›é€‰é€»è¾‘已经在computed中实现
}
// é‡ç½®ç­›é€‰æ¡ä»¶
const resetFilter = () => {
  filterForm.value = {
    name: '',
    model: '',
    status: ''
  }
}
// å¤„理分页大小变化
const handleSizeChange = (size) => {
  pageSize.value = size
  currentPage.value = 1
}
// å¤„理当前页变化
const handleCurrentChange = (current) => {
  currentPage.value = current
}
// å¤„理文件变化(导入)
const handleFileChange = (file) => {
  importFile.value = file
}
// å¤„理导入
const handleImport = () => {
  // æ¨¡æ‹Ÿå¯¼å…¥
  if (importFile.value) {
    importDialogVisible.value = false
    ElMessage.success('设备导入成功')
    importFile.value = null
  } else {
    ElMessage.warning('请选择要导入的文件')
  }
}
// å¯¼å‡ºè®¾å¤‡
const exportDevices = () => {
  // æ¨¡æ‹Ÿå¯¼å‡º
  ElMessage.success('设备导出成功')
}
// å¤„理选择变化(用于批量操作)
const handleSelectionChange = (selection) => {
  selectedDevices.value = selection
}
</script>
<style scoped>
.device-management-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.header-buttons {
  display: flex;
  gap: 10px;
}
.device-filter-form {
  margin-bottom: 20px;
  padding: 10px 0;
  border-bottom: 1px solid #ebeef5;
}
.pagination-container {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
}
:deep(.el-icon-plus),
:deep(.el-icon-download),
:deep(.el-icon-upload) {
  margin-right: 5px;
}
</style>
src/views/diagnosis/FaultDiagnosis.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,411 @@
<template>
  <div class="fault-diagnosis-container">
    <el-row :gutter="20">
      <!-- å·¦ä¾§ï¼šæ•…障预警列表 -->
      <el-col :span="12">
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>故障预警列表</span>
            </div>
          </template>
          <el-table :data="warningList" stripe style="width: 100%" @row-click="handleWarningClick">
            <el-table-column prop="deviceName" label="设备名称" width="180"></el-table-column>
            <el-table-column prop="warningType" label="预警类型" width="120"></el-table-column>
            <el-table-column prop="riskLevel" label="风险等级" width="100">
              <template #default="scope">
                <el-tag :type="scope.row.riskLevel === 'high' ? 'danger' : scope.row.riskLevel === 'medium' ? 'warning' : 'info'">
                  {{ scope.row.riskLevel === 'high' ? '高' : scope.row.riskLevel === 'medium' ? '中' : '低' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="occurTime" label="发生时间" width="180"></el-table-column>
            <el-table-column prop="status" label="处理状态" width="100">
              <template #default="scope">
                <el-tag :type="scope.row.status === 'pending' ? 'warning' : 'success'">
                  {{ scope.row.status === 'pending' ? '待处理' : '已处理' }}
                </el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
        <!-- æ•…障历史记录查询 -->
        <el-card shadow="hover" style="margin-top: 20px;">
          <template #header>
            <div class="card-header">
              <span>故障历史记录</span>
            </div>
          </template>
          <el-form :inline="true" :model="historyFilterForm" class="history-filter-form">
            <el-form-item label="设备">
              <el-select v-model="historyFilterForm.deviceId" placeholder="请选择设备" clearable>
                <el-option
                  v-for="device in devices"
                  :key="device.id"
                  :label="device.name"
                  :value="device.id"
                ></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="时间范围">
              <el-date-picker
                v-model="historyTimeRange"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="handleHistorySearch">查询</el-button>
            </el-form-item>
          </el-form>
          <el-table :data="historyList" stripe style="width: 100%" size="small">
            <el-table-column prop="deviceName" label="设备名称" width="150"></el-table-column>
            <el-table-column prop="faultType" label="故障类型" width="120"></el-table-column>
            <el-table-column prop="occurTime" label="发生时间" width="150"></el-table-column>
            <el-table-column prop="dealTime" label="处理时间" width="150"></el-table-column>
            <el-table-column prop="status" label="状态" width="100">
              <template #default="scope">
                <el-tag type="success">{{ scope.row.status }}</el-tag>
              </template>
            </el-table-column>
          </el-table>
          <div class="pagination-container">
            <el-pagination
              background
              layout="total, prev, pager, next"
              :total="historyList.length"
              :page-size="5"
              size="small"
            ></el-pagination>
          </div>
        </el-card>
      </el-col>
      <!-- å³ä¾§ï¼šæ•…障诊断结果 -->
      <el-col :span="12">
        <!-- æ•…障诊断结果 -->
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>故障诊断结果</span>
              <el-button type="primary" size="small" @click="handleDiagnosis">重新诊断</el-button>
            </div>
          </template>
          <div v-if="currentWarning" class="diagnosis-result">
            <h3>{{ currentWarning.deviceName }} - {{ currentWarning.warningType }}</h3>
            <el-descriptions :column="1" border>
              <el-descriptions-item label="风险等级">
                <el-tag :type="currentWarning.riskLevel === 'high' ? 'danger' : currentWarning.riskLevel === 'medium' ? 'warning' : 'info'">
                  {{ currentWarning.riskLevel === 'high' ? '高' : currentWarning.riskLevel === 'medium' ? '中' : '低' }}
                </el-tag>
              </el-descriptions-item>
              <el-descriptions-item label="发生时间">{{ currentWarning.occurTime }}</el-descriptions-item>
              <el-descriptions-item label="原因推测">{{ diagnosisResult.reason }}</el-descriptions-item>
              <el-descriptions-item label="影响范围">{{ diagnosisResult.impact }}</el-descriptions-item>
              <el-descriptions-item label="处理建议">{{ diagnosisResult.suggestion }}</el-descriptions-item>
            </el-descriptions>
          </div>
          <div v-else class="no-selection">
            <el-empty description="请选择一个预警项查看诊断结果"></el-empty>
          </div>
        </el-card>
        <!-- é¢„测性诊断结果 -->
        <el-card shadow="hover" style="margin-top: 20px;">
          <template #header>
            <div class="card-header">
              <span>预测性诊断结果(未来7日故障风险)</span>
            </div>
          </template>
          <div class="prediction-result">
            <el-timeline>
              <el-timeline-item
                v-for="item in predictionList"
                :key="item.date"
                :timestamp="item.date"
                :type="item.riskLevel === 'high' ? 'danger' : item.riskLevel === 'medium' ? 'warning' : 'success'"
              >
                <div class="timeline-content">
                  <h4>{{ item.deviceName }}</h4>
                  <p class="risk-level">
                    é£Žé™©ç­‰çº§ï¼š
                    <el-tag :type="item.riskLevel === 'high' ? 'danger' : item.riskLevel === 'medium' ? 'warning' : 'success'">
                      {{ item.riskLevel === 'high' ? '高' : item.riskLevel === 'medium' ? '中' : '低' }}
                    </el-tag>
                  </p>
                  <p class="fault-type">可能故障类型:{{ item.possibleFault }}</p>
                  <p class="probability">发生概率:{{ item.probability }}%</p>
                </div>
              </el-timeline-item>
            </el-timeline>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// è®¾å¤‡åˆ—表
const devices = ref([
  { id: 'D001', name: '空压机A-001' },
  { id: 'D002', name: '冷却塔B-002' },
  { id: 'D003', name: 'æ°´æ³µC-003' },
  { id: 'D004', name: '发电机D-004' },
  { id: 'D005', name: '变压器E-005' }
])
// æ•…障预警列表
const warningList = ref([
  {
    id: 1,
    deviceName: '空压机A-001',
    warningType: '压力异常',
    riskLevel: 'high',
    occurTime: '2024-12-16 14:32:15',
    status: 'pending'
  },
  {
    id: 2,
    deviceName: '冷却塔B-002',
    warningType: '温度过高',
    riskLevel: 'medium',
    occurTime: '2024-12-16 14:30:45',
    status: 'pending'
  },
  {
    id: 3,
    deviceName: 'æ°´æ³µC-003',
    warningType: '振动过大',
    riskLevel: 'medium',
    occurTime: '2024-12-16 14:28:30',
    status: 'pending'
  },
  {
    id: 4,
    deviceName: '发电机D-004',
    warningType: '电流异常',
    riskLevel: 'high',
    occurTime: '2024-12-16 14:25:10',
    status: 'pending'
  },
  {
    id: 5,
    deviceName: '变压器E-005',
    warningType: '电压波动',
    riskLevel: 'low',
    occurTime: '2024-12-16 14:20:05',
    status: 'pending'
  }
])
// å½“前选中的预警项
const currentWarning = ref(warningList.value[0])
// æ•…障诊断结果
const diagnosisResult = reactive({
  reason: '根据设备运行数据推测,故障原因可能是设备内部部件磨损导致的压力异常,需要进一步检查设备的活塞环和气缸套。',
  impact: '如果不及时处理,可能导致设备停机,影响生产线的正常运行,预计停机时间为4-6小时。',
  suggestion: '1. ç«‹å³å®‰æŽ’维修人员进行设备检查;2. æ£€æŸ¥è®¾å¤‡çš„æ´»å¡žçŽ¯å’Œæ°”ç¼¸å¥—ï¼›3. æ›´æ¢ç£¨æŸä¸¥é‡çš„部件;4. æ£€æŸ¥è®¾å¤‡çš„æ¶¦æ»‘系统,确保润滑正常。'
})
// é¢„测性诊断结果
const predictionList = ref([
  {
    date: '2024-12-17',
    deviceName: '空压机A-001',
    riskLevel: 'medium',
    possibleFault: '压力异常',
    probability: 65
  },
  {
    date: '2024-12-18',
    deviceName: '冷却塔B-002',
    riskLevel: 'high',
    possibleFault: '温度过高',
    probability: 85
  },
  {
    date: '2024-12-19',
    deviceName: 'æ°´æ³µC-003',
    riskLevel: 'medium',
    possibleFault: '振动过大',
    probability: 70
  },
  {
    date: '2024-12-20',
    deviceName: '发电机D-004',
    riskLevel: 'high',
    possibleFault: '电流异常',
    probability: 90
  },
  {
    date: '2024-12-21',
    deviceName: '变压器E-005',
    riskLevel: 'low',
    possibleFault: '电压波动',
    probability: 45
  },
  {
    date: '2024-12-22',
    deviceName: '空压机A-001',
    riskLevel: 'high',
    possibleFault: '压力异常',
    probability: 80
  },
  {
    date: '2024-12-23',
    deviceName: '冷却塔B-002',
    riskLevel: 'medium',
    possibleFault: '温度过高',
    probability: 60
  }
])
// æ•…障历史记录查询表单
const historyFilterForm = ref({
  deviceId: ''
})
// åŽ†å²è®°å½•æ—¶é—´èŒƒå›´
const historyTimeRange = ref([])
// æ•…障历史记录
const historyList = ref([
  {
    id: 1,
    deviceName: '空压机A-001',
    faultType: '压力异常',
    occurTime: '2024-12-15 08:30:00',
    dealTime: '2024-12-15 10:45:00',
    status: '已处理'
  },
  {
    id: 2,
    deviceName: '冷却塔B-002',
    faultType: '温度过高',
    occurTime: '2024-12-14 14:20:00',
    dealTime: '2024-12-14 16:15:00',
    status: '已处理'
  },
  {
    id: 3,
    deviceName: 'æ°´æ³µC-003',
    faultType: '振动过大',
    occurTime: '2024-12-13 09:15:00',
    dealTime: '2024-12-13 11:30:00',
    status: '已处理'
  },
  {
    id: 4,
    deviceName: '发电机D-004',
    faultType: '电流异常',
    occurTime: '2024-12-12 16:45:00',
    dealTime: '2024-12-12 18:30:00',
    status: '已处理'
  },
  {
    id: 5,
    deviceName: '变压器E-005',
    faultType: '电压波动',
    occurTime: '2024-12-11 11:20:00',
    dealTime: '2024-12-11 13:15:00',
    status: '已处理'
  }
])
// å¤„理预警项点击
const handleWarningClick = (row) => {
  currentWarning.value = row
}
// é‡æ–°è¯Šæ–­
const handleDiagnosis = () => {
  // æ¨¡æ‹Ÿé‡æ–°è¯Šæ–­
  ElMessage.success('重新诊断完成')
}
// å¤„理历史记录查询
const handleHistorySearch = () => {
  // æ¨¡æ‹ŸæŸ¥è¯¢åŽ†å²è®°å½•
  ElMessage.success('历史记录查询成功')
}
</script>
<style scoped>
.fault-diagnosis-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.diagnosis-result h3 {
  margin-bottom: 20px;
  color: #303133;
}
.no-selection {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 200px;
}
.prediction-result {
  padding: 10px 0;
}
.timeline-content {
  padding: 10px;
  background-color: #fafafa;
  border-radius: 4px;
}
.timeline-content h4 {
  margin-bottom: 10px;
  color: #303133;
}
.timeline-content p {
  margin: 5px 0;
  font-size: 14px;
  color: #606266;
}
.risk-level {
  display: flex;
  align-items: center;
  gap: 5px;
}
.fault-type {
  color: #606266;
}
.probability {
  color: #606266;
}
.history-filter-form {
  margin-bottom: 20px;
  padding: 10px 0;
  border-bottom: 1px solid #ebeef5;
}
.pagination-container {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
}
</style>
src/views/index.vue
@@ -1,674 +1,392 @@
<template>
  <div class="dashboard">
    <!-- é¡¶éƒ¨æ¨ªå‘两栏 -->
    <div class="dashboard-top">
      <!-- å·¦ï¼šç³»ç»Ÿæ¦‚览+数据卡片 -->
      <div class="top-left">
        <div class="system-info">
          <div class="section-title">承包商管理系统</div>
          <div style="display: flex; align-items: center; gap: 20px">
            <div class="system-card">
              <div class="system-name">承包商管理系统</div>
              <div class="system-meta">资质审核 Â· åˆåŒç®¡ç† Â· ç»©æ•ˆè¯„ä¼°</div>
            </div>
            <div style="display: flex; align-items: center; gap: 8px">
              <el-icon color="#5053B5" size="22"><Clock /></el-icon>
              <span>当前时间:{{ currentTime }}</span>
  <div class="dashboard-container">
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <el-row :gutter="20">
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">设备在线率</div>
            <div class="card-value">{{ onlineRate }}%</div>
            <div class="card-desc">
              å½“前在线设备数:{{ onlineCount }} / {{ totalDevices }}
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">故障预警数</div>
            <div class="card-value">{{ warningCount }}</div>
            <div class="card-desc">
              é«˜é£Žé™©ï¼š{{ highRiskCount }} | ä¸­é£Žé™©ï¼š{{ mediumRiskCount }} |
              ä½Žé£Žé™©ï¼š{{ lowRiskCount }}
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">待处理维修单</div>
            <div class="card-value">{{ pendingOrders }}</div>
            <div class="card-desc">
              å¤„理中:{{ processingOrders }} | å·²å®Œæˆï¼š{{ completedOrders }}
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card class="statistics-card" shadow="hover">
          <div class="card-content">
            <div class="card-title">重点设备运行状态</div>
            <div class="card-value">{{ normalDevices }} æ­£å¸¸</div>
            <div class="card-desc">
              å¼‚常:{{ abnormalDevices }} | æ•…障:{{ faultDevices }}
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <el-row :gutter="20" style="margin-top: 20px">
      <!-- è®¾å¤‡å¥åº·åº¦è¶‹åŠ¿å›¾ -->
      <el-col :span="12">
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>设备健康度趋势图</span>
            </div>
          </template>
          <div ref="healthChartRef" class="chart-container"></div>
        </el-card>
      </el-col>
      <!-- è¿‘7日故障类型统计 -->
      <el-col :span="12">
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>近7日故障类型统计</span>
            </div>
          </template>
          <div ref="faultChartRef" class="chart-container"></div>
        </el-card>
      </el-col>
    </el-row>
    <!-- é‡ç‚¹è®¾å¤‡è¿è¡ŒçŠ¶æ€å¡ç‰‡ -->
    <el-card shadow="hover" style="margin-top: 20px">
      <template #header>
        <div class="card-header">
          <span>重点设备运行状态</span>
        </div>
        <div class="data-cards">
          <div class="data-card total">
            <div class="data-title">总承包商数</div>
            <div class="data-value">{{ contractorStats.total }}</div>
            <div class="data-desc">
              å·²å®¡æ ¸ {{ contractorStats.approved }} | å¾…审核
              {{ contractorStats.pending }}
            </div>
          </div>
          <div class="data-card pending">
            <div class="data-title">待审核承包商</div>
            <div class="data-value">{{ contractorStats.pending }}</div>
            <div class="data-desc">
              A级 {{ contractorStats.aPending }} | B级
              {{ contractorStats.bPending }} | C级
              {{ contractorStats.cPending }}
            </div>
          </div>
          <div class="data-card today">
            <div class="data-title">本月新增</div>
            <div class="data-value">{{ contractorStats.monthly }}</div>
            <div class="data-desc">
              åŒæ¯” {{ contractorStats.monthly同比 }}% | çŽ¯æ¯”
              {{ contractorStats.monthly环比 }}%
            </div>
          </div>
        </div>
      </div>
      <!-- å³ï¼šå¾…处理报警列表 -->
      <div class="alarm-panel">
        <div class="section-title">待审核承包商</div>
        <ul class="alarm-list" v-if="pendingContractors.length > 0">
          <li v-for="item in pendingContractors" :key="item.id">
            <div
              style="
                display: flex;
                flex-direction: column;
                justify-content: space-between;
                width: 100%;
                gap: 10px;
      </template>
      <el-table :data="keyDevices" stripe style="width: 100%">
        <el-table-column
          prop="name"
          label="设备名称"
          width="180"
        ></el-table-column>
        <el-table-column
          prop="model"
          label="型号"
          width="120"
        ></el-table-column>
        <el-table-column prop="ip" label="IP地址" width="150"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag
              :type="
                scope.row.status === 'online'
                  ? 'success'
                  : scope.row.status === 'warning'
                  ? 'warning'
                  : 'danger'
              "
            >
              <div
                style="
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
                "
              >
                <div class="alarm-title">{{ item.name }} - {{ item.type }}</div>
                <el-tag :type="getContractorLevelType(item.level)"
                  >{{ item.level }}级</el-tag
                >
              </div>
              <div
                style="
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
                "
              >
                <div class="alarm-value">
                  è”系人: {{ item.contact }} | æ³¨å†Œæ—¶é—´: {{ item.registerDate }}
                </div>
                <div class="alarm-time">{{ item.applyDate }}</div>
              </div>
            </div>
          </li>
        </ul>
        <div v-else style="text-align: center; color: #909399; padding: 20px">
          æš‚无待审核承包商
        </div>
      </div>
    </div>
    <!-- ä¸­éƒ¨æ¨ªå‘两栏 -->
    <div class="dashboard-row">
      <div class="main-panel">
        <div class="section-title">承包商增长趋势</div>
        <Echarts
          ref="chart"
          :chartStyle="chartStyle"
          :grid="grid"
          :legend="lineLegend"
          :series="lineSeries"
          :tooltip="tooltipLine"
          :xAxis="xAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
      <div class="main-panel">
        <div class="section-title">承包商资质分布</div>
        <div
          style="
            display: flex;
            align-items: center;
            gap: 20px;
            justify-content: space-evenly;
            height: 300px;
          "
        >
          <div style="width: 50%">
            <Echarts
              ref="chart"
              :legend="pieLegend"
              :chartStyle="chartStylePie"
              :series="levelPieSeries"
              :tooltip="pieTooltip"
            ></Echarts>
          </div>
          <ul class="level-list" style="width: 50%">
            <li v-for="item in levelPieSeries[0].data" :key="item.name">
              <div
                style="
                  display: flex;
                  align-items: center;
                  justify-content: space-between;
                  width: 100%;
                "
              >
                <div class="line" :style="{ color: item.itemStyle.color }">
                  â—{{ item.name }}
                </div>
                <div style="width: 60px">{{ item.value }}å®¶</div>
                <div style="width: 60px">{{ item.rate }}%</div>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <!-- åº•部横向两栏 -->
    <div class="dashboard-row">
      <div class="main-panel">
        <div class="section-title">承包商类型分布</div>
        <Echarts
          ref="chart"
          :color="barColors"
          :chartStyle="chartStyle"
          :grid="grid"
          :series="equipmentBarSeries"
          :tooltip="tooltip"
          :xAxis="equipmentXAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
      <div class="main-panel">
        <div class="section-title">承包商审核时效</div>
        <Echarts
          ref="chart"
          :color="barColors"
          :chartStyle="chartStyle"
          :grid="grid"
          :series="handlingTimeSeries"
          :tooltip="tooltip"
          :xAxis="handlingTimeXAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
    </div>
              {{
                scope.row.status === "online"
                  ? "在线"
                  : scope.row.status === "warning"
                  ? "预警"
                  : "故障"
              }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          prop="temperature"
          label="温度(℃)"
          width="100"
        ></el-table-column>
        <el-table-column
          prop="pressure"
          label="压力(MPa)"
          width="100"
        ></el-table-column>
        <el-table-column
          prop="speed"
          label="转速(rpm)"
          width="100"
        ></el-table-column>
        <el-table-column prop="health" label="健康度" width="120">
          <template #default="scope">
            <el-progress
              :percentage="scope.row.health"
              :stroke-width="10"
            ></el-progress>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>
<script setup>
import { ref, onMounted, reactive, computed } from "vue";
import Echarts from "@/components/Echarts/echarts.vue";
import { Clock } from "@element-plus/icons-vue";
import { ref, onMounted, onUnmounted } from "vue";
import * as echarts from "echarts";
// å½“前时间
const currentTime = computed(() => {
  const now = new Date();
  return now.toLocaleString("zh-CN");
});
// ç»Ÿè®¡æ•°æ®
const onlineRate = ref(85);
const onlineCount = ref(170);
const totalDevices = ref(200);
const warningCount = ref(23);
const highRiskCount = ref(5);
const mediumRiskCount = ref(12);
const lowRiskCount = ref(6);
const pendingOrders = ref(15);
const processingOrders = ref(8);
const completedOrders = ref(45);
const normalDevices = ref(162);
const abnormalDevices = ref(18);
const faultDevices = ref(10);
// æ‰¿åŒ…商统计数据
const contractorStats = reactive({
  total: 156,
  approved: 132,
  pending: 24,
  aPending: 8,
  bPending: 12,
  cPending: 4,
  monthly: 28,
  monthly同比: "+18.5",
  monthly环比: "+5.2",
});
// å¾…审核承包商列表
const pendingContractors = ref([
// é‡ç‚¹è®¾å¤‡åˆ—表
const keyDevices = ref([
  {
    id: 1,
    name: "建筑工程有限公司",
    type: "建筑施工",
    contact: "张三",
    registerDate: "2025-01-15",
    applyDate: "2025-12-16 14:30:23",
    level: "A",
    name: "空压机A-001",
    model: "KA-200",
    ip: "192.168.1.101",
    status: "online",
    temperature: 42,
    pressure: 0.8,
    speed: 1450,
    health: 92,
  },
  {
    id: 2,
    name: "机电安装工程公司",
    type: "机电安装",
    contact: "李四",
    registerDate: "2025-03-20",
    applyDate: "2025-12-16 14:28:15",
    level: "B",
    name: "冷却塔B-002",
    model: "CT-300",
    ip: "192.168.1.102",
    status: "warning",
    temperature: 58,
    pressure: 0.6,
    speed: 980,
    health: 75,
  },
  {
    id: 3,
    name: "装饰装修工程公司",
    type: "装饰装修",
    contact: "王五",
    registerDate: "2025-05-10",
    applyDate: "2025-12-16 14:22:18",
    level: "B",
    name: "æ°´æ³µC-003",
    model: "WP-150",
    ip: "192.168.1.103",
    status: "online",
    temperature: 38,
    pressure: 1.2,
    speed: 1200,
    health: 88,
  },
  {
    id: 4,
    name: "绿化工程有限公司",
    type: "园林绿化",
    contact: "赵六",
    registerDate: "2025-08-05",
    applyDate: "2025-12-16 14:18:55",
    level: "C",
    name: "发电机D-004",
    model: "GE-500",
    ip: "192.168.1.104",
    status: "danger",
    temperature: 75,
    pressure: 0.5,
    speed: 1500,
    health: 60,
  },
  {
    name: "变压器E-005",
    model: "TR-1000",
    ip: "192.168.1.105",
    status: "online",
    temperature: 45,
    pressure: 0,
    speed: 0,
    health: 95,
  },
]);
// å›¾è¡¨æ ·å¼
const chartStyle = {
  width: "100%",
  height: "100%",
// å›¾è¡¨å¼•用
const healthChartRef = ref(null);
const faultChartRef = ref(null);
let healthChart = null;
let faultChart = null;
// å¥åº·åº¦è¶‹åŠ¿æ•°æ®
const healthTrendData = {
  dates: ["12-10", "12-11", "12-12", "12-13", "12-14", "12-15", "12-16"],
  values: [88, 90, 85, 87, 92, 91, 93],
};
const chartStylePie = {
  width: "100%",
  height: "100%",
// æ•…障类型统计数据
const faultTypeData = {
  types: ["温度异常", "压力超标", "转速异常", "振动过大", "其他"],
  values: [15, 8, 12, 6, 3],
};
const grid = {
  left: "3%",
  right: "4%",
  bottom: "3%",
  containLabel: true,
};
// æ‰¿åŒ…商增长趋势 - æŠ˜çº¿å›¾
const lineLegend = {
  show: true,
  data: ["A级", "B级", "C级"],
};
const tooltipLine = {
  trigger: "axis",
  axisPointer: {
    type: "cross",
  },
};
const xAxis = ref({
  type: "category",
  data: ["12-01", "12-02", "12-03", "12-04", "12-05", "12-06", "12-07"],
});
const yAxis = ref({
  type: "value",
  name: "承包商数量",
});
const lineSeries = ref([
  {
    name: "A级",
    type: "line",
    data: [15, 18, 20, 22, 25, 28, 30],
    itemStyle: {
      color: "#f56c6c",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
  {
    name: "B级",
    type: "line",
    data: [45, 52, 58, 65, 70, 75, 80],
    itemStyle: {
      color: "#e6a23c",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
  {
    name: "C级",
    type: "line",
    data: [30, 35, 40, 45, 50, 55, 60],
    itemStyle: {
      color: "#67c23a",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
]);
// æ‰¿åŒ…商资质分布 - é¥¼å›¾
const pieLegend = {
  show: false,
};
const pieTooltip = {
  trigger: "item",
  formatter: "{b}: {c}å®¶ ({d}%)",
};
const levelPieSeries = ref([
  {
    type: "pie",
    radius: ["60%", "80%"],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: "#fff",
      borderWidth: 2,
    },
    label: {
      show: false,
    },
    data: [
      {
        name: "A级",
        value: 45,
        rate: 28.85,
        itemStyle: { color: "#f56c6c" },
// åˆå§‹åŒ–健康度趋势图
const initHealthChart = () => {
  if (healthChartRef.value) {
    healthChart = echarts.init(healthChartRef.value);
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: {
          type: "cross",
          label: {
            backgroundColor: "#6a7985",
          },
        },
      },
      {
        name: "B级",
        value: 70,
        rate: 44.87,
        itemStyle: { color: "#e6a23c" },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "3%",
        containLabel: true,
      },
      {
        name: "C级",
        value: 41,
        rate: 26.28,
        itemStyle: { color: "#67c23a" },
      xAxis: {
        type: "category",
        boundaryGap: false,
        data: healthTrendData.dates,
      },
    ],
  },
]);
// æ‰¿åŒ…商类型分布 - æŸ±çж图
const barColors = ["#409eff", "#67c23a", "#e6a23c", "#f56c6c", "#909399"];
const tooltip = {
  trigger: "axis",
  axisPointer: {
    type: "shadow",
  },
};
const equipmentXAxis = ref({
  type: "category",
  data: ["建筑施工", "机电安装", "装饰装修", "园林绿化", "其他"],
});
const equipmentBarSeries = ref([
  {
    name: "承包商数量",
    type: "bar",
    data: [45, 35, 25, 20, 31],
    itemStyle: {
      color: function (params) {
        return barColors[params.dataIndex % barColors.length];
      yAxis: {
        type: "value",
        min: 70,
        max: 100,
        name: "健康度(%)",
      },
    },
    label: {
      show: true,
      position: "top",
    },
  },
]);
// æ‰¿åŒ…商审核时效分析 - æŸ±çж图
const handlingTimeXAxis = ref({
  type: "category",
  data: ["0-1天", "1-3天", "3-7天", "7天以上"],
});
const handlingTimeSeries = ref([
  {
    name: "审核数量",
    type: "bar",
    data: [85, 45, 20, 10],
    itemStyle: {
      color: function (params) {
        return barColors[params.dataIndex % barColors.length];
      },
    },
    label: {
      show: true,
      position: "top",
    },
  },
]);
// èŽ·å–æ‰¿åŒ…å•†çº§åˆ«æ ·å¼
const getContractorLevelType = (level) => {
  switch (level) {
    case "A":
      return "danger";
    case "B":
      return "warning";
    case "C":
      return "info";
    default:
      return "info";
      series: [
        {
          name: "健康度",
          type: "line",
          stack: "总量",
          areaStyle: {},
          emphasis: {
            focus: "series",
          },
          data: healthTrendData.values,
          itemStyle: {
            color: "#67c23a",
          },
          lineStyle: {
            width: 3,
          },
        },
      ],
    };
    healthChart.setOption(option);
  }
};
// åˆå§‹åŒ–故障类型统计饼图
const initFaultChart = () => {
  if (faultChartRef.value) {
    faultChart = echarts.init(faultChartRef.value);
    const option = {
      tooltip: {
        trigger: "item",
      },
      legend: {
        orient: "vertical",
        left: "left",
      },
      series: [
        {
          name: "故障类型",
          type: "pie",
          radius: "50%",
          data: faultTypeData.types.map((type, index) => ({
            name: type,
            value: faultTypeData.values[index],
          })),
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)",
            },
          },
        },
      ],
    };
    faultChart.setOption(option);
  }
};
// ç›‘听窗口大小变化,调整图表大小
const handleResize = () => {
  healthChart?.resize();
  faultChart?.resize();
};
onMounted(() => {
  // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–æ“ä½œ
  initHealthChart();
  initFaultChart();
  window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
  window.removeEventListener("resize", handleResize);
  healthChart?.dispose();
  faultChart?.dispose();
});
</script>
<style scoped>
.dashboard {
  background: #f5f7fa;
.dashboard-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
  padding: 20px;
  box-sizing: border-box;
}
.dashboard-top {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
.statistics-card {
  height: 180px;
}
.system-info {
.card-content {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 20px;
  min-width: 0;
  background-color: #eff2fb;
  border-radius: 12px;
  height: 138px;
}
.system-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  padding-right: 15px;
}
.system-name {
  font-weight: 600;
  font-size: 18px;
  color: #161a9a;
}
.system-meta {
  font-weight: 400;
  font-size: 12px;
  color: #818185;
}
.data-cards {
  display: flex;
  gap: 16px;
  justify-content: flex-start;
  background: #ffffff;
  border-radius: 12px;
  padding: 20px;
}
.data-card {
  background: #fff;
  border-radius: 12px;
  padding: 16px;
  min-width: 160px;
  box-shadow: 0 2px 8px #eee;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100%;
  justify-content: center;
  width: 32%;
  height: 140px;
  transition: all 0.3s ease;
  align-items: center;
}
.data-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.data-card.total {
  background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
  color: #fff;
}
.data-card.pending {
  background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
  color: #fff;
}
.data-card.today {
  background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
  color: #fff;
}
.data-title {
  font-weight: 600;
.card-title {
  font-size: 16px;
  margin-bottom: 12px;
  color: #606266;
  margin-bottom: 10px;
}
.data-value {
.card-value {
  font-size: 32px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 8px;
}
.data-desc {
  font-size: 12px;
  opacity: 0.9;
}
.top-left {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 60%;
}
.alarm-panel {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  width: 40%;
}
.alarm-list {
  list-style: none;
  padding: 0;
  margin: 0;
.card-desc {
  font-size: 14px;
  overflow-y: auto;
  height: 260px;
}
.alarm-list li {
  border-radius: 8px;
  margin-bottom: 12px;
  padding: 12px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #f8f9fa;
  border-left: 4px solid #409eff;
  transition: all 0.3s ease;
}
.alarm-list li:hover {
  background: #e9ecef;
  transform: translateX(4px);
}
.alarm-title {
  font-weight: 600;
  font-size: 14px;
  color: #303133;
}
.alarm-value {
  font-size: 12px;
  color: #606266;
}
.alarm-time {
  font-size: 12px;
  color: #909399;
}
.dashboard-row {
.card-header {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  justify-content: space-between;
  align-items: center;
}
.main-panel {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.section-title {
  position: relative;
  font-size: 18px;
  color: #333;
  padding-left: 10px;
  margin-bottom: 20px;
  font-weight: 700;
}
.section-title::before {
  position: absolute;
  left: 0;
  top: 4px;
  content: "";
  width: 4px;
  height: 18px;
  background-color: #409eff;
  border-radius: 2px;
}
.level-list {
  margin: 0;
  padding: 0;
  list-style: none;
  height: 200px;
  overflow-y: auto;
.chart-container {
  width: 100%;
}
.level-list li {
  margin-bottom: 15px;
  padding: 10px;
  background: #f8f9fa;
  border-radius: 6px;
  transition: all 0.3s ease;
}
.level-list li:hover {
  background: #e9ecef;
}
.line {
  position: relative;
  width: 80px;
  font-weight: 500;
  height: 350px;
}
</style>
src/views/maintenance/MaintenanceManagement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,526 @@
<template>
  <div class="maintenance-management-container">
    <!-- é¡¶éƒ¨æ“ä½œæ  -->
    <el-card shadow="hover" style="margin-bottom: 20px;">
      <div class="card-header">
        <span>维修管理</span>
        <div class="header-buttons">
          <el-button type="primary" @click="showCreateWorkOrderDialog">
            <el-icon-plus /> åˆ›å»ºå·¥å•
          </el-button>
        </div>
      </div>
    </el-card>
    <el-row :gutter="20">
      <!-- å·¦ä¾§ï¼šå·¥å•列表 -->
      <el-col :span="16">
        <el-card shadow="hover">
          <!-- å·¥å•状态标签页 -->
          <el-tabs v-model="activeTab" @tab-change="handleTabChange">
            <el-tab-pane label="待处理" name="pending"></el-tab-pane>
            <el-tab-pane label="处理中" name="processing"></el-tab-pane>
            <el-tab-pane label="已完成" name="completed"></el-tab-pane>
          </el-tabs>
          <!-- å·¥å•列表 -->
          <el-table :data="filteredWorkOrders" stripe style="width: 100%" @row-click="handleWorkOrderClick">
            <el-table-column prop="orderNo" label="工单编号" width="180"></el-table-column>
            <el-table-column prop="deviceName" label="设备名称" width="150"></el-table-column>
            <el-table-column prop="faultType" label="故障类型" width="120"></el-table-column>
            <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
            <el-table-column prop="assignee" label="负责人" width="120"></el-table-column>
            <el-table-column prop="priority" label="优先级" width="100">
              <template #default="scope">
                <el-tag :type="scope.row.priority === 'high' ? 'danger' : scope.row.priority === 'medium' ? 'warning' : 'info'">
                  {{ scope.row.priority === 'high' ? '高' : scope.row.priority === 'medium' ? '中' : '低' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="150">
              <template #default="scope">
                <el-button size="small" @click="showEditWorkOrderDialog(scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="handleDeleteWorkOrder(scope.row.id)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
          <!-- åˆ†é¡µ -->
          <div class="pagination-container">
            <el-pagination
              background
              layout="total, sizes, prev, pager, next, jumper"
              :total="filteredWorkOrders.length"
              :current-page="currentPage"
              :page-sizes="[10, 20, 50, 100]"
              :page-size="pageSize"
              @size-change="handleSizeChange"
              @current-change="handleCurrentChange"
            ></el-pagination>
          </div>
        </el-card>
      </el-col>
      <!-- å³ä¾§ï¼šç»´ä¿®ç»Ÿè®¡å’Œå¤‡ä»¶æŽ¨è -->
      <el-col :span="8">
        <!-- ç»´ä¿®åŽ†å²ç»Ÿè®¡ -->
        <el-card shadow="hover" style="margin-bottom: 20px;">
          <template #header>
            <div class="card-header">
              <span>维修历史统计</span>
            </div>
          </template>
          <div class="statistics-content">
            <div class="stat-item">
              <div class="stat-label">本月完成工单</div>
              <div class="stat-value">{{ monthlyCompleted }}</div>
            </div>
            <div class="stat-item">
              <div class="stat-label">平均维修时长</div>
              <div class="stat-value">{{ averageRepairTime }}小时</div>
            </div>
            <div class="stat-item">
              <div class="stat-label">设备故障率</div>
              <div class="stat-value">{{ failureRate }}%</div>
            </div>
            <div class="stat-item">
              <div class="stat-label">常用维修设备</div>
              <div class="stat-value">{{ commonDevice }}</div>
            </div>
          </div>
        </el-card>
        <!-- å¸¸ç”¨å¤‡ä»¶å…³è”推荐 -->
        <el-card shadow="hover">
          <template #header>
            <div class="card-header">
              <span>常用备件推荐</span>
            </div>
          </template>
          <div class="spare-parts-content">
            <el-table :data="spareParts" stripe style="width: 100%" size="small">
              <el-table-column prop="name" label="备件名称" width="120"></el-table-column>
              <el-table-column prop="model" label="型号" width="100"></el-table-column>
              <el-table-column prop="stock" label="库存" width="80">
                <template #default="scope">
                  <el-tag :type="scope.row.stock < 10 ? 'danger' : 'success'">
                    {{ scope.row.stock }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="usageCount" label="使用次数" width="80"></el-table-column>
            </el-table>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- åˆ›å»ºå·¥å•对话框 -->
    <el-dialog v-model="createWorkOrderDialogVisible" title="创建维修工单" width="600px">
      <el-form :model="workOrderForm" :rules="workOrderRules" ref="workOrderFormRef" label-width="100px">
        <el-form-item label="设备" prop="deviceId">
          <el-select v-model="workOrderForm.deviceId" placeholder="请选择设备">
            <el-option
              v-for="device in devices"
              :key="device.id"
              :label="device.name"
              :value="device.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="故障类型" prop="faultType">
          <el-input v-model="workOrderForm.faultType" placeholder="请输入故障类型"></el-input>
        </el-form-item>
        <el-form-item label="故障描述" prop="faultDescription">
          <el-input v-model="workOrderForm.faultDescription" type="textarea" placeholder="请详细描述故障情况" :rows="3"></el-input>
        </el-form-item>
        <el-form-item label="优先级" prop="priority">
          <el-select v-model="workOrderForm.priority" placeholder="请选择优先级">
            <el-option label="高" value="high"></el-option>
            <el-option label="中" value="medium"></el-option>
            <el-option label="低" value="low"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="createWorkOrderDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleCreateWorkOrder">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç¼–辑工单对话框 -->
    <el-dialog v-model="editWorkOrderDialogVisible" title="编辑维修工单" width="600px">
      <el-form :model="workOrderForm" :rules="workOrderRules" ref="workOrderFormRef" label-width="100px">
        <el-form-item label="工单编号" disabled>
          <el-input v-model="workOrderForm.orderNo"></el-input>
        </el-form-item>
        <el-form-item label="设备" disabled>
          <el-input v-model="workOrderForm.deviceName"></el-input>
        </el-form-item>
        <el-form-item label="故障类型" disabled>
          <el-input v-model="workOrderForm.faultType"></el-input>
        </el-form-item>
        <el-form-item label="负责人" prop="assignee">
          <el-select v-model="workOrderForm.assignee" placeholder="请选择负责人">
            <el-option
              v-for="user in users"
              :key="user.id"
              :label="user.name"
              :value="user.name"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="维修状态" prop="status">
          <el-select v-model="workOrderForm.status" placeholder="请选择状态">
            <el-option label="待处理" value="pending"></el-option>
            <el-option label="处理中" value="processing"></el-option>
            <el-option label="已完成" value="completed"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="维修结果" prop="repairResult">
          <el-input v-model="workOrderForm.repairResult" type="textarea" placeholder="请填写维修结果" :rows="3"></el-input>
        </el-form-item>
        <el-form-item label="备件使用" prop="usedParts">
          <el-input v-model="workOrderForm.usedParts" type="textarea" placeholder="请填写使用的备件" :rows="2"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="editWorkOrderDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleEditWorkOrder">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
// è®¾å¤‡åˆ—表
const devices = ref([
  { id: 'D001', name: '空压机A-001' },
  { id: 'D002', name: '冷却塔B-002' },
  { id: 'D003', name: 'æ°´æ³µC-003' },
  { id: 'D004', name: '发电机D-004' },
  { id: 'D005', name: '变压器E-005' }
])
// ç”¨æˆ·åˆ—表(用于分配负责人)
const users = ref([
  { id: 'U001', name: '张三' },
  { id: 'U002', name: '李四' },
  { id: 'U003', name: '王五' },
  { id: 'U004', name: '赵六' }
])
// å·¥å•列表数据
const workOrders = ref([
  {
    id: 1,
    orderNo: 'WO20241216001',
    deviceId: 'D001',
    deviceName: '空压机A-001',
    faultType: '压力异常',
    faultDescription: '设备运行时压力超过设定阈值,伴有异常噪音',
    priority: 'high',
    assignee: '',
    status: 'pending',
    repairResult: '',
    usedParts: '',
    createTime: '2024-12-16 14:32:15',
    startTime: '',
    endTime: ''
  },
  {
    id: 2,
    orderNo: 'WO20241216002',
    deviceId: 'D002',
    deviceName: '冷却塔B-002',
    faultType: '温度过高',
    faultDescription: '冷却塔出水温度超过设定值,冷却效果不佳',
    priority: 'medium',
    assignee: '张三',
    status: 'processing',
    repairResult: '',
    usedParts: '',
    createTime: '2024-12-16 14:30:45',
    startTime: '2024-12-16 15:00:00',
    endTime: ''
  },
  {
    id: 3,
    orderNo: 'WO20241215001',
    deviceId: 'D003',
    deviceName: 'æ°´æ³µC-003',
    faultType: '振动过大',
    faultDescription: '水泵运行时振动值超过标准,可能影响设备寿命',
    priority: 'medium',
    assignee: '李四',
    status: 'completed',
    repairResult: '更换了水泵轴承,调整了联轴器,振动值恢复正常',
    usedParts: '轴承×2,联轴器×1',
    createTime: '2024-12-15 08:30:00',
    startTime: '2024-12-15 09:00:00',
    endTime: '2024-12-15 10:45:00'
  },
  {
    id: 4,
    orderNo: 'WO20241214001',
    deviceId: 'D004',
    deviceName: '发电机D-004',
    faultType: '电流异常',
    faultDescription: '发电机运行时电流波动较大,可能存在短路风险',
    priority: 'high',
    assignee: '王五',
    status: 'completed',
    repairResult: '检查并修复了发电机绕组短路问题,电流恢复正常',
    usedParts: '绝缘材料×1,导线×5ç±³',
    createTime: '2024-12-14 14:20:00',
    startTime: '2024-12-14 14:30:00',
    endTime: '2024-12-14 16:15:00'
  },
  {
    id: 5,
    orderNo: 'WO20241213001',
    deviceId: 'D005',
    deviceName: '变压器E-005',
    faultType: '电压波动',
    faultDescription: '变压器输出电压波动较大,影响下游设备运行',
    priority: 'low',
    assignee: '赵六',
    status: 'completed',
    repairResult: '调整了变压器分接开关,电压稳定在正常范围内',
    usedParts: '',
    createTime: '2024-12-13 09:15:00',
    startTime: '2024-12-13 10:00:00',
    endTime: '2024-12-13 11:30:00'
  }
])
// å½“前激活的标签页(工单状态)
const activeTab = ref('pending')
// åˆ†é¡µæ•°æ®
const currentPage = ref(1)
const pageSize = ref(10)
// å¯¹è¯æ¡†çŠ¶æ€
const createWorkOrderDialogVisible = ref(false)
const editWorkOrderDialogVisible = ref(false)
// å·¥å•表单数据
const workOrderForm = ref({
  id: '',
  orderNo: '',
  deviceId: '',
  deviceName: '',
  faultType: '',
  faultDescription: '',
  priority: 'medium',
  assignee: '',
  status: 'pending',
  repairResult: '',
  usedParts: '',
  createTime: '',
  startTime: '',
  endTime: ''
})
// è¡¨å•验证规则
const workOrderRules = ref({
  deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
  faultType: [{ required: true, message: '请输入故障类型', trigger: 'blur' }],
  faultDescription: [{ required: true, message: '请详细描述故障情况', trigger: 'blur' }],
  priority: [{ required: true, message: '请选择优先级', trigger: 'change' }],
  assignee: [{ required: true, message: '请选择负责人', trigger: 'change' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
})
// è¡¨å•引用
const workOrderFormRef = ref(null)
// ç­›é€‰åŽçš„工单列表
const filteredWorkOrders = computed(() => {
  return workOrders.value.filter(order => order.status === activeTab.value)
})
// ç»´ä¿®ç»Ÿè®¡æ•°æ®
const monthlyCompleted = ref(28)
const averageRepairTime = ref(2.5)
const failureRate = ref(3.2)
const commonDevice = ref('空压机A-001')
// å¸¸ç”¨å¤‡ä»¶æŽ¨è
const spareParts = ref([
  { id: 1, name: '轴承', model: '6308', stock: 15, usageCount: 23 },
  { id: 2, name: '密封件', model: 'MS-25', stock: 8, usageCount: 18 },
  { id: 3, name: '联轴器', model: 'CL-50', stock: 5, usageCount: 12 },
  { id: 4, name: '传感器', model: 'TS-100', stock: 3, usageCount: 15 },
  { id: 5, name: '润滑油', model: 'L-46', stock: 20, usageCount: 30 }
])
// æ˜¾ç¤ºåˆ›å»ºå·¥å•对话框
const showCreateWorkOrderDialog = () => {
  // é‡ç½®è¡¨å•
  workOrderForm.value = {
    id: '',
    orderNo: '',
    deviceId: '',
    deviceName: '',
    faultType: '',
    faultDescription: '',
    priority: 'medium',
    assignee: '',
    status: 'pending',
    repairResult: '',
    usedParts: '',
    createTime: '',
    startTime: '',
    endTime: ''
  }
  createWorkOrderDialogVisible.value = true
}
// æ˜¾ç¤ºç¼–辑工单对话框
const showEditWorkOrderDialog = (order) => {
  workOrderForm.value = { ...order }
  editWorkOrderDialogVisible.value = true
}
// å¤„理工单点击
const handleWorkOrderClick = (row) => {
  // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ æŸ¥çœ‹å·¥å•è¯¦æƒ…çš„é€»è¾‘
}
// å¤„理标签页切换
const handleTabChange = (tab) => {
  activeTab.value = tab
  currentPage.value = 1 // åˆ‡æ¢æ ‡ç­¾é¡µæ—¶é‡ç½®é¡µç 
}
// å¤„理创建工单
const handleCreateWorkOrder = () => {
  // æ¨¡æ‹Ÿåˆ›å»ºå·¥å•
  const newOrder = {
    ...workOrderForm.value,
    id: workOrders.value.length + 1,
    orderNo: `WO${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(workOrders.value.length + 1).padStart(3, '0')}`,
    deviceName: devices.value.find(d => d.id === workOrderForm.value.deviceId)?.name || '',
    createTime: new Date().toLocaleString(),
    status: 'pending'
  }
  workOrders.value.unshift(newOrder)
  createWorkOrderDialogVisible.value = false
  ElMessage.success('工单创建成功')
}
// å¤„理编辑工单
const handleEditWorkOrder = () => {
  // æ¨¡æ‹Ÿç¼–辑工单
  const index = workOrders.value.findIndex(order => order.id === workOrderForm.value.id)
  if (index !== -1) {
    // å¦‚果状态从待处理变为处理中,设置开始时间
    if (workOrders.value[index].status === 'pending' && workOrderForm.value.status === 'processing') {
      workOrderForm.value.startTime = new Date().toLocaleString()
    }
    // å¦‚果状态从处理中变为已完成,设置结束时间
    if (workOrders.value[index].status === 'processing' && workOrderForm.value.status === 'completed') {
      workOrderForm.value.endTime = new Date().toLocaleString()
    }
    workOrders.value[index] = { ...workOrderForm.value }
    editWorkOrderDialogVisible.value = false
    ElMessage.success('工单编辑成功')
  }
}
// å¤„理删除工单
const handleDeleteWorkOrder = (id) => {
  ElMessageBox.confirm('确定要删除该工单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    // æ¨¡æ‹Ÿåˆ é™¤å·¥å•
    const index = workOrders.value.findIndex(order => order.id === id)
    if (index !== -1) {
      workOrders.value.splice(index, 1)
      ElMessage.success('工单删除成功')
    }
  }).catch(() => {
    // å–消删除
  })
}
// å¤„理分页大小变化
const handleSizeChange = (size) => {
  pageSize.value = size
  currentPage.value = 1
}
// å¤„理当前页变化
const handleCurrentChange = (current) => {
  currentPage.value = current
}
</script>
<style scoped>
.maintenance-management-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.header-buttons {
  display: flex;
  gap: 10px;
}
.pagination-container {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
}
.statistics-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  padding: 10px 0;
}
.stat-item {
  text-align: center;
  padding: 15px;
  background-color: #fafafa;
  border-radius: 4px;
}
.stat-label {
  font-size: 14px;
  color: #606266;
  margin-bottom: 10px;
}
.stat-value {
  font-size: 24px;
  font-weight: bold;
  color: #303133;
}
.spare-parts-content {
  padding: 10px 0;
}
:deep(.el-icon-plus) {
  margin-right: 5px;
}
</style>
src/views/monitoring/RealTimeMonitoring.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,515 @@
<template>
  <div class="real-time-monitoring-container">
    <!-- è®¾å¤‡é€‰æ‹© -->
    <el-card shadow="hover" style="margin-bottom: 20px;">
      <el-form :inline="true" :model="deviceSelectForm" class="device-select-form">
        <el-form-item label="设备">
          <el-select v-model="deviceSelectForm.deviceId" placeholder="请选择设备" @change="handleDeviceChange">
            <el-option
              v-for="device in devices"
              :key="device.id"
              :label="device.name"
              :value="device.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="timeRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            :default-time="['00:00:00', '23:59:59']"
          ></el-date-picker>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleHistoryQuery">查询历史数据</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- å®žæ—¶å‚数卡片 -->
    <el-row :gutter="20" style="margin-bottom: 20px;">
      <el-col :span="8" v-for="param in realTimeParams" :key="param.name">
        <el-card class="param-card" :shadow="param.isAlarm ? 'always' : 'hover'" :class="{ 'alarm-card': param.isAlarm }">
          <div class="param-content">
            <div class="param-title">
              <span>{{ param.name }}</span>
              <el-tag v-if="param.isAlarm" type="danger">告警</el-tag>
            </div>
            <div class="param-value">{{ param.value }} {{ param.unit }}</div>
            <div class="param-range">
              æ­£å¸¸èŒƒå›´ï¼š{{ param.min }} - {{ param.max }} {{ param.unit }}
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- å‘Šè­¦æç¤º -->
    <el-card shadow="hover" v-if="alarmList.length > 0" style="margin-bottom: 20px;">
      <template #header>
        <div class="card-header">
          <span>告警提示</span>
        </div>
      </template>
      <el-scrollbar height="200px">
        <div class="alarm-item" v-for="alarm in alarmList" :key="alarm.id">
          <el-alert
            :title="alarm.message"
            :type="alarm.level === 'high' ? 'error' : alarm.level === 'medium' ? 'warning' : 'info'"
            show-icon
            closable
            @close="handleAlarmClose(alarm.id)"
          >
            <template #default>
              <div class="alarm-detail">
                <span>设备:{{ alarm.deviceName }}</span>
                <span>时间:{{ alarm.time }}</span>
              </div>
            </template>
          </el-alert>
        </div>
      </el-scrollbar>
    </el-card>
    <!-- åŽ†å²æ•°æ®è¶‹åŠ¿å›¾ -->
    <el-card shadow="hover">
      <template #header>
        <div class="card-header">
          <span>历史数据趋势</span>
          <div class="param-tabs">
            <el-radio-group v-model="activeParam" size="small" @change="handleParamChange">
              <el-radio-button v-for="param in historyParams" :key="param" :label="param">
                {{ param }}
              </el-radio-button>
            </el-radio-group>
          </div>
        </div>
      </template>
      <div ref="historyChartRef" class="chart-container"></div>
    </el-card>
    <!-- å•设备参数详情页 -->
    <el-dialog v-model="deviceDetailVisible" title="设备参数详情" width="800px">
      <div class="device-detail-content">
        <h3>{{ selectedDevice.name }} - å®žæ—¶å‚æ•°</h3>
        <el-row :gutter="20" style="margin-bottom: 20px;">
          <el-col :span="12" v-for="param in realTimeParams" :key="param.name">
            <div class="detail-param-item">
              <span class="param-label">{{ param.name }}:</span>
              <span class="param-value" :class="{ 'alarm-value': param.isAlarm }">
                {{ param.value }} {{ param.unit }}
              </span>
              <span class="param-range">
                ï¼ˆ{{ param.min }} - {{ param.max }} {{ param.unit }})
              </span>
            </div>
          </el-col>
        </el-row>
        <h3>参数阈值设置</h3>
        <el-form :model="thresholdForm" label-width="100px" style="margin-top: 20px;">
          <el-form-item v-for="param in realTimeParams" :key="param.name" :label="param.name">
            <el-input-number
              v-model="thresholdForm[param.name + 'Min']"
              :min="0"
              :max="1000"
              size="small"
              style="width: 120px; margin-right: 10px;"
              placeholder="最小值"
            ></el-input-number>
            <span style="margin: 0 10px;">-</span>
            <el-input-number
              v-model="thresholdForm[param.name + 'Max']"
              :min="0"
              :max="1000"
              size="small"
              style="width: 120px; margin-right: 10px;"
              placeholder="最大值"
            ></el-input-number>
            <span>{{ param.unit }}</span>
          </el-form-item>
        </el-form>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="deviceDetailVisible = false">取消</el-button>
          <el-button type="primary" @click="saveThresholds">保存设置</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
import * as echarts from 'echarts'
// è®¾å¤‡åˆ—表
const devices = ref([
  { id: 'D001', name: '空压机A-001' },
  { id: 'D002', name: '冷却塔B-002' },
  { id: 'D003', name: 'æ°´æ³µC-003' },
  { id: 'D004', name: '发电机D-004' },
  { id: 'D005', name: '变压器E-005' }
])
// è®¾å¤‡é€‰æ‹©è¡¨å•
const deviceSelectForm = ref({
  deviceId: 'D001' // é»˜è®¤é€‰æ‹©ç¬¬ä¸€ä¸ªè®¾å¤‡
})
// æ—¶é—´èŒƒå›´
const timeRange = ref([])
// å½“前选中的设备
const selectedDevice = ref(devices.value[0])
// å®žæ—¶å‚数数据
const realTimeParams = ref([
  { name: '温度', value: 45, unit: '℃', min: 0, max: 60, isAlarm: false },
  { name: '压力', value: 0.85, unit: 'MPa', min: 0.5, max: 0.8, isAlarm: true },
  { name: '转速', value: 1450, unit: 'rpm', min: 1000, max: 1500, isAlarm: false },
  { name: '振动', value: 3.2, unit: 'mm/s', min: 0, max: 2.5, isAlarm: true },
  { name: '电流', value: 25.6, unit: 'A', min: 0, max: 30, isAlarm: false },
  { name: '电压', value: 380, unit: 'V', min: 360, max: 400, isAlarm: false }
])
// å‘Šè­¦åˆ—表
const alarmList = ref([
  { id: 1, deviceName: '空压机A-001', message: '压力超标', level: 'high', time: '2024-12-16 14:32:15' },
  { id: 2, deviceName: '空压机A-001', message: '振动过大', level: 'medium', time: '2024-12-16 14:30:45' },
  { id: 3, deviceName: '冷却塔B-002', message: '温度异常', level: 'warning', time: '2024-12-16 14:28:30' }
])
// åŽ†å²å‚æ•°é€‰é¡¹
const historyParams = ref(['温度', '压力', '转速', '振动', '电流', '电压'])
const activeParam = ref('温度')
// åŽ†å²æ•°æ®
const historyData = ref({
  dates: [],
  values: []
})
// å›¾è¡¨å¼•用
const historyChartRef = ref(null)
let historyChart = null
// è®¾å¤‡è¯¦æƒ…对话框
const deviceDetailVisible = ref(false)
// é˜ˆå€¼è®¾ç½®è¡¨å•
const thresholdForm = ref({})
// åˆå§‹åŒ–阈值表单
const initThresholdForm = () => {
  const form = {}
  realTimeParams.value.forEach(param => {
    form[param.name + 'Min'] = param.min
    form[param.name + 'Max'] = param.max
  })
  thresholdForm.value = form
}
// å¤„理设备变更
const handleDeviceChange = (deviceId) => {
  selectedDevice.value = devices.value.find(device => device.id === deviceId) || devices.value[0]
  // æ¨¡æ‹Ÿåˆ‡æ¢è®¾å¤‡åŽæ›´æ–°å®žæ—¶å‚æ•°
  updateRealTimeParams()
  // åˆ·æ–°åŽ†å²æ•°æ®å›¾è¡¨
  initHistoryChart()
}
// æ›´æ–°å®žæ—¶å‚数(模拟)
const updateRealTimeParams = () => {
  realTimeParams.value = realTimeParams.value.map(param => {
    // ç”Ÿæˆéšæœºå€¼ï¼Œéƒ¨åˆ†å‚数可能触发告警
    const randomFactor = Math.random() * 0.2 - 0.1 // -0.1 åˆ° 0.1 ä¹‹é—´çš„随机数
    const baseValue = (param.min + param.max) / 2
    const newValue = baseValue + baseValue * randomFactor
    const isAlarm = newValue < param.min || newValue > param.max
    return {
      ...param,
      value: Number(newValue.toFixed(2)),
      isAlarm
    }
  })
}
// å¤„理历史数据查询
const handleHistoryQuery = () => {
  // æ¨¡æ‹ŸæŸ¥è¯¢åŽ†å²æ•°æ®
  generateHistoryData()
  initHistoryChart()
  ElMessage.success('历史数据查询成功')
}
// ç”ŸæˆåŽ†å²æ•°æ®ï¼ˆæ¨¡æ‹Ÿï¼‰
const generateHistoryData = () => {
  const dates = []
  const values = []
  // ç”Ÿæˆè¿‡åŽ»7天的日期和对应数据
  for (let i = 6; i >= 0; i--) {
    const date = new Date()
    date.setDate(date.getDate() - i)
    dates.push(date.toLocaleDateString())
    // ç”Ÿæˆéšæœºå€¼
    const baseValue = (realTimeParams.value.find(p => p.name === activeParam.value).min +
                      realTimeParams.value.find(p => p.name === activeParam.value).max) / 2
    const randomValue = baseValue + (Math.random() - 0.5) * baseValue * 0.4
    values.push(Number(randomValue.toFixed(2)))
  }
  historyData.value = {
    dates,
    values
  }
}
// åˆå§‹åŒ–历史数据趋势图
const initHistoryChart = () => {
  if (historyChartRef.value) {
    historyChart = echarts.init(historyChartRef.value)
    // å¦‚果没有历史数据,生成模拟数据
    if (historyData.value.dates.length === 0) {
      generateHistoryData()
    }
    const paramConfig = realTimeParams.value.find(p => p.name === activeParam.value)
    const option = {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross',
          label: {
            backgroundColor: '#6a7985'
          }
        }
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: historyData.value.dates
      },
      yAxis: {
        type: 'value',
        name: `${activeParam.value}(${paramConfig.unit})`,
        min: paramConfig.min * 0.9,
        max: paramConfig.max * 1.1
      },
      series: [
        {
          name: activeParam.value,
          type: 'line',
          stack: '总量',
          areaStyle: {},
          emphasis: {
            focus: 'series'
          },
          data: historyData.value.values,
          itemStyle: {
            color: '#409eff'
          },
          lineStyle: {
            width: 3
          }
        }
      ]
    }
    historyChart.setOption(option)
  }
}
// å¤„理参数切换
const handleParamChange = () => {
  // ç”Ÿæˆæ–°å‚数的历史数据
  generateHistoryData()
  initHistoryChart()
}
// å¤„理告警关闭
const handleAlarmClose = (alarmId) => {
  const index = alarmList.value.findIndex(alarm => alarm.id === alarmId)
  if (index !== -1) {
    alarmList.value.splice(index, 1)
  }
}
// æ‰“开设备详情页
const openDeviceDetail = () => {
  initThresholdForm()
  deviceDetailVisible.value = true
}
// ä¿å­˜é˜ˆå€¼è®¾ç½®
const saveThresholds = () => {
  // æ¨¡æ‹Ÿä¿å­˜é˜ˆå€¼
  realTimeParams.value = realTimeParams.value.map(param => {
    return {
      ...param,
      min: thresholdForm.value[param.name + 'Min'],
      max: thresholdForm.value[param.name + 'Max']
    }
  })
  deviceDetailVisible.value = false
  ElMessage.success('阈值设置保存成功')
}
// ç›‘听窗口大小变化,调整图表大小
const handleResize = () => {
  historyChart?.resize()
}
// æ¯5秒更新一次实时参数(模拟)
let updateTimer = null
const startRealTimeUpdate = () => {
  updateTimer = setInterval(() => {
    updateRealTimeParams()
  }, 5000)
}
onMounted(() => {
  // åˆå§‹åŒ–图表
  initHistoryChart()
  // åˆå§‹åŒ–阈值表单
  initThresholdForm()
  // å¼€å§‹å®žæ—¶æ›´æ–°
  startRealTimeUpdate()
  // ç›‘听窗口大小变化
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  // æ¸…除定时器
  if (updateTimer) {
    clearInterval(updateTimer)
  }
  // é”€æ¯å›¾è¡¨
  historyChart?.dispose()
  // ç§»é™¤äº‹ä»¶ç›‘听
  window.removeEventListener('resize', handleResize)
})
</script>
<style scoped>
.real-time-monitoring-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.device-select-form {
  margin-bottom: 0;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.param-tabs {
  display: flex;
  gap: 10px;
}
.param-card {
  transition: all 0.3s;
}
.alarm-card {
  border-left: 4px solid #f56c6c;
  box-shadow: 0 2px 12px 0 rgba(245, 108, 108, 0.1);
}
.param-content {
  text-align: center;
}
.param-title {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  color: #606266;
  margin-bottom: 10px;
}
.param-value {
  font-size: 32px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 10px;
  display: block;
}
.alarm-value {
  color: #f56c6c;
}
.param-range {
  font-size: 14px;
  color: #909399;
}
.alarm-item {
  margin-bottom: 10px;
}
.alarm-detail {
  display: flex;
  justify-content: space-between;
  margin-top: 5px;
  font-size: 14px;
  color: #606266;
}
.chart-container {
  width: 100%;
  height: 400px;
}
.device-detail-content {
  padding: 10px 0;
}
.device-detail-content h3 {
  margin-bottom: 20px;
  color: #303133;
}
.detail-param-item {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
  padding: 10px;
  background-color: #fafafa;
  border-radius: 4px;
}
.param-label {
  font-size: 14px;
  color: #606266;
  width: 80px;
}
.param-range {
  font-size: 12px;
  color: #909399;
  margin-left: 10px;
}
</style>
vite.config.js
@@ -8,8 +8,8 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
    VITE_APP_ENV == "development"
      ? "http://127.0.0.1:8018" // å¼€å‘环境后端接口
      : "http://10.136.12.71:8018"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
      ? "http://127.0.0.1:8020" // å¼€å‘环境后端接口
      : "http://10.136.12.71:8020"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。