Sfoglia il codice sorgente

Merge branch 'stable' of http://39.98.153.250:9080/yudao/yudao-admin-yiqun into stable

yangguanjin 1 settimana fa
parent
commit
47f3e9aa43

+ 41 - 0
yudao-ui-admin-vue3/src/api/pressure2/boilertaskorder/index.ts

@@ -353,6 +353,14 @@ export const BoilerTaskOrderApi = {
   addMajorIssues: async (data) => {
     return await request.post({url: `/pressure2/boiler-task-order/order-item/addMajorIssues`, data})
   },
+  // 任务单详情:从已有指导书引用创建新指导书
+  addReportFromExisting: async (data) => {
+    return await request.post({url: `/pressure2/boiler-task-order/order-item/add-report-from-existing`, data})
+  },
+  // 获取设备上次任务单的指导书列表
+  getHistoryInstructions: async (equipId: string, currentOrderItemId?: string) => {
+    return await request.get({url: `/pressure2/boiler-task-order/order-item/history-instructions`, params: { equipId, currentOrderItemId }})
+  },
   // 检验意见通知书: 发起审核
   submitOpinionNoticeApproval: async (data) => {
     return await request.put({url: `/pressure2/boiler-task-order/order-item/initiateApproval`, data})
@@ -519,6 +527,34 @@ export const BoilerTaskOrderApi = {
     return await request.put({ url: `/pressure2/boiler-task-order/order-item/main-checker`, data })
   },
 
+  // 获取任务单设备部件分页列表
+  getTaskOrderItemPartPage: async (params: any) => {
+    return await request.get({ url: `/pressure2/boiler-task-order-item-part/page`, params })
+  },
+
+  // 获取任务单设备关联报告分页列表
+  getTaskOrderItemReportPage: async (params: any) => {
+    return await request.get({ url: `/pressure2/boiler-task-order-item-report/page`, params })
+  },
+
+  // ============ 部件-操作指导书配置 ============
+  // 部件-指导书配置分页
+  getPartInstructionConfigPage: async (params: any) => {
+    return await request.get({ url: `/pressure2/boiler-part-instruction-config/page`, params })
+  },
+  // 新增部件-指导书配置
+  createPartInstructionConfig: async (data: any) => {
+    return await request.post({ url: `/pressure2/boiler-part-instruction-config/create`, data })
+  },
+  // 删除部件-指导书配置
+  deletePartInstructionConfig: async (id: string) => {
+    return await request.delete({ url: `/pressure2/boiler-part-instruction-config/delete`, params: { id } })
+  },
+  // 根据部件类型获取指导书模板ID列表
+  getInstructionTemplateIdsByPartType: async (partType: string) => {
+    return await request.get({ url: `/pressure2/boiler-part-instruction-config/list-by-part-type`, params: { partType } })
+  },
+
   // 添加任务单设备部件
   addTaskOrderItemPart: async (data: any) => {
     return await request.post({ url: `/pressure2/boiler-task-order-item-part/insert`, data })
@@ -854,6 +890,11 @@ export const BoilerTaskOrderApi = {
     return await request.get({ url: `/pressure2/boiler-task-order/inspection-opinion/reported-count`, params })
   },
 
+  /** 校验检验方案是否已完成审核审批 */
+  checkSchemeStatus: async (orderItemId: string) => {
+    return await request.get({ url: '/pressure2/boiler-task-order/inspection-opinion/check-scheme-status', params: { orderItemId } })
+  },
+
   /** 更新情况 */
   updateSituation: async (data: any) => {
     return await request.post({ url: `/pressure2/boiler-task-order/order-item/update-situation`, data })

+ 5 - 0
yudao-ui-admin-vue3/src/api/pressure2/pipetaskorder/index.ts

@@ -844,4 +844,9 @@ export const PipeTaskOrderApi = {
   majorIssuesRecovery: async (data) => {
     return await request.post({ url: '/pressure2/pipe-task-order/order-item/major-issues/recovery', data })
   },
+
+  /** 校验检验方案是否已完成审核审批 */
+  checkSchemeStatus: async (orderId: string) => {
+    return await request.get({ url: '/pressure2/pipe-task-order/inspection-opinion/check-scheme-status', params: { orderId } })
+  }
 }

+ 10 - 1
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/AssociationOperationManual.vue

@@ -134,7 +134,16 @@ const handleBack = () => {
 onMounted(() => {
   console.log('[关联操作指导书] 组件挂载,reportList 长度:', props.reportList?.length)
   reportData.value = props.reportList
-    .filter((item) => item.reportType === PressureReportType.WORKINSTRUCTION)
+    .filter((item) => {
+      // 只显示作业指导书
+      if (item.reportType !== PressureReportType.WORKINSTRUCTION) return false
+      // 如果当前独审报告属于某个部件,只能选择同部件下的指导书
+      const selectedPartId = props.selectedItem?.itemPartId
+      if (selectedPartId && selectedPartId !== '0') {
+        return item.itemPartId === selectedPartId
+      }
+      return true
+    })
     .map((item) => ({ ...item, checked: false }))
   console.log('[关联操作指导书] 筛选后 reportData 长度:', reportData.value.length,
     reportData.value.map(i => ({ id: i.id, reportName: i.reportName, templateId: i.templateId })))

+ 2 - 1
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/InspectionItemList.vue

@@ -44,7 +44,7 @@
       <div v-else class="item-list">
 
         <div v-for="(reportGroup, partId) in currentTabItems" :key="partId" class="report-group-card">
-          <div class="group-header" v-if="partId != ''">
+          <div class="group-header" v-if="partId != '' && activeTab == 'inspection'">
             <h5 class="group-title">{{ getPartNameById(partId) }}</h5>
             <div class="group-actions">
 <!--              <el-button-->
@@ -110,6 +110,7 @@
                     <span >({{ getCurrentStepName(item) }})</span>
                   </span>
                     <span v-else>
+                    <span v-if="partId != ''">【{{ getPartNameById(partId) }}】</span>
                     {{ item.reportName || '检验项目' }}
                   </span>
                     <span style="color: red" v-if="isOtherReport(item) && getOtherStepName(item)">({{ getOtherStepName(item) }})</span>

+ 8 - 1
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/myTask.vue

@@ -782,7 +782,14 @@ const canCheckInput = (row: BoilerTaskOrderOrderItemVO) => {
 }
 
 // 检验录入
-const handleCheckInput = (row: any) => {
+const handleCheckInput = async (row: any) => {
+
+  // 校验是否有完成审核审批的检验方案
+  const res = await BoilerTaskOrderApi.checkSchemeStatus(row.id)
+  if (!res) {
+    ElMessage.warning('请先完成检验方案的审核审批')
+    return
+  }
 
   //判断报告编制时是否有结束检验日期
   if (row.taskStatus >= PressureTaskOrderTaskStatus.REPORT_INPUT){

+ 28 - 6
yudao-ui-admin-vue3/src/views/pressure2/boilerconnectrecordreport/BoilerConnectRecordReportForm.vue

@@ -86,14 +86,19 @@
           <el-checkbox label="服务收费" value="0"/>
         </el-checkbox-group>
       </el-form-item>-->
-
-      <el-form-item label="费用计算类型" prop="feeCalcType">
+      <el-form-item label="是否收费" prop="isFee">
+        <el-select v-model="formData.isFee" placeholder="请选择是否收费" clearable>
+          <el-option label="是" value="1"/>
+          <el-option label="否" value="0"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="费用计算类型" prop="feeCalcType" v-if="formData.isFee === '1'">
         <el-radio-group v-model="formData.feeCalcType">
           <el-radio value="1">固定费用</el-radio>
           <el-radio value="0">参数计算</el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="费用" prop="fee" v-if="formData.feeCalcType === '1'">
+      <el-form-item label="费用" prop="fee" v-if="formData.isFee === '1' && formData.feeCalcType === '1'">
         <el-input-number
           v-model="formData.fee"
           placeholder="请输入费用"
@@ -102,7 +107,7 @@
           controls-position="right"
         />
       </el-form-item>
-      <el-form-item label="费用计算" prop="feeFileUrl" v-if="formData.feeCalcType === '0'">
+      <el-form-item label="费用计算" prop="feeFileUrl" v-if="formData.isFee === '1' && formData.feeCalcType === '0'">
          <el-button @click="handleEditFeeFile">编辑</el-button>
       </el-form-item>
       <el-form-item label="操作指导书" prop="operationInstruction" v-if="formData.projectType === 'STANDALONE'">
@@ -171,8 +176,9 @@ const formData = ref({
   projectCode: undefined,
   feeTypes: [],
   acceptOrderTemplateId: undefined,
+  isFee: '0', // 是否收费:1-是, 0-否
   feeCalcType: '1', // 1-固定费用, 0-参数计算
-  fee: undefined,
+  fee: 0,
   isAutoAmount: '0',
   feeFileUrl: undefined, // 费用计算配置
   operationInstruction: undefined,
@@ -244,6 +250,14 @@ const qualityInspectionList = ref([]) // 质量部检验项目列表
 const acceptOrderTemplateList = ref() // 受理单模板列表
 const showFeeCalculator = ref(false) // 是否显示费用计算器
 
+// 监听是否收费变化,切换时重置费用相关字段
+watch(() => formData.value.isFee, (val) => {
+  if (val === '1') {
+    formData.value.feeCalcType = '1'
+    formData.value.fee = 0
+  }
+})
+
 /** 打开弹窗 */
 const open = async (type: string, id?: string) => {
   formType.value = type
@@ -312,8 +326,9 @@ const resetForm = () => {
     projectCode: undefined,
     feeTypes: [],
     acceptOrderTemplateId: undefined,
+    isFee: '0',
     feeCalcType: '1',
-    fee: undefined,
+    fee: 0,
     isAutoAmount: '0',
     feeFileUrl: undefined,
     operationInstruction: undefined,
@@ -395,6 +410,13 @@ const handleEditFeeFile = async () => {
     message.error('请先保存项目信息')
     return
   }
+  // 提示是否保存
+  try {
+    await message.confirm('编辑费用计算前需要先保存当前数据,是否保存?', '提示')
+  } catch {
+    // 用户取消,不进行操作
+    return
+  }
   // 校验表单
   await formRef.value.validate()
   // 提交请求

+ 224 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilerpartinstructionconfig/index.vue

@@ -0,0 +1,224 @@
+<template>
+  <div class="boiler-part-instruction-config">
+    <ContentWrap title="部件-操作指导书配置">
+      <!-- 搜索 -->
+      <el-form :inline="true" :model="searchForm" class="search-form">
+        <el-form-item label="部件类型">
+          <el-select v-model="searchForm.partType" placeholder="请选择" clearable style="width: 160px">
+            <el-option
+              v-for="item in partTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 操作 -->
+      <div style="margin-bottom: 16px">
+        <el-button type="primary" @click="handleAdd">新增配置</el-button>
+      </div>
+
+      <el-table :data="tableData" border stripe v-loading="loading">
+        <el-table-column label="部件类型" width="180">
+          <template #default="{ row }">
+            {{ getPartTypeLabel(row.partType) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="instructionTemplateName" label="指导书模板" min-width="300" />
+        <el-table-column label="创建时间" width="180">
+          <template #default="{ row }">
+            {{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="80" fixed="right">
+          <template #default="{ row }">
+            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        v-model:current-page="pageNo"
+        v-model:page-size="pageSize"
+        :total="total"
+        @current-change="fetchData"
+        @size-change="fetchData"
+        layout="total, sizes, prev, pager, next"
+      />
+    </ContentWrap>
+
+    <!-- 新增弹窗 -->
+    <el-dialog v-model="dialogVisible" title="新增部件-指导书配置" width="500px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="部件类型" prop="partType">
+          <el-select v-model="form.partType" placeholder="请选择部件类型" filterable style="width: 100%">
+            <el-option
+              v-for="item in partTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="指导书模板" prop="instructionTemplateIds">
+          <el-select v-model="form.instructionTemplateIds" placeholder="请选择指导书模板" filterable multiple style="width: 100%" @focus="fetchTemplates">
+            <el-option
+              v-for="item in templateList"
+              :key="item.id"
+              :label="item.tbName"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { BoilerTaskOrderApi } from '@/api/pressure2/boilertaskorder'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { getPressureReportTemplateListNoLimit } from '@/api/pressure2/reportTemplate'
+import dayjs from 'dayjs'
+
+const loading = ref(false)
+const tableData = ref<any[]>([])
+const pageNo = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+
+const searchForm = reactive({
+  partType: ''
+})
+
+const dialogVisible = ref(false)
+const submitLoading = ref(false)
+const formRef = ref()
+const form = reactive({
+  partType: '',
+  instructionTemplateIds: [] as string[]
+})
+
+const rules = {
+  partType: [{ required: true, message: '请选择部件类型', trigger: 'change' }],
+  instructionTemplateIds: [{ required: true, message: '请选择指导书模板', trigger: 'change' }]
+}
+
+const partTypeList = getStrDictOptions(DICT_TYPE.PRESSURE2_BOILER_PART_TYPE)
+const templateList = ref<any[]>([])
+
+// 部件类型字典值转标签
+const getPartTypeLabel = (val: string) => {
+  return partTypeList.find((item: any) => item.value === val)?.label || val || '-'
+}
+
+// 选择部件后带出已配置的指导书
+watch(() => form.partType, async (val) => {
+  if (val) {
+    try {
+      const res = await BoilerTaskOrderApi.getInstructionTemplateIdsByPartType(val)
+      form.instructionTemplateIds = res || []
+    } catch (e) {
+      console.error('获取已配置指导书失败', e)
+    }
+  } else {
+    form.instructionTemplateIds = []
+  }
+})
+
+const fetchTemplates = async () => {
+  if (templateList.value.length > 0) return
+  try {
+    const res = await getPressureReportTemplateListNoLimit({
+      reportType: '700',
+      pageNo: 1,
+      pageSize: 200,
+      status: 200
+    })
+    templateList.value = res?.data?.list || res?.list || res || []
+  } catch (e) {
+    console.error('获取模板列表失败', e)
+  }
+}
+
+const fetchData = async () => {
+  loading.value = true
+  try {
+    const params: any = {
+      pageNo: pageNo.value,
+      pageSize: pageSize.value
+    }
+    if (searchForm.partType) {
+      params.partType = searchForm.partType
+    }
+    const res = await BoilerTaskOrderApi.getPartInstructionConfigPage(params)
+    tableData.value = res?.list || []
+    total.value = res?.total || 0
+  } catch (e) {
+    console.error('获取配置列表失败', e)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleSearch = () => {
+  pageNo.value = 1
+  fetchData()
+}
+
+const handleReset = () => {
+  searchForm.partType = ''
+  handleSearch()
+}
+
+const handleAdd = () => {
+  form.partType = ''
+  form.instructionTemplateIds = []
+  dialogVisible.value = true
+}
+
+const handleSubmit = async () => {
+  await formRef.value?.validate()
+  submitLoading.value = true
+  try {
+    await BoilerTaskOrderApi.createPartInstructionConfig({
+      partType: form.partType,
+      instructionTemplateIds: form.instructionTemplateIds
+    })
+    ElMessage.success('添加成功')
+    dialogVisible.value = false
+    fetchData()
+  } catch (e) {
+    console.error('添加配置失败', e)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+const handleDelete = async (row: any) => {
+  await ElMessageBox.confirm(`确定删除该配置?部件【${getPartTypeLabel(row.partType)}】-【${row.instructionTemplateName}】`, '提示', { type: 'warning' })
+  try {
+    await BoilerTaskOrderApi.deletePartInstructionConfig(row.id)
+    ElMessage.success('删除成功')
+    fetchData()
+  } catch (e) {
+    // 用户取消
+  }
+}
+
+onMounted(() => {
+  fetchData()
+})
+</script>

+ 186 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilertaskorder/components/AddBookAndCheckSchemeType1.vue

@@ -0,0 +1,186 @@
+<!--
+ * @Description: 添加作业指导书(type=1:选择部件、模板生成/引用上次模板、备注)
+ -->
+
+<template>
+  <Dialog v-model="dialogVisible" :title="title" width="550">
+    <ElForm ref="formRef" :model="formData" :rules="rules" :labelWidth="120">
+      <!-- 设备注册代码 -->
+      <el-form-item label="设备注册代码">
+        <el-input :model-value="formData.equipCode" disabled />
+      </el-form-item>
+
+      <!-- 选择部件 -->
+      <el-form-item label="选择部件" prop="partId">
+        <el-select v-model="formData.partId" placeholder="请选择部件" filterable @change="handlePartChange">
+          <el-option v-for="item in partOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+
+      <!-- 添加方式 -->
+      <el-form-item label="添加方式" prop="addType">
+        <el-radio-group v-model="formData.addType" @change="handleAddTypeChange">
+          <el-radio value="2">模板生成</el-radio>
+          <el-radio value="1">引用上次模板</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <!-- 模板名称(仅模板生成时显示) -->
+      <el-form-item v-if="formData.addType === '2'" label="模板名称" prop="templateId">
+        <el-select v-model="formData.templateId" placeholder="请选择作业指导书模板" filterable>
+          <el-option v-for="item in partTemplateOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+
+      <!-- 选择上次模板(仅引用上次模板时显示) -->
+      <el-form-item v-if="formData.addType === '1'" label="选择上次模板" prop="instructionId">
+        <el-select v-model="formData.instructionId" placeholder="请选择上次模板" filterable>
+          <el-option v-for="item in filteredHistoryOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+
+      <!-- 备注 -->
+      <el-form-item label="备注">
+        <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
+      </el-form-item>
+    </ElForm>
+
+    <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 name="AddBookAndCheckSchemeType1">
+import { ref, computed, watch } from 'vue'
+import { ElForm, FormRules } from 'element-plus'
+import { BoilerTaskOrderApi } from '@/api/pressure2/boilertaskorder'
+
+const dialogVisible = ref(false)
+const title = ref('')
+const formLoading = ref(false)
+
+const formRef = ref<InstanceType<typeof ElForm>>()
+const formData = ref<Recordable>({})
+
+// 从父组件传入的全部模板选项
+const allTemplateOptions = ref<any[]>([])
+// 部件列表 [{ label, value, partType }]
+const partOptions = ref<any[]>([])
+// 当前部件可选的模板(按配置筛选后)
+const partTemplateOptions = ref<any[]>([])
+// 历史指导书列表 [{ label, value, templateId, itemPartId }]
+const historyOptions = ref<any[]>([])
+
+const rules: FormRules = {
+  partId: [{ required: true, message: '请选择部件', trigger: 'change' }],
+  addType: [{ required: true, message: '请选择添加方式', trigger: 'change' }],
+  templateId: [{ required: true, message: '请选择作业指导书模板', trigger: 'change' }],
+  instructionId: [{ required: true, message: '请选择上次模板', trigger: 'change' }]
+}
+
+// 根据选中部件的 partType 筛选历史指导书
+const filteredHistoryOptions = computed(() => {
+  if (!formData.value?.partId) return []
+  const part = partOptions.value.find((item: any) => item.value === formData.value.partId)
+  if (!part?.partType) return historyOptions.value
+  const sameTypeIds = partOptions.value
+    .filter((item: any) => item.partType === part.partType)
+    .map((item: any) => item.value)
+  return historyOptions.value.filter((item: any) => sameTypeIds.includes(item.itemPartId))
+})
+
+// 选择部件 → 按配置加载模板,同时清空已选
+const handlePartChange = async (val: string) => {
+  formData.value.templateId = ''
+  formData.value.instructionId = ''
+  if (!val) { partTemplateOptions.value = []; return }
+
+  const part = partOptions.value.find((item: any) => item.value === val)
+  if (!part?.partType) { partTemplateOptions.value = allTemplateOptions.value; return }
+
+  try {
+    const res = await BoilerTaskOrderApi.getInstructionTemplateIdsByPartType(part.partType)
+    const ids: string[] = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
+    console.log('ids',ids);
+
+    partTemplateOptions.value = ids.length > 0
+      ? allTemplateOptions.value.filter((t: any) => ids.includes(String(t.value)))
+      : []
+      console.log('partTemplateOptions',partTemplateOptions);
+      
+  } catch {
+    partTemplateOptions.value = []
+  }
+}
+
+// 切换添加方式时清空
+const handleAddTypeChange = () => {
+  formData.value.templateId = ''
+  formData.value.instructionId = ''
+}
+
+// 选历史指导书后自动回填 templateId
+watch(() => formData.value?.instructionId, (val) => {
+  if (!val) return
+  const item = historyOptions.value.find((i: any) => i.value === val)
+  if (item) formData.value.templateId = item.templateId
+})
+
+/** 打开弹窗(父组件调用) */
+const open = async (params: Record<string, any>, status: number) => {
+  title.value = '添加' + params.title
+  allTemplateOptions.value = params.options || []
+  partOptions.value = []
+  partTemplateOptions.value = []
+  historyOptions.value = []
+
+  formData.value = {
+    ...params.formData,
+    addType: '2',
+    partId: '',
+    templateId: '',
+    instructionId: '',
+    remark: '',
+    _fromType1: true
+  }
+  dialogVisible.value = true
+
+  const orderItemId = params.formData?.mainID
+  if (!orderItemId) return
+
+  // 加载部件列表
+  try {
+    const res = await BoilerTaskOrderApi.getTaskOrderItemPartPage({ orderItemId, pageNo: 1, pageSize: 100 })
+    partOptions.value = (res?.list || []).map((item: any) => ({
+      label: item.partName,
+      value: item.id,
+      partType: item.partType
+    }))
+  } catch { /* ignore */ }
+
+  // 加载上次任务单的指导书(跨任务单,按设备ID + 跳过当前)
+  try {
+    const equipId = params.formData?.equipId || params.formData?.id
+    const res = equipId ? await BoilerTaskOrderApi.getHistoryInstructions(equipId, orderItemId) : { data: [] }
+    const list = Array.isArray(res) ? res : (res?.data || res?.list || [])
+    historyOptions.value = list.map((item: any) => ({
+      label: item.reportName || item.reportNo,
+      value: item.id,
+      templateId: item.templateId,
+      itemPartId: item.itemPartId
+    }))
+  } catch { /* ignore */ }
+}
+
+const submitForm = () => {
+  formRef.value?.validate().then(() => {
+    emits('success', formData.value)
+    dialogVisible.value = false
+  })
+}
+
+const emits = defineEmits(['success'])
+defineExpose({ open })
+</script>

+ 35 - 3
yudao-ui-admin-vue3/src/views/pressure2/boilertaskorder/components/TaskOrderDetailDialog.vue

@@ -925,6 +925,9 @@
   <!-- 作业指导书 添加弹窗 -->
   <AddBookAndCheckScheme ref="addBookAndCheckSchemeRef" @success="handleSuccessReport" />
 
+  <!-- 作业指导书 type=1 添加弹窗(选择部件、引用上次模板、备注) -->
+  <AddBookAndCheckSchemeType1 ref="addBookAndCheckSchemeType1Ref" @success="handleSuccessReport" />
+
   <!-- 作业指导书 编辑弹窗 -->
   <EditWorkBookReport
     v-if="editWorkBookReportVisible"
@@ -1038,6 +1041,7 @@ import {useUserStore} from "@/store/modules/user";
 import AddInspectionplanDetail from "@/views/pressure2/boilertaskorder/components/AddInspectionplanDetail.vue";
 import ServiceRecordList from "@/views/pressure2/boilertaskorder/components/ServiceRecordList.vue";
 import AddBookAndCheckScheme from "@/views/pressure2/boilertaskorder/components/AddBookAndCheckScheme.vue";
+import AddBookAndCheckSchemeType1 from "@/views/pressure2/boilertaskorder/components/AddBookAndCheckSchemeType1.vue";
 import EditWorkBookReport from "@/views/pressure2/boilertaskorder/components/EditWorkBookReport.vue";
 import {EquipBoilerApi} from "@/api/pressure2/equipboiler";
 import {TaskOrderApi} from "@/api/pressure/taskorder";
@@ -2357,6 +2361,7 @@ const handleLookSecurityCheck = async () => {
 //添加作业指导书
 // 记录当前的报告类型相关信息
 const addBookAndCheckSchemeRef = ref<InstanceType<typeof AddBookAndCheckScheme>>()
+const addBookAndCheckSchemeType1Ref = ref<InstanceType<typeof AddBookAndCheckSchemeType1>>()
 const curReportType = ref()
 const editWorkBookReportVisible = ref(false)
 const curReportTypeInfo = reactive({
@@ -2364,7 +2369,6 @@ const curReportTypeInfo = reactive({
   data: {}
 })
 const handleAddReport = async (reportType, row?: Recordable) => {
-
   if (!isCanAddFlag(row)){
     return
   }
@@ -2444,7 +2448,11 @@ const handleAddReport = async (reportType, row?: Recordable) => {
       //   addInspectionplanRef.value?.open(newParams, '添加检验方案')
       //   return
       // }
-      addBookAndCheckSchemeRef.value?.open(newParams, reportType)
+      if(row.type == '1'){
+        addBookAndCheckSchemeType1Ref.value?.open(newParams, reportType)
+      }else{
+        addBookAndCheckSchemeRef.value?.open(newParams, reportType)
+      }
       return
     }
 
@@ -2495,11 +2503,35 @@ const handleAddReport = async (reportType, row?: Recordable) => {
 const handleSuccessReport = async (info) => {
   const reportList = filterReportType(info.reportRespVOList);
   const templateIds = (reportList || []).filter(item=>item.templateId === info.templateId)
-  if (templateIds.length > 0){
+  // 模板生成模式才做去重校验,引用上次模板允许同模板多条
+  if (templateIds.length > 0 && !(info?._fromType1 && info.addType === '1')){
     ElMessage.error('该设备已存在该作业指导书')
     return
   }
   if(!info) return handleEditOperationReportList()
+  // type=1 设备弹窗:统一走新接口(模板生成 / 引用上次模板)
+  if(info?._fromType1){
+    const params: Recordable = {
+      orderId: taskOrderDetail.value.id,
+      orderItemId: info.mainID,
+      addType: info.addType,
+      templateId: info.templateId || '',
+      instructionId: info.instructionId || '',
+      itemPartId: info.partId,
+      remark: info.remark
+    }
+    const newReportId = await BoilerTaskOrderApi.addReportFromExisting(params)
+    editOperationReportTypeTitle.value = PressureReportTypeMap[unref(curReportType)]
+    editOperationReportParams.value = {
+      templateId: info.templateId || '',
+      orderId: taskOrderDetail.value.id,
+      orderItemId: info.mainID,
+      reportId: newReportId
+    }
+    isWorkBookEdit.value = true
+    editWorkBookReportVisible.value = true
+    return
+  }
   // 如果选择的是模板,则还是走之前的逻辑,打开葡萄城进行编辑提交
   if(info?.addType === '2' && info.id){
     // 这里去打开模板

+ 87 - 60
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/BoilerPlanScheduleDialog.vue

@@ -353,14 +353,13 @@
                               style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background-color: #6cbcf5; color: #fff; font-size: 12px; border-radius: 2px; font-weight: bold;">主</span>
                         <span>{{ item.name }}</span>
                       </div>
-                      <span>
+                      <span v-if="item.isFee === '1'">
                         (
                         <el-button link type="primary" @click="() => {
                           openFeeDialog(item)
                         }"
                         >费用:{{ item.fee || '无' }}</el-button
                         >
-                        <!--                      >费用:{{ getCheckItemFeeType(item) }}</el-button-->
                         )
                       </span>
                     </div>
@@ -873,7 +872,13 @@ watch(() => preSelectedCheckers.value, (newVal) => {
 }, {deep: true})
 
 
-/** 监听日期变化 */
+/** 监听检验性质变化 */
+watch(() => formData.value.checkType, (newType, oldType) => {
+  if (newType && newType !== oldType && !isOpening.value) {
+    handleQueryCheckItemList(newType)
+  }
+})
+
 watch(() => formData.value.inDate, (newDate) => {
   if (newDate) {
     // 只有在未设置且未勾选"无需安排"的情况下才自动填充
@@ -1120,14 +1125,22 @@ const updateTeamList = (type) => {
 }
 
 
-// 查询项目列表
-const handleQueryCheckItemList = async () => {
-  checkItemList.value = []
-  const checkTypes = [
-    PressureBoilerCheckType.IN,
-    PressureBoilerCheckType.OUT,
-    PressureBoilerCheckType.PRESSURE
-  ];
+// 查询项目列表(按当前检验性质查询,并缓存结果)
+const checkItemListCache = ref<Record<string, any[]>>({})
+const isOpening = ref(false) // 标记弹窗是否正在打开,防止watch在数据未就绪时触发查询
+const handleQueryCheckItemList = async (checkType?: string) => {
+  const targetType = checkType || formData.value.checkType
+  // 如果已缓存,直接使用缓存数据
+  if (checkItemListCache.value[targetType]) {
+    // 合并缓存数据到 checkItemList(保留其他类型的数据,更新当前类型)
+    const cachedItems = checkItemListCache.value[targetType]
+    checkItemList.value = [
+      ...checkItemList.value.filter(i => i.inspectionNature != targetType),
+      ...cachedItems
+    ]
+    return
+  }
+
   // 校验所有选中的任务列表中的设备类型是否一致
   const allTasks = [
     ...formData.value.inTaskList,
@@ -1164,54 +1177,61 @@ const handleQueryCheckItemList = async () => {
   // 获取统一的设备类型,如果列表为空则设为 undefined
   const equipType = allTasks.length > 0 ? allTasks[0].type : undefined
 
-  for (const type of checkTypes) {
-    const params = {
-      orderId: "0",
-      itemIds: equipList.value.map(item => item.id),
-      equipmentCategory: 200,
-      inspectionNature: [type],
-      equipType:equipType
-    };
-    const queryResult = await querySchedulingCheckItemList(params);
-    const inspectionNatureType = await InspectionNatureTypeApi.getInspectionNatureTypePage({
-      pageNo: 1,
-      pageSize: 1,
-      equip: '200',
-      nature: type
-    })
-    const inType = inspectionNatureType.list[0].type
-    checkItemList.value.push({
-      inspectionNatureName: PressureBoilerCheckTypeMap[type] + " 法定收费项目",
-      inspectionNature: type,
-      type: '1',
-      isExpanded: inType == 1, // 根据 inType 决定是否默认展开
-      itemList: (queryResult || []).map((item) => (
-        {
-          ...item,
-          isAutoAmount: '0', // 批量模式下默认为'0'
-          inspectionNature: type,
-          type: '1',
-          use: inType == 1 ? item.use : false
-        }
-      ))
-    });
-    checkItemList.value.push({
-      inspectionNatureName: PressureBoilerCheckTypeMap[type] + " 服务收费项目",
-      inspectionNature: type,
-      type: '2',
-      isExpanded: inType == 2, // 根据 inType 决定是否默认展开
-      itemList: (queryResult || []).map((item) => (
-        {
-          ...item,
-          isAutoAmount: '0', // 批量模式下默认为'0'
-          inspectionNature: type,
-          type: '2',
-          use: inType == 2 ? item.use : false
-        }
-      ))
-    });
-  }
-
+  const type = Number(targetType) || targetType
+  const params = {
+    orderId: "0",
+    itemIds: equipList.value.map(item => item.id),
+    equipmentCategory: 200,
+    inspectionNature: [type],
+    equipType:equipType
+  };
+  const queryResult = await querySchedulingCheckItemList(params);
+  const inspectionNatureType = await InspectionNatureTypeApi.getInspectionNatureTypePage({
+    pageNo: 1,
+    pageSize: 1,
+    equip: '200',
+    nature: type
+  })
+  const inType = inspectionNatureType.list[0].type
+  const newItems: any[] = []
+  newItems.push({
+    inspectionNatureName: PressureBoilerCheckTypeMap[type] + " 法定收费项目",
+    inspectionNature: type,
+    type: '1',
+    isExpanded: inType == 1,
+    itemList: (queryResult || []).map((item) => (
+      {
+        ...item,
+        isAutoAmount: '0',
+        inspectionNature: type,
+        type: '1',
+        use: inType == 1 ? item.use : false
+      }
+    ))
+  });
+  newItems.push({
+    inspectionNatureName: PressureBoilerCheckTypeMap[type] + " 服务收费项目",
+    inspectionNature: type,
+    type: '2',
+    isExpanded: inType == 2,
+    itemList: (queryResult || []).map((item) => (
+      {
+        ...item,
+        isAutoAmount: '0',
+        inspectionNature: type,
+        type: '2',
+        use: inType == 2 ? item.use : false
+      }
+    ))
+  });
+
+  // 缓存当前类型的结果
+  checkItemListCache.value[type] = newItems
+  // 合并到 checkItemList
+  checkItemList.value = [
+    ...checkItemList.value.filter(i => i.inspectionNature != type),
+    ...newItems
+  ]
 }
 
 /** 切换检验项目展开/收缩状态 */
@@ -1253,6 +1273,7 @@ const checkEquipDept = async (selectedRows: any[]) => {
 /** 打开弹窗 */
 const open = async (selectedInList, selectedOutList, selectedPreList, type?: string) => {
   //console.log('open', props.selectedRows)
+  isOpening.value = true
   dialogVisible.value = true
 
   // 合并所有选中的设备列表
@@ -1309,7 +1330,11 @@ const open = async (selectedInList, selectedOutList, selectedPreList, type?: str
   formData.value.outTaskList = selectedOutList
   formData.value.pressureTaskList = selectedPreList
 
-  handleQueryCheckItemList()
+  // 清空缓存和项目列表,只查询当前检验性质
+  checkItemList.value = []
+  checkItemListCache.value = {}
+  await handleQueryCheckItemList(formData.value.checkType)
+  isOpening.value = false
 
   // 加载检验员列表(默认当前登录人的部门)
   const deptId = userStore.getUser.deptId?.toString()
@@ -1325,6 +1350,8 @@ const open = async (selectedInList, selectedOutList, selectedPreList, type?: str
 /** 取消操作 */
 const handleCancel = () => {
   dialogVisible.value = false
+  checkItemList.value = []
+  checkItemListCache.value = {}
   if (!isBatch.value) {
     emit('clear-selected-rows') // 新增事件用于清空选中行
   }

+ 8 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/myTask.vue

@@ -747,7 +747,14 @@ const canCheckInput = (row: PipeTaskOrderOrderItemVO) => {
 }
 
 // 检验录入
-const handleCheckInput = (row: PipeTaskOrderOrderItemVO) => {
+const handleCheckInput = async (row: PipeTaskOrderOrderItemVO) => {
+
+  // 校验是否有完成审核审批的检验方案
+  const res = await PipeTaskOrderApi.checkSchemeStatus(row.orderId)
+  if (!res) {
+    ElMessage.warning('请先完成检验方案的审核审批')
+    return
+  }
 
   //判断报告编制时是否有结束检验日期
   if (row.taskStatus >= PressureTaskOrderTaskStatus.REPORT_INPUT){

+ 85 - 61
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipePlanScheduleDialog.vue

@@ -261,14 +261,13 @@
                               style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background-color: #6cbcf5; color: #fff; font-size: 12px; border-radius: 2px; font-weight: bold;">主</span>
                         <span>{{ item.name }}</span>
                       </div>
-                      <span>
+                      <span v-if="item.isFee === '1'">
                       (
                       <el-button link type="primary" @click="() => {
                         openFeeDialog(item)
                       }"
                       >费用:{{ item.fee || '无' }}</el-button
                       >
-                        <!--                      >费用:{{ getCheckItemFeeType(item) }}</el-button-->
                       )
                     </span>
                     </div>
@@ -774,6 +773,13 @@ const showCheck = (checkItem) => {
   return true
 }
 
+// 监听检验性质切换,按需查询项目
+watch(() => formData.value.checkType, (newType, oldType) => {
+  if (newType && newType !== oldType && !isOpening.value) {
+    handleQueryCheckItemList(newType)
+  }
+})
+
 // 监听检验项目使用状态变化,自动重新计算金额
 watch(() => checkItemList.value, () => {
   calculateLegalAmounts()
@@ -826,9 +832,20 @@ const updateTeamList = (type) => {
 }
 
 
-// 查询项目列表
-const handleQueryCheckItemList = async () => {
-  checkItemList.value = []
+// 查询项目列表(按当前检验性质查询,并缓存结果)
+const checkItemListCache = ref<Record<string, any[]>>({})
+const isOpening = ref(false) // 标记弹窗是否正在打开,防止watch在数据未就绪时触发查询
+const handleQueryCheckItemList = async (checkType?: string) => {
+  const targetType = checkType || formData.value.checkType
+  // 如果已缓存,直接使用缓存数据
+  if (checkItemListCache.value[targetType]) {
+    checkItemList.value = [
+      ...checkItemList.value.filter(i => i.inspectionNature != targetType),
+      ...checkItemListCache.value[targetType]
+    ]
+    return
+  }
+
   // 校验所有选中的任务列表中的设备类型是否一致
   const allTasks = [
     ...formData.value.legalTaskList,
@@ -863,61 +880,61 @@ const handleQueryCheckItemList = async () => {
   // 获取统一的设备类型,如果列表为空则设为 undefined
   const equipType = allTasks.length > 0 ? allTasks[0].pipeCategory : undefined
 
-  equipList.value = []
-  checkItemList.value = []
-  const checkTypes = [
-    PressurePipeCheckType.REGULAR,
-    PressurePipeCheckType.ANNUAL
-  ];
-
-  for (const type of checkTypes) {
-    const params = {
-      orderId: "0",
-      itemIds: equipList.value.map(item => item.id),
-      equipmentCategory: 300,
-      inspectionNature: [type],
-      equipType:equipType
-    };
-    const queryResult = await querySchedulingCheckItemList(params);
-    const inspectionNatureType = await InspectionNatureTypeApi.getInspectionNatureTypePage({
-      pageNo: 1,
-      pageSize: 1,
-      equip: '300',
-      nature: type
-    })
-    const inType = inspectionNatureType.list[0].type
-    checkItemList.value.push({
-      inspectionNatureName: PressurePipeCheckTypeMap[type] + " 法定收费项目",
-      inspectionNature: type,
-      type: '1',
-      isExpanded: inType == 1, // 根据 inType 决定是否默认展开
-      itemList: (queryResult || []).map((item) => (
-        {
-          ...item,
-          isAutoAmount: '0', // 批量模式下默认为'0'
-          inspectionNature: type,
-          type: '1',
-          use: inType == 1 ? item.name === "一览表" ? true : item.use : false
-        }
-      ))
-    });
-    checkItemList.value.push({
-      inspectionNatureName: PressurePipeCheckTypeMap[type] + " 服务收费项目",
-      inspectionNature: type,
-      type: '2',
-      isExpanded: inType == 2, // 根据 inType 决定是否默认展开
-      itemList: (queryResult || []).map((item) => (
-        {
-          ...item,
-          isAutoAmount: '0', // 批量模式下默认为'0'
-          inspectionNature: type,
-          type: '2',
-          use: inType == 2 ? item.name === "一览表" ? true : item.use : false
-        }
-      ))
-    });
-  }
-
+  const type = Number(targetType) || targetType
+  const params = {
+    orderId: "0",
+    itemIds: equipList.value.map(item => item.id),
+    equipmentCategory: 300,
+    inspectionNature: [type],
+    equipType:equipType
+  };
+  const queryResult = await querySchedulingCheckItemList(params);
+  const inspectionNatureType = await InspectionNatureTypeApi.getInspectionNatureTypePage({
+    pageNo: 1,
+    pageSize: 1,
+    equip: '300',
+    nature: type
+  })
+  const inType = inspectionNatureType.list[0].type
+  const newItems: any[] = []
+  newItems.push({
+    inspectionNatureName: PressurePipeCheckTypeMap[type] + " 法定收费项目",
+    inspectionNature: type,
+    type: '1',
+    isExpanded: inType == 1,
+    itemList: (queryResult || []).map((item) => (
+      {
+        ...item,
+        isAutoAmount: '0',
+        inspectionNature: type,
+        type: '1',
+        use: inType == 1 ? item.name === "一览表" ? true : item.use : false
+      }
+    ))
+  });
+  newItems.push({
+    inspectionNatureName: PressurePipeCheckTypeMap[type] + " 服务收费项目",
+    inspectionNature: type,
+    type: '2',
+    isExpanded: inType == 2,
+    itemList: (queryResult || []).map((item) => (
+      {
+        ...item,
+        isAutoAmount: '0',
+        inspectionNature: type,
+        type: '2',
+        use: inType == 2 ? item.name === "一览表" ? true : item.use : false
+      }
+    ))
+  });
+
+  // 缓存当前类型的结果
+  checkItemListCache.value[type] = newItems
+  // 合并到 checkItemList
+  checkItemList.value = [
+    ...checkItemList.value.filter(i => i.inspectionNature != type),
+    ...newItems
+  ]
 }
 
 /** 切换检验项目展开/收缩状态 */
@@ -928,6 +945,7 @@ const toggleCheckItemCollapse = (checkItem: any) => {
 /** 打开弹窗 */
 const open = async (selectedLegalList?: EquipPipeSchedulingVO[], selectedYearList?: EquipPipeSchedulingVO[],checkType?) => {
   //console.log('open', props.selectedRows)
+  isOpening.value = true
   dialogVisible.value = true
 
 
@@ -975,7 +993,11 @@ const open = async (selectedLegalList?: EquipPipeSchedulingVO[], selectedYearLis
   formData.value.legalTaskList = selectedLegalList
   formData.value.yearTaskList = selectedYearList
 
-  handleQueryCheckItemList()
+  // 清空缓存和项目列表,只查询当前检验性质
+  checkItemList.value = []
+  checkItemListCache.value = {}
+  await handleQueryCheckItemList(formData.value.checkType)
+  isOpening.value = false
   // 获取当前登录用户的部门ID
   const deptId = userStore.getUser.deptId?.toString() || '1'
 
@@ -997,6 +1019,8 @@ const open = async (selectedLegalList?: EquipPipeSchedulingVO[], selectedYearLis
 /** 取消操作 */
 const handleCancel = () => {
   dialogVisible.value = false
+  checkItemList.value = []
+  checkItemListCache.value = {}
   emit('close')
 }
 

+ 2 - 2
yudao-ui-admin-vue3/src/views/pressure2/pipetaskorder/components/AddOrEditCheckItemForEquipment.vue

@@ -74,7 +74,7 @@
                           <span style="display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; word-break: break-all;">{{ item.name }}</span>
                         </el-tooltip>
                       </div>
-                      <template v-if="!item.use && true">
+                      <template v-if="!item.use && item.isFee === '1'">
                         <span style="flex-shrink: 0; white-space: nowrap;">
                         (
                         <el-button link type="primary" @click="() => handleInputCalcField(item)"
@@ -84,7 +84,7 @@
                         </span>
                       </template>
                     </div>
-                    <div class="grid-item-bottom" v-if="item.use">
+                    <div class="grid-item-bottom" v-if="item.use && item.isFee === '1'">
                       <el-input-number
                         v-model="item.quantity"
                         :min="1"