xuzhancheng пре 12 часа
родитељ
комит
754d4ae7e8

+ 1 - 0
yudao-ui-admin-vue3/package.json

@@ -120,6 +120,7 @@
     "@typescript-eslint/eslint-plugin": "^7.1.0",
     "@typescript-eslint/parser": "^7.1.0",
     "@unocss/eslint-config": "^0.57.4",
+    "@unocss/eslint-plugin": "^66.6.8",
     "@unocss/transformer-variant-group": "^0.58.5",
     "@vitejs/plugin-legacy": "^5.3.1",
     "@vitejs/plugin-vue": "^5.0.4",

+ 49 - 0
yudao-ui-admin-vue3/src/api/pressure2/inspectionNature/index.ts

@@ -0,0 +1,49 @@
+import request from '@/config/axios'
+
+// 承压检验性质管理:创建
+export const createPressure2InspectionNature = (data) => {
+  return request.post({
+    url: '/pressure2/inspection-nature/create',
+    data
+  })
+}
+
+// 承压检验性质管理:更新
+export const updatePressure2InspectionNature = (data) => {
+  return request.put({
+    url: '/pressure2/inspection-nature/update',
+    data
+  })
+}
+
+// 承压检验性质管理:删除
+export const deletePressure2InspectionNature = (params) => {
+  return request.delete({
+    url: '/pressure2/inspection-nature/delete',
+    params
+  })
+}
+
+// 承压检验性质管理:详情
+export const getPressure2InspectionNatureInfo = (params) => {
+  return request.get({
+    url: '/pressure2/inspection-nature/get',
+    params
+  })
+}
+
+// 承压检验性质管理:列表
+export const getPressure2InspectionNatureList = (params) => {
+  return request.get({
+    url: '/pressure2/inspection-nature/page',
+    params
+  })
+}
+
+// 承压检验性质管理:列表(不校验菜单权限)
+export const getPressure2InspectionNatureListNoLimit = (params) => {
+  return request.get({
+    url: '/pressure2/inspection-nature/page',
+    params
+  })
+}

+ 4 - 3
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/BoilerPlanScheduleDialog.vue

@@ -34,10 +34,11 @@
                 placeholder="选择日期"
                 value-format="YYYY-MM-DD"
                 :disabled="formData.inNoSchedule"
-                :disabled-date="disabledDatePlan"
+                :disabled-date="(time) => time.getTime() < Date.now() - 8.64e7"
                 class="!w-240px"
               />
-              <el-checkbox 
+<!--                :disabled-date="disabledDatePlan"-->
+              <el-checkbox
                 v-model="formData.inNoSchedule"
                 class="ml-4" 
                 @change="handleInNoScheduleChange"
@@ -168,7 +169,7 @@
                 无需安排
               </el-checkbox>
               <el-checkbox
-                v-model="formData.preIsOrderConfirm"
+                v-model="formData.pressureIsOrderConfirm"
                 class="ml-4"
                 :disabled="formData.preNoSchedule || nextPreCheckCount === 0"
               >

+ 1 - 1
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/BoilerPlanScheduleEquipDialog.vue

@@ -166,7 +166,7 @@
                 无需安排
               </el-checkbox>
               <el-checkbox
-                v-model="formData.preIsOrderConfirm"
+                v-model="formData.pressureIsOrderConfirm"
                 class="ml-4"
                 :disabled="formData.preNoSchedule || nextPreCheckCount === 0"
               >

+ 3 - 3
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/detail.vue

@@ -207,7 +207,7 @@
             <template #default="{ row }">
               <div v-if="row.nextInCheckDate"
                 class="cursor-pointer" 
-                @click="handleSingleSchedule(row, 'in')"
+                @click.stop="handleSingleSchedule(row, 'in')"
               >
                 <div class="flex items-center justify-center gap-1 schedule-link">
                   <span class="text-xs">{{ dayjs(row.nextInCheckDate).format('YYYY-MM-DD') }}</span>
@@ -224,7 +224,7 @@
             <template #default="{ row }">
               <div v-if="row.nextOutCheckDate"
                 class="cursor-pointer" 
-                @click="handleSingleSchedule(row, 'out')"
+                @click.stop="handleSingleSchedule(row, 'out')"
               >
                 <div class="flex items-center justify-center gap-1 schedule-link">
                   <span class="text-xs">{{ dayjs(row.nextOutCheckDate).format('YYYY-MM-DD') }}</span>
@@ -241,7 +241,7 @@
             <template #default="{ row }">
               <div v-if="row.nextPressureCheckDate"
                 class="cursor-pointer"
-                @click="handleSingleSchedule(row, 'pre')"
+                @click.stop="handleSingleSchedule(row, 'pre')"
               >
                 <div class="flex items-center justify-center gap-1 schedule-link">
                   <span class="text-xs">{{ dayjs(row.nextPressureCheckDate).format('YYYY-MM-DD') }}</span>

+ 4 - 6
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/index.vue

@@ -197,14 +197,12 @@
         align="center"
         prop="equipDistrictName"
         min-width="80"
-        sortable="custom"
       />
       <el-table-column
         label="街道"
         align="center"
         prop="equipStreetName"
         min-width="100"
-        sortable="custom"
       />
       <el-table-column label="使用单位名称" align="center" prop="unitName" min-width="180">
         <template #default="{ row }">
@@ -230,7 +228,7 @@
         <template #default="{ row }">
 <!--          <div class="check-number regular-check">{{ row.countIn }}</div>-->
           <div v-if="row.nextInCheckDate !== null" :class="['text-sm', hasPlanSchedule(row, 'in') ? '' : 'cursor-pointer schedule-link']"
-               @click="!hasPlanSchedule(row, 'in') && handleCheck(row,'in')">
+               @click.stop="!hasPlanSchedule(row, 'in') && handleCheck(row,'in')">
             <div class="flex items-center justify-center gap-1">
               <span>{{ dayjs(row.nextInCheckDate).format('YYYY-MM-DD') }}</span>
             </div>
@@ -256,7 +254,7 @@
           <div
             v-if="row.nextOutCheckDate !== null"
             :class="['text-sm', hasPlanSchedule(row, 'out') ? '' : 'cursor-pointer schedule-link']"
-            @click="!hasPlanSchedule(row, 'out') && handleCheck(row,'out')"
+            @click.stop="!hasPlanSchedule(row, 'out') && handleCheck(row,'out')"
           >
             <div class="flex items-center justify-center gap-1">
             <span>{{ dayjs(row.nextOutCheckDate).format('YYYY-MM-DD') }}</span>
@@ -283,7 +281,7 @@
           <div
             v-if="row.nextPressureCheckDate !== null"
             :class="['text-sm', hasPlanSchedule(row, 'pressure') ? '' : 'cursor-pointer schedule-link']"
-            @click="!hasPlanSchedule(row, 'pressure') && handleCheck(row,'pressure')"
+            @click.stop="!hasPlanSchedule(row, 'pressure') && handleCheck(row,'pressure')"
           >
             <div class="flex items-center justify-center gap-1">
               <span>{{ dayjs(row.nextPressureCheckDate).format('YYYY-MM-DD') }}</span>
@@ -322,7 +320,7 @@
       <!-- 操作 -->
       <el-table-column label="操作" align="center" width="150" fixed="right">
         <template #default="scope">
-          <el-button link type="primary" size="small" @click="handleSchedule(scope.row)">
+          <el-button link type="primary" size="small" @click.stop="handleSchedule(scope.row)">
             计划排期
           </el-button>
 <!--          <el-button link type="primary" size="small" @click="handleEdit(scope.row)">-->

+ 423 - 0
yudao-ui-admin-vue3/src/views/pressure2/inspectionNature/InspectionNatureDialog.vue

