Browse Source

ly 可靠性 预防性维修

ly 1 week ago
parent
commit
842c3035be

+ 5 - 0
master/src/main/resources/application.yml

@@ -95,6 +95,11 @@ spring:
 #    host: 127.0.0.1
 #    port: 6379
 #    password:
+    # 阿里云Redis配置 (BYC-IN-EMC-CPMS-REDIS-01)
+#    host: r-uf6jl950xjqofogjxi.redis.rds.aliyuncs.com
+#    port: 6379
+#    password: r-uf6jl950xjqofogjxi:kT6GShHXt3UvSEduX9K4
+#    database: 1
     # 连接超时时间
     timeout: 10s
     lettuce:

+ 214 - 28
ui/src/views/reliability/rel_device/detail.vue

@@ -13,7 +13,7 @@
     <div class="device-info-section" :style="{width: (activeTab === 'compo' || activeTab === 'record') ? '70%' : '400px'}">
       <div class="info-panel">
         <div class="panel-header">
-          <h3>设备信息</h3>
+          <h3>{{ deviceInfo ? deviceInfo.devName : '设备信息' }}</h3>
           <el-button type="text" @click="goBack">
             <i class="el-icon-back"></i> 返回
           </el-button>
@@ -29,9 +29,6 @@
               <el-descriptions-item label="设备位号">
                 {{ deviceInfo.devTag }}
               </el-descriptions-item>
-              <el-descriptions-item label="装置">
-                {{ deviceInfo.plant }}
-              </el-descriptions-item>
               <el-descriptions-item label="位置">
                 {{ deviceInfo.devLoc }}
               </el-descriptions-item>
@@ -73,7 +70,11 @@
           <!-- 关联部件标签页 -->
           <el-tab-pane label="关联部件" name="compo" v-if="deviceInfo.compoList && deviceInfo.compoList.length > 0">
             <el-table :data="deviceInfo.compoList" border size="small" :height="clientHeight" style="margin-top: 10px;">
-              <el-table-column label="部件名称" align="center" prop="compoName" width="120" :show-overflow-tooltip="true"/>
+              <el-table-column label="部件名称" align="center" prop="compoName" width="120" :show-overflow-tooltip="true">
+                <template slot-scope="scope">
+                  <el-button type="text" @click="handleCompoClick(scope.row)">{{ scope.row.compoName }}</el-button>
+                </template>
+              </el-table-column>
               <el-table-column label="检查频率" align="center" prop="inspFreq" width="110" :show-overflow-tooltip="true">
                 <template slot-scope="scope">
                   {{ translateFrequency(scope.row.inspFreq) }}
@@ -112,7 +113,7 @@
 
           <!-- 维修记录标签页 -->
           <el-tab-pane label="维修记录" name="record">
-            <el-table v-loading="maintRecordLoading" :data="maintRecordList" border size="small" :height="clientHeight" style="margin-top: 10px; width: 100%;">
+            <el-table v-loading="maintRecordLoading" :data="maintRecordList" border size="small" :height="clientHeight - 50" style="width: 100%;">
               <el-table-column label="部件名称" align="center" prop="compoName" width="120" :show-overflow-tooltip="true"/>
               <el-table-column label="维修类型" align="center" width="100">
                 <template slot-scope="scope">
@@ -140,6 +141,16 @@
                 </template>
               </el-table-column>
             </el-table>
+            <el-pagination
+              style="margin-top: 10px; text-align: right;"
+              @size-change="handleMaintRecordSizeChange"
+              @current-change="handleMaintRecordCurrentChange"
+              :current-page="maintRecordQuery.pageNum"
+              :page-sizes="[10, 20, 50, 100]"
+              :page-size="maintRecordQuery.pageSize"
+              layout="total, sizes, prev, pager, next, jumper"
+              :total="maintRecordTotal">
+            </el-pagination>
           </el-tab-pane>
         </el-tabs>
 
@@ -150,19 +161,71 @@
     </div>
 
     <!-- 部件详情对话框 -->
