Prechádzať zdrojové kódy

操作指导书、检验方案审核增加选择审批人弹窗;

yangguanjin 22 hodín pred
rodič
commit
b62564e80b

+ 250 - 2
src/pages/editor/workInstructionEditor.vue

@@ -13,22 +13,82 @@
 <template>
   <div>
     <SpreadDesignerGeneric
+      ref="designerRef"
       :businessConfig="businessConfig"
       :templateData="templateData"
       :templateBlob="templateBlob"
       @save="handleSave"
       @cancel="handleCancel"
-    />
+    >
+      <template #navBar>
+        <ReportNavBar>
+          <template #right>
+            <view class="nav-btn-group">
+              <view
+                class="nav-btn"
+                @click="designerRef?.handleNavButtonClick({ action: 'cancel' })"
+              >
+                取消
+              </view>
+              <view
+                class="nav-btn primary"
+                @click="designerRef?.handleNavButtonClick({ action: 'save' })"
+              >
+                保存
+              </view>
+              <view
+                class="nav-btn submit-btn"
+                @click="openAuditorDialog"
+              >
+                提交审核
+              </view>
+            </view>
+          </template>
+        </ReportNavBar>
+      </template>
+    </SpreadDesignerGeneric>
+
+    <!-- 审核人选择弹窗 -->
+    <view v-if="auditorVisible" class="auditor-overlay" @click="auditorVisible = false">
+      <view class="auditor-dialog" @click.stop>
+        <view class="auditor-title">选择审核人</view>
+        <scroll-view scroll-y class="auditor-list">
+          <view
+            v-for="item in auditorList"
+            :key="item.id"
+            :class="['auditor-item', { active: selectedAuditorId === item.id }]"
+            @click="selectedAuditorId = item.id"
+          >
+            <text class="auditor-name">{{ item.nickname || item.userName }}</text>
+            <view v-if="selectedAuditorId === item.id" class="auditor-check">&#10003;</view>
+          </view>
+          <view v-if="auditorLoading" class="auditor-loading">加载中...</view>
+          <view v-if="!auditorLoading && auditorList.length === 0" class="auditor-empty">
+            暂无可选审核人
+          </view>
+        </scroll-view>
+        <view class="auditor-footer">
+          <view class="auditor-btn cancel" @click="auditorVisible = false">取消</view>
+          <view class="auditor-btn confirm" @click="confirmSubmit">确定</view>
+        </view>
+      </view>
+    </view>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref } from 'vue'
 import SpreadDesignerGeneric from '@/components/SpreadDesigner/spreadDesignerGeneric.vue'
+import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
 import { onLoad } from '@dcloudio/uni-app'
 import { getEnvBaseUrl } from '@/utils/index'
 import { getStandardTemplate } from '@/api/index'
 import { getDynamicTbVal, saveDynamicTbVal } from '@/api/task'