@@ -0,0 +1,423 @@
+<script setup lang="ts">
+import {ref, computed, nextTick} from 'vue'
+import {ElMessage} from 'element-plus'
+import {DynamicTbApi} from '@/api/pressure2/dynamictb'
+import {useDictStore} from '@/store/modules/dict'
+import dayjs from 'dayjs'
+import {
+  createPressureInspectionNature,
+  updatePressureInspectionNature
+} from "@/api/pressure/inspectionNature";
+import {updatePressure2InspectionNature} from "@/api/pressure2/inspectionNature";
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  editType: {
+    type: String,
+    default: 'create'
+  },
+  editId: {
+    type: String,
+    default: ''
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'success'])
+
+const dictStore = useDictStore()
+
+// 锅炉检验部件类型字典
+const getPressureBoilerPartType = computed(() => dictStore.getDictMap['pressure2_boiler_part_type'])
+
+const getPressureEquipmentCategory = computed(() => [{
+  value: '-1',
+  label: '全部设备',
+  children: dictStore.getDictMap['pressure_equipment_category']
+}])
+
+// 字典数据
+const getPressureInspectionNatureType = ref({
+  '200': computed(() => dictStore.getDictMap['pressure_inspection_nature']),
+  '300': computed(() => dictStore.getDictMap['pressure_inspection_nature_boiler'])
+})
+
+const getPressureEquipType = ref({
+  '200': computed(() => dictStore.getDictMap['system_equip_pipe_type']),
+  '300': computed(() => dictStore.getDictMap['system_equip_boiler_type'])
+})
+
+// 表单相关
+const inspectionNatureFormRef = ref()
+const inspectionNatureForm = ref({
+  inspectionNature: '',
+  equipmentCategory: '',
+  equipmentType: '',
+  templateSaveReqVOList: [] as any[]
+})
+
+// 判断是否为电站锅炉内检
+const isPowerStationBoiler = computed(() => {
+  return inspectionNatureForm.value.equipmentType == '1' && inspectionNatureForm.value.equipmentCategory == '300' && inspectionNatureForm.value.inspectionNature == '100'
+})
+
+const inspectionNatureFormRules = ref({
+  inspectionNature: [
+    {required: true, message: '请选择检验性质', trigger: 'blur'}
+  ],
+  equipmentCategory: [
+    {required: true, message: '请选择设备类别', trigger: 'blur'}
+  ],
+  equipmentType: [
+    {required: true, message: '请选择设备类型', trigger: 'blur'}
+  ],
+  templateSaveReqVOList: [
+    {required: true, message: '请选择关联报告模板', trigger: 'blur'}
+  ]
+})
+
+const getTemplateNames = computed(() => inspectionNatureForm.value.templateSaveReqVOList.map(x => x.name))
+
+// 报告模板弹窗
+const reportTemplateDialog_Boiler = ref(false)
+const reportTemplateTableRef = ref()
+const reportPageNo = ref(1)
+const reportPageSize = ref(10)
+const reportTotal = ref(0)
+const reportTemplateListSearchForm = ref({})
+const reportTemplateList = ref([])
+
+const getPressureReportTemplateType = computed(() => dictStore.getDictMap['pressure_report_template_type'])
+
+const reportColumns = computed(() => ([
+  {
+    type: 'selection',
+    width: 55,
+    fieldProps: {
+      reserveSelection: true,
+      selectable: (row) => {
+        return inspectionNatureForm.value.templateSaveReqVOList.findIndex(item => item.templateId === row.id) < 0
+      }
+    }
+  },
+  {
+    label: '报告编号',
+    prop: 'id',
+  },
+  {
+    label: '报告名称',
+    prop: 'name',
+    search: {
+      type: 'input',
+      placeholder: '请输入报告名称'
+    }
+  },
+  {
+    label: '报告分类',
+    prop: 'className',
+    search: {
+      type: 'select',
+      options: [],
+      prop: 'classId'
+    },
+  },
+  {
+    label: '报告类型',
+    prop: 'type',
+    render: (row) => {
+      return getPressureReportTemplateType.value[row.type]?.label || '未知'
+    }
+  },
+  {
+    label: '生效日期',
+    prop: 'effectiveDate',
+  },
+  {
+    label: '失效日期',
+    prop: 'expiryDate',
+  }
+]))
+
+// 监听弹窗打开
+const handleDialogOpen = async () => {
+  if (props.editType === 'edit' && props.editId) {
+    await loadDetailInfo()
+  } else {
+    resetForm()
+  }
+}
+
+// 加载详情信息
+const loadDetailInfo = async () => {
+  try {
+    const detailInfo = await DynamicTbApi.getPressureInspectionNatureInfoBoiler({id: props.editId})
+
+    inspectionNatureForm.value = {
+      ...detailInfo,
+      templateSaveReqVOList: detailInfo.templateDetailRespVOList.map(item => ({
+        ...item,
+        name: item.templateName,
+        // 将后端返回的 part 字符串转换为数组
+        part: item.part ? (typeof item.part === 'string' ? item.part.split('/').filter(p => p) : (item.part || [])) : []
+      }))
+    }
+  } catch (error) {
+    console.error('加载详情失败:', error)
+    ElMessage.error('加载详情失败')
+  }
+}
+
+// 重置表单
+const resetForm = () => {
+  inspectionNatureForm.value = {
+    inspectionNature: '',
+    equipmentCategory: '',
+    equipmentType: '',
+    templateSaveReqVOList: []
+  }
+  inspectionNatureFormRef.value?.resetFields()
+}
+
+// 关闭弹窗
+const handleClose = () => {
+  emit('update:modelValue', false)
+  resetForm()
+}
+
+// 提交表单
+const handleSubmit = async () => {
+  try {
+    await inspectionNatureFormRef.value.validate()
+
+    const params = {
+      ...inspectionNatureForm.value,
+      templateSaveReqVOList: inspectionNatureForm.value.templateSaveReqVOList.map((item: any) => ({
+        id: item.id,
+        templateId: item.templateId,
+        isDefault: item.isDefault,
+        inspectionNatureId: item.inspectionNatureId || (props.editType === 'edit' ? props.editId : undefined),
+        part: isPowerStationBoiler.value ? (item.part || []).join('/') : ""
+      })),
+      id: props.editType === 'edit' ? props.editId : undefined
+    }
+
+    const result = props.editType === 'edit' ? await updatePressure2InspectionNature(params) : await createPressureInspectionNature(params)
+
+    if (result) {
+      ElMessage.success((props.editType === 'edit' ? '编辑' : '新增') + '成功')
+      emit('success')
+      handleClose()
+    }
+  } catch (error) {
+    console.error('提交失败:', error)
+  }
+}
+
+// 显示报告模板列表
+const handleShowReportList = () => {
+  reportTemplateDialog_Boiler.value = true
+  fetchReportTemplateList_Boiler()
+}
+
+// 获取报告模板列表
+const fetchReportTemplateList_Boiler = async () => {
+  const params = {
+    pageNo: reportPageNo.value,
+    pageSize: reportPageSize.value,
+    reportType: 100,
+    ...reportTemplateListSearchForm.value
+  }
+  const result = await DynamicTbApi.getDynamicTbPageInspection(params)
+  if (result) {
+    reportTemplateList.value = result.list
+    reportTotal.value = result.total
+
+    const selectedIds = inspectionNatureForm.value.templateSaveReqVOList.map(item => item.templateId)
+    nextTick(() => {
+      const tableRef = reportTemplateTableRef.value?.getTableRef()
+      if (tableRef) {
+        reportTemplateList.value.forEach(row => {
+          if (selectedIds.includes(row.id)) {
+            tableRef.toggleRowSelection(row, true)
+          }
+        })
+      }
+    })
+  }
+}
+
+// 确认选择报告模板
+const handleReportTemplateConfirm = () => {
+  const selectedRows = reportTemplateTableRef.value?.getTableRef().getSelectionRows() || []
+
+  selectedRows.forEach(item => {
+    const templateIds = inspectionNatureForm.value.templateSaveReqVOList.map(x => x.templateId)
+    if (!templateIds.includes(item.id)) {
+      inspectionNatureForm.value.templateSaveReqVOList.push({
+        templateId: item.id,
+        name: item.name,
+        effectiveDate: item.effectiveDate,
+        expiryDate: item.expiryDate,
+        isDefault: '0',
+        part: [] // 初始化部件类型数组
+      })
+    }
+  })
+}
+
+// 删除报告模板
+const handleDeleteReport = (row, index) => {
+  inspectionNatureForm.value.templateSaveReqVOList.splice(index, 1)
+}
+
+// 清除选中项
+const handleClearSelectedItem = () => {
+  inspectionNatureForm.value.templateSaveReqVOList = []
+}
+
+// 移除标签
+const handleRemoveTag = (name) => {
+  inspectionNatureForm.value.templateSaveReqVOList = inspectionNatureForm.value.templateSaveReqVOList.filter(item => item.name !== name)
+}
+
+</script>
+
+<template>
+  <CustomDialog
+    :model-value="modelValue"
+    :title="(editType === 'edit' ? '编辑' : '添加') + '检验性质'"
+    width="1200px"
+    :showFooter="false"
+    @open="handleDialogOpen"
+    @close="handleClose"
+  >
+    <el-form
+      ref="inspectionNatureFormRef"
+      :model="inspectionNatureForm"
+      :rules="inspectionNatureFormRules"
+      style="width: 400px;"
+      label-width="110px"
+    >
+      <el-form-item label="检验性质" prop="inspectionNature">
+        <el-select v-model="inspectionNatureForm.inspectionNature" placeholder="请选择检验性质">
+          <el-option
+            v-for="item in getPressureInspectionNatureType[inspectionNatureForm.equipmentCategory]"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属设备" prop="equipmentCategory">
+        <el-select
+          v-model="inspectionNatureForm.equipmentCategory"
+          placeholder="请选择所属设备"
+          @change="inspectionNatureForm.equipmentType = ''"
+        >
+          <el-option
+            v-for="item in getPressureEquipmentCategory[0].children"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备类型" prop="equipmentType">
+        <el-select v-model="inspectionNatureForm.equipmentType" placeholder="请选择设备类型">
+          <el-option
+            v-for="item in getPressureEquipType[inspectionNatureForm.equipmentCategory]"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="关联报告模板" prop="templateSaveReqVOList">
+        <el-select
+          :model-value="getTemplateNames"
+          clearable
+          multiple
+          @remove-tag="handleRemoveTag"
+          @click.stop.prevent="handleShowReportList"
+          @clear="handleClearSelectedItem"
+          placeholder="请选择关联报告模板"
+        />
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="inspectionNatureForm.templateSaveReqVOList">
+      <el-table-column label="检验项目名称" prop="name"/>
+      <el-table-column label="是否默认" prop="isDefault">
+        <template #default="scope">
+          <el-switch
+            v-model="scope.row.isDefault"
+            active-value="1"
+            inactive-value="0"
+            style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="部件" prop="part" v-if="isPowerStationBoiler">
+        <template #default="scope">
+          <el-select
+            v-model="scope.row.part"
+            multiple
+            placeholder="请选择部件"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in getPressureBoilerPartType"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleDeleteReport(scope.row, scope.$index)">
+            解除关联
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display: flex; justify-content: center; padding-top: 24px;">
+      <el-button type="default" @click="handleClose">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
+    </div>
+  </CustomDialog>
+
+  <!-- 报告模板选择弹窗 -->
+  <CustomDialog
+    v-model="reportTemplateDialog_Boiler"
+    title="报告模板列表"
+    width="1200px"
+    @confirm="handleReportTemplateConfirm"
+  >
+    <SmartTable
+      ref="reportTemplateTableRef"
+      v-model:columns="reportColumns"
+      v-model:pageNo="reportPageNo"
+      v-model:pageSize="reportPageSize"
+      v-model:formData="reportTemplateListSearchForm"
+      :data="reportTemplateList"
+      :total="reportTotal"
+      :refresh="false"
+      @on-page-no-change="fetchReportTemplateList_Boiler"
+      @on-page-size-change="fetchReportTemplateList_Boiler"
+      @on-reset="fetchReportTemplateList_Boiler"
+      @on-search="fetchReportTemplateList_Boiler"
+      @refresh="fetchReportTemplateList_Boiler"
+      :tableProps="{
+        height: 500
+      }"
+    />
+  </CustomDialog>
+</template>
+
+<style scoped lang="scss">
+</style>

+ 784 - 0
yudao-ui-admin-vue3/src/views/pressure2/inspectionNature/index.vue