-    <el-dialog :title="selectedCompo ? selectedCompo.compoName : '部件信息'" :visible.sync="compoDialogVisible" width="600px" append-to-body>
-      <el-descriptions v-if="selectedCompo" :column="2" border>
-        <el-descriptions-item label="检查频率">{{ translateFrequency(selectedCompo.inspFreq) }}</el-descriptions-item>
-        <el-descriptions-item label="上次检查日期">{{ parseTime(selectedCompo.lastInspDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="检查内容" :span="2">{{ selectedCompo.inspContent || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="维修频率">{{ translateFrequency(selectedCompo.fixFreq) }}</el-descriptions-item>
-        <el-descriptions-item label="上次维修日期">{{ parseTime(selectedCompo.lastFixDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="维修内容" :span="2">{{ selectedCompo.fixContent || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="更换频率">{{ translateFrequency(selectedCompo.replaceFreq) }}</el-descriptions-item>
-        <el-descriptions-item label="上次更换日期">{{ parseTime(selectedCompo.lastReplaceDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="更换内容" :span="2">{{ selectedCompo.replaceContent || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ selectedCompo.remarks || '-' }}</el-descriptions-item>
-      </el-descriptions>
+    <el-dialog :title="selectedCompo ? selectedCompo.compoName : '部件信息'" :visible.sync="compoDialogVisible" width="900px" append-to-body class="compo-detail-dialog">
+      <div v-if="selectedCompo" class="compo-detail-content">
+        <!-- 部件图片 -->
+        <div v-if="selectedCompo.compoPhoto" class="compo-photo-section">
+          <el-image
+            :src="getPhotoUrl(selectedCompo.compoPhoto)"
+            :preview-src-list="[getPhotoUrl(selectedCompo.compoPhoto)]"
+            fit="contain"
+            class="compo-photo">
+            <div slot="error" class="image-error">
+              <i class="el-icon-picture-outline"></i>
+              <span>图片加载失败</span>
+            </div>
+          </el-image>
+        </div>
+
+        <!-- 部件基本信息 -->
+        <el-descriptions :column="2" border size="small" class="compo-info">
+          <el-descriptions-item label="检查频率">{{ translateFrequency(selectedCompo.inspFreq) }}</el-descriptions-item>
+          <el-descriptions-item label="上次检查日期">{{ parseTime(selectedCompo.lastInspDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="检查内容" :span="2">{{ selectedCompo.inspContent || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="维修频率">{{ translateFrequency(selectedCompo.fixFreq) }}</el-descriptions-item>
+          <el-descriptions-item label="上次维修日期">{{ parseTime(selectedCompo.lastFixDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="维修内容" :span="2">{{ selectedCompo.fixContent || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="更换频率">{{ translateFrequency(selectedCompo.replaceFreq) }}</el-descriptions-item>
+          <el-descriptions-item label="上次更换日期">{{ parseTime(selectedCompo.lastReplaceDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="更换内容" :span="2">{{ selectedCompo.replaceContent || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="备注" :span="2">{{ selectedCompo.remarks || '-' }}</el-descriptions-item>
+        </el-descriptions>
+
+        <!-- 维修记录 -->
+        <div class="compo-maint-record-section">
+          <h4 class="section-title">维修记录</h4>
+          <el-table v-loading="compoMaintRecordLoading" :data="compoMaintRecordList" border size="small" max-height="250">
+            <el-table-column label="维修类型" align="center" width="80">
+              <template slot-scope="scope">
+                <span v-if="scope.row.maintType === '1'">检查</span>
+                <span v-else-if="scope.row.maintType === '2'">维修</span>
+                <span v-else-if="scope.row.maintType === '3'">更换</span>
+                <span v-else>{{ scope.row.maintType || '-' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="责任人" align="center" width="80">
+              <template slot-scope="scope">
+                <span>{{ getStaffNameById(scope.row.responsible) || '-' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="检查内容" align="center" prop="inspectContent" :show-overflow-tooltip="true"/>
+            <el-table-column label="检查时间" align="center" width="100">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.inspectTime, '{y}-{m}-{d}') || '-' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="维修内容" align="center" prop="maintContent" :show-overflow-tooltip="true"/>
+            <el-table-column label="维修时间" align="center" width="100">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.maintTime, '{y}-{m}-{d}') || '-' }}</span>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div v-if="compoMaintRecordList.length === 0 && !compoMaintRecordLoading" class="no-record-tip">
+            暂无维修记录
+          </div>
+        </div>
+      </div>
       <div v-else class="no-compo-tip">
         <el-empty description="未找到对应的部件信息"></el-empty>
       </div>
@@ -191,11 +254,18 @@ export default {
       activeTab: 'info',
       maintRecordList: [],
       maintRecordLoading: false,
+      maintRecordTotal: 0,
+      maintRecordQuery: {
+        pageNum: 1,
+        pageSize: 20
+      },
       clientHeight: 0,
       staffOptions: [],
       // 部件详情对话框
       compoDialogVisible: false,
-      selectedCompo: null
+      selectedCompo: null,
+      compoMaintRecordList: [],
+      compoMaintRecordLoading: false
     }
   },
   created() {
@@ -242,10 +312,43 @@ export default {
       if (!compoName || !this.deviceInfo || !this.deviceInfo.compoList) {
         return;
       }
+      // 去掉 .xxx 后缀(如 "部件名.123" -> "部件名")
+      const cleanName = compoName.replace(/\.[^.]+$/, '');
       // 在部件列表中查找匹配的部件
-      const compo = this.deviceInfo.compoList.find(c => c.compoName === compoName);
-      this.selectedCompo = compo || null;
+      const compo = this.deviceInfo.compoList.find(c => c.compoName === cleanName);
+      // 只有找到对应部件时才显示弹框
+      if (compo) {
+        this.handleCompoClick(compo);
+      }
+    },
+
+    // 处理部件点击(表格或流程图)
+    handleCompoClick(compo) {
+      this.selectedCompo = compo;
       this.compoDialogVisible = true;
+      // 加载该部件的维修记录
+      this.loadCompoMaintRecords(compo.compoName);
+    },
+
+    // 加载部件维修记录
+    async loadCompoMaintRecords(compoName) {
+      try {
+        this.compoMaintRecordLoading = true;
+        const response = await listRel_maint_record({
+          plant: this.deviceInfo?.plant,
+          devTag: this.deviceInfo?.devTag,
+          compoName: compoName,
+          recordStatus: '2',
+          pageNum: 1,
+          pageSize: 50
+        });
+        this.compoMaintRecordList = response.rows || [];
+      } catch (error) {
+        console.error('加载部件维修记录失败:', error);
+        this.compoMaintRecordList = [];
+      } finally {
+        this.compoMaintRecordLoading = false;
+      }
     },
 
     // 返回上一页
@@ -292,12 +395,14 @@ export default {
       try {
         this.maintRecordLoading = true;
         const response = await listRel_maint_record({
-          devId: this.deviceId,
+          plant: this.deviceInfo?.plant,
+          devTag: this.deviceInfo?.devTag,
           recordStatus: '2',  // 只查询已完成的维修记录
-          pageNum: 1,
-          pageSize: 1000
+          pageNum: this.maintRecordQuery.pageNum,
+          pageSize: this.maintRecordQuery.pageSize
         });
         this.maintRecordList = response.rows || [];
+        this.maintRecordTotal = response.total || 0;
       } catch (error) {
         console.error('加载维修记录失败:', error);
         this.$message.error('加载维修记录失败');
@@ -306,6 +411,18 @@ export default {
       }
     },
 
+    // 维修记录分页大小改变
+    handleMaintRecordSizeChange(val) {
+      this.maintRecordQuery.pageSize = val;
+      this.loadMaintRecords();
+    },
+
+    // 维修记录页码改变
+    handleMaintRecordCurrentChange(val) {
+      this.maintRecordQuery.pageNum = val;
+      this.loadMaintRecords();
+    },
+
     // 获取维修状态标签类型
     getRecordStatusTagType(status) {
       const statusMap = {
@@ -406,9 +523,10 @@ export default {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 20px;
+  padding: 10px 20px;
   border-bottom: 1px solid #e5e7eb;
-  background: #f8fafc;
+  background: #ffffff;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
 }
 
 .panel-header h3 {
@@ -430,6 +548,10 @@ export default {
   flex-direction: column;
 }
 
+.panel-tabs /deep/ .el-tabs__header {
+  margin-bottom: 5px;
+}
+
 .panel-tabs /deep/ .el-tabs__content {
   flex: 1;
   overflow: hidden;
@@ -437,7 +559,7 @@ export default {
 }
 
 .panel-tabs /deep/ .el-tab-pane {
-  padding: 20px;
+  padding: 5px 15px;
 }
 
 .panel-tabs /deep/ .el-table {
@@ -505,4 +627,68 @@ export default {
   font-size: 16px;
   font-weight: 600;
 }
+
+/* 部件详情对话框样式 */
+.compo-detail-dialog /deep/ .el-dialog__body {
+  padding: 15px 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.compo-detail-content {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.compo-photo-section {
+  text-align: center;
+  background: #f9fafb;
+  padding: 15px;
+  border-radius: 8px;
+}
+
+.compo-photo {
+  max-width: 100%;
+  max-height: 200px;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.image-error {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 120px;
+  color: #909399;
+}
+
+.image-error i {
+  font-size: 36px;
+  margin-bottom: 8px;
+}
+
+.compo-info {
+  margin-bottom: 10px;
+}
+
+.compo-maint-record-section {
+  border-top: 1px solid #e5e7eb;
+  padding-top: 15px;
+}
+
+.section-title {
+  margin: 0 0 10px 0;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.no-record-tip {
+  text-align: center;
+  color: #909399;
+  padding: 20px 0;
+  font-size: 14px;
+}
 </style>

+ 127 - 115
ui/src/views/reliability/rel_device/index.vue

@@ -163,6 +163,12 @@
       <el-table-column label="备注" align="center" prop="remarks" width="150" :show-overflow-tooltip="true"/>
       <el-table-column label="操作" align="center" fixed="right" width="180" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleDeviceDetail(scope.row)"
+          >详情</el-button>
           <el-button
             size="mini"
             type="text"
@@ -177,13 +183,6 @@
             @click="handleUpdate(scope.row)"
             v-hasPermi="['reliability:rel_device:edit']"
           >修改</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-delete"
-            @click="handleDelete(scope.row)"
-            v-hasPermi="['reliability:rel_device:remove']"
-          >删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -197,61 +196,89 @@
     />
 
     <!-- 添加或修改可靠性设备清单对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="装置" prop="plant">
-          <el-input v-model="form.plant" placeholder="请输入装置" />
-        </el-form-item>
-        <el-form-item label="位置" prop="devLoc">
-          <el-input v-model="form.devLoc" placeholder="请输入位置" />
-        </el-form-item>
-        <el-form-item label="设备名称" prop="devName">
-          <el-input v-model="form.devName" placeholder="请输入设备名称" />
-        </el-form-item>
-        <el-form-item label="设备位号" prop="devTag">
-          <el-input v-model="form.devTag" placeholder="请输入设备位号" />
-        </el-form-item>
-        <el-form-item label="设备类型" prop="devType">
-          <el-input v-model="form.devType" placeholder="请输入设备类型" />
-        </el-form-item>
-        <el-form-item label="设备描述" prop="devDesc">
-          <el-input v-model="form.devDesc" placeholder="请输入设备描述" />
-        </el-form-item>
-        <el-form-item label="区域" prop="devArea">
-          <el-input v-model="form.devArea" placeholder="请输入区域" />
-        </el-form-item>
-        <el-form-item label="区域负责人" prop="areaResponsible">
-          <el-select v-model="form.areaResponsible" placeholder="请选择区域负责人" clearable filterable>
-            <el-option
-              v-for="staff in staffOptions"
-              :key="staff.id"
-              :label="staff.name"
-              :value="staff.staffid">
-            </el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="设备照片" prop="devPhoto">
-          <el-upload
-            class="upload-demo"
-            :action="uploadPhoto.url"
-            :headers="uploadPhoto.headers"
-            :data="uploadPhoto.data"
-            :on-success="handlePhotoSuccess"
-            :on-remove="handleRemovePhoto"
-            :before-upload="beforePhotoUpload"
-            :file-list="photoFileList"
-            :limit="1"
-            list-type="picture-card"
-            :on-exceed="handleExceedPhoto"
-            accept="image/*">
-            <i class="el-icon-plus"></i>
-            <div slot="tip" class="el-upload__tip">只能上传jpg/png/gif文件,且不超过15MB</div>
-          </el-upload>
-        </el-form-item>
+    <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body class="device-dialog">
+      <el-form ref="form" :model="form" :rules="rules" label-width="90px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="装置" prop="plant">
+              <el-input v-model="form.plant" placeholder="请输入装置" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="位置" prop="devLoc">
+              <el-input v-model="form.devLoc" placeholder="请输入位置" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="设备名称" prop="devName">
+              <el-input v-model="form.devName" placeholder="请输入设备名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备位号" prop="devTag">
+              <el-input v-model="form.devTag" placeholder="请输入设备位号" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="设备类型" prop="devType">
+              <el-input v-model="form.devType" placeholder="请输入设备类型" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="区域" prop="devArea">
+              <el-input v-model="form.devArea" placeholder="请输入区域" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="区域负责人" prop="areaResponsible">
+              <el-select v-model="form.areaResponsible" placeholder="请选择区域负责人" clearable filterable style="width: 100%">
+                <el-option
+                  v-for="staff in staffOptions"
+                  :key="staff.id"
+                  :label="staff.name"
+                  :value="staff.staffid">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备描述" prop="devDesc">
+              <el-input v-model="form.devDesc" placeholder="请输入设备描述" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="设备照片" prop="devPhoto">
+              <el-upload
+                class="upload-demo"
+                :action="uploadPhoto.url"
+                :headers="uploadPhoto.headers"
+                :data="uploadPhoto.data"
+                :on-success="handlePhotoSuccess"
+                :on-remove="handleRemovePhoto"
+                :before-upload="beforePhotoUpload"
+                :file-list="photoFileList"
+                :limit="1"
+                list-type="picture-card"
+                :on-exceed="handleExceedPhoto"
+                accept="image/*">
+                <i class="el-icon-plus"></i>
+              </el-upload>
+              <div class="upload-tip">只能上传jpg/png/gif文件,且不超过15MB</div>
+            </el-form-item>
+          </el-col>
+        </el-row>
       </el-form>
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
         <el-button @click="cancel">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
       </div>
     </el-dialog>
       <!-- 用户导入对话框 -->
@@ -294,14 +321,6 @@
         </div>
       </el-dialog>
 
-      <!-- 维修计划表单对话框 -->
-      <maint-plan-form
-        :visible.sync="maintPlanFormOpen"
-        :title="maintPlanFormTitle"
-        :form="maintPlanFormData"
-        @submit="handleMaintPlanFormSubmit"
-        @cancel="handleMaintPlanFormCancel"
-      />
   </div>
 </template>
 
@@ -312,11 +331,10 @@ import { listStaffmgrAll } from "@/api/plant/staffmgr";
 import { getToken } from "@/utils/auth";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
-import MaintPlanForm from "@/views/reliability/rel_maint_plan/MaintPlanForm.vue";
 
 export default {
   name: "Rel_device",
-  components: { Treeselect, MaintPlanForm },
+  components: { Treeselect },
   data() {
     return {
       // 遮罩层
@@ -373,10 +391,6 @@ export default {
       // 图片预览相关
       photoPreviewVisible: false,
       previewPhotoUrl: '',
-      // 维修计划表单相关
-      maintPlanFormOpen: false,
-      maintPlanFormTitle: "",
-      maintPlanFormData: {},
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -674,43 +688,16 @@ export default {
 
       /** 新增维修计划按钮操作 */
       handleAddPlan(row) {
-        // 初始化维修计划表单数据,预填设备信息
-        this.maintPlanFormData = {
-          planId: null,
-          devId: row.devId,
-          plant: row.plant,
-          devName: row.devName,
-          devTag: row.devTag,
-          planTime: null,
-          approvalStatus: "0",
-          responsible: null,
-          completionStatus: "0",
-          processId: null,
-          approver: null,
-          delFlag: null,
-          createrCode: null,
-          createdate: null,
-          updaterCode: null,
-          updatedate: null,
-          deptId: null,
-          remarks: null,
-          maintComponents: []
-        };
-        this.maintPlanFormTitle = "添加设备维修计划";
-        this.maintPlanFormOpen = true;
-      },
-
-      /** 维修计划表单提交 */
-      handleMaintPlanFormSubmit(formData) {
-        // 这里可以处理维修计划表单提交后的逻辑
-        // 例如刷新列表等
-        this.getList();
-      },
-
-      /** 维修计划表单取消 */
-      handleMaintPlanFormCancel() {
-        this.maintPlanFormOpen = false;
-        this.maintPlanFormData = {};
+        // 跳转到新增维修计划页面,预填设备信息
+        this.$router.push({
+          path: '/reliability/rel_maint_plan/form',
+          query: {
+            devId: row.devId,
+            plant: row.plant,
+            devName: row.devName,
+            devTag: row.devTag
+          }
+        });
       },
 
       /** 根据人员ID获取人员姓名 */
@@ -724,14 +711,39 @@ export default {
 </script>
 
 <style scoped>
+/* 设备对话框样式 */
+.device-dialog >>> .el-dialog__body {
+  padding: 20px 30px;
+}
+
+.device-dialog >>> .el-form-item {
+  margin-bottom: 18px;
+}
+
+.device-dialog >>> .el-form-item__label {
+  font-weight: 500;
+}
+
+/* 上传组件样式 */
 .upload-demo >>> .el-upload--picture-card {
-  width: 148px;
-  height: 148px;
-  line-height: 148px;
+  width: 120px;
+  height: 120px;
+  line-height: 120px;
 }
 
 .upload-demo >>> .el-upload-list--picture-card .el-upload-list__item {
-  width: 148px;
-  height: 148px;
+  width: 120px;
+  height: 120px;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 8px;
+}
+
+/* 对话框底部按钮 */
+.dialog-footer {
+  text-align: right;
 }
 </style>

+ 163 - 55
ui/src/views/reliability/rel_device/process-diagram.vue

@@ -1,12 +1,18 @@
 <template>
 	<div class="svg-page">
-	  <!-- 工具栏 -->
-	  <div class="toolbar">
-		<button @click="resetView" class="tool-btn">重置视图</button>
+	  <!-- 工具栏(仅在有SVG时显示) -->
+	  <div class="toolbar" v-if="svgPath">
+		<div class="toolbar-left">
+		  <button @click="resetView" class="tool-btn">重置视图</button>
+		  <button @click="zoomIn" class="tool-btn">放大</button>
+		  <button @click="zoomOut" class="tool-btn">缩小</button>
+		</div>
+		<span v-if="hoverShape && hoverShape.name" class="hover-name">{{ hoverShape.name }}</span>
 	  </div>
 
-	  <!-- 左侧 SVG 区域 -->
+	  <!-- SVG 流程图区域 -->
 	  <div
+		v-if="svgPath"
 		class="svg-container"
 		ref="svgContainer"
 		@mouseleave="handleMouseLeave"
@@ -23,10 +29,11 @@
 		  @dragging="handleDragging"
 		  @resizing="handleResizing"
 		  class="svg-wrapper"
+		  :style="{ transform: `scale(${scale})`, transformOrigin: 'top left' }"
 		>
 		  <object
 			ref="svgObject"
-			:data= this.defaultSvgPath
+			:data="svgPath"
 			type="image/svg+xml"
 			@load="initSvg"
 			class="svg-object"
@@ -38,16 +45,24 @@
 		  <div class="loading-spinner"></div>
 		  <p>正在加载流程图...</p>
 		</div>
+	  </div>
 
-		<!-- 设备详情工具提示框 -->
-		<div
-		  v-if="hoverShape && hoverShape.name"
-		  class="tooltip-container"
-		  :style="tooltipStyle"
-		>
-		  <div class="tooltip-content">
-			<span class="tooltip-title">{{ hoverShape.name }}</span>
+	  <!-- 设备照片区域(当没有SVG时显示) -->
+	  <div v-else class="photo-container">
+		<el-image
+		  v-if="selectedDevice && selectedDevice.devPhoto"
+		  :src="getPhotoUrl(selectedDevice.devPhoto)"
+		  :preview-src-list="[getPhotoUrl(selectedDevice.devPhoto)]"
+		  fit="contain"
+		  class="device-photo">
+		  <div slot="error" class="image-slot">
+			<i class="el-icon-picture-outline"></i>
+			<p>图片加载失败</p>
 		  </div>
+		</el-image>
+		<div v-else class="no-photo">
+		  <i class="el-icon-picture-outline"></i>
+		  <p>暂无设备照片</p>
 		</div>
 	  </div>
 	</div>
@@ -71,30 +86,51 @@
     },
     data() {
       return {
-        // 默认 SVG 路径
-        defaultSvgPath: require('@/assets/visio/224U.svg'),
         // vue-draggable-resizable 相关
         svgPosition: { x: 0, y: 0 },
         svgSize: { width: 1200, height: 900 },
         loading: true,
+        // 缩放比例
+        scale: 1,
         // 当前悬停的形状(只保留name用于tooltip显示)
-        hoverShape: null
+        hoverShape: null,
+        // SVG 文件映射
+        svgMap: {
+          '224U': require('@/assets/visio/224U.svg'),
+          '80U': require('@/assets/visio/80U.svg'),
+          '12M': require('@/assets/visio/12M.svg')
+        }
       }
     },
 
     computed: {
-      // 计算工具提示框的位置样式 - 固定在组件右上角
-      tooltipStyle() {
-        return {
-          position: 'absolute',
-          right: '20px',
-          top: '20px',
-          zIndex: 1000
+      // 根据设备类型动态获取 SVG 路径
+      svgPath() {
+        if (this.selectedDevice && this.selectedDevice.devType) {
+          const devType = this.selectedDevice.devType;
+          // 根据设备类型匹配 SVG
+          if (devType.includes('224U')) {
+            return this.svgMap['224U'];
+          } else if (devType.includes('80U')) {
+            return this.svgMap['80U'];
+          } else if (devType.includes('12M')) {
+            return this.svgMap['12M'];
+          }
         }
+        // 不匹配时返回 null,显示设备照片
+        return null;
       }
     },
 
     methods: {
+      // 获取完整的图片URL
+      getPhotoUrl(photoPath) {
+        if (!photoPath) return '';
+        if (photoPath && !photoPath.startsWith('http')) {
+          return process.env.VUE_APP_BASE_API + photoPath;
+        }
+        return photoPath;
+      },
       // 初始化 SVG
       initSvg() {
         this.loading = false;
@@ -118,27 +154,48 @@
           group.addEventListener("mouseenter", (e) => {
             e.stopPropagation();
 
-            const innerPath = group.querySelector("path, line, rect, circle, polygon");
-            if (!innerPath) return;
-
-            // 应用视觉效果
-            this.applyHoverEffect(innerPath);
-
-            // 显示工具提示框(只显示title)
+            // 获取当前元素的 title,去掉 .xxx 后缀
             const title = group.querySelector("title")?.textContent;
-            if (title) {
-              this.hoverShape = { name: title };
-            }
+            if (!title) return;
+            const cleanName = title.replace(/\.[^.]+$/, '');
+
+            // 高亮所有相同名称的元素
+            groups.forEach((g) => {
+              const gTitle = g.querySelector("title")?.textContent;
+              if (!gTitle) return;
+              const gCleanName = gTitle.replace(/\.[^.]+$/, '');
+              if (gCleanName === cleanName) {
+                const innerPath = g.querySelector("path, line, rect, circle, polygon");
+                if (innerPath) {
+                  this.applyHoverEffect(innerPath);
+                }
+              }
+            });
+
+            // 显示工具提示框(显示去掉后缀的名称)
+            this.hoverShape = { name: cleanName };
           });
 
           group.addEventListener("mouseleave", (e) => {
             e.stopPropagation();
 
-            const innerPath = group.querySelector("path, line, rect, circle, polygon");
-            if (!innerPath) return;
-
-            // 清除视觉效果
-            this.clearHoverEffect(innerPath);
+            // 获取当前元素的 title,去掉 .xxx 后缀
+            const title = group.querySelector("title")?.textContent;
+            if (!title) return;
+            const cleanName = title.replace(/\.[^.]+$/, '');
+
+            // 清除所有相同名称元素的高亮
+            groups.forEach((g) => {
+              const gTitle = g.querySelector("title")?.textContent;
+              if (!gTitle) return;
+              const gCleanName = gTitle.replace(/\.[^.]+$/, '');
+              if (gCleanName === cleanName) {
+                const innerPath = g.querySelector("path, line, rect, circle, polygon");
+                if (innerPath) {
+                  this.clearHoverEffect(innerPath);
+                }
+              }
+            });
 
             // 隐藏工具提示框
             this.hoverShape = null;
@@ -200,6 +257,17 @@
       resetView() {
         this.svgPosition = { x: 0, y: 0 };
         this.svgSize = { width: 1200, height: 900 };
+        this.scale = 1;
+      },
+
+      // 放大
+      zoomIn() {
+        this.scale = Math.min(3, this.scale + 0.2);
+      },
+
+      // 缩小
+      zoomOut() {
+        this.scale = Math.max(0.5, this.scale - 0.2);
       },
 
       // 保存元素的原始样式
@@ -361,13 +429,27 @@
   .toolbar {
 	display: flex;
 	align-items: center;
-	gap: 10px;
+	justify-content: space-between;
 	padding: 10px 20px;
 	background: #ffffff;
 	border-bottom: 1px solid #e5e7eb;
 	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
   }
 
+  .toolbar-left {
+	display: flex;
+	gap: 10px;
+  }
+
+  .hover-name {
+	font-size: 14px;
+	font-weight: 500;
+	color: #3b82f6;
+	background: #eff6ff;
+	padding: 6px 12px;
+	border-radius: 4px;
+  }
+
   .tool-btn {
 	padding: 8px 16px;
 	background: #3b82f6;
@@ -436,26 +518,52 @@
 	100% { transform: rotate(360deg); }
   }
 
-  /* 工具提示框样式 */
-  .tooltip-container {
-	pointer-events: none;
-	position: absolute;
-	z-index: 1000;
+  /* 设备照片区域样式 */
+  .photo-container {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	background: #f6f8fa;
+	padding: 20px;
   }
 
-  .tooltip-content {
-	background: rgba(0, 0, 0, 0.75);
-	color: #fff;
-	padding: 6px 12px;
-	border-radius: 4px;
-	font-size: 13px;
-	white-space: nowrap;
+  .device-photo {
+	max-width: 100%;
+	max-height: 100%;
+	border-radius: 8px;
+	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   }
 
-  .tooltip-title {
-	margin: 0;
-	font-weight: 500;
+  .no-photo {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	color: #909399;
+	font-size: 14px;
   }
 
-  /* SVG 元素过渡效果 - 通过JavaScript动态添加 */
+  .no-photo i {
+	font-size: 64px;
+	margin-bottom: 16px;
+	color: #c0c4cc;
+  }
+
+  .image-slot {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	width: 300px;
+	height: 200px;
+	background: #f5f7fa;
+	color: #909399;
+	font-size: 14px;
+  }
+
+  .image-slot i {
+	font-size: 48px;
+	margin-bottom: 10px;
+  }
   </style>

+ 14 - 0
ui/src/views/reliability/rel_maint_plan/form.vue

@@ -326,6 +326,20 @@ export default {
       } else {
         // 新增模式
         this.resetForm();
+        // 检查是否有从设备页面传递过来的参数
+        const { devId, plant, devName, devTag } = this.$route.query;
+        if (devId || devTag) {
+          this.formData.devId = devId ? Number(devId) : null;
+          this.formData.plant = plant || null;
+          this.formData.devTag = devTag || null;
+          // 预填设备信息
+          if (devId && devName && devTag) {
+            this.selectedDevice = { devId: Number(devId), devName, devTag, plant };
+            this.deviceOptions = [this.selectedDevice];
+            // 加载该设备的部件列表
+            this.loadCompoList(devTag);
+          }
+        }
       }
     },
     /** 加载计划详情 */