Browse Source

ly 可靠性 预防性维修 redis配置修改

ly 3 days ago
parent
commit
89ad5875a5

+ 57 - 0
master/src/main/java/com/ruoyi/framework/config/RedisConfig.java

@@ -1,16 +1,26 @@
 package com.ruoyi.framework.config;
 
+import io.lettuce.core.ClientOptions;
+import io.lettuce.core.SslOptions;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.util.StringUtils;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import java.io.File;
+import java.time.Duration;
+
 /**
  * redis配置
  *
@@ -20,6 +30,53 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 @EnableCaching
 public class RedisConfig extends CachingConfigurerSupport
 {
+    @Value("${spring.redis.host}")
+    private String host;
+
+    @Value("${spring.redis.port}")
+    private int port;
+
+    @Value("${spring.redis.password}")
+    private String password;
+
+    @Value("${spring.redis.database:0}")
+    private int database;
+
+    @Value("${spring.redis.timeout:10s}")
+    private Duration timeout;
+
+    // 阿里云 TLS 证书路径(JKS),如果为空则不启用 SSL
+    @Value("${spring.redis.ssl.trust-store:}")
+    private String trustStorePath;
+
+    /**
+     * 创建 Redis 连接工厂(支持阿里云 TLS)
+     */
+    @Bean
+    public RedisConnectionFactory redisConnectionFactory() {
+        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
+        config.setHostName(host);
+        config.setPort(port);
+        config.setPassword(password);
+        config.setDatabase(database);
+
+        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder =
+            LettuceClientConfiguration.builder().commandTimeout(timeout);
+
+        // 如果配置了证书路径,启用 SSL(按照阿里云官方示例)
+        if (StringUtils.hasText(trustStorePath)) {
+            File trustStoreFile = new File(trustStorePath);
+            if (trustStoreFile.exists()) {
+                ClientOptions clientOptions = ClientOptions.builder().sslOptions(
+                    SslOptions.builder().jdkSslProvider().truststore(trustStoreFile).build()
+                ).build();
+                builder.clientOptions(clientOptions).useSsl();
+            }
+        }
+
+        return new LettuceConnectionFactory(config, builder.build());
+    }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)

+ 23 - 7
master/src/main/java/com/ruoyi/project/ehs/controller/TApproveDangerController.java

@@ -395,8 +395,12 @@ public class TApproveDangerController extends BaseController {
                         devProcess.setApproveObject(t);
                     } else if (pi.getProcessDefinitionName().equals("可靠性设备维修计划申请")) {
                         TRelMaintPlan plan = tRelMaintPlanService.selectTRelMaintPlanById(Long.parseLong(pi.getBusinessKey()));
-                        devProcess.setApproveObject(plan);
-                        devProcess.setApNo(plan.getPlanId().toString());
+                        if (plan != null) {
+                            devProcess.setApproveObject(plan);
+                            if (plan.getPlanId() != null) {
+                                devProcess.setApNo(plan.getPlanId().toString());
+                            }
+                        }
                     }
                 } catch (Exception e) {
                     logger.error(e.toString());
@@ -569,8 +573,12 @@ public class TApproveDangerController extends BaseController {
                         devTask.setApproveObject(t);
                     } else if (pi.getProcessDefinitionName().equals("可靠性设备维修计划申请")) {
                         TRelMaintPlan plan = tRelMaintPlanService.selectTRelMaintPlanById(Long.parseLong(pi.getBusinessKey()));
-                        devTask.setApproveObject(plan);
-                        devTask.setApNo(plan.getPlanId().toString());
+                        if (plan != null) {
+                            devTask.setApproveObject(plan);
+                            if (plan.getPlanId() != null) {
+                                devTask.setApNo(plan.getPlanId().toString());
+                            }
+                        }
                     }
                     list.add(devTask);
                 }