@@ -0,0 +1,784 @@
+<template>
+  <el-row :gutter="20" class="standard-class-list">
+    <div class="left-tree">
+      <el-tree
+        :data="getPressureEquipmentCategory"
+        node-key="value"
+        default-expand-all
+        :highlight-current="false"
+        :expand-on-click-node="false"
+        :icon="''"
+        :draggable="true"
+        @node-click="handleNodeClick"
+      >
+        <template #default="{ data }">
+          <div class="class-content">
+            <el-icon size="16" color="#015293">
+              <Management/>
+            </el-icon>
+            <span
+              class="custom-tree-node"
+            >
+                {{ data.label }}
+              </span>
+          </div>
+        </template>
+      </el-tree>
+    </div>
+    <div class="right-table">
+      <SmartTable
+        ref="inspectionNatureTableRef"
+        v-model:columns="columns"
+        v-model:pageNo="pageNo"
+        v-model:pageSize="pageSize"
+        v-model:formData="inspectionNatureListSearchForm"
+        :data="inspectionNatureList"
+        :buttons="buttons"
+        :total="total"
+        @on-page-no-change="fetchInspectionNatureList"
+        @on-page-size-change="fetchInspectionNatureList"
+        @on-reset="fetchInspectionNatureList"
+        @on-search="fetchInspectionNatureList"
+        @refresh="fetchInspectionNatureList"
+      />
+    </div>
+  </el-row>
+  <CustomDialog
+    v-model="inspectionNatureCreateOrEditDialog"
+    :title="(editType === 'edit' ? '编辑' : '添加') + '检验性质'"
+    width="1200px"
+    :showFooter="false"
+    @cancel="handleInspectionNatureDialogClose"
+  >
+    <el-form ref="inspectionNatureFormRef" :model="inspectionNatureForm"
+             :rules="inspectionNatureFormRules" style="width: 400px;" width="400px"
+             label-width="110px">
+      <el-form-item label="检验性质" prop="inspectionNature">
+        <el-select v-model="inspectionNatureForm.inspectionNature" placeholder="请选择检验性质">
+          <el-option
+            v-for="item in getPressureInspectionNature"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属设备" prop="equipmentCategory">
+        <el-select v-model="inspectionNatureForm.equipmentCategory" placeholder="请选择所属设备"
+                   @change="handleEquipmentCategoryChange">
+          <el-option
+            v-for="item in getPressureEquipmentCategory[0].children"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备类型" prop="equipmentType">
+        <el-select v-model="inspectionNatureForm.equipmentType" placeholder="请选择设备类型">
+          <el-option
+            v-for="item in getPressureEquipContainerType"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="关联报告模板" prop="templateSaveReqVOList">
+        <!-- <el-select readonly placeholder="请选择关联报告模板" @click.stop.prevent="handleShowReportList"/> -->
+        <el-select
+          :model-value="getTemplateNames"
+          clearable
+          multiple
+          @remove-tag="handleRemoveTag"
+          @click.stop.prevent="() => handleShowReportList()"
+          @clear="handleClearSelectedItem"
+          placeholder="请选择关联报告模板"
+        />
+        <!-- <div class="custom-select" @click.stop.prevent="handleShowReportList">
+          <el-input readonly placeholder="请选择关联报告模板"/>
+          <el-icon size="14"><ArrowDown /></el-icon>
+        </div> -->
+      </el-form-item>
+    </el-form>
+    <el-table :data="inspectionNatureForm.templateSaveReqVOList">
+      <el-table-column label="检验项目名称" prop="name"/>
+      <el-table-column label="是否默认" prop="isDefault">
+        <template #default="scope">
+          <el-switch v-model="scope.row.isDefault" active-value="1" inactive-value="0"
+                     style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"/>
+        </template>
+      </el-table-column>
+      <!-- 电站锅炉内检时显示部件选择 -->
+      <el-table-column label="部件" prop="part" v-if="isPowerStationBoiler">
+        <template #default="scope">
+          <el-select
+            v-model="scope.row.part"
+            multiple
+            placeholder="请选择部件"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in getPressureBoilerPartType"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <el-button link type="primary" @click="() => handleDeleteReport(scope.row, scope.$index)">
+            解除关联
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div style="display: flex; justify-content: center; padding-top: 24px;">
+      <el-button type="default" @click="handleInspectionNatureDialogClose">取消</el-button>
+      <el-button type="primary" @click="handleInspectionNatureSubmit">确定</el-button>
+    </div>
+  </CustomDialog>
+  <CustomDialog
+    v-model="reportTemplateDialog"
+    title="报告模板列表"
+    width="1200px"
+    @confirm="handleReportTemplateConfirm"
+  >
+    <SmartTable
+      ref="reportTemplateTableRef"
+      v-model:columns="reportColumns"
+      v-model:pageNo="reportPageNo"
+      v-model:pageSize="reportPageSize"
+      v-model:formData="reportTemplateListSearchForm"
+      :data="reportTemplateList"
+      :total="reportTotal"
+      :refresh="false"
+      @on-page-no-change="fetchReportTemplateList"
+      @on-page-size-change="fetchReportTemplateList"
+      @on-reset="fetchReportTemplateList"
+      @on-search="fetchReportTemplateList"
+      @refresh="fetchReportTemplateList"
+      :tableProps="{
+        height: 500,
+        onSelect: handSelectionChange
+      }"
+    />
+  </CustomDialog>
+  <CustomDialog
+    v-model="reportTemplateDialog_Boiler"
+    title="报告模板列表"
+    width="1200px"
+    @confirm="handleReportTemplateConfirm"
+  >
+    <SmartTable
+      ref="reportTemplateTableRef"
+      v-model:columns="reportColumns"
+      v-model:pageNo="reportPageNo"
+      v-model:pageSize="reportPageSize"
+      v-model:formData="reportTemplateListSearchForm"
+      :data="reportTemplateList"
+      :total="reportTotal"
+      :refresh="false"
+      @on-page-no-change="fetchReportTemplateList_Boiler"
+      @on-page-size-change="fetchReportTemplateList_Boiler"
+      @on-reset="fetchReportTemplateList_Boiler"
+      @on-search="fetchReportTemplateList_Boiler"
+      @refresh="fetchReportTemplateList_Boiler"
+      :tableProps="{
+        height: 500,
+        onSelect: handSelectionChange
+      }"
+    />
+  </CustomDialog>
+</template>
+<script lang="tsx" setup>
+import {DynamicTbApi} from "@/api/pressure2/dynamictb";
+
+const SmartTable = defineAsyncComponent(() => import('@/components/SmartTable/SmartTable'))
+const CustomDialog = defineAsyncComponent(() => import('@/components/CustomDialog/index.vue'))
+
+import {Management, MoreFilled, Plus, ArrowDown} from '@element-plus/icons-vue'
+import {useDictStore} from '@/store/modules/dict'
+import dayjs from 'dayjs'
+import {
+  getPressureInspectionNatureList,
+  createPressureInspectionNature,
+  updatePressureInspectionNature,
+  deletePressureInspectionNature,
+  getPressureInspectionNatureInfo
+} from '@/api/pressure/inspectionNature/index'
+import {getPressureReportTemplateListNoLimit} from '@/api/pressure/reportTemplate/index'
+import {has} from 'lodash'
+import {updatePressure2InspectionNature} from "@/api/pressure2/inspectionNature";
+
+const dictStore = useDictStore()
+// 检验性质字典
+const getPressureInspectionNatureType = ref({
+  '100': computed(() => dictStore.getDictMap['pressure_inspection_nature']),
+  '200': computed(() => dictStore.getDictMap['pressure_inspection_nature']), //管道
+  '300': computed(() => dictStore.getDictMap['pressure_inspection_nature_boiler']) //锅炉
+})
+
+let getPressureInspectionNature = computed(() => dictStore.getDictMap['pressure_inspection_nature'])
+// 所属设备
+const getPressureEquipmentCategory = computed(() => [{
+  value: '-1', label: '全部设备', children: [
+    {label: '锅炉', value: '300'}, {
+      label: '管道', value: '200'
+    }
+  ]
+}])
+
+// 设备类型字典
+const getPressureEquipType = ref({
+  '100': computed(() => dictStore.getDictMap['system_equip_container_type']),
+  '200': computed(() => dictStore.getDictMap['system_equip_pipe_type']), //管道
+  '300': computed(() => dictStore.getDictMap['system_equip_boiler_type']) //锅炉
+})
+let getPressureEquipContainerType = computed(() => dictStore.getDictMap['system_equip_container_type'])
+
+// 报告类型
+const getPressureReportTemplateType = computed(() => dictStore.getDictMap['pressure_report_template_type'])
+
+// 锅炉检验部件类型字典
+const getPressureBoilerPartType = computed(() => dictStore.getDictMap['pressure2_boiler_part_type'])
+
+const inspectionNatureTableRef = ref()
+const inspectionNatureList = ref([])
+const pageNo = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const inspectionNatureListSearchForm = ref({})
+const getTemplateNames = computed(() => inspectionNatureForm.value.templateSaveReqVOList.map(x => x.name))
+
+const columns = ref([
+  {
+    type: 'selection',
+    width: 50,
+    align: 'center'
+  },
+  {
+    label: '检验性质',
+    prop: 'inspectionNature',
+    search: {
+      type: 'select',
+      options: getPressureInspectionNature.value
+    },
+    render: (row, value) => getPressureInspectionNatureType.value[row.equipmentCategory].find(x => x.value === value)?.label || '-'
+  },
+  {
+    label: '所属设备',
+    prop: 'equipmentCategory',
+    render: (row, value) => getPressureEquipmentCategory.value[0].children.find(x => x.value === value)?.label || '-'
+  },
+  {
+    label: '设备类型',
+    prop: 'equipmentType',
+    render: (row, value) => getPressureEquipType.value[row.equipmentCategory].find(x => x.value === value)?.label || '-'
+  },
+  {
+    label: '默认报告模板数',
+    prop: 'defaultTemplateCount'
+  },
+  {
+    label: '关联报告模板数',
+    prop: 'templateCount',
+  },
+  {
+    label: '最近修改人',
+    prop: 'updaterName'
+  },
+  {
+    label: '最近修改时间',
+    prop: 'updateTime',
+    render: (row, value) => !value ? '' : dayjs(value).format('YYYY-MM-DD HH:mm:ss')
+  },
+  {
+    label: '操作',
+    render: (row) => <el-button link type="primary"
+                                onClick={() => handleCreateOrEditInspectionNature(row)}>编辑</el-button>
+  }
+])
+
+const buttons = ref([
+  {
+    label: '新增',
+    render: () => <el-button type="primary"
+                             onClick={() => handleCreateOrEditInspectionNature()}>新增</el-button>
+  },
+  {
+    label: '删除',
+    render: () => <el-button type="danger"
+                             onClick={() => handleDeleteInspectionNature()}>删除</el-button>
+  }
+])
+
+// 获取检验性质列表
+const fetchInspectionNatureList = async (equipmentCategory?: string) => {
+  const params = {
+    pageNo: pageNo.value,
+    pageSize: pageSize.value,
+    equipmentCategory,
+    ...inspectionNatureListSearchForm.value
+  }
+  const result = await getPressureInspectionNatureList(params)
+  if (result) {
+    inspectionNatureList.value = result.list
+    total.value = result.total
+  }
+}
+fetchInspectionNatureList()
+
+// 设备类型查询
+const handleNodeClick = (data) => {
+  const equipmentCategory = data.value === '-1' ? null : data.value
+  fetchInspectionNatureList(equipmentCategory)
+}
+
+// 编辑 & 新增 检验性质按钮
+const handleCreateOrEditInspectionNature = async (row?: any) => {
+  if (row) {
+    editInspectionNatureId.value = row.id
+    editType.value = 'edit'
+    let detailInfo = [];
+    
+    // 判断是否为锅炉或管道(需要特殊处理)
+    if (row.equipmentCategory == 200 || row.equipmentCategory == 300) {
+      getPressureInspectionNature = getPressureInspectionNatureType.value[row.equipmentCategory]
+      getPressureEquipContainerType = getPressureEquipType.value[row.equipmentCategory]
+      inspectionNatureCreateOrEditDialog.value = true
+      await loadDetailInfo(row.id)
+      return
+    } else {
+      detailInfo = await getPressureInspectionNatureInfo({id: row.id})
+      getPressureInspectionNature = getPressureInspectionNatureType.value['100']
+      getPressureEquipContainerType = getPressureEquipType.value['100']
+    }
+    
+    inspectionNatureForm.value = {
+      ...detailInfo,
+      templateSaveReqVOList: detailInfo.templateDetailRespVOList.map(item => ({
+        ...item,
+        name: item.templateName
+      }))
+    }
+  } else {
+    editType.value = 'create'
+    // 重置为默认字典
+    getPressureInspectionNature = getPressureInspectionNatureType.value['100']
+    getPressureEquipContainerType = getPressureEquipType.value['100']
+  }
+  inspectionNatureCreateOrEditDialog.value = true
+}
+
+// 加载详情信息(锅炉/管道专用)
+const loadDetailInfo = async (id: string) => {
+  try {
+    const detailInfo = await DynamicTbApi.getPressureInspectionNatureInfoBoiler({id})
+    inspectionNatureForm.value = {
+      ...detailInfo,
+      templateSaveReqVOList: detailInfo.templateDetailRespVOList.map(item => ({
+        ...item,
+        name: item.templateName,
+        // 将后端返回的 part 字符串转换为数组
+        part: item.part ? (typeof item.part === 'string' ? item.part.split('/').filter(p => p) : (item.part || [])) : []
+      }))
+    }
+  } catch (error) {
+    console.error('加载详情失败:', error)
+    ElMessage.error('加载详情失败')
+  }
+}
+
+const handleEquipmentCategoryChange = (value) => {
+  if (value == 200 || value == 300) {
+    getPressureInspectionNature = getPressureInspectionNatureType.value[value]
+    getPressureEquipContainerType = getPressureEquipType.value[value]
+  } else {
+    getPressureInspectionNature = getPressureInspectionNatureType.value['100']
+    getPressureEquipContainerType = getPressureEquipType.value['100']
+  }
+  // 切换设备类别时清空设备类型
+  inspectionNatureForm.value.equipmentType = ''
+}
+
+// 删除检验性质
+const handleDeleteInspectionNature = () => {
+  const selectedRows = inspectionNatureTableRef.value?.getTableRef().getSelectionRows()
+  if (!selectedRows.length) return ElMessage.warning('请选择检验性质')
+  if (selectedRows.length > 1) return ElMessage.warning('仅限选择一个检验性质')
+  ElMessageBox.confirm('确定删除选中的检验性质吗?', '删除提示', {
+    confirmButtonText: '确 认',
+    cancelButtonText: '取 消'
+  }).then(async () => {
+    const res = await deletePressureInspectionNature({id: selectedRows[0].id})
+    if (res) {
+      ElMessage.success('删除成功')
+      fetchInspectionNatureList()
+    }
+  })
+}
+
+// 新增&编辑 检验性质
+const inspectionNatureCreateOrEditDialog = ref(false)
+const editType = ref('')
+const editInspectionNatureId = ref('')
+const inspectionNatureFormRef = ref()
+const inspectionNatureForm = ref({
+  inspectionNature: '',
+  equipmentCategory: '',
+  equipmentType: '',
+  templateSaveReqVOList: [] as any[]
+})
+const inspectionNatureFormRules = ref({
+  inspectionNature: [
+    {required: true, message: '请选择检验性质', trigger: 'blur'}
+  ],
+  equipmentCategory: [
+    {required: true, message: '请选择设备类别', trigger: 'blur'}
+  ],
+  equipmentType: [
+    {required: true, message: '请选择设备类型', trigger: 'blur'}
+  ],
+  templateSaveReqVOList: [
+    {required: true, message: '请选择关联报告模板', trigger: 'blur'}
+  ]
+})
+
+const handleDeleteReport = (row, $index) => {
+  inspectionNatureForm.value.templateSaveReqVOList.splice($index, 1)
+}
+
+const handleInspectionNatureDialogClose = () => {
+  inspectionNatureCreateOrEditDialog.value = false
+  editType.value = ''
+  inspectionNatureFormRef.value.resetFields()
+}
+
+// 提交检验性质
+const handleInspectionNatureSubmit = async () => {
+  try {
+    await inspectionNatureFormRef.value.validate()
+    
+    // 判断是否为电站锅炉内检
+    const isPowerStationBoiler = inspectionNatureForm.value.equipmentType == '1' && 
+                                  inspectionNatureForm.value.equipmentCategory == '300' && 
+                                  inspectionNatureForm.value.inspectionNature == '100'
+    
+    const params = {
+      ...inspectionNatureForm.value,
+      templateSaveReqVOList: inspectionNatureForm.value.templateSaveReqVOList.map((item: any) => ({
+        id: item.id,
+        templateId: item.templateId,
+        isDefault: item.isDefault,
+        inspectionNatureId: has(item, 'inspectionNatureId') ? item.inspectionNatureId : (editType.value === 'edit' ? editInspectionNatureId.value : undefined),
+        // 如果是电站锅炉内检,将 part 数组转换为字符串
+        part: isPowerStationBoiler ? (item.part || []).join('/') : ""
+      })),
+      id: editType.value === 'edit' ? editInspectionNatureId.value : undefined
+    }
+    
+    // 根据设备类型选择不同的更新接口
+    const result = editType.value === 'edit' 
+      ? (inspectionNatureForm.value.equipmentCategory == 200 || inspectionNatureForm.value.equipmentCategory == 300
+          ? await updatePressure2InspectionNature(params)
+          : await updatePressureInspectionNature(params))
+      : await createPressureInspectionNature(params)
+      
+    if (result) {
+      ElMessage.success((editType.value === 'edit' ? '编辑' : '新增') + '成功')
+      handleInspectionNatureDialogClose()
+      fetchInspectionNatureList()
+    }
+  } catch (error) {
+    console.error('提交失败:', error)
+  }
+}
+
+// 报告模板列表
+const reportTemplateTableRef = ref()
+const reportPageNo = ref(1)
+const reportPageSize = ref(10)
+const reportTotal = ref(0)
+const reportTemplateListSearchForm = ref({})
+const reportTemplateList = ref([])
+const reportColumns = computed(() => ([
+  {
+    type: 'selection',
+    width: 55,
+    fieldProps: {
+      reserveSelection: true,
+      selectable: (row) => {
+        return inspectionNatureForm.value.templateSaveReqVOList.findIndex(item => item.templateId === row.id) < 0
+      }
+    }
+  },
+  {
+    label: '报告编号',
+    prop: 'id',
+  },
+  {
+    label: '报告名称',
+    prop: 'name',
+    search: {
+      type: 'input',
+      placeholder: '请输入报告名称'
+    }
+  },
+  {
+    label: '报告分类',
+    prop: 'className',
+    search: {
+      type: 'select',
+      options: [],
+      prop: 'classId'
+    },
+  },
+  {
+    label: '报告类型',
+    prop: 'type',
+    render: (row) => {
+      return getPressureReportTemplateType.value[row.type].label || '未知'
+    }
+  },
+  {
+    label: '生效日期',
+    prop: 'effectiveDate',
+  },
+  {
+    label: '失效日期',
+    prop: 'expiryDate',
+  }
+]))
+
+const fetchReportTemplateList = async () => {
+  const params = {
+    pageNo: reportPageNo.value,
+    pageSize: reportPageSize.value,
+    ...reportTemplateListSearchForm.value
+  }
+  const result = await getPressureReportTemplateListNoLimit(params)
+  if (result) {
+    reportTemplateList.value = result.list
+    reportTotal.value = result.total
+    // 获取当前已关联的报告模板ID列表
+    const selectedIds = inspectionNatureForm.value.templateSaveReqVOList.map(item => item.templateId)
+    // 反选表格中已关联的项
+    nextTick(() => {
+      const tableRef = reportTemplateTableRef.value?.getTableRef()
+      if (tableRef) {
+        reportTemplateList.value.forEach(row => {
+          if (selectedIds.includes(row.id)) {
+            tableRef.toggleRowSelection(row, true)
+          }
+        })
+      }
+    })
+  }
+}
+
+const fetchReportTemplateList_Boiler = async () => {
+  const params = {
+    pageNo: reportPageNo.value,
+    pageSize: reportPageSize.value,
+    reportType: 100,
+    ...reportTemplateListSearchForm.value
+  }
+  const result = await DynamicTbApi.getDynamicTbPageInspection(params)
+  if (result) {
+    reportTemplateList.value = result.list
+    reportTotal.value = result.total
+    // 获取当前已关联的报告模板ID列表
+    const selectedIds = inspectionNatureForm.value.templateSaveReqVOList.map(item => item.templateId)
+    // 反选表格中已关联的项
+    nextTick(() => {
+      const tableRef = reportTemplateTableRef.value?.getTableRef()
+      if (tableRef) {
+        reportTemplateList.value.forEach(row => {
+          if (selectedIds.includes(row.id)) {
+            tableRef.toggleRowSelection(row, true)
+          }
+        })
+      }
+    })
+  }
+}
+
+// 报告模板弹窗
+const reportTemplateDialog = ref(false)
+const reportTemplateDialog_Boiler = ref(false)
+const handleShowReportList = () => {
+
+  if (inspectionNatureForm.value.equipmentCategory == 200 || inspectionNatureForm.value.equipmentCategory == 300) {
+    reportTemplateDialog_Boiler.value = true
+    fetchReportTemplateList_Boiler()
+  } else {
+    reportTemplateDialog.value = true
+    fetchReportTemplateList()
+  }
+
+}
+
+const handleClearSelectedItem = () => {
+  inspectionNatureForm.value.templateSaveReqVOList = []
+}
+const handleRemoveTag = (name) => {
+  inspectionNatureForm.value.templateSaveReqVOList = inspectionNatureForm.value.templateSaveReqVOList.filter(item => item.name !== name)
+}
+
+const handleReportTemplateConfirm = () => {
+  const selectedRows = reportTemplateTableRef.value?.getTableRef().getSelectionRows() || [];
+
+  // 过滤掉已存在的项
+  selectedRows.forEach(item => {
+    const templateIds = inspectionNatureForm.value.templateSaveReqVOList.map(x => x.templateId)
+    if (!templateIds.includes(item.id)) {
+      // 合并新数据
+      inspectionNatureForm.value.templateSaveReqVOList.push({
+        templateId: item.id,
+        name: item.name,
+        effectiveDate: item.effectiveDate,
+        expiryDate: item.expiryDate,
+        isDefault: '0', // 默认值
+        part: [] // 初始化部件类型数组(锅炉/管道专用)
+      })
+    }
+  });
+}
+
+
+const handSelectionChange = (newSelection, row) => {
+  console.log('newSelection', newSelection, row)
+}
+
+// 判断是否为电站锅炉内检
+const isPowerStationBoiler = computed(() => {
+  return inspectionNatureForm.value.equipmentType == '1' && 
+         inspectionNatureForm.value.equipmentCategory == '300' && 
+         inspectionNatureForm.value.inspectionNature == '100'
+})
+</script>
+<style lang="scss" scoped>
+.standard-class-list {
+  display: flex;
+  align-items: stretch;
+  flex-wrap: nowrap;
+  margin: 0 !important;
+
+  .right-table {
+    flex: 1;
+  }
+
+  .left-tree {
+    min-width: 240px;
+    margin-right: 24px;
+    border-right: 1px solid var(--el-border-color);
+  }
+
+  :deep(.el-tree) {
+    .el-tree-node {
+      &.is-current {
+        // background: none !important;
+      }
+    }
+
+    .el-tree-node__content {
+      height: 48px;
+
+      .el-tree-node__expand-icon {
+        display: none;
+      }
+
+      &:hover, &:visited, &:active, &:focus, &:checked {
+        background-color: var(--el-color-primary-light-9);
+      }
+
+      &.is-current {
+        background-color: var(--el-color-primary-light-9);
+      }
+    }
+  }
+
+  .add-class-btn {
+    width: 100%;
+    height: 38px;
+    margin-right: 10px;
+    border-radius: 0;
+
+    .el-icon {
+      margin-right: 6px;
+    }
+  }
+
+  .class-content {
+    position: relative;
+    display: flex;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+
+    .el-icon {
+      margin-top: 2px;
+    }
+
+    .custom-tree-node {
+      padding-left: 8px;
+    }
+
+    .more {
+      position: absolute;
+      right: 12px;
+      top: 50%;
+      transform: translateY(-50%) rotate(90deg);
+      margin: 0;
+    }
+  }
+}
+
+.custom-tree-node-popover {
+  color: var(--el-text-color-primary);
+
+  .el-button {
+    display: block;
+    margin-left: 0;
+    margin-bottom: 8px;
+    height: 36px;
+    width: 100%;
+    line-height: 36px;
+    text-align: center;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.custom-select {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  line-height: 32px;
+  min-height: 32px;
+
+  :deep(.el-input) {
+    width: 100%;
+    height: 100%;
+    flex: 1;
+
+    .el-input__inner {
+      cursor: pointer;
+    }
+  }
+
+  :deep(.el-icon) {
+    position: absolute;
+    right: 12px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: var(--el-text-color-placeholder);
+  }
+}
+</style>

+ 2 - 2
yudao-ui-admin-vue3/src/views/pressure2/orderConfirm/pipeDetail.vue

@@ -1455,7 +1455,7 @@ const handleGenerateAcceptance = () => {
       return
     }
     
-    if (selectedCheckers.value.length === 0) {
+    if (formData.value.teamList.length === 0) {
       ElMessage.warning('请选择检验员')
       return
     }
@@ -1560,7 +1560,7 @@ const handleSave = () => {
     if (!valid) return ElMessage.warning('请完善必填信息')
     // 检查排期时间和检验员
     if (!formData.value.appointmentDate) return ElMessage.warning('请选择约检时间')
-    if (selectedCheckers.value.length === 0) return ElMessage.warning('请选择检验员')
+    if (formData.value.teamList.length === 0) return ElMessage.warning('请选择检验员')
    
     // 二次确认
     ElMessageBox.confirm(

+ 2 - 2
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/StatusOperationPanel.vue

@@ -892,7 +892,7 @@ const handleSelectVerfiyer = async () => {
   // 获取审核人\校核人配置信息
   let res = await UserApi.getApprovalDetail({})
   // 如果存在多份待提交校核的报告,执行批量校核弹窗
-  console.log('getCanSubmitRecheckReport', getCanSubmitRecheckReport,props.reportList)
+  console.log('getCanSubmitRecheckReport', getCanSubmitRecheckReport,props.reportList,res)
   if(getCanSubmitRecheckReport.value.length > 1) {
     if(res && res.recheckUser) {
       batchRecheckForm.value.recheckUser = res.recheckUser
@@ -919,7 +919,7 @@ const handleBatchSubmitToRecheck = async () => {
     return
   }
 
-  if (_.isEmpty(form.value.recheckUser)) {
+  if (_.isEmpty(batchRecheckForm.value.recheckUser)) {
     return ElMessage.error('请选择校核人')
   }
 

+ 14 - 12
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipePlanScheduleDialog.vue

@@ -33,7 +33,7 @@
                 placeholder="选择日期"
                 value-format="YYYY-MM-DD"
                 :disabled="formData.legalNoSchedule"
-                :disabled-date="disabledDatePlan"
+                :disabled-date="(time) => time.getTime() < Date.now() - 8.64e7"
                 class="!w-240px"
               />
               <el-checkbox 
@@ -395,11 +395,12 @@ const formRules = {
   }],
   legalTeamList: [{
     validator: (rule, value, callback) => {
-      if (!formData.value.legalNoSchedule && (!value || value.length === 0)) {
-        callback(new Error('请选择检验员'))
-      } else {
-        callback()
-      }
+      // if (!formData.value.legalNoSchedule && (!value || value.length === 0)) {
+      //   callback(new Error('请选择检验员'))
+      // } else {
+      //   callback()
+      // }
+      callback()
     },
     trigger: 'change' 
   }],
@@ -415,11 +416,12 @@ const formRules = {
   }],
   yearTeamList: [{
     validator: (rule, value, callback) => {
-      if (!formData.value.yearNoSchedule && (!value || value.length === 0)) {
-        callback(new Error('请选择检验员'))
-      } else {
-        callback()
-      }
+      // if (!formData.value.yearNoSchedule && (!value || value.length === 0)) {
+      //   callback(new Error('请选择检验员'))
+      // } else {
+      //   callback()
+      // }
+      callback()
     },
     trigger: 'change' 
   }]
@@ -752,7 +754,7 @@ const handleConfirm = async () => {
 
   //验证
   await form.validate()
-  
+
   try {
     // 组装新的数据结构
     const submitData: { taskList: any[]; source: number } = {

+ 129 - 52
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/detail.vue

@@ -194,7 +194,7 @@
       <ContentWrap>
         <el-table
           v-loading="loading"
-          ref="tableRef"
+          ref="mainTableRef"
           :data="list"
           border
           @selection-change="handleSelectionChange"
@@ -202,23 +202,26 @@
           :row-key="(row) => row.id"
           :row-class-name="getMainRowClassName"
           @row-click="handleMainRowClick"
+          @expand-change="handleExpandChange"
+          :expand-row-keys="expandRowKeys"
         >
           <el-table-column type="selection" width="40" fixed="left"/>
-          <el-table-column type="expand" width="1">
+          <el-table-column type="expand" width="40">
             <template #default="props">
               <div class="ml-15px mr-15px">
 
-                <el-table :data="props.row.detailSaveReqVOS" border
-                          :header-cell-style="{background: '#f5f7fa', color: '#606266'}"
-                          :ref="(el) => setDetailTableRef(el, props.row.id)"
-                          @selection-change="(selection) => handleDetailSelectionChange(selection, props.row)"
-                          :row-class-name="getRowClassName"
-                          class="inner-table"
-                          @row-click="handleRowClick"
+                <el-table
+                  :data="props.row.pipes" border
+                  :header-cell-style="{background: '#f5f7fa', color: '#606266'}"
+                  :ref="(el) => setDetailTableRef(el, props.row.id)"
+                  @selection-change="(selection) => handleDetailSelectionChange(selection, props.row)"
+                  :row-class-name="getRowClassName"
+                  class="inner-table"
+                  @row-click="handleRowClick"
                 >
                   <el-table-column type="selection" width="40" fixed="left"/>
                   <el-table-column type="index" label="序号" min-width="60" align="center" :index="indexMethod1" />
-                  <el-table-column label="注册代码" prop="equipCode" min-width="120" show-overflow-tooltip/>
+                  <el-table-column label="注册代码" prop="pipeRegCode" min-width="120" show-overflow-tooltip/>
                   <el-table-column label="管道名称" prop="pipeName" min-width="120" show-overflow-tooltip/>
                   <el-table-column label="管道编号" prop="pipeNo" min-width="120" show-overflow-tooltip/>
                   <el-table-column label="管道级别" prop="pipeLevel" min-width="120" show-overflow-tooltip/>
@@ -449,6 +452,7 @@ import PipePlanScheduleDialog
   from "@/views/pressure2/pipescheduling/components/PipePlanScheduleDialog.vue";
 import PlanScheduleEquipPipeDialog
   from "@/views/pressure2/planNew/components/PlanScheduleEquipPipeDialog.vue";
+import {ref} from "vue";
 
 const message = useMessage()
 const dialogVisible = ref(false)
@@ -462,7 +466,7 @@ const loading = ref(true) // 列表的加载中
 const list = ref<BoilerPlanSchedulingEquipEditVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const selectedRows = ref<BoilerPlanSchedulingEquipEditVO[]>([]) // 选中的行
-const selectedDetailRows = ref<PipeEquipmentDetailVO[]>([]) // 选中的行
+const selectedDetailRows = ref<any[]>([]) // 选中的行
 const planScheduleEquipPipeDialogRef = ref() // 计划排期弹窗引用
 const selectedTypeList = ref<StringDictDataType[]>([]) // 选中的管道归类
 // 添加锅炉类型字典选项变量
@@ -580,12 +584,16 @@ const handleQuery = async () => {
     }
 
     // 关闭所有已展开的行
-    expandRows.value.forEach(row => {
-      tableRef.value?.toggleRowExpansion(row, false)
+    expandRowKeys.value.forEach(key => {
+      const tableRef = getDetailTableRef(key)
+      if (tableRef) {
+        tableRef.clearSelection()
+      }
     })
     // 清空展开行记录
     expandRowKeys.value = []
-    expandRows.value = []
+    // 清空展开行缓存
+    expandedRowsCache.value.clear()
 
     selectedRows.value = []
     selectedDetailRows.value = []
@@ -626,30 +634,37 @@ const resetQuery = () => {
 }
 
 /** 表格选择框变化 */
-const handleSelectionChange = async (selection: BoilerPlanSchedulingEquipEditVO[]) => {
+const handleSelectionChange = async (selection: any[]) => {
   // 计算取消选择的行
   const deselectedRows = selectedRows.value.filter(
     item => !selection.includes(item)
   );
   selectedRows.value = selection
-  // 每一个勾选了主表的要找到对应的子表
-  for (const item of selection) {
-    // 如果没有子表,展开当前子表
-    if (!expandRowKeys.value.includes(item.id)) {
-      await toggleExpand(item)
+
+  // 清除取消选中行的子表选择(包括展开的和收起的)
+  deselectedRows.forEach(item => {
+    const rowId = item.id
+    // 如果该行是展开状态,直接清除表格选中
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      tableRef.clearSelection()
     }
-    // 如果没有勾选,勾选子表
-    if (selectedDetailRows.value.findIndex(selectedDetailRow => selectedDetailRow.equipPipeId === item.id) === -1) {
-      getDetailTableRef(item.id)?.toggleAllSelection(true)
+
+    // 无论是否展开,都清除缓存中的选中状态
+    if (expandedRowsCache.value.has(rowId)) {
+      expandedRowsCache.value.get(rowId)!.selectedIds.clear()
     }
-  }
-  deselectedRows.forEach(item => {
-    getDetailTableRef(item.id)?.clearSelection()
+
+    // 从全局选中的子表数据中移除
+    selectedDetailRows.value = selectedDetailRows.value.filter(
+      detailRow => detailRow.equipPipeId !== rowId
+    )
   })
 }
 
-const getMainRowClassName = ({row}) => {
-  const isSelected = tableRef.value?.getSelectionRows().some((item) =>
+
+const getMainRowClassName = ({ row }) => {
+  const isSelected = mainTableRef.value?.getSelectionRows().some((item) =>
     item.id === row.id
   )
   return isSelected ? 'selected-row' : ''
@@ -667,11 +682,11 @@ const getRowClassName = ({row}) => {
   return ''
 }
 /** 处理行点击勾选 */
-const handleMainRowClick = (row) => {
-  tableRef.value?.toggleRowSelection(row)
+const handleMainRowClick = (row, _event,_column) => {
+  mainTableRef.value?.toggleRowSelection(row)
 }
 
-const handleRowClick = (row, event, column) => {
+const handleRowClick = (row, _event, column) => {
   // 如果点击的是选择列,不处理
   if (column.type === 'selection') {
     return
@@ -700,8 +715,12 @@ const handleScheduleSuccess = () => {
   handleQuery()
 }
 
-const tableRef = ref()
+const mainTableRef = ref()
 const detailTableRefs = ref<Record<string, any>>({})
+const expandedRowsCache = ref<Map<string, { pipes: any[], selectedIds: Set<string> }>>(new Map())
+// 标志位:防止展开时恢复选中状态触发 handleDetailSelectionChange 覆盖缓存
+const isRestoringSelection = ref(false)
+
 const setDetailTableRef = (el: any, parentId: string) => {
   if (el) {
     detailTableRefs.value[parentId] = el;
@@ -710,46 +729,106 @@ const setDetailTableRef = (el: any, parentId: string) => {
 const getDetailTableRef = (parentId: string) => {
   return detailTableRefs.value[parentId];
 }
-const handleDetailSelectionChange = (selection: any[], mainRow: PipeEquipmentVO) => {
+const handleDetailSelectionChange = (selection: any[], mainRow: any) => {
+  // 如果正在恢复选中状态,不更新缓存
+  if (isRestoringSelection.value) {
+    return
+  }
+
+  const rowId = mainRow.id
+
+  // 更新缓存中的选中状态(如果缓存不存在则创建)
+  if (!expandedRowsCache.value.has(rowId)) {
+    // 如果该行还未展开,初始化缓存
+    expandedRowsCache.value.set(rowId, {
+      pipes: mainRow.pipes || [],
+      selectedIds: new Set<string>()
+    })
+  }
+  const cache = expandedRowsCache.value.get(rowId)!
+  cache.selectedIds = new Set(selection.map((item: any) => item.id))
+
+  // 更新全局选中的子表数据
   selectedDetailRows.value = selectedDetailRows.value.filter((item) => item.equipPipeId !== mainRow.id)
-  selectedDetailRows.value = [...selectedDetailRows.value, ...selection]
-  // tableRef.value.toggleRowSelection(mainRow, true)
+  selectedDetailRows.value = [...selectedDetailRows.value.filter(row => !selection.some(sel => sel.id === row.id)), ...selection]
+
   // 如果子表有选中项,则自动选中主表行;否则取消选中主表行
   nextTick(() => {
     if (selection.length > 0) {
       // 子表有选中项,选中主表行
-      tableRef.value.toggleRowSelection(mainRow, true);
-    }else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)){
+      mainTableRef.value.toggleRowSelection(mainRow, true);
+    } else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)) {
       const currentlySelected = selectedRows.value.some(row => row.id === mainRow.id);
       if (currentlySelected && selection.length === 0) {
         // 只有当主行当前是选中状态且子表没有任何选中项时才取消主行选择
-        tableRef.value.toggleRowSelection(mainRow,false);
+        mainTableRef.value.toggleRowSelection(mainRow, false);
       }
     }
   });
 }
 const expandRowKeys = ref<string[]>([])
-const expandRows = ref([])
 const toggleExpand = async (row: any) => {
   const key = row.id
   // 第一次点击展开,第二次点击同一行收起
   if (expandRowKeys.value.includes(key)) {
-    expandRowKeys.value = expandRowKeys.value.filter((key) => key !== key)
-    expandRows.value = expandRows.value.filter((row) => row.id !== key)
-    selectedDetailRows.value = selectedDetailRows.value.filter((row) => row.equipPipeId !== key)
-    tableRef.value.toggleRowExpansion(row, false)
+    mainTableRef.value.toggleRowExpansion(row, false)
   } else {
-    expandRowKeys.value.push(key)
-    expandRows.value.push(row)
-    tableRef.value.toggleRowExpansion(row, true)
-    await EquipPipeSchedulingApi.getPipeEquipmentDetailListByPipeEquipmentId(key,{equipIds:[]}).then((res) => {
-      let find = list.value.find(item => item.id === key);
-      find.detailSaveReqVOS = res.list
+    mainTableRef.value.toggleRowExpansion(row, true)
+  }
+
+}
+
+const handleExpandChange = (row, expandedRows: any[]) => {
+  const rowId = row.id
+  const isExpanded = expandedRows.some((r) => r.id === rowId)
+
+  if (isExpanded) {
+    // 展开时,从缓存中恢复或初始化
+    if (!expandedRowsCache.value.has(rowId)) {
+      // 首次展开,初始化缓存
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: new Set<string>()
+      })
+    }
+    // 恢复选中状态
+    nextTick(() => {
+      const cache = expandedRowsCache.value.get(rowId)
+      const tableRef = getDetailTableRef(rowId)
+      if (cache && tableRef && cache.pipes.length > 0) {
+        // 设置标志位,防止触发 handleDetailSelectionChange
+        isRestoringSelection.value = true
+        
+        cache.pipes.forEach((pipe) => {
+          if (cache.selectedIds.has(pipe.id)) {
+            tableRef.toggleRowSelection(pipe, true)
+          }
+        })
+        
+        // 恢复完成后,延迟重置标志位
+        nextTick(() => {
+          isRestoringSelection.value = false
+        })
+      }
     })
+  } else {
+    // 收起时,保存当前选中状态
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      const currentSelection = tableRef.getSelectionRows()
+      const selectedIds = new Set(currentSelection.map((item: any) => item.id))
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: selectedIds
+      })
+    }
   }
 
+  // 更新展开的行 key 列表
+  expandRowKeys.value = expandedRows.map((r) => r.id)
 }
 
+
 /** 处理容器归类变化 */
 const handleTypeListChange = (values: string[]) => {
   selectedTypeList.value = containerTypeOptions.filter(dict => values.includes(dict.value))
@@ -889,9 +968,7 @@ defineExpose({
     color: var(--el-color-primary-light-3);
   }
 }
-:deep(.el-table__expand-icon) {
-  display: none !important;
-}
+
 
 :deep(.inner-table .el-checkbox__input.is-checked .el-checkbox__inner),
 :deep(.inner-table .el-checkbox__input.is-indeterminate .el-checkbox__inner) {

+ 117 - 52
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/index.vue

@@ -6,7 +6,7 @@
       :model="queryParams"
       ref="queryFormRef"
       :inline="true"
-      label-width="68px"
+      label-width="100px"
     >
       <!-- 基本信息查询部分 -->
       <div class="flex flex-wrap items-start gap-x-2">
@@ -20,10 +20,10 @@
           />
         </el-form-item>
 
-        <el-form-item label="单位地址" prop="pipeAddress">
+        <el-form-item label="管道使用地址" prop="pipeAddress">
           <el-input
             v-model="queryParams.pipeAddress"
-            placeholder="请输入单位地址"
+            placeholder="请输入管道使用地址"
             clearable
             @keyup.enter="getList"
             class="!w-240px"
@@ -172,7 +172,7 @@
             plain
             v-if="source === 'pressure'"
             @click="handleBatchSchedule"
-            :disabled="selectedDetailRows.length === 0"
+            :disabled="selectedRows.length === 0"
           >
             <Icon icon="ep:calendar" class="mr-5px" /> 批量排期
           </el-button>
@@ -217,7 +217,7 @@
         sortable="custom"
       />
       <el-table-column label="使用单位名称 " align="center" prop="unitName" min-width="150" />
-      <el-table-column label="使用单位地址" align="center" prop="pipeAddress" min-width="150" />
+      <el-table-column label="使用管道使用地址" align="center" prop="pipeAddress" min-width="150" />
       &lt;!&ndash; 法定检验 &ndash;&gt;
       <el-table-column
         label="定期检验"
@@ -286,6 +286,7 @@
               :row-class-name="getMainRowClassName"
               @row-click="handleMainRowClick"
               @expand-change="handleExpandChange"
+              :expand-row-keys="expandRowKeys"
     >
       <el-table-column type="selection" width="40"/>
       <el-table-column type="expand">
@@ -335,14 +336,12 @@
         align="center"
         prop="equipDistrictName"
         min-width="80"
-        sortable="custom"
       />
       <el-table-column
         label="街道"
         align="center"
         prop="equipStreetName"
         min-width="100"
-        sortable="custom"
       />
       <el-table-column label="使用单位名称 " align="center" prop="unitName" min-width="150" />
       <el-table-column label="管道使用地址" align="center" prop="pipeAddress" min-width="150" />
@@ -469,19 +468,13 @@ const getList = async () => {
     const data = await EquipPipeSchedulingApi.getEquipPipeSchedulingPagePipe(queryParams.value)
     list.value = data.list
     total.value = data.total
-    //初始化
-    // list.value.forEach(item => {
-    //   childSelectionsMap.value[item.id] = [];
-    // })
-    expandRows.value.forEach(row => {
-      mainTableRef.value?.toggleRowExpansion(row, false)
-    })
+    // 重置展开状态、选中状态和缓存
     expandRowKeys.value = []
-    expandRows.value = []
     selectedDetailRows.value = []
     selectedRows.value = []
     selectedLegalList.value = []
     selectedYearList.value = []
+    expandedRowsCache.value.clear()
   } finally {
     loading.value = false
   }
@@ -715,7 +708,7 @@ const handleEdit = (row: EquipPipeSchedulingVO) => {
 
 /** 处理批量排期 */
 const handleBatchSchedule = () => {
-  if (selectedDetailRows.value.length === 0) {
+  if (selectedDetailRows.value.length === 0 && selectedRows.value.length === 0) {
     message.warning('请至少选择一条记录')
     return
   }
@@ -732,6 +725,23 @@ const handleBatchSchedule = () => {
   //按定检和年检分组
   selectedLegalList.value = []
   selectedYearList.value = []
+
+  // 选择了主表但是没有选子表,则获取该主表下的所有子表数据
+  let ids = selectedDetailRows.value.map(item => item.equipPipeId)
+  selectedRows.value.forEach(item => {
+    if (!ids.includes(item.id)) {
+      item.pipes.forEach(pipe => {
+        if (!pipe.hasLegalScheduling && pipe.planLegalCheckDate == null) {
+          selectedLegalList.value.push(pipe)
+        }
+
+        if (!pipe.hasYearScheduling && pipe.planYearCheckDate == null) {
+          selectedYearList.value.push(pipe)
+        }
+      })
+
+    }
+  })
   selectedDetailRows.value.forEach(item => {
 
     if (!item.hasLegalScheduling && item.planLegalCheckDate == null){
@@ -876,6 +886,9 @@ const getDetailTableRef = (parentId: string) => {
   return detailTableRefs.value[parentId];
 }
 
+// 标志位:防止展开时恢复选中状态触发 handleDetailSelectionChange 覆盖缓存
+const isRestoringSelection = ref(false)
+
 /** 处理表格排序 */
 const handleSortChange = ({ prop, order }) => {
   queryParams.value.sort = prop
@@ -902,12 +915,53 @@ const getRowClassName = ({row}) => {
   return ''
 }
 const handleExpandChange = (row, expandedRows: any[]) => {
-  if (!expandedRows.includes(row)){
-    toggleExpand(row)
-  }else {
-    expandRowKeys.value.push(row.id)
-    expandRows.value.push(row)
+  const rowId = row.id
+  const isExpanded = expandedRows.some((r) => r.id === rowId)
+  
+  if (isExpanded) {
+    // 展开时,从缓存中恢复或初始化
+    if (!expandedRowsCache.value.has(rowId)) {
+      // 首次展开,初始化缓存
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: new Set<string>()
+      })
+    }
+    // 恢复选中状态
+    nextTick(() => {
+      const cache = expandedRowsCache.value.get(rowId)
+      const tableRef = getDetailTableRef(rowId)
+      if (cache && tableRef && cache.pipes.length > 0) {
+        // 设置标志位,防止触发 handleDetailSelectionChange
+        isRestoringSelection.value = true
+        
+        cache.pipes.forEach((pipe) => {
+          if (cache.selectedIds.has(pipe.id)) {
+            tableRef.toggleRowSelection(pipe, true)
+          }
+        })
+        
+        // 恢复完成后,延迟重置标志位
+        nextTick(() => {
+          isRestoringSelection.value = false
+        })
+      }
+    })
+  } else {
+    // 收起时,保存当前选中状态
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      const currentSelection = tableRef.getSelectionRows()
+      const selectedIds = new Set(currentSelection.map((item: any) => item.id))
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: selectedIds
+      })
+    }
   }
+  
+  // 更新展开的行 key 列表
+  expandRowKeys.value = expandedRows.map((r) => r.id)
 }
 
 /** 处理行点击勾选 */
@@ -927,25 +981,11 @@ const handleRowClick = (row, _event, column) => {
   }
 }
 
-// 已展开行
+// 已展开行的 key 列表
 const expandRowKeys = ref<string[]>([])
-const expandRows = ref<any[]>([])
-const toggleExpand = async (row: any) => {
-  const key = row.id
-  // 第一次点击展开,第二次点击同一行收起
-  if (expandRowKeys.value.includes(key)) {
-    expandRowKeys.value = expandRowKeys.value.filter((key) => key !== key)
-    expandRows.value = expandRows.value.filter((row) => row.id !== key)
-    selectedDetailRows.value = selectedDetailRows.value.filter((row) => row.equipPipeId !== key)
-    mainTableRef.value.toggleRowExpansion(row, false)
-    mainTableRef.value.toggleRowSelection(row, false)
-  } else {
-    expandRowKeys.value.push(key)
-    expandRows.value.push(row)
-    mainTableRef.value.toggleRowExpansion(row, true)
-  }
+// 缓存展开行的子表数据和选中状态
+const expandedRowsCache = ref<Map<string, { pipes: any[], selectedIds: Set<string> }>>(new Map())
 
-}
 
 /** 表格选择框变化 */
 const handleSelectionChange = async (selection: any[]) => {
@@ -954,37 +994,62 @@ const handleSelectionChange = async (selection: any[]) => {
     item => !selection.includes(item)
   );
   selectedRows.value = selection
-  // 每一个勾选了主表的要找到对应的子表
-  for (const item of selection) {
-    // 如果没有子表,展开当前子表
-    if (!expandRowKeys.value.includes(item.id)) {
-      await toggleExpand(item)
+  
+  // 清除取消选中行的子表选择(包括展开的和收起的)
+  deselectedRows.forEach(item => {
+    const rowId = item.id
+    // 如果该行是展开状态,直接清除表格选中
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      tableRef.clearSelection()
     }
-    // 如果没有勾选,勾选子表
-    if (selectedDetailRows.value.findIndex(selectedDetailRow => selectedDetailRow.equipPipeId === item.id) === -1){
-      getDetailTableRef(item.id)?.toggleAllSelection(true)
+    
+    // 无论是否展开,都清除缓存中的选中状态
+    if (expandedRowsCache.value.has(rowId)) {
+      expandedRowsCache.value.get(rowId)!.selectedIds.clear()
     }
-  }
-  deselectedRows.forEach(item => {
-    getDetailTableRef(item.id)?.clearSelection()
+    
+    // 从全局选中的子表数据中移除
+    selectedDetailRows.value = selectedDetailRows.value.filter(
+      detailRow => detailRow.equipPipeId !== rowId
+    )
   })
 }
 
 /** 处理子表选择变化 */
 const handleDetailSelectionChange = (selection: any[], mainRow: any) => {
+  // 如果正在恢复选中状态,不更新缓存(避免展开时 toggleRowSelection 触发此函数覆盖缓存)
+  if (isRestoringSelection.value) {
+    return
+  }
+  
+  const rowId = mainRow.id
+  
+  // 更新缓存中的选中状态(如果缓存不存在则创建)
+  if (!expandedRowsCache.value.has(rowId)) {
+    // 如果该行还未展开,初始化缓存
+    expandedRowsCache.value.set(rowId, {
+      pipes: mainRow.pipes || [],
+      selectedIds: new Set<string>()
+    })
+  }
+  const cache = expandedRowsCache.value.get(rowId)!
+  cache.selectedIds = new Set(selection.map((item: any) => item.id))
+  
+  // 更新全局选中的子表数据
   selectedDetailRows.value = selectedDetailRows.value.filter((item) => item.equipPipeId !== mainRow.id)
   selectedDetailRows.value = [...selectedDetailRows.value.filter(row => !selection.some(sel => sel.id === row.id)), ...selection]
-  // tableRef.value.toggleRowSelection(mainRow, true)
+  
   // 如果子表有选中项,则自动选中主表行;否则取消选中主表行
   nextTick(() => {
     if (selection.length > 0) {
       // 子表有选中项,选中主表行
       mainTableRef.value.toggleRowSelection(mainRow, true);
-    }else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)){
+    } else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)) {
       const currentlySelected = selectedRows.value.some(row => row.id === mainRow.id);
       if (currentlySelected && selection.length === 0) {
         // 只有当主行当前是选中状态且子表没有任何选中项时才取消主行选择
-        mainTableRef.value.toggleRowSelection(mainRow,false);
+        mainTableRef.value.toggleRowSelection(mainRow, false);
       }
     }
   });

+ 12 - 10
yudao-ui-admin-vue3/src/views/pressure2/planNew/components/PlanScheduleEquipPipeDialog.vue

@@ -420,11 +420,12 @@ const formRules = {
   }],
   regularCheckTeamList: [{
     validator: (_rule, value, callback) => {
-      if (dialogVisible.value && !formData.value.regularNoSchedule && (!value || value.length === 0)) {
-        callback(new Error('请选择检验员'))
-      } else {
-        callback()
-      }
+      // if (dialogVisible.value && !formData.value.regularNoSchedule && (!value || value.length === 0)) {
+      //   callback(new Error('请选择检验员'))
+      // } else {
+      //   callback()
+      // }
+      callback()
     },
     trigger: 'change'
   }],
@@ -440,11 +441,12 @@ const formRules = {
   }],
   annualCheckTeamList: [{
     validator: (_rule, value, callback) => {
-      if (dialogVisible.value && !formData.value.annualNoSchedule && (!value || value.length === 0)) {
-        callback(new Error('请选择检验员'))
-      } else {
-        callback()
-      }
+      // if (dialogVisible.value && !formData.value.annualNoSchedule && (!value || value.length === 0)) {
+      //   callback(new Error('请选择检验员'))
+      // } else {
+      //   callback()
+      // }
+      callback()
     },
     trigger: 'change'
   }]

+ 115 - 37
yudao-ui-admin-vue3/src/views/pressure2/planNew/pipeDetail.vue

@@ -244,7 +244,7 @@
       </el-row>
       <el-table
         v-loading="loading"
-        ref="tableRef"
+        ref="mainTableRef"
         :data="list"
         border
         @selection-change="handleSelectionChange"
@@ -252,23 +252,26 @@
         :row-key="(row) => row.id"
         :row-class-name="getMainRowClassName"
         @row-click="handleMainRowClick"
+        @expand-change="handleExpandChange"
+        :expand-row-keys="expandRowKeys"
       >
         <el-table-column type="selection" width="50" fixed="left"/>
-        <el-table-column type="expand" width="1">
+        <el-table-column type="expand">
           <template #default="props">
             <div class="ml-15px mr-15px">
 
-              <el-table :data="props.row.detailSaveReqVOS" border
-                        :header-cell-style="{background: '#f5f7fa', color: '#606266'}"
-                        :ref="(el) => setDetailTableRef(el, props.row.id)"
-                        @selection-change="(selection) => handleDetailSelectionChange(selection, props.row)"
-                        :row-class-name="getRowClassName"
-                        class="inner-table"
-                        @row-click="handleRowClick"
+              <el-table
+                :data="props.row.pipes" border
+                :header-cell-style="{background: '#f5f7fa', color: '#606266'}"
+                :ref="(el) => setDetailTableRef(el, props.row.id)"
+                @selection-change="(selection) => handleDetailSelectionChange(selection, props.row)"
+                :row-class-name="getRowClassName"
+                class="inner-table"
+                @row-click="handleRowClick"
               >
                 <el-table-column type="selection" width="50" fixed="left"/>
                 <el-table-column type="index" label="序号" width="60" align="center" :index="indexMethod1" />
-                <el-table-column label="注册代码" prop="equipCode" min-width="120" show-overflow-tooltip/>
+                <el-table-column label="注册代码" prop="pipeRegCode" min-width="120" show-overflow-tooltip/>
                 <el-table-column label="管道名称" prop="pipeName" min-width="120" show-overflow-tooltip/>
                 <el-table-column label="管道编号" prop="pipeNo" min-width="120" show-overflow-tooltip/>
                 <el-table-column label="管道级别" prop="pipeLevel" min-width="120" show-overflow-tooltip/>
@@ -496,6 +499,7 @@ import PlanScheduleEquipPipeDialog
 import {EquipPipeSchedulingApi, PipePlanSchedulingVO} from "@/api/pressure2/pipescheduling";
 import PlanScheduleBoilerDialog
   from "@/views/pressure2/planNew/components/PlanScheduleEquipBoilerDialog.vue";
+import {ref} from "vue";
 
 const router = useRouter()
 
@@ -541,7 +545,7 @@ const equipTypeMap = {
   1: '容器'
 }
 
-const tableRef = ref()
+const mainTableRef = ref()
 
 // 拒绝原因
 const refuseInspectedCategoryOptions = getStrDictOptions('refuseInspectedCategory')
@@ -572,7 +576,9 @@ const containerTypeOptions = getStrDictOptions(DICT_TYPE.PIPE_TYPE)
 // 单条/批量排期相关
 const currentEquip = ref<PipeEquipmentVO>()
 const currentCheckType = ref<scheduleType>('')
-
+const expandedRowsCache = ref<Map<string, { pipes: any[], selectedIds: Set<string> }>>(new Map())
+// 标志位:防止展开时恢复选中状态触发 handleDetailSelectionChange 覆盖缓存
+const isRestoringSelection = ref(false)
 // 存储所有子表引用的对象
 const detailTableRefs = ref<Record<string, any>>({})
 const setDetailTableRef = (el: any, parentId: string) => {
@@ -585,7 +591,7 @@ const getDetailTableRef = (parentId: string) => {
 }
 
 const getMainRowClassName = ({row}) => {
-  const isSelected = tableRef.value?.getSelectionRows().some((item) =>
+  const isSelected = mainTableRef.value?.getSelectionRows().some((item) =>
     item.id === row.id
   )
   return isSelected ? 'selected-row' : ''
@@ -604,7 +610,7 @@ const getRowClassName = ({row}) => {
 }
 /** 处理行点击勾选 */
 const handleMainRowClick = (row) => {
-  tableRef.value?.toggleRowSelection(row)
+  mainTableRef.value?.toggleRowSelection(row)
 }
 
 const handleRowClick = (row, event, column) => {
@@ -619,6 +625,56 @@ const handleRowClick = (row, event, column) => {
   }
 }
 
+const handleExpandChange = (row, expandedRows: any[]) => {
+  const rowId = row.id
+  const isExpanded = expandedRows.some((r) => r.id === rowId)
+
+  if (isExpanded) {
+    // 展开时,从缓存中恢复或初始化
+    if (!expandedRowsCache.value.has(rowId)) {
+      // 首次展开,初始化缓存
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: new Set<string>()
+      })
+    }
+    // 恢复选中状态
+    nextTick(() => {
+      const cache = expandedRowsCache.value.get(rowId)
+      const tableRef = getDetailTableRef(rowId)
+      if (cache && tableRef && cache.pipes.length > 0) {
+        // 设置标志位,防止触发 handleDetailSelectionChange
+        isRestoringSelection.value = true
+
+        cache.pipes.forEach((pipe) => {
+          if (cache.selectedIds.has(pipe.id)) {
+            tableRef.toggleRowSelection(pipe, true)
+          }
+        })
+
+        // 恢复完成后,延迟重置标志位
+        nextTick(() => {
+          isRestoringSelection.value = false
+        })
+      }
+    })
+  } else {
+    // 收起时,保存当前选中状态
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      const currentSelection = tableRef.getSelectionRows()
+      const selectedIds = new Set(currentSelection.map((item: any) => item.id))
+      expandedRowsCache.value.set(rowId, {
+        pipes: row.pipes || [],
+        selectedIds: selectedIds
+      })
+    }
+  }
+
+  // 更新展开的行 key 列表
+  expandRowKeys.value = expandedRows.map((r) => r.id)
+}
+
 /** 打开弹窗 */
 const open = async (row: PlanNewPageVO) => {
   equipIds.value = [...new Set([row.equipIds, row.yearEquipIds, row.expiredEquipIds])]
@@ -699,9 +755,9 @@ const handleQuery = async () => {
   loading.value = true
   try {
     // 关闭所有已展开的行
-    expandRows.value.forEach(row => {
-      tableRef.value?.toggleRowExpansion(row, false)
-    })
+    // expandRows.value.forEach(row => {
+    //   tableRef.value?.toggleRowExpansion(row, false)
+    // })
     // 清空展开行记录
     expandRowKeys.value = []
     expandRows.value = []
@@ -739,45 +795,70 @@ const resetQuery = () => {
   handleQuery()
 }
 /** 处理子表选择变化 */
-const handleDetailSelectionChange = (selection: any[], mainRow: PipeEquipmentVO) => {
+const handleDetailSelectionChange = (selection: any[], mainRow: any) => {
+  // 如果正在恢复选中状态,不更新缓存(避免展开时 toggleRowSelection 触发此函数覆盖缓存)
+  if (isRestoringSelection.value) {
+    return
+  }
+
+  const rowId = mainRow.id
+
+  // 更新缓存中的选中状态(如果缓存不存在则创建)
+  if (!expandedRowsCache.value.has(rowId)) {
+    // 如果该行还未展开,初始化缓存
+    expandedRowsCache.value.set(rowId, {
+      pipes: mainRow.pipes || [],
+      selectedIds: new Set<string>()
+    })
+  }
+  const cache = expandedRowsCache.value.get(rowId)!
+  cache.selectedIds = new Set(selection.map((item: any) => item.id))
+
+  // 更新全局选中的子表数据
   selectedDetailRows.value = selectedDetailRows.value.filter((item) => item.equipPipeId !== mainRow.id)
   selectedDetailRows.value = [...selectedDetailRows.value.filter(row => !selection.some(sel => sel.id === row.id)), ...selection]
-  // tableRef.value.toggleRowSelection(mainRow, true)
+
   // 如果子表有选中项,则自动选中主表行;否则取消选中主表行
   nextTick(() => {
     if (selection.length > 0) {
       // 子表有选中项,选中主表行
-      tableRef.value.toggleRowSelection(mainRow, true);
-    }else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)){
+      mainTableRef.value.toggleRowSelection(mainRow, true);
+    } else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)) {
       const currentlySelected = selectedRows.value.some(row => row.id === mainRow.id);
       if (currentlySelected && selection.length === 0) {
         // 只有当主行当前是选中状态且子表没有任何选中项时才取消主行选择
-        tableRef.value.toggleRowSelection(mainRow,false);
+        mainTableRef.value.toggleRowSelection(mainRow, false);
       }
     }
   });
 }
 
 /** 表格选择框变化 */
-const handleSelectionChange = async (selection: PipeEquipmentVO[]) => {
+const handleSelectionChange = async (selection: any[]) => {
   // 计算取消选择的行
   const deselectedRows = selectedRows.value.filter(
     item => !selection.includes(item)
   );
   selectedRows.value = selection
-  // 每一个勾选了主表的要找到对应的子表
-  for (const item of selection) {
-    // 如果没有子表,展开当前子表
-    if (!expandRowKeys.value.includes(item.id)) {
-      await toggleExpand(item)
+
+  // 清除取消选中行的子表选择(包括展开的和收起的)
+  deselectedRows.forEach(item => {
+    const rowId = item.id
+    // 如果该行是展开状态,直接清除表格选中
+    const tableRef = getDetailTableRef(rowId)
+    if (tableRef) {
+      tableRef.clearSelection()
     }
-    // 如果没有勾选,勾选子表
-    if (selectedDetailRows.value.findIndex(selectedDetailRow => selectedDetailRow.equipPipeId === item.id) === -1){
-      getDetailTableRef(item.id)?.toggleAllSelection(true)
+
+    // 无论是否展开,都清除缓存中的选中状态
+    if (expandedRowsCache.value.has(rowId)) {
+      expandedRowsCache.value.get(rowId)!.selectedIds.clear()
     }
-  }
-  deselectedRows.forEach(item => {
-    getDetailTableRef(item.id)?.clearSelection()
+
+    // 从全局选中的子表数据中移除
+    selectedDetailRows.value = selectedDetailRows.value.filter(
+      detailRow => detailRow.equipPipeId !== rowId
+    )
   })
 }
 
@@ -995,9 +1076,6 @@ defineExpose({
     flex: 1;
   }
 }
-:deep(.el-table__expand-icon) {
-  display: none !important;
-}
 
 :deep(.inner-table .el-checkbox__input.is-checked .el-checkbox__inner),
 :deep(.inner-table .el-checkbox__input.is-indeterminate .el-checkbox__inner) {