+import { getAuditList } from '@/api/system/user'
+import { useConfigStore } from '@/store/config'
+import { EquipmentType, PressureReportType } from '@/utils/dictMap'
+import { requestFunc, TaskOrderFuncName } from '@/api/ApiRouter/taskOrder'
+import dayjs from 'dayjs'
 
 const businessConfig = ref({
   businessType: 'CZZD',
@@ -40,13 +100,19 @@ const businessConfig = ref({
     saveButtonText: '保存',
     cancelButtonText: '取消',
     showAdditionalToolbar: true,
-    customButtons: [],
   },
 })
+const equipType = useConfigStore().getEquipType()
 const templateBlob = ref<string>('')
 const templateData = ref<any>({})
 const skipEntryKeys = ref<string[]>([])
 
+// 审核人选择相关
+const auditorVisible = ref(false)
+const auditorList = ref<any[]>([])
+const auditorLoading = ref(false)
+const selectedAuditorId = ref<string>('')
+
 let templateId: string = ''
 let orderId: string = ''
 let mode: 'edit' | 'create' = 'edit'
@@ -161,4 +227,186 @@ const handleSave = async (data: any) => {
 const handleCancel = () => {
   uni.navigateBack()
 }
+
+const designerRef = ref<any>(null)
+
+// 打开审核人选择弹窗
+const openAuditorDialog = async () => {
+  auditorVisible.value = true
+  auditorLoading.value = true
+  auditorList.value = []
+  selectedAuditorId.value = ''
+  try {
+    const roleCode = 'WorkBookApprover'
+    const res = await getAuditList({ roleCode })
+    auditorList.value = (res as any)?.data?.list || []
+  } catch (e) {
+    console.error('获取审核人列表失败:', e)
+    uni.showToast({ title: '获取审核人列表失败', icon: 'none' })
+  } finally {
+    auditorLoading.value = false
+  }
+}
+
+// 确认提交审核
+const confirmSubmit = async () => {
+  if (!selectedAuditorId.value) {
+    uni.showToast({ title: '请选择审核人', icon: 'none' })
+    return
+  }
+  auditorVisible.value = false
+  try {
+    const dataSource = designerRef.value?.getSpreadDataSource()
+    if (dataSource) {
+      const saveResult = await saveDynamicTbVal({ params: dataSource, instId: instId.value })
+      if (saveResult?.code !== 0) {
+        throw new Error(saveResult?.msg || '保存报表失败,请重试')
+      }
+    }
+    const reqData = {
+      id: workInstructionId,
+      reportType: PressureReportType.WORKINSTRUCTION,
+      reportUrl: workInstructionId,
+      prepareJson: JSON.stringify({
+        prepareName: uni.getStorageSync('userInfo')?.name || '',
+        prepareDate: dayjs().format('YYYY年MM月DD日'),
+      }),
+      auditUserIds: [selectedAuditorId.value],
+      approveUserIds: [],
+    }
+    uni.showLoading({ title: '提交审核中...', mask: true })
+    const res = await requestFunc(TaskOrderFuncName.SubmitOpinionNoticeApproval, equipType, reqData)
+    if (res?.code === 0) {
+      uni.showToast({ title: '提交审核成功', icon: 'success' })
+    } else {
+      throw new Error(res?.msg || '提交审核失败,请重试')
+    }
+  } catch (error: any) {
+    console.error('提交审核异常:', error)
+    uni.showToast({ title: error.message || '提交审核失败', icon: 'none' })
+  } finally {
+    uni.navigateBack()
+    uni.hideLoading()
+  }
+}
 </script>
+
+<style scoped>
+.nav-btn-group {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.nav-btn {
+  padding: 0 12px;
+  font-size: 12px;
+  line-height: 28px;
+  color: #495057;
+  white-space: nowrap;
+  background-color: #fff;
+  border: 1px solid #dee2e6;
+  border-radius: 4px;
+}
+
+.nav-btn.primary {
+  color: white;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.nav-btn.submit-btn {
+  color: white;
+  background-color: #67c23a;
+  border-color: #67c23a;
+}
+
+/* 审核人弹窗 */
+.auditor-overlay {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 10001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.auditor-dialog {
+  width: 80%;
+  max-height: 70vh;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 12px;
+}
+
+.auditor-title {
+  padding: 16px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  text-align: center;
+  border-bottom: 1px solid #eee;
+}
+
+.auditor-list {
+  max-height: 50vh;
+  padding: 8px 0;
+}
+
+.auditor-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  cursor: pointer;
+}
+
+.auditor-item.active {
+  background-color: #ecf5ff;
+}
+
+.auditor-name {
+  font-size: 14px;
+  color: #333;
+}
+
+.auditor-check {
+  font-size: 16px;
+  color: #007bff;
+}
+
+.auditor-loading,
+.auditor-empty {
+  padding: 20px;
+  font-size: 14px;
+  color: #999;
+  text-align: center;
+}
+
+.auditor-footer {
+  display: flex;
+  border-top: 1px solid #eee;
+}
+
+.auditor-btn {
+  flex: 1;
+  padding: 14px 0;
+  font-size: 15px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.auditor-btn.cancel {
+  color: #666;
+  border-right: 1px solid #eee;
+}
+
+.auditor-btn.confirm {
+  font-weight: 600;
+  color: #007bff;
+}
+</style>

+ 17 - 18
src/pages/equipment/detail/components/OtherReport.vue

@@ -203,6 +203,16 @@ const showDelReportPopup = () => {
     return
   }
 
+  const invalidItems = selectedProjects.value.filter(
+    (item) =>
+      (item.reportType === PressureReportType.WORKINSTRUCTION && item.status !== 0) ||
+      (item.reportType === PressureReportType.INSPECTIONPLAN && item.status !== 0),
+  )
+  if (invalidItems.length) {
+    uni.showToast({ title: '只能作废待提交或者被退回的操作指导书和检验方案', icon: 'error' })
+    uni.hideLoading()
+    return
+  }
   cancelReason.value = ''
   popupVisible.value = true
 }
@@ -225,18 +235,9 @@ const handlePopupConfirm = () => {
 const handleDelReport = async (reason: string) => {
   try {
     uni.showLoading({ title: '作废中...' })
-    const invalidItems = selectedProjects.value.filter(
-      (item) => (item.reportType === PressureReportType.WORKINSTRUCTION && item.status !== 0) 
-      || (item.reportType === PressureReportType.INSPECTIONPLAN && item.status !== 0)
-    )
-    if (invalidItems.length) {
-      uni.showToast({ title: '只能作废待提交的操作指导书和检验方案', icon: 'error' })
-      uni.hideLoading()
-      return
-    }
 
-    const cancelReportReq = selectedProjects.value.map((item) => ({ 
-      id: item.id, 
+    const cancelReportReq = selectedProjects.value.map((item) => ({
+      id: item.id,
       reason,
       isDelete: true,
       reportType: item.reportType,
@@ -280,12 +281,10 @@ const initSelected = () => {
 
 const showStatusTag = (item: ReportItem): boolean => {
   return (
-    [PressureReportType.WORKINSTRUCTION].includes(item.reportType) &&
-    item.status != null &&
-    item.status !== 200
-  ) || (
-    item.reportType === PressureReportType.INSPECTIONPLAN &&
-    item.status !== 300
+    ([PressureReportType.WORKINSTRUCTION].includes(item.reportType) &&
+      item.status != null &&
+      item.status !== 200) ||
+    (item.reportType === PressureReportType.INSPECTIONPLAN && item.status !== 300)
   )
 }
 
@@ -323,7 +322,7 @@ const getStatusText = (item: ReportItem): string => {
 const canEdit = (item: ReportItem): boolean => {
   const { reportType, status } = item
   if (reportType === PressureReportType.WORKINSTRUCTION) {
-    return [0, 200, 300].includes(status)
+    return [0].includes(status)
   } else if (reportType === PressureReportType.INSPECTIONPLAN) {
     return [0].includes(status)
   }

+ 5 - 1
src/pages/inspectionPlanAudit/components/Item.vue

@@ -61,7 +61,7 @@
 
     <view class="btn-group">
       <button v-show="item.status == '100'" class="main-btn" @click="handleOperation">
-          <text class="main-btn-text">{{ operateText }}</text>
+          <text class="main-btn-text">{{ isAuditStage == 1 ? '审核' : '审批' }}</text>
       </button>
     </view>
 
@@ -87,6 +87,10 @@ const checkDate = computed(() => {
   return props.item.checkDate ? props.item.checkDate.join('-') : ''
 })
 
+const isAuditStage = computed(() => {
+  return props.item.approvalId == null ? 1 : 0
+})
+
 const statusDict = computed(() => {
   const statusMap: Record<string, string> = {
     '100': '审核中',

+ 7 - 2
src/pages/inspectionPlanAudit/list/InspectionPlanAuditList.vue

@@ -64,6 +64,7 @@ const params = reactive({
   reportType: 600,
   status: '100',
   bpmUserId: '',
+  approverId: '',
 })
 
 const userStore = useUserStore()
@@ -76,8 +77,8 @@ params.bpmUserId = userInfo.value?.id || ''
 const rightType = ref('100')
 const rightTypeList = [
   // { value: '全部', id: '' },
-  { value: '已通过', id: '200' },
   { value: '审核中', id: '100' },
+  { value: '已通过', id: '200' },
   { value: '已拒绝', id: '300' },
 ]
 
@@ -86,12 +87,15 @@ const fetchList = async (refresh = false) => {
   if (loading.value) return
 
   if (params.status && params.status !== '100') {
+    // 非审核中,查找当前用户审核审批流程结束后的数据
     params.bpmUserId = null
     params.approverId = userInfo.value?.id
   } else if (params.status && params.status === '100') {
+    // 审核中,查找需要当前用户审核审批的数据
     params.bpmUserId = userInfo.value?.id || ''
     params.approverId = ''
   } else if (!params.status) {
+    // 全部,包括需要当前用户审核审批、已处理的数据,但是这部分后端没有处理好,先把全部去掉了
     params.bpmUserId = userInfo.value?.id || ''
     params.approverId = null
     params.status = null
@@ -140,8 +144,9 @@ const handleRightRefresh = (value: string | number | boolean) => {
 
 // 跳转操作
 const pushAction = (item: any) => {
+  const isAuditStage = item.approvalId == null ? 1 : 0
   uni.navigateTo({
-    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}&manualUrl=${encodeURIComponent(item.manualUrl)}`,
+    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}&isAuditStage=${isAuditStage}&manualUrl=${encodeURIComponent(item.manualUrl)}`,
   })
 }
 

+ 172 - 6
src/pages/inspectionPlanAudit/preViewReport/index.vue

@@ -48,6 +48,32 @@
         </view>
       </view>
     </wd-popup>
+
+    <!-- 审核人选择弹窗 -->
+    <view v-if="auditorVisible" class="auditor-overlay" @click="auditorVisible = false">
+      <view class="auditor-dialog" @click.stop>
+        <view class="auditor-title">选择审批人</view>
+        <scroll-view scroll-y class="auditor-list">
+          <view
+            v-for="item in auditorList"
+            :key="item.id"
+            :class="['auditor-item', { active: selectedAuditorId === item.id }]"
+            @click="selectedAuditorId = item.id"
+          >
+            <text class="auditor-name">{{ item.nickname || item.userName }}</text>
+            <view v-if="selectedAuditorId === item.id" class="auditor-check">&#10003;</view>
+          </view>
+          <view v-if="auditorLoading" class="auditor-loading">加载中...</view>
+          <view v-if="!auditorLoading && auditorList.length === 0" class="auditor-empty">
+            暂无可选审批人
+          </view>
+        </scroll-view>
+        <view class="auditor-footer">
+          <view class="auditor-btn cancel" @click="auditorVisible = false">取消</view>
+          <view class="auditor-btn confirm" @click="confirmApproveWithAuditor">确定</view>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -59,6 +85,8 @@ import { useToast } from 'wot-design-uni'
 import BackendPDFViewer from '@/components/BackendPDFViewer/inspectionPlanPDFViewer.vue'
 
 import { useConfigStore } from '@/store/config'
+import { EquipmentType } from '@/utils/dictMap'
+import { getAuditList } from '@/api/system/user'
 
 import { requestFunc, ApprovalFuncName } from '@/api/ApiRouter/approval'
 
@@ -68,6 +96,7 @@ defineOptions({
 
 const id = ref('')
 const templateIdFromRoute = ref('')
+const isAuditStage = ref('')
 
 const equipType = useConfigStore().getEquipType()
 
@@ -76,10 +105,17 @@ const templateId = ref('')
 const refId = ref('')
 const manualUrl = ref('')
 
+// 审核人选择相关
+const auditorVisible = ref(false)
+const auditorList = ref<any[]>([])
+const auditorLoading = ref(false)
+const selectedAuditorId = ref<string>('')
+
 onLoad((options: any) => {
   id.value = options?.id || ''
   manualUrl.value = decodeURIComponent(options?.manualUrl || '' || '')
   templateIdFromRoute.value = options?.templateId || ''
+  isAuditStage.value = options?.isAuditStage || ''
 })
 
 watch(id, (newVal) => {
@@ -142,7 +178,11 @@ const confirmReject = async () => {
 const showSelectUser = ref(false)
 
 const showSelectUserPopupFn = () => {
-  showSelectUser.value = true
+  if (isAuditStage.value == '1') {
+    openAuditorDialog()
+  } else {
+    showSelectUser.value = true
+  }
 }
 
 const closeSelectUserPopup = () => {
@@ -150,18 +190,55 @@ const closeSelectUserPopup = () => {
 }
 
 const confirmApprove = async () => {
+  await doApprove()
+}
+
+// 打开审批人选择弹窗
+const openAuditorDialog = async () => {
+  auditorVisible.value = true
+  auditorLoading.value = true
+  auditorList.value = []
+  selectedAuditorId.value = ''
+  try {
+    const roleCode = equipType === EquipmentType.BOILER ? 'Boiler_Department_Head' : 'Minister_Pipelines'
+    const res = await getAuditList({ roleCode })
+    auditorList.value = (res as any)?.data?.list || []
+  } catch (e) {
+    console.error('获取审批人列表失败:', e)
+    uni.showToast({ title: '获取审批人列表失败', icon: 'none' })
+  } finally {
+    auditorLoading.value = false
+  }
+}
+
+// 确认通过(审批阶段,带审批人)
+const confirmApproveWithAuditor = async () => {
+  if (!selectedAuditorId.value) {
+    uni.showToast({ title: '请选择审批人', icon: 'none' })
+    return
+  }
+  auditorVisible.value = false
+  await doApprove(selectedAuditorId.value)
+}
+
+// 执行通过操作
+const doApprove = async (approveUserId?: string) => {
   uni.showLoading({ title: '提交中...' })
 
   try {
-    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanApprove, equipType, {
+    const params: any = {
       ids: [id.value],
       reason: '',
       reportType: 600,
-    })
+    }
+    if (approveUserId) {
+      params.ratifyId = approveUserId
+    }
+    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanApprove, equipType, params)
     uni.hideLoading()
 
     if (result?.code === 0) {
-      uni.showToast({ title: '审核成功', icon: 'success' })
+      uni.showToast({ title: '提交成功', icon: 'success' })
       uni.$emit('UpdateNum', ['inspectionPlanAudit', 'inspectionApproval'])
       uni.$emit('onRefresh')
       closeSelectUserPopup()
@@ -169,11 +246,11 @@ const confirmApprove = async () => {
         uni.navigateBack()
       }, 1000)
     } else {
-      uni.showToast({ title: result?.msg || '审核失败', icon: 'error' })
+      uni.showToast({ title: result?.msg || '提交失败', icon: 'error' })
     }
   } catch (error) {
     uni.hideLoading()
-    uni.showToast({ title: '审核失败,请重试', icon: 'error' })
+    uni.showToast({ title: '提交失败,请重试', icon: 'error' })
   }
 }
 </script>
@@ -289,4 +366,93 @@ const confirmApprove = async () => {
   margin-bottom: 30px;
   padding: 0 20px;
 }
+
+/* 审批人弹窗 */
+.auditor-overlay {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 10001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.auditor-dialog {
+  width: 80%;
+  max-height: 70vh;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 12px;
+}
+
+.auditor-title {
+  padding: 16px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  text-align: center;
+  border-bottom: 1px solid #eee;
+}
+
+.auditor-list {
+  max-height: 50vh;
+  padding: 8px 0;
+}
+
+.auditor-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  cursor: pointer;
+}
+
+.auditor-item.active {
+  background-color: #ecf5ff;
+}
+
+.auditor-name {
+  font-size: 14px;
+  color: #333;
+}
+
+.auditor-check {
+  font-size: 16px;
+  color: #007bff;
+}
+
+.auditor-loading,
+.auditor-empty {
+  padding: 20px;
+  font-size: 14px;
+  color: #999;
+  text-align: center;
+}
+
+.auditor-footer {
+  display: flex;
+  border-top: 1px solid #eee;
+}
+
+.auditor-btn {
+  flex: 1;
+  padding: 14px 0;
+  font-size: 15px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.auditor-btn.cancel {
+  color: #666;
+  border-right: 1px solid #eee;
+}
+
+.auditor-btn.confirm {
+  font-weight: 600;
+  color: #007bff;
+}
 </style>

+ 1 - 1
src/pages/workInstructionAudit/list/WorkInstructionAuditList.vue

@@ -68,8 +68,8 @@ const equipType = useConfigStore().getEquipType()
 const rightType = ref('100')
 const rightTypeList = [
   // { value: '全部', id: '' },
-  { value: '已通过', id: '200' },
   { value: '审核中', id: '100' },
+  { value: '已通过', id: '200' },
   { value: '已拒绝', id: '300' },
 ]