Explorar el Código

设备详情基本信息调整UI样式

yangguanjin hace 2 meses
padre
commit
f35fa214ab

+ 33 - 17
src/api/task.ts

@@ -1,4 +1,5 @@
 import { httpGet, httpPost, httpPUT, httpDelete } from '@/utils/http'
+import { PressureReportType } from '@/utils/dictMap'
 
 // 任务确认列表
 export const getTaskConfirmList = (params: any) => {
@@ -376,27 +377,42 @@ export const updateTaskOrderSafeManager = (data: any) => {
 
 /**
  * 过滤报告列表,将检验项目和其他项目分开
+ * 与 PJ 版本 getFilterReport 逻辑一致
  */
-export const getFilterReport = (reportList: any[], equipCode: string) => {
+export const getFilterReport = (reportList: any[] | undefined, equipCode?: string) => {
   if (!reportList || !reportList.length) {
-    return {
-      reportList: [],
-      otherReportList: [],
-    }
+    return {}
   }
 
-  // 根据报告类型过滤
-  const reportTypeList = [100, 200, 300, 400, 500, 600, 700]
-  const reportListFiltered = reportList.filter((item) => {
-    return reportTypeList.includes(item.reportType)
-  })
+  const checkReportList: any[] = []
+  const otherReportList: any[] = []
+
+  const filterArr = reportList.filter((rep) => rep.taskStatus >= 400)
+
+  for (const item of filterArr) {
+    if (
+      item.reportType === PressureReportType.MAINQUESTION ||
+      item.reportType === PressureReportType.INSPECTIONPLAN ||
+      item.reportType === PressureReportType.WORKINSTRUCTION ||
+      item.reportType === PressureReportType.SUBPACKAGE ||
+      item.reportType === PressureReportType.FINITE_SPACE
+    ) {
+      if ([600].includes(item.reportType as number)) {
+        if ([200].includes(item.status)) {
+          otherReportList.push({ ...item, equipCode })
+        }
+      } else {
+        otherReportList.push({ ...item, equipCode })
+      }
+    } else {
+      checkReportList.push(item)
+    }
+  }
 
-  const otherReportList = reportList.filter((item) => {
-    return !reportTypeList.includes(item.reportType)
-  })
+  return { reportList: checkReportList, otherReportList }
+}
 
-  return {
-    reportList: reportListFiltered,
-    otherReportList,
-  }
+// 任务单-报告编制录入提交
+export const submitReportApi = (data: any) => {
+  return httpPUT('/pressure/task-order/order-item/report/prepare/submit', data)
 }

+ 89 - 118
src/pages/equipment/detail/components/BaseInfo.vue

@@ -1,13 +1,13 @@
 <template>
   <view class="base-info-container">
     <!-- 编辑按钮 -->
-    <view v-if="canEdit" class="edit-bar">
-      <button class="edit-btn" @click="handleEdit">
-        <text>{{ isEdit ? '保存' : '编辑' }}</text>
-      </button>
-      <button v-if="isEdit" class="ocr-btn" @click="handleOcr">
-        <text>拍照录入</text>
-      </button>
+    <view v-if="canEdit" class="action-buttons">
+      <view class="operate-btn" @click="handleEdit">
+        <text class="operate-btn-text">{{ isEdit ? '保存' : '编辑' }}</text>
+      </view>
+      <view class="operate-btn ocr-btn" @click="handleOcr">
+        <text class="operate-btn-text">拍照录入</text>
+      </view>
     </view>
 
     <!-- 使用单位信息 -->
@@ -48,8 +48,8 @@
           </view>
         </view>
 
-        <!-- 统一社会信用代码 -->
-        <view class="table-row">
+        <!-- 统一社会信用代码 + 邮政编码 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">统一社会信用代码</text>
           </view>
@@ -58,14 +58,10 @@
               v-if="isEdit"
               v-model="equipment.socialCreditCode"
               class="edit-input-text"
-              placeholder="请输入统一社会信用代码"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.socialCreditCode || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 邮政编码 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">邮政编码</text>
           </view>
@@ -74,7 +70,7 @@
               v-if="isEdit"
               v-model="equipment.zipCode"
               class="edit-input-text"
-              placeholder="请输入邮政编码"
+              placeholder="请输入"
               maxlength="6"
             />
             <text v-else class="cell-text">{{ equipment.zipCode || '--' }}</text>
@@ -104,8 +100,8 @@
           </view>
         </view>
 
-        <!-- 设备所在行政区 -->
-        <view class="table-row">
+        <!-- 设备所在行政区 + 设备所在街道 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">设备所在行政区</text>
             <text v-if="isEdit" class="must">*</text>
@@ -117,10 +113,6 @@
             </view>
             <text v-else class="cell-text">{{ equipment.equipDistrictName || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 设备所在街道 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">设备所在街道</text>
             <text v-if="isEdit" class="must">*</text>
@@ -134,8 +126,8 @@
           </view>
         </view>
 
-        <!-- 约检联系人 -->
-        <view class="table-row">
+        <!-- 约检联系人 + 约检联系人电话 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">约检联系人</text>
           </view>
@@ -144,14 +136,10 @@
               v-if="isEdit"
               v-model="equipment.contact"
               class="edit-input-text"
-              placeholder="请输入约检联系人"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.contact || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 约检联系人电话 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">约检联系人电话</text>
           </view>
@@ -160,15 +148,15 @@
               v-if="isEdit"
               v-model="equipment.contactPhone"
               class="edit-input-text"
-              placeholder="请输入约检联系人电话"
+              placeholder="请输入"
               maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.contactPhone || '--' }}</text>
           </view>
         </view>
 
-        <!-- 安全管理人 -->
-        <view class="table-row">
+        <!-- 安全管理人 + 安全管理人电话 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">安全管理人</text>
           </view>
@@ -177,14 +165,10 @@
               v-if="isEdit"
               v-model="equipment.safeManager"
               class="edit-input-text"
-              placeholder="请输入安全管理人"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.safeManager || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 安全管理人电话 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">安全管理人电话</text>
           </view>
@@ -193,7 +177,7 @@
               v-if="isEdit"
               v-model="equipment.safeManagerPhone"
               class="edit-input-text"
-              placeholder="请输入安全管理人电话"
+              placeholder="请输入"
               maxlength="11"
             />
             <text v-else class="cell-text">{{ equipment.safeManagerPhone || '--' }}</text>
@@ -230,8 +214,8 @@
           </view>
         </view>
 
-        <!-- 设备使用地址经度 -->
-        <view class="table-row">
+        <!-- 设备使用地址经度 + 纬度 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">设备使用地址经度</text>
           </view>
@@ -240,15 +224,11 @@
               v-if="isEdit"
               v-model="equipment.equipLongitude"
               class="edit-input-text"
-              placeholder="请输入经度"
+              placeholder="请输入"
               disabled
             />
             <text v-else class="cell-text">{{ equipment.equipLongitude || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 设备使用地址纬度 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">设备使用地址纬度</text>
           </view>
@@ -257,15 +237,15 @@
               v-if="isEdit"
               v-model="equipment.equipLatitude"
               class="edit-input-text"
-              placeholder="请输入纬度"
+              placeholder="请输入"
               disabled
             />
             <text v-else class="cell-text">{{ equipment.equipLatitude || '--' }}</text>
           </view>
         </view>
 
-        <!-- 设备状态 -->
-        <view class="table-row">
+        <!-- 设备状态 + 是否超年限 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">设备状态</text>
             <text v-if="isEdit" class="must">*</text>
@@ -277,10 +257,6 @@
             </view>
             <text v-else class="cell-text">{{ EquipmentStatusMap[equipment.status] || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 是否超年限 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">是否超年限</text>
           </view>
@@ -289,8 +265,8 @@
           </view>
         </view>
 
-        <!-- 超年限时间 -->
-        <view class="table-row">
+        <!-- 超年限时间 + 使用年限 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">超年限时间</text>
           </view>
@@ -308,10 +284,6 @@
             </picker>
             <text v-else class="cell-text">{{ equipment.exceedDate || '--' }}</text>
           </view>
-        </view>
-
-        <!-- 使用年限 -->
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">使用年限</text>
           </view>
@@ -351,7 +323,8 @@
         <text class="section-title">法定检验信息</text>
       </view>
       <view class="table">
-        <view class="table-row">
+        <!-- 上次定检报告编号 + 下次定检日期 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">上次定检报告编号</text>
           </view>
@@ -360,13 +333,10 @@
               v-if="isEdit"
               v-model="equipment.lastCheckReportNo"
               class="edit-input-text"
-              placeholder="请输入上次定检报告编号"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.lastCheckReportNo || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">下次定检日期</text>
           </view>
@@ -386,7 +356,8 @@
           </view>
         </view>
 
-        <view class="table-row">
+        <!-- 安全状况等级 + 允许使用压力 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">安全状况等级</text>
           </view>
@@ -395,13 +366,10 @@
               v-if="isEdit"
               v-model="equipment.lastCheckSafetyStatus"
               class="edit-input-text"
-              placeholder="请输入安全状况等级"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.lastCheckSafetyStatus || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">允许使用压力</text>
           </view>
@@ -410,13 +378,14 @@
               v-if="isEdit"
               v-model="equipment.lastCheckAllowWorkPressure"
               class="edit-input-text"
-              placeholder="请输入允许使用压力"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.lastCheckAllowWorkPressure || '--' }}</text>
           </view>
         </view>
 
-        <view class="table-row">
+        <!-- 允许使用温度 + 允许使用介质 -->
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">允许使用温度</text>
           </view>
@@ -425,13 +394,10 @@
               v-if="isEdit"
               v-model="equipment.lastCheckAllowWorkTemperature"
               class="edit-input-text"
-              placeholder="请输入允许使用温度"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.lastCheckAllowWorkTemperature || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">允许使用介质</text>
           </view>
@@ -440,7 +406,7 @@
               v-if="isEdit"
               v-model="equipment.lastCheckAllowWorkMedium"
               class="edit-input-text"
-              placeholder="请输入允许使用介质"
+              placeholder="请输入"
             />
             <text v-else class="cell-text">{{ equipment.lastCheckAllowWorkMedium || '--' }}</text>
           </view>
@@ -484,7 +450,7 @@
         <text class="section-title">年度检查信息</text>
       </view>
       <view class="table">
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">上次年检报告编号</text>
           </view>
@@ -497,9 +463,6 @@
             />
             <text v-else class="cell-text">{{ equipment.lastYearCheckReportNo || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">下次年检日期</text>
           </view>
@@ -519,7 +482,7 @@
           </view>
         </view>
 
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">安全状况等级</text>
           </view>
@@ -532,9 +495,6 @@
             />
             <text v-else class="cell-text">{{ equipment.lastYearCheckSafetyStatus || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">上次年检结论</text>
           </view>
@@ -549,7 +509,7 @@
           </view>
         </view>
 
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">上次检查结论压力</text>
           </view>
@@ -562,9 +522,6 @@
             />
             <text v-else class="cell-text">{{ equipment.lastYearCheckConclusionPressure || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">上次检查结论温度</text>
           </view>
@@ -655,7 +612,7 @@
         <text class="section-title">超年限检验信息</text>
       </view>
       <view class="table">
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">上次超年限检验报告编号</text>
           </view>
@@ -668,9 +625,6 @@
             />
             <text v-else class="cell-text">{{ equipment.expiredCheckReportNo || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">下次超期限检查日期</text>
           </view>
@@ -690,7 +644,7 @@
           </view>
         </view>
 
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">安全状况等级</text>
           </view>
@@ -703,9 +657,6 @@
             />
             <text v-else class="cell-text">{{ equipment.lastExpiredCheckSafetyStatus || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">允许使用压力</text>
           </view>
@@ -720,7 +671,7 @@
           </view>
         </view>
 
-        <view class="table-row">
+        <view class="table-row-split">
           <view class="table-cell title">
             <text class="cell-title">允许使用温度</text>
           </view>
@@ -733,9 +684,6 @@
             />
             <text v-else class="cell-text">{{ equipment.lastExpiredCheckAllowWorkTemperature || '--' }}</text>
           </view>
-        </view>
-
-        <view class="table-row">
           <view class="table-cell title">
             <text class="cell-title">允许使用介质</text>
           </view>
@@ -1087,30 +1035,35 @@ defineExpose({
   padding: 10px;
 }
 
-.edit-bar {
+.action-buttons {
   display: flex;
   flex-direction: row;
+  align-items: center;
   justify-content: flex-end;
-  gap: 10px;
+  gap: 12px;
   margin-bottom: 10px;
-  
-  .edit-btn,
-  .ocr-btn {
-    padding: 8px 20px;
-    border-radius: 5px;
-    font-size: 14px;
-    border: none;
-  }
-  
-  .edit-btn {
-    background-color: rgb(47, 142, 255);
-    color: #fff;
-  }
-  
-  .ocr-btn {
-    background-color: #faad14;
-    color: #fff;
-  }
+  padding: 10px 15px;
+}
+
+.operate-btn {
+  min-width: 66px;
+  height: 36px;
+  padding: 0 18px;
+  margin-left: 12px;
+  border-radius: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: rgb(47, 142, 255);
+}
+
+.operate-btn-text {
+  font-size: 14px;
+  color: #fff;
+}
+
+.ocr-btn {
+  background-color: #faad14;
 }
 
 .section {
@@ -1144,6 +1097,24 @@ defineExpose({
   border-right: 1px solid rgb(235, 238, 245);
 }
 
+.table-row-split {
+  display: flex;
+  flex-direction: row;
+  border-bottom: 1px solid rgb(235, 238, 245);
+  border-right: 1px solid rgb(235, 238, 245);
+  
+  .table-cell {
+    &.title {
+      width: 110px;
+      min-width: 110px;
+    }
+    
+    &.content {
+      flex: 1;
+    }
+  }
+}
+
 .table-cell {
   padding: 10px;
   

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1127 - 369
src/pages/equipment/detail/components/InspectProject.vue


+ 217 - 0
src/pages/equipment/detail/components/inspectProject/component/UpdateConclusionPopup.vue

@@ -0,0 +1,217 @@
+<template>
+  <view v-if="!isConclusion" class="popup-overlay" @click="handleCancel">
+    <view class="popup-content" @click.stop="preventClose">
+      <view class="popup-header">
+        <text class="popup-title">修改检验结果</text>
+        <view class="close-btn" @click="handleCancel">
+          <text class="close-icon">✕</text>
+        </view>
+      </view>
+
+      <view class="popup-body">
+        <view class="input-section">
+          <textarea
+            v-model="inputValue"
+            class="input-textarea"
+            placeholder="请输入检验结果"
+            maxlength="200"
+          />
+        </view>
+      </view>
+
+      <view class="popup-footer">
+        <view class="footer-btn cancel-btn" @click="handleCancel">
+          <text class="footer-btn-text">取消</text>
+        </view>
+        <view class="footer-btn-btn confirm-btn" @click="handleConfirm">
+          <text class="footer-btn-text confirm-text">确定</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed, watch, onMounted } from 'vue'
+
+interface Props {
+  checkItem?: any
+  fieldKey?: 'reportConclusion' | 'reportResult'
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  fieldKey: 'reportConclusion'
+})
+
+const emit = defineEmits<{
+  hide: []
+  confirm: [value: string]
+}>()
+
+const inputValue = ref('')
+
+const isConclusion = computed(() => props.fieldKey === 'reportConclusion')
+
+const conclusionOptions = ['', '符合', '不符合']
+
+onMounted(() => {
+  if (isConclusion.value) {
+    showConclusionActionSheet()
+  }
+})
+
+const showConclusionActionSheet = () => {
+  const itemList = ['空白', '符合', '不符合']
+  
+  uni.showActionSheet({
+    itemList,
+    success: (res) => {
+      const selectedValue = conclusionOptions[res.tapIndex]
+      emit('confirm', selectedValue)
+      emit('hide')
+    },
+    fail: () => {
+      emit('hide')
+    }
+  })
+}
+
+watch(() => props.checkItem, (newItem) => {
+  if (newItem && newItem.recordJson) {
+    try {
+      const recordJson = typeof newItem.recordJson === 'string'
+        ? JSON.parse(newItem.recordJson)
+        : newItem.recordJson
+      inputValue.value = recordJson[props.fieldKey] || ''
+    } catch {
+      inputValue.value = ''
+    }
+  } else {
+    inputValue.value = ''
+  }
+}, { immediate: true })
+
+const handleConfirm = () => {
+  if (!inputValue.value.trim()) {
+    uni.showToast({
+      title: '请输入检验结果',
+      icon: 'none'
+    })
+    return
+  }
+  emit('confirm', inputValue.value.trim())
+}
+
+const handleCancel = () => {
+  emit('hide')
+}
+
+const preventClose = () => {}
+</script>
+
+<style lang="scss" scoped>
+.popup-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  z-index: 9999;
+}
+
+.popup-content {
+  width: 100%;
+  background-color: #FFFFFF;
+  border-radius: 16rpx 16rpx 0 0;
+  overflow: hidden;
+}
+
+.popup-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  padding: 32rpx;
+  border-bottom: 1rpx solid #EEEEEE;
+}
+
+.popup-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333333;
+}
+
+.close-btn {
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.close-icon {
+  font-size: 32rpx;
+  color: #999999;
+}
+
+.popup-body {
+  padding: 32rpx;
+}
+
+.input-section {
+  width: 100%;
+}
+
+.input-textarea {
+  width: 100%;
+  height: 200rpx;
+  padding: 24rpx;
+  border: 1rpx solid #DDDDDD;
+  border-radius: 8rpx;
+  font-size: 28rpx;
+  color: #333333;
+  background-color: #FFFFFF;
+  resize: none;
+  box-sizing: border-box;
+}
+
+.input-textarea:focus {
+  border-color: #4B8CD9;
+}
+
+.popup-footer {
+  display: flex;
+  flex-direction: row;
+  border-top: 1rpx solid #EEEEEE;
+}
+
+.footer-btn {
+  flex: 1;
+  height: 96rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.footer-btn:active {
+  background-color: #F5F5F5;
+}
+
+.cancel-btn {
+  border-right: 1rpx solid #EEEEEE;
+}
+
+.footer-btn-text {
+  font-size: 28rpx;
+  color: #666666;
+}
+
+.confirm-text {
+  color: #4B8CD9;
+  font-weight: 500;
+}
+</style>

+ 15 - 4
src/pages/equipment/detail/components/inspectProject/component/calcCheckItemPopup.vue

@@ -78,7 +78,21 @@ import {
   queryCheckItemCalcPreFillField,
   updateCheckerReportItemFee
 } from '@/api/task'
-import { useOnline } from '@/hooks/useOnline'
+
+const isOnline = ref(true)
+
+const getOnlineStatus = () => {
+  try {
+    const networkType = uni.getSystemInfoSync?.()?.platform || ''
+    return networkType !== 'devtools'
+  } catch {
+    return true
+  }
+}
+
+onMounted(() => {
+  isOnline.value = getOnlineStatus()
+})
 
 interface CheckItem {
   id: string
@@ -113,9 +127,6 @@ interface Emits {
 const props = defineProps<Props>()
 const emit = defineEmits<Emits>()
 
-const { status: onlineStatus } = useOnline()
-const isOnline = computed(() => onlineStatus.value === '1')
-
 const enterDataList = ref<FormField[]>([])
 const outputDataList = ref<FormField[]>([])
 const feeCalculateJson = ref<Record<string, any>>({})

+ 149 - 0
src/pages/equipment/detail/components/inspectProjectComponent/TipsPopup.vue

@@ -0,0 +1,149 @@
+<template>
+  <view v-if="visible" class="tips-popup-overlay" @click="handleCancel">
+    <view class="tips-popup-content" @click.stop="preventClose">
+      <view class="tips-header">
+        <text class="tips-title">提示</text>
+      </view>
+
+      <view class="tips-body">
+        <text class="tips-text">{{ content }}</text>
+      </view>
+
+      <view class="tips-footer">
+        <view class="tips-btn cancel-btn" @click="handleCancel">
+          <text class="tips-btn-text">取消</text>
+        </view>
+        <view class="tips-btn confirm-btn" @click="handleConfirm">
+          <text class="tips-btn-text confirm-btn-text">确定</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+
+const visible = ref(false)
+const content = ref('')
+let confirmCallback: (() => void) | null = null
+let cancelCallback: (() => void) | null = null
+
+interface PopupOptions {
+  text: string
+  confirm?: () => void
+  cancel?: () => void
+}
+
+const show = (options: PopupOptions) => {
+  content.value = options.text
+  confirmCallback = options.confirm || null
+  cancelCallback = options.cancel || null
+  visible.value = true
+}
+
+const hide = () => {
+  visible.value = false
+  content.value = ''
+  confirmCallback = null
+  cancelCallback = null
+}
+
+const handleConfirm = () => {
+  if (confirmCallback) {
+    confirmCallback()
+  }
+  hide()
+}
+
+const handleCancel = () => {
+  if (cancelCallback) {
+    cancelCallback()
+  }
+  hide()
+}
+
+const preventClose = () => {}
+
+defineExpose({
+  show,
+  hide
+})
+</script>
+
+<style lang="scss" scoped>
+.tips-popup-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+}
+
+.tips-popup-content {
+  width: 80%;
+  max-width: 320px;
+  background-color: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.tips-header {
+  padding: 20px 15px 15px;
+  text-align: center;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.tips-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #333;
+}
+
+.tips-body {
+  padding: 20px 15px;
+  text-align: center;
+}
+
+.tips-text {
+  font-size: 15px;
+  color: #666;
+  line-height: 1.5;
+}
+
+.tips-footer {
+  display: flex;
+  flex-direction: row;
+  border-top: 1px solid #f0f0f0;
+}
+
+.tips-btn {
+  flex: 1;
+  padding: 15px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.cancel-btn {
+  border-right: 1px solid #f0f0f0;
+}
+
+.tips-btn-text {
+  font-size: 16px;
+  color: #666;
+}
+
+.confirm-btn {
+  background-color: #fff;
+}
+
+.confirm-btn-text {
+  color: #4B8CD9;
+  font-weight: 500;
+}
+</style>

+ 61 - 6
src/pages/equipment/detail/equipmentDetail.vue

@@ -69,6 +69,13 @@
           :order-id="orderId"
           :order-item-id="orderItemId"
           :use-online="useOnline"
+          :is-main-checker="dataSource?.isMainChecker"
+          :can-modify-assignments="dataSource?.permissions?.canModifyAssignments"
+          :fetch-get-equipment-detail="fetchGetOnlineEquipmentDetail"
+          :refresh-detail="refreshDetail"
+          :show-select-user-popup="showSelectUserPopupFn"
+          :show-check-project-popup-fn="showCheckProjectPopup"
+          :handle-association-operation-manual="handleAssociationOperationManual"
           @update-report-list="handleUpdateReportList"
           @refresh="refreshDetail"
         />
@@ -123,6 +130,7 @@ import {
   recheckApi,
   getUserGroupUserList,
   getApprovalDetail,
+  getReportTemplateInfo,
 } from '@/api/task'
 import { PressureCheckerMyTaskStatus, PressureReportType } from '@/utils/dictMap'
 import { isMainChecker, canViewCheckItem } from '@/utils/equipmentPermissions'
@@ -164,6 +172,9 @@ onLoad((options) => {
   
   // 如果是检验录入模式
   isInspectMode.value = pageType === 'INSPECT'
+  
+  // 初始化 Tab 列表
+  initTabList()
 })
 
 // 初始化 Tab 列表
@@ -183,6 +194,12 @@ const initTabList = () => {
   tabs.push({ value: '记录文件', id: 'OTHER_REPORT' })
   
   tabList.value = tabs
+  
+  // 根据 pageType 计算初始 Tab 索引
+  const initIndex = tabs.findIndex(item => item.id === pageType)
+  if (initIndex !== -1) {
+    currentTab.value = initIndex
+  }
 }
 
 // 切换 Tab
@@ -204,13 +221,13 @@ const fetchGetOnlineEquipmentDetail = async () => {
       const isMain = isMainChecker(equipment, userInfo.value)
       
       // 过滤检验项目
-      const filteredReportList = reportDic?.reportList
-        ?.map((item: any) => ({
+      const filteredReportList = reportDic?.reportList?.map(
+        (item: any) => ({
           ...item,
           equipId: result.data.taskOrderItem.equipId,
           equipCode: result.data.taskOrderItem.equipCode,
-        }))
-        .filter((item: any) => [
+        })
+      ).filter((item: any) => [
           PressureCheckerMyTaskStatus.CONFIRMED,
           PressureCheckerMyTaskStatus.RECORD_INPUT,
           PressureCheckerMyTaskStatus.RECORD_CHECK,
@@ -218,7 +235,7 @@ const fetchGetOnlineEquipmentDetail = async () => {
           PressureCheckerMyTaskStatus.REPORT_AUDIT,
           PressureCheckerMyTaskStatus.REPORT_APPROVE,
           PressureCheckerMyTaskStatus.REPORT_END,
-        ].includes(item.taskStatus))
+      ].includes(item.taskStatus))
         .filter((item: any) => canViewCheckItem(item, userInfo.value, equipment))
       
       const taskInfo = {
@@ -350,8 +367,46 @@ const goBack = () => {
   uni.navigateBack()
 }
 
+// 显示添加项目弹窗
+const showCheckProjectPopup = () => {
+  uni.showToast({ title: '添加项目功能开发中', icon: 'none' })
+}
+
+// 操作指导书关联
+const handleAssociationOperationManual = async (checkItem: any) => {
+  const { templateId, id, instructionId } = checkItem || {}
+
+  try {
+    const templateResult: any = await getReportTemplateInfo({ id: templateId })
+    const templateInfo = templateResult?.data || {}
+    const { instructionTempId } = templateInfo
+
+    if (!instructionTempId) {
+      uni.showToast({ title: '该模板未配置操作指导书类型', icon: 'error' })
+      return
+    }
+
+    const instructionTempIdList = instructionTempId.split(',')
+    const otherReportList = dataSource.value?.otherReportList || []
+    const filteredOtherReport = otherReportList.filter((item: any) =>
+      item.reportType === PressureReportType.WORKINSTRUCTION &&
+      instructionTempIdList.includes(item.templateId)
+    )
+
+    if (!filteredOtherReport.length) {
+      uni.showToast({ title: '没有符合报告类型的指导书', icon: 'error' })
+      return
+    }
+
+    const url = `/pages/AssociationOperationManual/AssociationOperationManual?orderItemId=${orderItemId}&reportId=${id}&instructionId=${instructionId}&reportDOList=${encodeURIComponent(JSON.stringify(filteredOtherReport))}`
+    uni.navigateTo({ url })
+  } catch (error) {
+    console.error('获取模板详情失败:', error)
+    uni.showToast({ title: '获取模板详情失败', icon: 'error' })
+  }
+}
+
 onMounted(() => {
-  initTabList()
   if (useOnline === '1' && orderItemId) {
     refreshDetail()
   }

+ 78 - 76
src/utils/dictMap.ts

@@ -1,91 +1,93 @@
-/**
- * 字典映射工具
- * 包含检验性质、任务状态等枚举映射
- */
-
-// 检验性质映射
-export const PressureCheckType = {
-  REGULAR: 100, // 定期检验
-  ANNUAL: 200, // 年度检查
-  OVERDUE: 300, // 超年限检验
-} as const
-
-export type PressureCheckTypeValue = typeof PressureCheckType[keyof typeof PressureCheckType]
-
-export const PressureCheckTypeMap: Record<number, string> = {
-  100: '定期检验',
-  200: '年度检查',
-  300: '超年限检验',
+export enum PressureCheckerMyTaskStatus {
+  WAIT_CONFIRM = 100,
+  CANCELLATION = 200,
+  CONFIRMED = 400,
+  RECORD_INPUT = 500,
+  RECORD_CHECK = 510,
+  REPORT_INPUT = 520,
+  REPORT_AUDIT = 600,
+  REPORT_APPROVE = 700,
+  REPORT_CONFIRMATION = 710,
+  REPORT_END = 800,
 }
 
-// 从文本获取检验性质代码
-export const getCheckTypeFromText = (text: string): number | undefined => {
-  const map: Record<string, number> = {
-    '定期检验': 100,
-    '年度检查': 200,
-    '超年限检验': 300,
-  }
-  return map[text]
+export const PressureCheckerMyTaskStatusMap: Record<number, string> = {
+  [PressureCheckerMyTaskStatus.WAIT_CONFIRM]: '待认领',
+  [PressureCheckerMyTaskStatus.CANCELLATION]: '作废',
+  [PressureCheckerMyTaskStatus.CONFIRMED]: '待录入',
+  [PressureCheckerMyTaskStatus.RECORD_INPUT]: '记录录入',
+  [PressureCheckerMyTaskStatus.RECORD_CHECK]: '记录校核',
+  [PressureCheckerMyTaskStatus.REPORT_INPUT]: '报告编制',
+  [PressureCheckerMyTaskStatus.REPORT_AUDIT]: '报告审核',
+  [PressureCheckerMyTaskStatus.REPORT_APPROVE]: '报告审批',
+  [PressureCheckerMyTaskStatus.REPORT_CONFIRMATION]: '意见书处理',
+  [PressureCheckerMyTaskStatus.REPORT_END]: '报告办结',
 }
 
-// 从代码获取检验性质文本
-export const getCheckTypeText = (code: number): string => {
-  return PressureCheckTypeMap[code] || ''
+export enum PressureReportType {
+  MAIN = 100,
+  SUB = 200,
+  SINGLE = 300,
+  SUGGUESTION = 400,
+  MAINQUESTION = 500,
+  INSPECTIONPLAN = 600,
+  WORKINSTRUCTION = 700,
+  SUBPACKAGE = 900,
+  FINITE_SPACE = 1000,
 }
 
-// 检验员我的任务状态
-export const PressureCheckerMyTaskStatus = {
-  CANCELLATION: 200, // 作废
-  CONFIRMED: 400, // 待录入
-  RECORD_INPUT: 500, // 记录录入
-  RECORD_CHECK: 510, // 记录校核
-  REPORT_INPUT: 520, // 报告编制
-} as const
-
-export type PressureCheckerMyTaskStatusValue = typeof PressureCheckerMyTaskStatus[keyof typeof PressureCheckerMyTaskStatus]
-
-export const PressureCheckerMyTaskStatusMap: Record<number, string> = {
-  200: '作废',
-  400: '待录入',
-  500: '记录录入',
-  510: '记录校核',
-  520: '报告编制',
+export const PressureReportTypeMap: Record<number, string> = {
+  [PressureReportType.MAIN]: '主报告',
+  [PressureReportType.SUB]: '子报告',
+  [PressureReportType.SINGLE]: '独走报告',
+  [PressureReportType.SUGGUESTION]: '检验意见通知书',
+  [PressureReportType.MAINQUESTION]: '重大问题线索告知表',
+  [PressureReportType.INSPECTIONPLAN]: '检验方案',
+  [PressureReportType.WORKINSTRUCTION]: '操作指导书',
+  [PressureReportType.SUBPACKAGE]: '分包项目',
+  [PressureReportType.FINITE_SPACE]: '有限空间检验',
 }
 
-// 报告类型
-export const PressureReportType = {
-  MAIN: 100, // 主报告
-  SUB: 200, // 子报告
-  SINGLE: 300, // 独审报告
-  SUGGUESTION: 400, // 意见
-  MAINQUESTION: 500, // 重大问题
-  WORKINSTRUCTION: 600, // 操作指导书
-  INSPECTIONPLAN: 700, // 检验方案
-} as const
-
-export type PressureReportTypeValue = typeof PressureReportType[keyof typeof PressureReportType]
+export enum RecheckStatus {
+  PENDING = 100,
+  APPROVED = 200,
+  REJECTED = 300,
+}
 
-export const PressureReportTypeMap: Record<number, string> = {
-  100: '主报告',
-  200: '子报告',
-  300: '独审报告',
-  400: '意见',
-  500: '重大问题',
-  600: '操作指导书',
-  700: '检验方案',
+export enum ReportStatus {
+  DRAFT = 0,
+  SUBMITTED = 100,
+  APPROVED = 200,
+  REJECTED = 300,
 }
 
-// 设备状态
-export const EquipmentStatus = {
-  IN_USE: 100, // 在用
-  STOPPED: 200, // 停运
-  CANCELLED: 300, // 注销
-} as const
+export enum PressureCheckType {
+  REGULAR = 100,
+  ANNUAL = 200,
+  EXPIRED = 300,
+}
 
-export type EquipmentStatusValue = typeof EquipmentStatus[keyof typeof EquipmentStatus]
+export const PressureCheckTypeMap: Record<number, string> = {
+  [PressureCheckType.REGULAR]: '定期检验',
+  [PressureCheckType.ANNUAL]: '年度检查',
+  [PressureCheckType.EXPIRED]: '超年限检验',
+}
 
 export const EquipmentStatusMap: Record<number, string> = {
-  100: '在用',
-  200: '停运',
-  300: '注销',
+  [PressureCheckType.REGULAR]: '在用',
+  [PressureCheckType.ANNUAL]: '停运',
+  [PressureCheckType.EXPIRED]: '注销',
+}
+
+export function getCheckTypeFromText(checkTypeName?: string): number | undefined {
+  switch (checkTypeName) {
+    case '定期检验':
+      return 100;
+    case '年度检查':
+      return 200;
+    case '超年限检验':
+      return 300;
+    default:
+      return undefined;
+  }
 }

+ 44 - 130
src/utils/equipmentPermissions.ts

@@ -1,151 +1,65 @@
-import { PressureCheckerMyTaskStatus, PressureReportType } from '@/utils/dictMap'
+import { PressureReportType } from './dictMap'
 
-/**
- * 判断是否为主检人
- */
-export const isMainChecker = (equipment: any, userInfo: any): boolean => {
-  if (!equipment || !userInfo) return false
-  
-  // 主检人字段匹配
-  return equipment.mainCheckerId === userInfo.id || 
-         equipment.mainCheckerUser?.id === userInfo.id
+interface CheckItem {
+  id: string
+  reportType?: number
+  checkUsers?: any[]
+  taskStatus?: number
+  [key: string]: any
 }
 
-/**
- * 判断检验项目是否可见
- */
-export const canViewCheckItem = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 主检人可以查看所有项目
-  if (isMainChecker(equipment, userInfo)) {
-    return true
-  }
-  
-  // 检验员只能查看分配给自己的项目
-  const checkUsers = item.checkUsers || []
-  return checkUsers.some((user: any) => user.id === userInfo.id)
-}
-
-/**
- * 判断检验项目是否可编辑
- */
-export const isCheckItemEditable = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 待录入状态且是分配给当前用户的
-  if (item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT) {
-    return canViewCheckItem(item, userInfo, equipment)
-  }
-  
-  return false
+interface Equipment {
+  mainCheckerUser?: any
+  [key: string]: any
 }
 
-/**
- * 判断是否可以修改任务分配
- */
-export const canModifyAssignments = (equipment: any, userInfo: any): boolean => {
-  // 只有主检人可以修改分配
-  return isMainChecker(equipment, userInfo)
+interface UserInfo {
+  id?: string
+  [key: string]: any
 }
 
-/**
- * 判断是否可以查看报告详情
- */
-export const canViewReportDetail = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 主检人可以查看所有报告
-  if (isMainChecker(equipment, userInfo)) {
-    return true
+export const isMainChecker = (equipment: Equipment | undefined, userInfo: UserInfo | undefined): boolean => {
+  if (!equipment?.mainCheckerUser?.id || !userInfo?.id) {
+    return false
   }
-  
-  // 检验员可以查看自己参与的项目报告
-  const checkUsers = item.checkUsers || []
-  return checkUsers.some((user: any) => user.id === userInfo.id)
+  return equipment.mainCheckerUser.id === userInfo.id
 }
 
-/**
- * 判断是否可以提交校核
- */
-export const canSubmitCheck = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 只有待校核状态且是主检人才能提交
-  if (item.taskStatus === PressureCheckerMyTaskStatus.RECORD_CHECK) {
-    return isMainChecker(equipment, userInfo)
+export const isAssignedToCurrentUser = (item: CheckItem, userInfo: UserInfo): boolean => {
+  if (item.reportType === PressureReportType.MAIN) {
+    return item?.equipment?.mainCheckerUser?.id === userInfo?.id
   }
-  
-  return false
+  return item?.checkUsers?.some((user: any) => user?.id === userInfo?.id) || false
 }
 
-/**
- * 判断是否可以编制报告
- */
-export const canCreateReport = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 待报告状态且是分配给当前用户的
-  if (item.taskStatus === PressureCheckerMyTaskStatus.REPORT_INPUT) {
-    return canViewCheckItem(item, userInfo, equipment)
-  }
-  
-  return false
-}
+export const canViewCheckItem = (item: CheckItem, userInfo: UserInfo | undefined, equipment: Equipment): boolean => {
+  if (!userInfo) return false
 
-/**
- * 判断是否可以查看操作指导书
- */
-export const canViewWorkInstruction = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 操作指导书类型
-  if (item.reportType === PressureReportType.WORKINSTRUCTION) {
-    return canViewCheckItem(item, userInfo, equipment)
-  }
-  
-  return false
-}
+  const mainChecker = isMainChecker(equipment, userInfo)
+  if (mainChecker) return true
 
-/**
- * 判断是否可以修改操作指导书
- */
-export const canEditWorkInstruction = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 操作指导书类型且是待录入状态
-  if (item.reportType === PressureReportType.WORKINSTRUCTION && 
-      item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT) {
-    return isMainChecker(equipment, userInfo)
-  }
-  
-  return false
+  return isAssignedToCurrentUser(item, userInfo)
 }
 
-/**
- * 判断是否可以查看检验方案
- */
-export const canViewInspectionPlan = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 检验方案类型
-  if (item.reportType === PressureReportType.INSPECTIONPLAN) {
-    return canViewCheckItem(item, userInfo, equipment)
-  }
-  
-  return false
+export const isCheckItemEditable = (item: CheckItem, userInfo: UserInfo | undefined, equipment: Equipment): boolean => {
+  if (!userInfo) return false
+
+  const mainChecker = isMainChecker(equipment, userInfo)
+  if (mainChecker) return !isAssignedToOthers(item, userInfo)
+
+  return isAssignedToCurrentUser(item, userInfo)
 }
 
-/**
- * 判断是否可以修改检验方案
- */
-export const canEditInspectionPlan = (item: any, userInfo: any, equipment: any): boolean => {
-  if (!item || !userInfo) return false
-  
-  // 检验方案类型且是主检人
-  if (item.reportType === PressureReportType.INSPECTIONPLAN) {
-    return isMainChecker(equipment, userInfo)
+export const isAssignedToOthers = (item: CheckItem, userInfo: UserInfo | undefined): boolean => {
+  if (!userInfo) return false
+
+  let assignedUserId: string | undefined
+
+  if (item.reportType === PressureReportType.MAIN) {
+    assignedUserId = item?.equipment?.mainCheckerUser?.id
+  } else {
+    assignedUserId = item?.checkUsers?.[0]?.id
   }
-  
-  return false
+
+  return !!assignedUserId && assignedUserId !== userInfo.id
 }

+ 59 - 0
src/utils/eventBus.ts

@@ -0,0 +1,59 @@
+type EventCallback = (...args: any[]) => void
+
+class EventBus {
+  private listeners: Map<string, EventCallback[]> = new Map()
+
+  on(event: string, callback: EventCallback): void {
+    if (!this.listeners.has(event)) {
+      this.listeners.set(event, [])
+    }
+    this.listeners.get(event)!.push(callback)
+  }
+
+  off(event: string, callback?: EventCallback): void {
+    if (!callback) {
+      this.listeners.delete(event)
+      return
+    }
+
+    const callbacks = this.listeners.get(event)
+    if (callbacks) {
+      const index = callbacks.indexOf(callback)
+      if (index > -1) {
+        callbacks.splice(index, 1)
+      }
+      if (callbacks.length === 0) {
+        this.listeners.delete(event)
+      }
+    }
+  }
+
+  emit(event: string, ...args: any[]): void {
+    const callbacks = this.listeners.get(event)
+    if (callbacks) {
+      callbacks.forEach(callback => {
+        try {
+          callback(...args)
+        } catch (error) {
+          console.error(`EventBus.emit error for event "${event}":`, error)
+        }
+      })
+    }
+  }
+
+  once(event: string, callback: EventCallback): void {
+    const wrapper = (...args: any[]) => {
+      this.off(event, wrapper)
+      callback(...args)
+    }
+    this.on(event, wrapper)
+  }
+
+  clear(): void {
+    this.listeners.clear()
+  }
+}
+
+const eventBus = new EventBus()
+
+export default eventBus