Explorar o código

待约检设备调整

xy hai 2 días
pai
achega
0102e8332b

+ 90 - 0
yudao-ui-admin-vue3/src/views/pressure/equipmentAppointment/components/batchEditForm.vue

@@ -0,0 +1,90 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="130px"
+    >
+      <el-form-item label="约检联系人" prop="contact">
+        <el-input v-model.trim="formData.contact" :maxlength="50" placeholder="请输入约检联系人" />
+      </el-form-item>
+      <el-form-item label="约检联系人电话" prop="contactPhone">
+        <el-input v-model.trim="formData.contactPhone" placeholder="请输入约检联系人电话" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { reactive, ref } from 'vue'
+import { PlanSchedulingApi } from '@/api/pressure/planScheduling'
+import FetchApis from '../index.api'
+import { Dialog } from '@/components/Dialog'
+defineOptions({ name: 'BatchEditForm' })
+const dialogVisible = ref(false)
+const dialogTitle = ref<string>('批量修改约检联系人')
+const formLoading = ref(false)
+const formData = ref({
+  contact: '',
+  contactPhone: ''
+})
+const detailList = ref<Array<Record<string, any>>>([])
+const currentEquipMainType = ref<string>('boiler')
+const formRules = reactive({
+  contact: [
+    { required: true, message: '约检联系人不能为空', trigger: 'blur' },
+    { max: 50, message: '长度在50个字符以内', trigger: 'blur' }
+  ],
+  contactPhone: [{ required: true, message: '约检联系人电话不能为空', trigger: 'blur' }]
+})
+const formRef = ref()
+
+const open = async (list: Array<Record<string, any>>, equipMainType?: string) => {
+  dialogVisible.value = true
+  detailList.value = list
+  if (equipMainType) {
+    currentEquipMainType.value = equipMainType
+  }
+  resetForm()
+}
+defineExpose({ open })
+
+const emit = defineEmits(['success'])
+const submitForm = async () => {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  formLoading.value = true
+  try {
+    const data = {
+      ids: detailList.value.map((item) => item.id),
+      contact: formData.value.contact,
+      contactPhone: formData.value.contactPhone
+    }
+    if (currentEquipMainType.value === 'boiler' || currentEquipMainType.value === 'pipe') {
+      await FetchApis.updateContactBatch(data, currentEquipMainType.value)
+    } else {
+      await PlanSchedulingApi.contactBatchUpdateApi(data)
+    }
+    ElMessage.success('批量修改约检联系人成功')
+    dialogVisible.value = false
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+const resetForm = () => {
+  formData.value = {
+    contact: '',
+    contactPhone: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 15 - 1
yudao-ui-admin-vue3/src/views/pressure/equipmentAppointment/index.api.ts

@@ -16,7 +16,21 @@ export default {
   // 容器导出Excel(pressure模块)
   downloadExportEquipsExcelApi: async (data) => {
     const timeout = 1000 * 60 * 5 // 5min
-    return await request.download2({ url: `/pressure/equip-container/pending-inspection-export-excel`, data, timeout })
+    return await request.download2({ url: `/pressure2/plan-scheduling/pending-inspection-export-excel`, data, timeout })
+  },
+  updatePendingInspectionRemark: (data: any, equipMainType: string) => {
+    if (equipMainType === 'boiler' || equipMainType === 'pipe') {
+      const typeCode = equipMainType === 'boiler' ? 200 : 300
+      return request.put({ url: `/pressure2/plan-scheduling/update-pending-inspection-remark`, params: { equipMainType: typeCode }, data })
+    }
+    // return request.put({ url: '/pressure/equip-container/update-pending-inspection-remark', data })
+  },
+  updateContactBatch: (data: any, equipMainType: string) => {
+    if (equipMainType === 'boiler' || equipMainType === 'pipe') {
+      const typeCode = equipMainType === 'boiler' ? 200 : 300
+      return request.put({ url: `/pressure2/plan-scheduling/contact/batch-update`, params: { equipMainType: typeCode }, data })
+    }
+    // return request.put({ url: '/pressure/equip-container/equip-container/contact/batch-update', data })
   },
 }
 

+ 504 - 56
yudao-ui-admin-vue3/src/views/pressure/equipmentAppointment/index.vue

@@ -10,27 +10,27 @@
             <el-option label="管道" value="pipe" />
           </el-select>
         </el-form-item>
-        
+
         <!-- 使用单位 -->
         <el-form-item label="使用单位">
           <el-input v-model="searchFormDataTable.unitName" placeholder="请输入使用单位" clearable />
         </el-form-item>
-        
+
         <!-- 设备代码/工程号 -->
         <el-form-item :label="selectedEquipMainType === 'boiler' ? '设备注册代码' : '工程号'">
           <el-input v-model="searchFormDataTable.equipCode" placeholder="请输入" clearable />
         </el-form-item>
-        
+
         <!-- 区域 -->
         <el-form-item label="区域">
           <AreaSelect v-model="searchFormDataTable.equipDistrict" />
         </el-form-item>
-        
+
         <!-- 联系电话 -->
         <el-form-item label="联系电话">
           <el-input v-model="searchFormDataTable.contactPhone" placeholder="请输入联系电话" clearable />
         </el-form-item>
-        
+
         <!-- 操作按钮 -->
         <el-form-item>
           <el-button type="primary" @click="getListPageFn">
@@ -44,7 +44,7 @@
         </el-form-item>
       </el-form>
     </div>
-    
+
     <!-- 工具栏区域 -->
     <div class="toolbar">
       <div class="toolbar-left">
@@ -68,13 +68,22 @@
         </el-radio-group>
       </div>
       <div class="toolbar-right">
+        <el-button type="primary" :disabled="!canBatchAppointment()" @click="handleBatchAppointment">
+          批量约检
+        </el-button>
+        <el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchEditContact">
+          修改约检联系人
+        </el-button>
+        <el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchRemark">
+          批量添加备注
+        </el-button>
         <el-button type="primary" @click="handleExportFn">
           <el-icon><Download /></el-icon>
           导出Excel
         </el-button>
       </div>
     </div>
-    
+
     <!-- 设备类型筛选区域 -->
     <div class="equip-type-filter" v-if="selectedEquipMainType">
       <span class="label-text">{{ selectedEquipMainType === 'boiler' ? '锅炉归类' : '管道类别' }}:</span>
@@ -88,7 +97,7 @@
         </el-checkbox>
       </el-checkbox-group>
     </div>
-    
+
     <!-- 表格区域 -->
     <div class="table-container">
       <el-table
@@ -100,17 +109,20 @@
         @selection-change="handleSelectionChange"
       >
         <el-table-column type="selection" width="55" />
-        <el-table-column label="操作" width="80" fixed="left">
+        <el-table-column label="操作" width="120" fixed="left">
           <template #default="scope">
-            <el-button type="primary" link @click="handleAppointment(scope.row)">
+            <el-button v-if="canAppointment(scope.row)" type="primary" link @click="handleAppointment(scope.row)">
               约检
             </el-button>
+            <el-button type="primary" link @click="openRemarkDialog(scope.row)">
+              备注
+            </el-button>
           </template>
         </el-table-column>
-        
+
         <!-- 使用单位 -->
         <el-table-column label="使用单位" prop="unitName" min-width="150" show-overflow-tooltip />
-        
+
         <!-- 锅炉/管道特有列 -->
         <template v-if="selectedEquipMainType === 'boiler'">
           <el-table-column label="设备注册代码" prop="equipCode" min-width="180" show-overflow-tooltip>
@@ -124,14 +136,14 @@
           <el-table-column label="设备分类" prop="equipTypeName" min-width="120" show-overflow-tooltip />
           <el-table-column label="设备名称" prop="equipName" min-width="150" show-overflow-tooltip />
         </template>
-        
+
         <template v-if="selectedEquipMainType === 'pipe'">
           <el-table-column label="工程号" prop="projectNo" min-width="150" show-overflow-tooltip />
           <el-table-column label="使用证编号" prop="useRegisterNo" min-width="150" show-overflow-tooltip />
           <el-table-column label="设备分类" prop="equipTypeName" min-width="120" show-overflow-tooltip />
           <el-table-column label="工程名称" prop="projectName" min-width="180" show-overflow-tooltip />
         </template>
-        
+
         <!-- 通用列 -->
         <el-table-column label="区域" min-width="100" show-overflow-tooltip>
           <template #default="scope">
@@ -146,11 +158,72 @@
         <el-table-column label="联系人" prop="contact" min-width="100" show-overflow-tooltip />
         <el-table-column label="联系电话" prop="contactPhone" min-width="120" show-overflow-tooltip />
         <el-table-column label="检验性质" prop="checkNature" min-width="100" show-overflow-tooltip />
-        <el-table-column label="下次检验日期" prop="nextCheckDate" min-width="120" show-overflow-tooltip />
+
+        <!-- 下次检验日期(带颜色和拒检提示) -->
+        <el-table-column label="下次检验日期" min-width="140" show-overflow-tooltip>
+          <template #default="scope">
+            <el-popover
+              v-if="scope.row.isRefused && getRefuseTip(scope.row).length > 0"
+              placement="top"
+              :width="250"
+              trigger="hover"
+            >
+              <template #reference>
+                <div :style="getCellStyle(scope.row)">
+                  <span>{{ scope.row.nextCheckDate || '-' }}</span>
+                  <span v-if="scope.row.isExceedTimeLimit && showExceedTag" class="exceed-tag">超</span>
+                </div>
+              </template>
+              <template #default>
+                <div v-for="(line, idx) in getRefuseTip(scope.row)" :key="idx">{{ line }}</div>
+              </template>
+            </el-popover>
+            <el-popover
+              v-else-if="scope.row.hasPendingAppointment"
+              placement="top"
+              :width="200"
+              trigger="hover"
+            >
+              <template #reference>
+                <div :style="getCellStyle(scope.row)">
+                  <span>{{ scope.row.nextCheckDate || '-' }}</span>
+                  <span v-if="scope.row.isExceedTimeLimit && showExceedTag" class="exceed-tag">超</span>
+                </div>
+              </template>
+              <template #default>
+                <div>待约检</div>
+                <div>检验时间:{{ scope.row.appointmentDate || '-' }}</div>
+              </template>
+            </el-popover>
+            <el-popover
+              v-else-if="isRowScheduled(scope.row)"
+              placement="top"
+              :width="200"
+              trigger="hover"
+            >
+              <template #reference>
+                <div :style="getCellStyle(scope.row)">
+                  <span>{{ scope.row.nextCheckDate || '-' }}</span>
+                  <span v-if="scope.row.isExceedTimeLimit && showExceedTag" class="exceed-tag">超</span>
+                </div>
+              </template>
+              <template #default>
+                <div>已排期</div>
+                <div>排期时间:{{ getScheduledDate(scope.row) || '-' }}</div>
+              </template>
+            </el-popover>
+            <div v-else :style="getCellStyle(scope.row)">
+              <span>{{ scope.row.nextCheckDate || '-' }}</span>
+              <span v-if="scope.row.isExceedTimeLimit && showExceedTag" class="exceed-tag">超</span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="备注" prop="pendingInspectionRemark" min-width="120" show-overflow-tooltip />
         <el-table-column label="部门" prop="deptName" min-width="120" show-overflow-tooltip />
       </el-table>
     </div>
-    
+
     <!-- 分页区域 -->
     <div class="pagination-container">
       <el-pagination
@@ -164,20 +237,54 @@
       />
     </div>
   </div>
-  
+
   <!-- 设备详情弹窗 -->
   <EquipContainerForm ref="equipContainerFormRef" />
   <EquipBoilerForm ref="equipBoilerFormRef" />
   <EquipPipeForm ref="equipPipeFormRef" />
+
+  <!-- 备注弹窗 -->
+  <el-dialog
+    v-model="remarkDialogVisible"
+    :title="Array.isArray(currentRemarkRow) ? '批量添加备注' : '备注'"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-form :model="remarkForm" label-width="80px">
+      <el-form-item label="备注内容">
+        <el-input
+          v-model="remarkForm.pendingInspectionRemark"
+          type="textarea"
+          :rows="4"
+          :placeholder="Array.isArray(currentRemarkRow) ? `已选择 ${currentRemarkRow.length} 台设备,请输入备注内容` : '请输入备注内容'"
+          maxlength="300"
+          show-word-limit
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button :loading="remarkDialogLoading" @click="handleRemarkCancel">取消</el-button>
+        <el-button type="primary" :loading="remarkDialogLoading" @click="handleRemarkConfirm">
+          确定
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+
+  <!-- 批量修改约检联系人弹窗 -->
+  <BatchEditForm ref="batchEditFormRef" @success="handleBatchEditFormSuccess" />
 </template>
 
 <script lang="ts" setup name="EquipmentAppointment">
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, onUnmounted, computed, nextTick, watch } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Search, Refresh, Download } from '@element-plus/icons-vue'
 import EquipContainerForm from '@/components/EquipContainerForm/index.vue'
 import EquipBoilerForm from '@/components/EquipBoilerForm/index.vue'
 import EquipPipeForm from '@/components/EquipPipeForm/index.vue'
+import BatchEditForm from './components/batchEditForm.vue'
 import AreaSelect from '@/views/system/equipcontainer/components/AreaSelect.vue'
 import { getStrDictOptions, getDictLabel, DICT_TYPE } from '@/utils/dict'
 import FetchApis from './index.api'
@@ -196,10 +303,11 @@ const equipContainerFormRef = ref()
 const equipBoilerFormRef = ref()
 const equipPipeFormRef = ref()
 const { addPlanNewData } = usePlanNewStore()
+const { setPageParams, getPageParams, deletePageParams } = usePageParamsStore('qtyj')
 const router = useRouter()
 
 // 设备类型
-const selectedEquipMainType = ref('boiler') // 默认锅炉
+const selectedEquipMainType = ref('boiler')
 
 // 初始化默认日期值
 const getDefaultDate = () => {
@@ -211,7 +319,7 @@ const getDefaultDate = () => {
 const searchFormDataTable = ref({
   deptId: userStore?.user?.deptId,
   checkDate: getDefaultDate(),
-  equipMainType: 'boiler', // 默认锅炉
+  equipMainType: 'boiler',
   unitName: '',
   equipCode: '',
   equipDistrict: '',
@@ -237,6 +345,145 @@ const pageSize = ref(10)
 const total = ref(0)
 const loading = ref(false)
 
+// 备注弹窗相关
+const remarkDialogVisible = ref(false)
+const remarkDialogLoading = ref(false)
+const remarkForm = ref({
+  pendingInspectionRemark: ''
+})
+const currentRemarkRow = ref<Record<string, any> | Array<Record<string, any>> | null>(null)
+
+// 拒检原因字典映射
+const refuseReasonDictMap: Record<number, string> = {
+  1: '客户准备注销',
+  2: '客户准备报停',
+  3: '无法联系用户',
+  4: '数据有误',
+  5: '其他'
+}
+
+// 年检处理状态字典映射
+const yearCheckProcessStatusMap: Record<number, string> = {
+  0: '待处理',
+  1: '无需处理',
+  2: '拒绝年检'
+}
+
+// 定检拒绝状态字典映射
+const checkRefuseStatusMap: Record<number, string> = {
+  1: '待处理',
+  2: '无需处理',
+  3: '审核中',
+  4: '审核已拒绝',
+  5: '已作废',
+  6: '待上报',
+  7: '已上报'
+}
+
+// 是否显示"超"字标签(当前 tab 为定检/超年限检验时显示)
+const showExceedTag = computed(() => {
+  if (selectedEquipMainType.value === 'boiler') return checkType.value === 100 || checkType.value === 300
+  if (selectedEquipMainType.value === 'pipe') return checkType.value === 100
+  return false
+})
+
+const getRefuseTip = (row: Record<string, any>) => {
+  if (!row) return []
+  const checkTypeVal = row.checkType
+  if (checkTypeVal === 200) {
+    const { processStatus, refuseYearItemDict, refuseYearItemReason } = row
+    if (processStatus !== undefined && processStatus !== null) {
+      const statusText = yearCheckProcessStatusMap[processStatus] || ''
+      let reasonText = ''
+      if (refuseYearItemDict) {
+        const reasonCategory = refuseReasonDictMap[refuseYearItemDict] || ''
+        if (refuseYearItemDict == 5 && refuseYearItemReason) {
+          reasonText = `${reasonCategory}(${refuseYearItemReason})`
+        } else if (refuseYearItemReason) {
+          reasonText = refuseYearItemReason
+        } else if (reasonCategory) {
+          reasonText = reasonCategory
+        }
+      } else if (refuseYearItemReason) {
+        reasonText = refuseYearItemReason
+      }
+      const lines: string[] = []
+      if (statusText) lines.push(`拒绝年检状态:${statusText}`)
+      if (reasonText) lines.push(`拒绝年检原因:${reasonText}`)
+      return lines
+    }
+  } else {
+    const { refuseCheckStatus, reasonDict, reason } = row
+    if (refuseCheckStatus !== undefined && refuseCheckStatus !== null) {
+      const statusText = checkRefuseStatusMap[refuseCheckStatus] || ''
+      let reasonText = ''
+      if (reasonDict) {
+        const reasonCategory = refuseReasonDictMap[reasonDict] || ''
+        if (reasonDict == 5 && reason) {
+          reasonText = `${reasonCategory}(${reason})`
+        } else if (reason) {
+          reasonText = reason
+        } else if (reasonCategory) {
+          reasonText = reasonCategory
+        }
+      } else if (reason) {
+        reasonText = reason
+      }
+      const lines: string[] = []
+      if (statusText) lines.push(`拒绝定检状态:${statusText}`)
+      if (reasonText) lines.push(`拒绝定检原因:${reasonText}`)
+      return lines
+    }
+  }
+  return []
+}
+
+const getCellStyle = (row: Record<string, any>) => {
+  const baseStyle: Record<string, any> = {
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: '4px',
+    width: '100%',
+    height: '100%',
+    padding: '0',
+    margin: '0',
+    minHeight: '40px'
+  }
+  if (row.isRefused) {
+    baseStyle.backgroundColor = '#ff3d33'
+    baseStyle.color = 'white'
+  } else if (row.hasPendingAppointment) {
+    baseStyle.backgroundColor = '#54aeea'
+    baseStyle.color = 'white'
+  } else if (isRowScheduled(row)) {
+    baseStyle.backgroundColor = '#ffd89b'
+    baseStyle.color = '#333'
+  }
+  return baseStyle
+}
+
+const isRowScheduled = (row: Record<string, any>) => {
+  return (row.checkType == 200 && row.yearSchedulingPlanDate) ||
+         (row.checkType == 100 && row.schedulingPlanDate) ||
+         (row.checkType == 300 && row.expiredSchedulingPlanDate)
+}
+
+const getScheduledDate = (row: Record<string, any>) => {
+  if (row.checkType == 200) return row.yearSchedulingPlanDate
+  if (row.checkType == 100) return row.schedulingPlanDate
+  if (row.checkType == 300) return row.expiredSchedulingPlanDate
+  return null
+}
+
+const canAppointment = (row: Record<string, any>) => {
+  if (row.hasPendingAppointment === true) return false
+  if (row.checkType == 200 && row.yearSchedulingPlanDate) return false
+  if (row.checkType == 100 && row.schedulingPlanDate) return false
+  if (row.checkType == 300 && row.expiredSchedulingPlanDate) return false
+  return true
+}
+
 // 设备类型变化处理
 const handleEquipTypeChange = (value: string) => {
   selectedEquipMainType.value = value
@@ -246,7 +493,7 @@ const handleEquipTypeChange = (value: string) => {
   getListPageFn()
 }
 
-// 构建通用请求参数(匹配后端 PendingInspectionEquipReqVO)
+// 构建通用请求参数
 const buildCommonRequestParams = (baseParams: any) => {
   const data: any = {
     ...cloneDeep(baseParams),
@@ -257,14 +504,12 @@ const buildCommonRequestParams = (baseParams: any) => {
     pageSize: pageSize.value
   }
 
-  // 清理空值
   for (const key in data) {
     if (isEmpty(data[key])) {
       delete data[key]
     }
   }
 
-  // 处理检验日期范围:checkDate 数组 → checkDateStart / checkDateEnd
   if (data?.checkDate) {
     if (Array.isArray(data.checkDate) && data.checkDate.length === 2) {
       data.checkDateStart = data.checkDate[0]
@@ -273,7 +518,6 @@ const buildCommonRequestParams = (baseParams: any) => {
     delete data.checkDate
   }
 
-  // 处理拒检设备筛选:'flag'→不筛选, 'true'/'false'→布尔
   if (data.isRefused === 'flag') {
     delete data.isRefused
   } else if (data.isRefused !== undefined) {
@@ -286,11 +530,9 @@ const buildCommonRequestParams = (baseParams: any) => {
 // 将后端返回的设备数据映射为表格列所需的字段名
 const mapEquipDataForTable = (item: any) => {
   const mapped = { ...item }
-  // 检验性质:后端直接返回 checkTypeStr
   if (mapped.checkTypeStr !== undefined && mapped.checkTypeStr !== null && mapped.checkTypeStr !== '') {
     mapped.checkNature = mapped.checkTypeStr
   }
-  // 设备分类:后端 equipType(dict ID) → 前端 equipTypeName(dict label)
   if (mapped.equipType !== undefined && mapped.equipType !== null) {
     if (selectedEquipMainType.value === 'boiler') {
       mapped.equipTypeName = getDictLabel(DICT_TYPE.SYSTEM_EQUIP_BOILER_TYPE, mapped.equipType)
@@ -298,7 +540,6 @@ const mapEquipDataForTable = (item: any) => {
       mapped.equipTypeName = getDictLabel(DICT_TYPE.PIPE_TYPE, mapped.equipType)
     }
   }
-  // 管道特殊映射:后端 equipCode → 前端 projectNo,equipName → projectName
   if (selectedEquipMainType.value === 'pipe') {
     mapped.projectNo = mapped.equipCode
     mapped.projectName = mapped.equipName
@@ -314,19 +555,15 @@ const getListPageFn = async () => {
     let result
 
     if (equipMainType === 'container') {
-      // 容器使用 pressure 模块的 POST 接口
       result = await FetchApis.getContainerPage(data)
     } else if (equipMainType === 'boiler') {
-      // 锅炉使用 pressure2 模块的 GET 接口
       result = await FetchApis.getBoilerPage(data)
     } else if (equipMainType === 'pipe') {
-      // 管道使用 pressure2 模块的 GET 接口
       result = await FetchApis.getPipePage(data)
     }
 
     if (result) {
       const list = result.list || []
-      // 将后端字段映射为前端表格列所需的字段名
       tableData.value = list.map(mapEquipDataForTable)
       total.value = result.total || 0
     }
@@ -355,16 +592,10 @@ const handleReset = () => {
 }
 
 const handleExportFn = async () => {
-  const equipMainType = selectedEquipMainType.value
-  if (equipMainType !== 'container') {
-    ElMessage.warning('锅炉和管道的导出功能暂未开放,请使用容器设备导出')
-    return
-  }
   loading.value = true
   try {
     const data = buildCommonRequestParams(searchFormDataTable.value)
     data.ids = selectedRows.value.map((item: any) => item.id)
-    // 导出时如果没有选中行,则导出全部
     if (!selectedRows.value.length) {
       data.pageSize = total.value
     }
@@ -390,11 +621,9 @@ const handleExportFn = async () => {
 }
 
 const handleAppointment = (row: any) => {
-  const { deletePageParams } = usePageParamsStore('qtyj')
   deletePageParams()
 
   if (selectedEquipMainType.value === 'container') {
-    // 容器使用原来的压力模块
     const routerValue = {
       name: 'PlanNewDetail',
       path: '/planNew/detail'
@@ -403,32 +632,152 @@ const handleAppointment = (row: any) => {
     addPlanNewData(row)
     router.push({
       name: 'PlanNewDetail',
-      query: {
-        id: row.id || row.taskId,
-        equipCode: row.equipCode
-      }
+      query: { id: row.id, equipCode: row.equipCode, t: Date.now() }
     })
   } else if (selectedEquipMainType.value === 'boiler') {
-    // 锅炉使用pressure2模块的锅炉详情页
+    const routerValue = {
+      name: 'BoilerDetail',
+      path: '/planNew/boilerDetail'
+    }
+    tagsViewStore.delVisitedNameView(routerValue)
+    addPlanNewData(row)
     router.push({
       path: '/ywgl/planNew/boilerDetail',
-      query: {
-        id: row.id || row.taskId,
-        equipCode: row.equipCode
-      }
+      query: { id: row.id, equipCode: row.equipCode, t: Date.now() }
     })
   } else if (selectedEquipMainType.value === 'pipe') {
-    // 管道使用pressure2模块的管道详情页
+    const routerValue = {
+      name: 'PipeDetail',
+      path: '/planNew/pipeDetail'
+    }
+    tagsViewStore.delVisitedNameView(routerValue)
+    addPlanNewData(row)
     router.push({
       path: '/ywgl/planNew/pipeDetail',
-      query: {
-        id: row.id || row.taskId,
-        equipCode: row.equipCode
-      }
+      query: { id: row.id, equipCode: row.equipCode, t: Date.now() }
+    })
+  }
+}
+
+// 批量约检
+const canBatchAppointment = () => {
+  const selectionRows = selectedRows.value
+  if (selectionRows.length === 0) return false
+  const firstUnitId = selectionRows[0].unitId
+  if (!selectionRows.every((row: any) => row.unitId === firstUnitId)) return false
+  return selectionRows.every((row: any) => canAppointment(row))
+}
+
+const handleBatchAppointment = () => {
+  const selectionRows = selectedRows.value
+  if (selectionRows.length === 0) {
+    ElMessage.warning('请先选择设备')
+    return
+  }
+  const firstUnitId = selectionRows[0].unitId
+  if (!selectionRows.every((row: any) => row.unitId === firstUnitId)) {
+    ElMessage.warning('请选择同一使用单位的设备')
+    return
+  }
+  const cannotRows = selectionRows.filter((row: any) => !canAppointment(row))
+  if (cannotRows.length > 0) {
+    ElMessage.warning('所选设备中存在不符合约检条件的设备,无法批量约检')
+    return
+  }
+
+  const equipCodes = selectionRows.map((row: any) => row.equipCode).filter(Boolean)
+  if (equipCodes.length === 0) {
+    ElMessage.warning('选中的设备没有设备注册代码')
+    return
+  }
+
+  const firstRow = selectionRows[0]
+  deletePageParams()
+  addPlanNewData(firstRow)
+
+  if (selectedEquipMainType.value === 'container') {
+    const routerValue = { name: 'PlanNewDetail', path: '/planNew/detail' }
+    tagsViewStore.delVisitedNameView(routerValue)
+    router.push({
+      name: 'PlanNewDetail',
+      query: { id: firstRow.id, equipCode: firstRow.equipCode, t: Date.now() }
     })
+  } else if (selectedEquipMainType.value === 'boiler') {
+    router.push({
+      path: '/ywgl/planNew/boilerDetail',
+      query: { id: firstRow.id, equipCode: firstRow.equipCode, t: Date.now() }
+    })
+  } else if (selectedEquipMainType.value === 'pipe') {
+    router.push({
+      path: '/ywgl/planNew/pipeDetail',
+      query: { id: firstRow.id, equipCode: firstRow.equipCode, t: Date.now() }
+    })
+  }
+}
+
+// 批量修改约检联系人
+const batchEditFormRef = ref()
+const handleBatchEditContact = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请先选择设备')
+    return
+  }
+  batchEditFormRef.value?.open(selectedRows.value, selectedEquipMainType.value)
+}
+const handleBatchEditFormSuccess = () => {
+  getListPageFn()
+}
+
+// 备注弹窗
+const openRemarkDialog = (row: Record<string, any>) => {
+  currentRemarkRow.value = row
+  remarkForm.value.pendingInspectionRemark = row.pendingInspectionRemark || ''
+  remarkDialogVisible.value = true
+}
+
+const handleBatchRemark = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请先选择设备')
+    return
+  }
+  currentRemarkRow.value = selectedRows.value
+  remarkForm.value.pendingInspectionRemark = ''
+  remarkDialogVisible.value = true
+}
+
+const handleRemarkConfirm = async () => {
+  if (!currentRemarkRow.value) return
+  remarkDialogLoading.value = true
+  try {
+    const isBatch = Array.isArray(currentRemarkRow.value)
+    const ids = isBatch
+      ? (currentRemarkRow.value as Record<string, any>[]).map((row: any) => row.id)
+      : [(currentRemarkRow.value as Record<string, any>).id]
+    await FetchApis.updatePendingInspectionRemark({
+      ids,
+      pendingInspectionRemark: remarkForm.value.pendingInspectionRemark
+    }, selectedEquipMainType.value)
+    ElMessage.success(isBatch ? '批量备注保存成功' : '备注保存成功')
+    remarkDialogVisible.value = false
+    getListPageFn()
+  } catch (error) {
+    console.error('保存备注失败:', error)
+  } finally {
+    remarkDialogLoading.value = false
   }
 }
 
+const handleRemarkCancel = () => {
+  remarkDialogLoading.value = true
+  setTimeout(() => {
+    remarkDialogVisible.value = false
+    remarkDialogLoading.value = false
+    remarkForm.value.pendingInspectionRemark = ''
+    currentRemarkRow.value = null
+  }, 300)
+}
+
+// 设备详情弹窗
 const handleEquipCodeFn = (id: string) => {
   if (selectedEquipMainType.value === 'container') {
     equipContainerFormRef.value?.open(id)
@@ -461,9 +810,95 @@ const handleQuery = () => {
 const selectedRows = ref([])
 const checkType = ref(100)
 
+// 监听搜索表单 checkDate 变化
+watch(
+  () => searchFormDataTable.value.checkDate,
+  (newVal) => {
+    if (isResetting.value) return
+    if (newVal && Array.isArray(newVal) && newVal.length === 2) {
+      savedCheckDate.value = newVal
+    }
+  },
+  { deep: true }
+)
+
 onMounted(() => {
+  const route = router.currentRoute.value
+  const query = route.query || {}
+
+  // 优先从 store 恢复搜索条件(切换 tab 回来时)
+  const savedParams = getPageParams()
+  if (savedParams) {
+    if (savedParams.selectedEquipMainType) {
+      selectedEquipMainType.value = savedParams.selectedEquipMainType
+      searchFormDataTable.value.equipMainType = savedParams.selectedEquipMainType
+    }
+    if (savedParams.checkType !== undefined) {
+      checkType.value = savedParams.checkType
+    }
+    if (savedParams.searchFormDataTable) {
+      Object.assign(searchFormDataTable.value, savedParams.searchFormDataTable)
+    }
+    if (savedParams.searchFormData) {
+      Object.assign(searchFormData.value, savedParams.searchFormData)
+    }
+    if (savedParams.pageNo) pageNo.value = savedParams.pageNo
+    if (savedParams.pageSize) pageSize.value = savedParams.pageSize
+    getListPageFn()
+    return
+  }
+
+  // 其次从路由 query 恢复(外部跳转进来时)
+  if (query.isClaim !== undefined) {
+    // 兼容 isClaim 参数
+  }
+  if (query.equipMainType !== undefined) {
+    const type = String(query.equipMainType)
+    if (['boiler', 'pipe', 'container'].includes(type)) {
+      selectedEquipMainType.value = type
+      searchFormDataTable.value.equipMainType = type
+    }
+  }
+  if (query.deptId !== undefined && query.deptId !== null && query.deptId !== '') {
+    searchFormDataTable.value.deptId = String(query.deptId)
+  }
+  if (query.equipDistrict !== undefined && query.equipDistrict !== null && query.equipDistrict !== '') {
+    searchFormDataTable.value.equipDistrict = String(query.equipDistrict)
+  }
+  if (query.unitName !== undefined && query.unitName !== null && query.unitName !== '') {
+    searchFormDataTable.value.unitName = String(query.unitName)
+  }
+  if (query.contactPhone !== undefined && query.contactPhone !== null && query.contactPhone !== '') {
+    searchFormDataTable.value.contactPhone = String(query.contactPhone)
+  }
+  if (query.equipCode !== undefined && query.equipCode !== null && query.equipCode !== '') {
+    searchFormDataTable.value.equipCode = String(query.equipCode)
+  }
+  if (query.checkType !== undefined && query.checkType !== null) {
+    checkType.value = Number(query.checkType)
+  }
+  if (query.isRefused !== undefined && query.isRefused !== null) {
+    searchFormData.value.isRefused = String(query.isRefused)
+  }
+  if (query.checkDateStart !== undefined && query.checkDateEnd !== undefined) {
+    searchFormDataTable.value.checkDate = [String(query.checkDateStart), String(query.checkDateEnd)]
+    savedCheckDate.value = [String(query.checkDateStart), String(query.checkDateEnd)]
+  }
+
   getListPageFn()
 })
+
+onUnmounted(() => {
+  // 保存当前搜索条件,切换 tab 回来时恢复
+  setPageParams({
+    selectedEquipMainType: selectedEquipMainType.value,
+    checkType: checkType.value,
+    pageNo: pageNo.value,
+    pageSize: pageSize.value,
+    searchFormDataTable: { ...searchFormDataTable.value },
+    searchFormData: { ...searchFormData.value }
+  })
+})
 </script>
 
 <style scoped lang="scss">
@@ -486,18 +921,19 @@ onMounted(() => {
   align-items: center;
   padding: 12px 0;
   margin-bottom: 16px;
-  
+
   .toolbar-left {
     display: flex;
     align-items: center;
     gap: 16px;
     flex-wrap: wrap;
   }
-  
+
   .toolbar-right {
     display: flex;
     align-items: center;
     gap: 12px;
+    flex-wrap: wrap;
   }
 }
 
@@ -507,7 +943,7 @@ onMounted(() => {
   margin-bottom: 16px;
   flex-wrap: wrap;
   gap: 12px;
-  
+
   .label-text {
     font-size: 14px;
     color: #606266;
@@ -525,4 +961,16 @@ onMounted(() => {
   justify-content: flex-end;
   padding: 12px 0;
 }
+
+.exceed-tag {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  background-color: red;
+  color: white;
+  padding: 2px 6px;
+  border-radius: 2px;
+  font-size: 12px;
+  line-height: 1;
+}
 </style>