@@ -676,9 +684,17 @@ public class TApproveDangerController extends BaseController {
                     devProcess.setApproveObject(approve);
                 } else if (pi.getProcessDefinitionName().equals("可靠性设备维修计划申请")) {
                     TRelMaintPlan plan = tRelMaintPlanService.selectTRelMaintPlanById(Long.parseLong(pi.getBusinessKey()));
-                    SysUser user = sysUserService.selectUserById(Long.parseLong(plan.getCreaterCode()));
-                    devProcess.setApName(user.getNickName());
-                    devProcess.setApproveObject(plan);
+                    if (plan != null && plan.getCreaterCode() != null) {
+                        devProcess.setApproveObject(plan);
+                        try {
+                            SysUser user = sysUserService.selectUserById(Long.parseLong(plan.getCreaterCode()));
+                            if (user != null) {
+                                devProcess.setApName(user.getNickName());
+                            }
+                        } catch (NumberFormatException ex) {
+                            // createrCode 非数字时忽略姓名填充
+                        }
+                    }
                 } else if (pi.getProcessDefinitionName().equals("预约开票审批流程")) {
                     TApproveReserveInvoice approve = tApproveReserveInvoiceService.selectTApproveReserveInvoiceById(Long.parseLong(pi.getBusinessKey()));
                     SysUser user = sysUserService.selectUserById(approve.getUserId());

+ 37 - 3
master/src/main/java/com/ruoyi/project/reliability/controller/TRelMaintPlanController.java

@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.interceptor.annotation.RepeatSubmit;
 import com.ruoyi.project.reliability.domain.TRelMaintPlan;
 import com.ruoyi.project.reliability.domain.TRelMaintRecord;
 import com.ruoyi.project.reliability.service.ITRelMaintPlanService;
@@ -166,6 +167,7 @@ public class TRelMaintPlanController extends BaseController
     @PreAuthorize("@ss.hasPermi('reliability:rel_maint_plan:add')")
     @Log(title = "维修计划申请", businessType = BusinessType.INSERT)
     @PostMapping("/submitApprove")
+    @RepeatSubmit
     public AjaxResult submitApprove(@RequestBody TRelMaintPlan tRelMaintPlan)
     {
         try {
@@ -173,16 +175,46 @@ public class TRelMaintPlanController extends BaseController
             logger.info("维修部件数量: " + (tRelMaintPlan.getMaintComponents() != null ? tRelMaintPlan.getMaintComponents().size() : 0));
 
             Long userId = getUserId();
-            
+
             // 记录原来的计划ID(如果是计划中状态的修改提交)
             Long oldPlanId = tRelMaintPlan.getPlanId();
-            boolean isFromPlannedStatus = oldPlanId != null && 
+            boolean isFromPlannedStatus = oldPlanId != null &&
                 ("9".equals(tRelMaintPlan.getApprovalStatus()) || "9".equals(tRelMaintPlan.getCompletionStatus()));
-            
+
             if (isFromPlannedStatus) {
                 logger.info("从计划中状态提交申请,原计划ID: " + oldPlanId);
             }
 
+            // ===== 新增:校验当前设备是否已有未完成的维修计划 =====
+            if (tRelMaintPlan.getPlant() != null && tRelMaintPlan.getDevTag() != null) {
+                TRelMaintPlan query = new TRelMaintPlan();
+                query.setPlant(tRelMaintPlan.getPlant());
+                query.setDevTag(tRelMaintPlan.getDevTag());
+                // 不设置 completionStatus,让 Mapper 按设备查出所有计划后在内存中过滤
+                List<TRelMaintPlan> existingPlans = tRelMaintPlanService.selectTRelMaintPlanList(query);
+
+                if (existingPlans != null && !existingPlans.isEmpty()) {
+                    for (TRelMaintPlan existing : existingPlans) {
+                        // DataScope 过滤后只会返回当前用户有权限看到的计划
+
+                        // 跳过当前“计划中”记录自身(从计划状态转换为申请时允许提交)
+                        if (isFromPlannedStatus && oldPlanId != null &&
+                                existing.getPlanId() != null && oldPlanId.equals(existing.getPlanId())) {
+                            continue;
+                        }
+
+                        String completionStatus = existing.getCompletionStatus();
+                        // completionStatus:9-计划中,0-未完成,1-已完成
+                        // 这里只拦截未完成(0),计划中(9)允许继续创建申请
+                        if ("0".equals(completionStatus)) {
+                            logger.info("设备 {}-{} 已存在未完成的维修计划,planId={},completionStatus={}",
+                                    existing.getPlant(), existing.getDevTag(), existing.getPlanId(), completionStatus);
+                            return AjaxResult.error("当前设备已存在未完成的维修计划,无法重复创建");
+                        }
+                    }
+                }
+            }
+
             // 清空planId,创建新计划
             tRelMaintPlan.setPlanId(null);
             
@@ -304,6 +336,7 @@ public class TRelMaintPlanController extends BaseController
     @PreAuthorize("@ss.hasPermi('reliability:rel_maint_plan:edit')")
     @Log(title = "维修计划再次提交申请", businessType = BusinessType.UPDATE)
     @PostMapping("/resubmitApprove")
+    @RepeatSubmit
     public AjaxResult resubmitApprove(@RequestBody TRelMaintPlan tRelMaintPlan)
     {
         try {
@@ -446,6 +479,7 @@ public class TRelMaintPlanController extends BaseController
                 String comment = devTask.getComment();
                 devTask.setComment("拒绝" + symbol + (comment != null && !comment.isEmpty() ? comment : ""));
                 tRelMaintPlan.setApprovalStatus("-1"); // 未通过
+                tRelMaintPlan.setCompletionStatus("-1"); // 已拒绝
                 param.put("condition", "0");
             } else {
                 return AjaxResult.error("审批条件参数错误");

+ 7 - 11
master/src/main/resources/application.yml

@@ -84,24 +84,20 @@ spring:
       enabled: false
   # redis 配置
   redis:
-    # 地址
     host: 47.114.101.16
-#    host: localhost
+    password: ssy666666
     # 端口,默认为6379
     port: 6379
-    # 密码
-    password: ssy666666
     database: 1
-#    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
+#    # 密码
+#    password: "r-uf6jl950xjqofogjxi:kT6GShHXt3UvSEduX9K4"
+
+#    ssl:
+#      trust-store: /home/appadmin/ApsaraDB-CA-Chain.jks
     # 连接超时时间
     timeout: 10s
+
     lettuce:
       pool:
         # 连接池中的最小空闲连接

+ 1 - 0
ui/src/views/reliability/rel_maint_plan/MaintPlanDetailContent.vue

@@ -31,6 +31,7 @@
           <el-tag v-if="planData.completionStatus === '9'" type="" size="mini">计划中</el-tag>
           <el-tag v-else-if="planData.completionStatus === '0'" type="info" size="mini">未完成</el-tag>
           <el-tag v-else-if="planData.completionStatus === '1'" type="success" size="mini">已完成</el-tag>
+          <el-tag v-else-if="planData.completionStatus === '-1'" type="danger" size="mini">已拒绝</el-tag>
           <span v-else>{{ planData.completionStatus || '-' }}</span>
         </el-descriptions-item>
         <el-descriptions-item label="创建人">{{ planData.createrCode || '-' }}</el-descriptions-item>

+ 216 - 153
ui/src/views/reliability/rel_maint_plan/form.vue

@@ -3,10 +3,10 @@
     <!-- 页面头部 -->
     <div class="page-header">
       <div class="header-left">
-        <el-button icon="el-icon-arrow-left" @click="goBack">返回</el-button>
         <span class="page-title">{{ pageTitle }}</span>
       </div>
       <div class="header-right">
+        <el-button icon="el-icon-arrow-left" @click="goBack">返回</el-button>
         <el-button type="primary" @click="handleSubmit" :loading="submitting">
           {{ isPlannedStatus ? '提交申请' : (formData.planId && formData.approvalStatus === '1' ? '再次提交申请' : '确 定') }}
         </el-button>
@@ -18,118 +18,128 @@
       <el-form ref="form" :model="formData" :rules="rules" label-width="120px">
         <!-- 基础信息区域 -->
         <div class="form-section">
-          <div class="section-header">
-            <i class="el-icon-info"></i>
-            <span class="section-title">基础信息</span>
-          </div>
-          <el-row :gutter="20">
-            <el-col :span="8">
-              <el-form-item label="选择设备" prop="devId">
-                <el-select
-                  v-model="formData.devId"
-                  filterable
-                  remote
-                  :remote-method="searchDevice"
-                  :loading="deviceLoading"
-                  placeholder="请输入设备位号或名称搜索"
-                  style="width: 100%"
-                  @change="handleDeviceChange"
-                  @focus="handleDeviceFocus"
-                  :disabled="!!formData.planId"
-                  clearable>
-                  <el-option
-                    v-for="device in deviceOptions"
-                    :key="device.devId"
-                    :label="`${device.devTag} - ${device.devName}`"
-                    :value="device.devId">
-                    <span style="float: left">{{ device.devTag }}</span>
-                    <span style="float: right; color: #8492a6; font-size: 13px">{{ device.devName }}</span>
-                  </el-option>
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item label="计划开始时间" prop="planTime">
-                <el-date-picker
-                  v-model="formData.planTime"
-                  type="date"
-                  value-format="yyyy-MM-dd"
-                  placeholder="选择开始时间"
-                  style="width: 100%">
-                </el-date-picker>
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item label="计划结束时间" prop="planEndTime">
-                <el-date-picker
-                  v-model="formData.planEndTime"
-                  type="date"
-                  value-format="yyyy-MM-dd"
-                  placeholder="选择结束时间"
-                  style="width: 100%">
-                </el-date-picker>
-              </el-form-item>
-            </el-col>
-          </el-row>
           <el-row :gutter="20">
-            <el-col :span="8">
-              <el-form-item label="责任人" prop="responsible">
-                <el-select
-                  v-model="formData.responsible"
-                  placeholder="请选择责任人"
-                  style="width: 100%"
-                  clearable
-                  filterable>
-                  <el-option
-                    v-for="staff in staffOptions"
-                    :key="staff.staffid"
-                    :label="`${staff.name} (${staff.staffid})`"
-                    :value="staff.staffid">
-                  </el-option>
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item label="审批人" prop="approver">
-                <el-select
-                  v-model="formData.approver"
-                  placeholder="请选择审批人"
-                  style="width: 100%"
-                  clearable
-                  filterable
-                  :disabled="isApproverDisabled">
-                  <el-option
-                    v-for="staff in staffOptions"
-                    :key="staff.staffid"
-                    :label="`${staff.name} (${staff.staffid})`"
-                    :value="staff.staffid">
-                  </el-option>
-                </el-select>
-              </el-form-item>
+            <!-- 左侧:基础信息 -->
+            <el-col :span="14">
+              <div class="section-header">
+                <i class="el-icon-info"></i>
+                <span class="section-title">计划基础信息</span>
+              </div>
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="选择设备" prop="devId">
+                    <el-select
+                      v-model="formData.devId"
+                      filterable
+                      remote
+                      :remote-method="searchDevice"
+                      :loading="deviceLoading"
+                      placeholder="请输入设备位号或名称搜索"
+                      style="width: 100%"
+                      @change="handleDeviceChange"
+                      @focus="handleDeviceFocus"
+                      :disabled="!!formData.planId"
+                      clearable>
+                      <el-option
+                        v-for="device in deviceOptions"
+                        :key="device.devId"
+                        :label="`${device.devTag} - ${device.devName}`"
+                        :value="device.devId">
+                        <span style="float: left">{{ device.devTag }}</span>
+                        <span style="float: right; color: #8492a6; font-size: 13px">{{ device.devName }}</span>
+                      </el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="计划开始时间" prop="planTime">
+                    <el-date-picker
+                      v-model="formData.planTime"
+                      type="date"
+                      value-format="yyyy-MM-dd"
+                      placeholder="选择开始时间"
+                      style="width: 100%">
+                    </el-date-picker>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="计划结束时间" prop="planEndTime">
+                    <el-date-picker
+                      v-model="formData.planEndTime"
+                      type="date"
+                      value-format="yyyy-MM-dd"
+                      placeholder="选择结束时间"
+                      style="width: 100%">
+                    </el-date-picker>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="责任人" prop="responsible">
+                    <el-select
+                      v-model="formData.responsible"
+                      placeholder="请选择责任人"
+                      style="width: 100%"
+                      clearable
+                      filterable>
+                      <el-option
+                        v-for="staff in staffOptions"
+                        :key="staff.staffid"
+                        :label="`${staff.name} (${staff.staffid})`"
+                        :value="staff.staffid">
+                      </el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="审批人" prop="approver">
+                    <el-select
+                      v-model="formData.approver"
+                      placeholder="请选择审批人"
+                      style="width: 100%"
+                      clearable
+                      filterable
+                      :disabled="isApproverDisabled">
+                      <el-option
+                        v-for="staff in staffOptions"
+                        :key="staff.staffid"
+                        :label="`${staff.name} (${staff.staffid})`"
+                        :value="staff.staffid">
+                      </el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="备注" prop="remarks">
+                    <el-input v-model="formData.remarks" type="textarea" :rows="1" placeholder="请输入备注" />
+                  </el-form-item>
+                </el-col>
+              </el-row>
             </el-col>
-            <el-col :span="8">
-              <el-form-item label="备注" prop="remarks">
-                <el-input v-model="formData.remarks" type="textarea" :rows="1" placeholder="请输入备注" />
-              </el-form-item>
+
+            <!-- 设备信息展示:右侧 -->
+            <el-col :span="10" v-if="selectedDevice">
+              <!-- 设备信息展示 -->
+              <div class="section-header">
+                <i class="el-icon-s-platform"></i>
+                <span class="section-title">设备信息</span>
+              </div>
+              <el-descriptions :column="2" border size="small">
+                <!-- 设备名称单独占一行 -->
+                <el-descriptions-item label="设备名称" :span="2">{{ selectedDevice.devName }}</el-descriptions-item>
+                <!-- 其余字段两列排列 -->
+                <el-descriptions-item label="设备位号">{{ selectedDevice.devTag }}</el-descriptions-item>
+                <el-descriptions-item label="位置">{{ selectedDevice.devLoc }}</el-descriptions-item>
+                <el-descriptions-item label="设备类型">{{ selectedDevice.devType }}</el-descriptions-item>
+                <el-descriptions-item label="区域负责人">{{ getStaffNameById(selectedDevice.areaResponsible) }}</el-descriptions-item>
+              </el-descriptions>
             </el-col>
           </el-row>
         </div>
 
-        <!-- 设备信息展示 -->
-        <div class="form-section" v-if="selectedDevice">
-          <div class="section-header">
-            <i class="el-icon-s-platform"></i>
-            <span class="section-title">设备信息</span>
-          </div>
-          <el-descriptions :column="5" border size="small">
-            <el-descriptions-item label="设备位号">{{ selectedDevice.devTag }}</el-descriptions-item>
-            <el-descriptions-item label="设备名称">{{ selectedDevice.devName }}</el-descriptions-item>
-            <el-descriptions-item label="位置">{{ selectedDevice.devLoc }}</el-descriptions-item>
-            <el-descriptions-item label="设备类型">{{ selectedDevice.devType }}</el-descriptions-item>
-            <el-descriptions-item label="区域负责人">{{ selectedDevice.areaResponsible }}</el-descriptions-item>
-          </el-descriptions>
-        </div>
-
         <!-- 部件列表及维修记录选择 -->
         <div class="form-section" v-if="compoList.length > 0">
           <div class="section-header">
@@ -148,7 +158,7 @@
                 {{ (compoCurrentPage - 1) * compoPageSize + scope.$index + 1 }}
               </template>
             </el-table-column>
-            <el-table-column label="部件名称" prop="compoName" width="150" :show-overflow-tooltip="true" />
+            <el-table-column label="部件名称" prop="compoName" :show-overflow-tooltip="true" />
             <el-table-column label="检查频率/日期" width="120" align="center">
               <template slot-scope="scope">
                 <div style="line-height: 1.2;">
@@ -313,10 +323,16 @@ export default {
     this.initPage();
     // 计算表格高度
     this.$nextTick(() => {
-      this.tableHeight = Math.max(300, window.innerHeight - 500);
+      this.tableHeight = Math.max(380, window.innerHeight - 440);
     });
   },
   methods: {
+    /** 根据员工号获取姓名 */
+    getStaffNameById(staffid) {
+      if (!staffid) return '';
+      const staff = this.staffOptions.find(s => s.staffid === staffid);
+      return staff ? staff.name : staffid;
+    },
     /** 初始化页面 */
     initPage() {
       const planId = this.$route.query.planId;
@@ -326,17 +342,21 @@ export default {
       } else {
         // 新增模式
         this.resetForm();
+
         // 检查是否有从设备页面传递过来的参数
         const { devId, plant, devName, devTag } = this.$route.query;
-        if (devId || devTag) {
-          this.formData.devId = devId ? Number(devId) : null;
+
+        // 如果带了 devId,直接复用 handleDeviceChange 逻辑,加载完整设备信息和部件
+        if (devId) {
+          const idNum = Number(devId);
+          this.formData.devId = idNum;
+          this.handleDeviceChange(idNum);
+        } else if (devTag || plant || devName) {
+          // 兼容只传 devTag/plant/devName 的场景,尽量预填基础字段和部件列表
           this.formData.plant = plant || null;
+          this.formData.devName = devName || null;
           this.formData.devTag = devTag || null;
-          // 预填设备信息
-          if (devId && devName && devTag) {
-            this.selectedDevice = { devId: Number(devId), devName, devTag, plant };
-            this.deviceOptions = [this.selectedDevice];
-            // 加载该设备的部件列表
+          if (devTag) {
             this.loadCompoList(devTag);
           }
         }
@@ -363,8 +383,16 @@ export default {
     },
     /** 查询人员列表 */
     getStaffList() {
-      listStaffmgrAll().then(response => {
+      return listStaffmgrAll().then(response => {
         this.staffOptions = response.rows || response.data || [];
+
+        // 如果此时已经选中了设备且尚未设置责任人,根据设备的区域负责人预填责任人
+        if (this.selectedDevice && this.selectedDevice.areaResponsible && !this.formData.responsible) {
+          const staff = this.staffOptions.find(s => s.staffid === this.selectedDevice.areaResponsible);
+          if (staff) {
+            this.formData.responsible = staff.staffid;
+          }
+        }
       });
     },
     /** 表单重置 */
@@ -423,6 +451,16 @@ export default {
         this.formData.plant = response.data.plant;
         this.formData.devName = response.data.devName;
         this.formData.devTag = response.data.devTag;
+
+        // 确保设备下拉框选项中包含当前设备,便于“选择设备”下拉回显
+        if (response.data && response.data.devId) {
+          const exists = this.deviceOptions.some(d => d.devId === response.data.devId);
+          if (!exists) {
+            this.deviceOptions = [response.data, ...this.deviceOptions];
+          }
+        }
+
+        // 根据设备的区域负责人预填责任人(前提是人员列表已加载)
         if (response.data.areaResponsible && this.staffOptions.length > 0) {
           const staff = this.staffOptions.find(s => s.staffid === response.data.areaResponsible);
           if (staff) {
@@ -543,47 +581,60 @@ export default {
     },
     /** 提交表单 */
     handleSubmit() {
+      // 前端防止重复提交:如果已经在提交中,直接返回
+      if (this.submitting) {
+        return;
+      }
+
+      // 先标记为提交中,避免校验期间被多次点击
+      this.submitting = true;
+
       this.$refs.form.validate(valid => {
-        if (valid) {
-          this.submitting = true;
-          if (this.compoList.length > 0) {
-            const maintComponents = this.compoList
-              .filter(compo => compo.needMaint)
-              .map(compo => ({
-                compoId: compo.compoId,
-                compoName: compo.compoName,
-                maintType: compo.maintType,
-                responsible: compo.maintResponsible
-              }));
-            this.formData.maintComponents = maintComponents;
-          }
+        if (!valid) {
+          // 校验失败,恢复提交状态
+          this.submitting = false;
+          return;
+        }
 
-          // 选择合适的API
-          let apiCall;
-          if (!this.formData.planId || this.isPlannedStatus) {
-            apiCall = submitApprove(this.formData);
-          } else if (this.formData.approvalStatus === '1') {
-            apiCall = resubmitApprove(this.formData);
-          } else {
-            apiCall = submitApprove(this.formData);
-          }
+        if (this.compoList.length > 0) {
+          const maintComponents = this.compoList
+            .filter(compo => compo.needMaint)
+            .map(compo => ({
+              compoId: compo.compoId,
+              compoName: compo.compoName,
+              maintType: compo.maintType,
+              responsible: compo.maintResponsible
+            }));
+          this.formData.maintComponents = maintComponents;
+        }
 
-          apiCall.then(response => {
-            this.$modal.msgSuccess("操作成功");
-            this.goBack();
-          }).finally(() => {
-            this.submitting = false;
-          });
+        // 选择合适的API
+        let apiCall;
+        if (!this.formData.planId || this.isPlannedStatus) {
+          apiCall = submitApprove(this.formData);
+        } else if (this.formData.approvalStatus === '1') {
+          apiCall = resubmitApprove(this.formData);
+        } else {
+          apiCall = submitApprove(this.formData);
         }
+
+        apiCall.then(response => {
+          // 使用 Element UI 消息提示,避免 $modal 未注册导致报错
+          this.$message.success("操作成功");
+          this.goBack();
+        }).finally(() => {
+          this.submitting = false;
+        });
       });
     },
     /** 返回列表页 */
     goBack() {
-      // 先跳转再关闭当前标签,避免闪烁
+      // 先记录当前视图
       const view = this.$route;
-      this.$router.push({ path: "/reliability/rel_maint_plan" }).then(() => {
-        this.$store.dispatch("tagsView/delView", view);
-      });
+      // 跳转到维修计划列表
+      this.$router.push({ path: "/reliability/rel_maint_plan" });
+      // 直接关闭当前标签(当前项目中 $router.push 不返回 Promise,不能再调用 .then)
+      this.$store.dispatch("tagsView/delView", view);
     }
   }
 };
@@ -591,12 +642,16 @@ export default {
 
 <style scoped lang="scss">
 .maint-plan-form-page {
+  // 缩小与顶部的间距
+  padding-top: 8px;
+  margin-top: 0;
+
   .page-header {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    margin-bottom: 10px;
-    padding: 8px 0;
+    margin-bottom: 8px;
+    padding: 4px 0 6px;
     border-bottom: 1px solid #ebeef5;
 
     .header-left {
@@ -604,12 +659,20 @@ export default {
       align-items: center;
 
       .page-title {
-        margin-left: 15px;
         font-size: 18px;
         font-weight: 600;
         color: #303133;
       }
     }
+
+    .header-right {
+      display: flex;
+      align-items: center;
+
+      .el-button + .el-button {
+        margin-left: 8px;
+      }
+    }
   }
 
   .form-card {
@@ -618,7 +681,7 @@ export default {
     }
 
     .form-section {
-      margin-bottom: 12px;
+      margin-bottom: 2px;
 
       .section-header {
         display: flex;

+ 127 - 36
ui/src/views/reliability/rel_maint_plan/index.vue

@@ -49,13 +49,15 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="handleQuery"></right-toolbar>
     </el-row>
 
-    <!-- 三栏Tab布局 -->
+    <!-- Tab 布局:先显示进行中,再显示计划中、已完成、已拒绝 -->
     <el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
-      <!-- 计划中 Tab -->
-      <el-tab-pane name="planned">
-        <span slot="label"><i class="el-icon-time"></i> 计划中</span>
+      <!-- 进行中 Tab (completionStatus=0) -->
+      <el-tab-pane name="incomplete">
+        <span slot="label">
+          <i class="el-icon-loading"></i> 进行中<span v-if="incompleteTotal > 0" class="tab-count">({{ incompleteTotal }})</span>
+        </span>
         <div class="tab-content">
-          <el-table v-loading="plannedLoading" :data="plannedList" :height="tableHeight" border size="small">
+          <el-table v-loading="incompleteLoading" :data="incompleteList" :height="tableHeight" border size="small">
             <el-table-column label="装置" align="center" prop="plant" width="100" :show-overflow-tooltip="true"/>
             <el-table-column label="设备名称" align="center" prop="devName" width="140" :show-overflow-tooltip="true"/>
             <el-table-column label="设备位号" align="center" prop="devTag" width="120" :show-overflow-tooltip="true"/>
@@ -71,8 +73,7 @@
             </el-table-column>
             <el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
               <template slot-scope="scope">
-                <el-tag v-if="scope.row.approvalStatus === '9'" type="" size="mini">未开始</el-tag>
-                <el-tag v-else-if="scope.row.approvalStatus === '0'" type="warning" size="mini">待审批</el-tag>
+                <el-tag v-if="scope.row.approvalStatus === '0'" type="warning" size="mini">待审批</el-tag>
                 <el-tag v-else-if="scope.row.approvalStatus === '1'" type="success" size="mini">已通过</el-tag>
                 <el-tag v-else-if="scope.row.approvalStatus === '-1'" type="danger" size="mini">未通过</el-tag>
                 <span v-else>{{ scope.row.approvalStatus }}</span>
@@ -83,6 +84,7 @@
                 <el-tag v-if="scope.row.completionStatus === '9'" type="info" size="mini">计划中</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '0'" type="warning" size="mini">未完成</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '1'" type="success" size="mini">已完成</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '-1'" type="danger" size="mini">已拒绝</el-tag>
                 <span v-else>{{ scope.row.completionStatus }}</span>
               </template>
             </el-table-column>
@@ -97,32 +99,37 @@
               </template>
             </el-table-column>
             <el-table-column label="备注" align="center" prop="remarks" :show-overflow-tooltip="true"/>
-            <el-table-column label="操作" align="center" fixed="right" width="150">
+            <el-table-column label="操作" align="center" fixed="right" width="180">
               <template slot-scope="scope">
                 <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
                 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
+                <el-button
+                  v-if="scope.row.approvalStatus === '-1'"
+                  size="mini"
+                  type="text"
+                  icon="el-icon-delete"
+                  @click="handleDelete(scope.row)"
+                >删除</el-button>
               </template>
             </el-table-column>
           </el-table>
           <pagination
-            v-show="plannedTotal > 0"
-            :total="plannedTotal"
-            :page.sync="plannedPage.pageNum"
-            :limit.sync="plannedPage.pageSize"
-            @pagination="getPlannedList"
+            v-show="incompleteTotal > 0"
+            :total="incompleteTotal"
+            :page.sync="incompletePage.pageNum"
+            :limit.sync="incompletePage.pageSize"
+            @pagination="getIncompleteList"
             :page-sizes="[10, 20, 50]"
             layout="total, sizes, prev, pager, next"
           />
         </div>
       </el-tab-pane>
 
-      <!-- 未完成 Tab -->
-      <el-tab-pane name="incomplete">
-        <span slot="label">
-          <i class="el-icon-loading"></i> 进行中<span v-if="incompleteTotal > 0" class="tab-count">({{ incompleteTotal }})</span>
-        </span>
+      <!-- 计划中 Tab (completionStatus=9) -->
+      <el-tab-pane name="planned">
+        <span slot="label"><i class="el-icon-time"></i> 计划中</span>
         <div class="tab-content">
-          <el-table v-loading="incompleteLoading" :data="incompleteList" :height="tableHeight" border size="small">
+          <el-table v-loading="plannedLoading" :data="plannedList" :height="tableHeight" border size="small">
             <el-table-column label="装置" align="center" prop="plant" width="100" :show-overflow-tooltip="true"/>
             <el-table-column label="设备名称" align="center" prop="devName" width="140" :show-overflow-tooltip="true"/>
             <el-table-column label="设备位号" align="center" prop="devTag" width="120" :show-overflow-tooltip="true"/>
@@ -138,7 +145,8 @@
             </el-table-column>
             <el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
               <template slot-scope="scope">
-                <el-tag v-if="scope.row.approvalStatus === '0'" type="warning" size="mini">待审批</el-tag>
+                <el-tag v-if="scope.row.approvalStatus === '9'" type="" size="mini">未开始</el-tag>
+                <el-tag v-else-if="scope.row.approvalStatus === '0'" type="warning" size="mini">待审批</el-tag>
                 <el-tag v-else-if="scope.row.approvalStatus === '1'" type="success" size="mini">已通过</el-tag>
                 <el-tag v-else-if="scope.row.approvalStatus === '-1'" type="danger" size="mini">未通过</el-tag>
                 <span v-else>{{ scope.row.approvalStatus }}</span>
@@ -149,6 +157,7 @@
                 <el-tag v-if="scope.row.completionStatus === '9'" type="info" size="mini">计划中</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '0'" type="warning" size="mini">未完成</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '1'" type="success" size="mini">已完成</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '-1'" type="danger" size="mini">已拒绝</el-tag>
                 <span v-else>{{ scope.row.completionStatus }}</span>
               </template>
             </el-table-column>
@@ -163,33 +172,26 @@
               </template>
             </el-table-column>
             <el-table-column label="备注" align="center" prop="remarks" :show-overflow-tooltip="true"/>
-            <el-table-column label="操作" align="center" fixed="right" width="180">
+            <el-table-column label="操作" align="center" fixed="right" width="150">
               <template slot-scope="scope">
                 <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
                 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
-                <el-button
-                  v-if="scope.row.approvalStatus === '-1'"
-                  size="mini"
-                  type="text"
-                  icon="el-icon-delete"
-                  @click="handleDelete(scope.row)"
-                >删除</el-button>
               </template>
             </el-table-column>
           </el-table>
           <pagination
-            v-show="incompleteTotal > 0"
-            :total="incompleteTotal"
-            :page.sync="incompletePage.pageNum"
-            :limit.sync="incompletePage.pageSize"
-            @pagination="getIncompleteList"
+            v-show="plannedTotal > 0"
+            :total="plannedTotal"
+            :page.sync="plannedPage.pageNum"
+            :limit.sync="plannedPage.pageSize"
+            @pagination="getPlannedList"
             :page-sizes="[10, 20, 50]"
             layout="total, sizes, prev, pager, next"
           />
         </div>
       </el-tab-pane>
 
-      <!-- 已完成 Tab -->
+      <!-- 已完成 Tab (completionStatus=1) -->
       <el-tab-pane name="completed">
         <span slot="label"><i class="el-icon-circle-check"></i> 已完成</span>
         <div class="tab-content">
@@ -218,6 +220,7 @@
                 <el-tag v-if="scope.row.completionStatus === '9'" type="info" size="mini">计划中</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '0'" type="warning" size="mini">未完成</el-tag>
                 <el-tag v-else-if="scope.row.completionStatus === '1'" type="success" size="mini">已完成</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '-1'" type="danger" size="mini">已拒绝</el-tag>
                 <span v-else>{{ scope.row.completionStatus }}</span>
               </template>
             </el-table-column>
@@ -249,6 +252,68 @@
           />
         </div>
       </el-tab-pane>
+
+      <!-- 已拒绝 Tab (completionStatus=-1) -->
+      <el-tab-pane name="rejected">
+        <span slot="label"><i class="el-icon-close"></i> 已拒绝</span>
+        <div class="tab-content">
+          <el-table v-loading="rejectedLoading" :data="rejectedList" :height="tableHeight" border size="small">
+            <el-table-column label="装置" align="center" prop="plant" width="100" :show-overflow-tooltip="true"/>
+            <el-table-column label="设备名称" align="center" prop="devName" width="140" :show-overflow-tooltip="true"/>
+            <el-table-column label="设备位号" align="center" prop="devTag" width="120" :show-overflow-tooltip="true"/>
+            <el-table-column label="计划开始时间" align="center" prop="planTime" width="110">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.planTime, '{y}-{m}-{d}') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="计划结束时间" align="center" prop="planEndTime" width="110">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.planEndTime, '{y}-{m}-{d}') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
+              <template slot-scope="scope">
+                <el-tag v-if="scope.row.approvalStatus === '-1'" type="danger" size="mini">未通过</el-tag>
+                <span v-else>{{ scope.row.approvalStatus }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="完成状态" align="center" prop="completionStatus" width="100">
+              <template slot-scope="scope">
+                <el-tag v-if="scope.row.completionStatus === '9'" type="info" size="mini">计划中</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '0'" type="warning" size="mini">未完成</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '1'" type="success" size="mini">已完成</el-tag>
+                <el-tag v-else-if="scope.row.completionStatus === '-1'" type="danger" size="mini">已拒绝</el-tag>
+                <span v-else>{{ scope.row.completionStatus }}</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" width="80">
+              <template slot-scope="scope">
+                <span>{{ getStaffNameById(scope.row.approver) || '-' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="备注" align="center" prop="remarks" :show-overflow-tooltip="true"/>
+            <el-table-column label="操作" align="center" fixed="right" width="80">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="rejectedTotal > 0"
+            :total="rejectedTotal"
+            :page.sync="rejectedPage.pageNum"
+            :limit.sync="rejectedPage.pageSize"
+            @pagination="getRejectedList"
+            :page-sizes="[10, 20, 50]"
+            layout="total, sizes, prev, pager, next"
+          />
+        </div>
+      </el-tab-pane>
     </el-tabs>
 
     <!-- 添加或修改设备维修计划对话框 -->
@@ -283,8 +348,8 @@ export default {
   components: { Treeselect, MaintPlanForm, MaintPlanDetail },
   data() {
     return {
-      // 当前激活的Tab
-      activeTab: 'planned',
+      // 当前激活的Tab(默认显示进行中)
+      activeTab: 'incomplete',
       // 表格高度
       tableHeight: 400,
       // 生成计划中
@@ -317,6 +382,12 @@ export default {
       completedLoading: false,
       completedPage: { pageNum: 1, pageSize: 20 },
 
+      // 已拒绝数据
+      rejectedList: [],
+      rejectedTotal: 0,
+      rejectedLoading: false,
+      rejectedPage: { pageNum: 1, pageSize: 20 },
+
       // 弹出层标题
       title: "",
       // 是否显示弹出层
@@ -368,6 +439,7 @@ export default {
       this.getPlannedList();
       this.getIncompleteList();
       this.getCompletedList();
+      this.getRejectedList();
     },
     /** 获取当前Tab列表 */
     getCurrentTabList() {
@@ -377,6 +449,8 @@ export default {
         this.getIncompleteList();
       } else if (this.activeTab === 'completed') {
         this.getCompletedList();
+      } else if (this.activeTab === 'rejected') {
+        this.getRejectedList();
       }
     },
     /** 搜索按钮操作 */
@@ -443,6 +517,21 @@ export default {
         this.completedLoading = false;
       });
     },
+    /** 获取已拒绝列表 (completionStatus=-1) */
+    getRejectedList() {
+      this.rejectedLoading = true;
+      listRel_maint_plan({
+        ...this.queryParams,
+        completionStatus: '-1',
+        pageNum: this.rejectedPage.pageNum,
+        pageSize: this.rejectedPage.pageSize
+      }).then(response => {
+        this.rejectedList = response.rows;
+        this.rejectedTotal = response.total;
+      }).finally(() => {
+        this.rejectedLoading = false;
+      });
+    },
     /** Tab切换事件 */
     handleTabClick(tab) {
       // 切换时刷新对应Tab的数据
@@ -452,6 +541,8 @@ export default {
         this.getIncompleteList();
       } else if (tab.name === 'completed') {
         this.getCompletedList();
+      } else if (tab.name === 'rejected') {
+        this.getRejectedList();
       }
     },
     // 取消按钮