Bladeren bron

完成操作指导书和检验方案预览与审批

yangguanjin 1 maand geleden
bovenliggende
commit
be6437204f

+ 48 - 2
src/api/ApiRouter/approval.ts

@@ -1,6 +1,16 @@
 import { EquipmentType } from '@/utils/dictMap'
-import { approvePipeRecheck, rejectPipeRecheck } from '@/api/pipe/pipeApproval'
-import { approveBoilerRecheck, rejectBoilerRecheck } from '@/api/boiler/boilerApproval'
+import {
+  approvePipeRecheck,
+  rejectPipeRecheck,
+  approvePipeInspectionPlan,
+  rejectPipeInspectionPlan,
+} from '@/api/pipe/pipeApproval'
+import {
+  approveBoilerRecheck,
+  rejectBoilerRecheck,
+  approveBoilerInspectionPlan,
+  rejectBoilerInspectionPlan,
+} from '@/api/boiler/boilerApproval'
 import { approveApi, rejectApi } from '@/api/pendingVerification'
 
 type Adapter = {
@@ -12,6 +22,8 @@ type Adapter = {
 export enum ApprovalFuncName {
   Approve,
   Reject,
+  InspectionPlanApprove,
+  InspectionPlanReject,
 }
 
 const map = {
@@ -49,6 +61,40 @@ const map = {
       outputAdapter: null,
     },
   },
+  [ApprovalFuncName.InspectionPlanApprove]: {
+    [EquipmentType.BOILER]: {
+      inputAdapter: null,
+      reqFunction: approveBoilerInspectionPlan,
+      outputAdapter: null,
+    },
+    [EquipmentType.PIPE]: {
+      inputAdapter: null,
+      reqFunction: approvePipeInspectionPlan,
+      outputAdapter: null,
+    },
+    [EquipmentType.CONTAINER]: {
+      inputAdapter: null,
+      reqFunction: approveApi,
+      outputAdapter: null,
+    },
+  },
+  [ApprovalFuncName.InspectionPlanReject]: {
+    [EquipmentType.BOILER]: {
+      inputAdapter: null,
+      reqFunction: rejectBoilerInspectionPlan,
+      outputAdapter: null,
+    },
+    [EquipmentType.PIPE]: {
+      inputAdapter: null,
+      reqFunction: rejectPipeInspectionPlan,
+      outputAdapter: null,
+    },
+    [EquipmentType.CONTAINER]: {
+      inputAdapter: null,
+      reqFunction: rejectApi,
+      outputAdapter: null,
+    },
+  },
 }
 
 export const requestFunc = (funcName: ApprovalFuncName, equipType: EquipmentType, params: any) => {

+ 11 - 1
src/api/boiler/boilerApproval.ts

@@ -9,4 +9,14 @@ export const approveBoilerRecheck = (data: any) => {
 // 退回
 export const rejectBoilerRecheck = (data: any) => {
   return httpPost('/pressure2/boiler-task-order/order-item/recheck/reject', data)
-}
+}
+
+// 检验方案通过
+export const approveBoilerInspectionPlan = (data: any) => {
+  return httpPUT('/pressure2/boiler-task-order/order-item/initiateApproval/approve', data)
+}
+
+// 检验方案退回
+export const rejectBoilerInspectionPlan = (data: any) => {
+  return httpPUT('/pressure2/boiler-task-order/order-item/initiateApproval/reject', data)
+}

+ 11 - 1
src/api/pipe/pipeApproval.ts

@@ -9,4 +9,14 @@ export const approvePipeRecheck = (data: any) => {
 // 退回
 export const rejectPipeRecheck = (data: any) => {
   return httpPost('/pressure2/pipe-task-order/order-item/recheck/reject', data)
-}
+}
+
+// 检验方案通过
+export const approvePipeInspectionPlan = (data: any) => {
+  return httpPUT('/pressure2/pipe-task-order/order-item/initiateApproval/approve', data)
+}
+
+// 检验方案退回
+export const rejectPipeInspectionPlan = (data: any) => {
+  return httpPUT('/pressure2/pipe-task-order/order-item/initiateApproval/reject', data)
+}

+ 1 - 2
src/components/components.d.ts

@@ -1,10 +1,9 @@
 declare module 'vue' {
   export interface GlobalComponents {
-AreaSelect: typeof import('./AreaSelect/AreaSelect.vue')['default']
 AreaCascader: typeof import('./AreaCascader/AreaCascader.vue')['default']
-Cascader: typeof import('./Cascader/Cascader.vue')['default']
 BottomOperate: typeof import('./BottomOperate/BottomOperate.vue')['default']
 Breadcrumb: typeof import('./Breadcrumb/Breadcrumb.vue')['default']
+Cascader: typeof import('./Cascader/Cascader.vue')['default']
 CategorySelect: typeof import('./CategorySelect/CategorySelect.vue')['default']
 DateTime: typeof import('./DateTime/DateTime.vue')['default']
 Grid: typeof import('./Grid/Grid.vue')['default']

+ 4 - 2
src/pages.json

@@ -305,7 +305,8 @@
       "layout": "default",
       "style": {
         "navigationBarTitleText": "检验方案审核",
-        "navigationStyle": "custom"
+        "navigationStyle": "custom",
+        "disableScroll": true
       }
     },
     {
@@ -433,7 +434,8 @@
       "layout": "default",
       "style": {
         "navigationBarTitleText": "操作指导书批准",
-        "navigationStyle": "custom"
+        "navigationStyle": "custom",
+        "disableScroll": true
       }
     }
   ],

+ 44 - 44
src/pages/home/index.vue

@@ -364,35 +364,35 @@ const cardList: CardObj[] = [
     getCountFun: getVerificationListNum,
     netWork: true,
   },
+  // {
+  //   title: '待编制',
+  //   id: 'endingPreparation',
+  //   description: '条待编制',
+  //   path: '/pages/pendingPreparation/list/PendingPreparationList',
+  //   iconUrl: 'unitQuery',
+  //   getCountFun: getPendingPreparationListNum,
+  //   netWork: true,
+  // },
+  // {
+  //   title: '待审核',
+  //   id: 'pendingApproval',
+  //   description: '条待审核',
+  //   path: '/pages/pendingApproval/list/PendingApprovalList',
+  //   iconUrl: 'unitQuery',
+  //   getCountFun: getApprovalListNum,
+  //   netWork: true,
+  // },
+  // {
+  //   title: '待审批',
+  //   id: 'pendingRatify',
+  //   description: '条待审批',
+  //   path: '/pages/pendingRatify/list/PendingRatifyList',
+  //   iconUrl: 'unitQuery',
+  //   getCountFun: getRatifyListNum,
+  //   netWork: true,
+  // },
   {
-    title: '待编制',
-    id: 'endingPreparation',
-    description: '条待编制',
-    path: '/pages/pendingPreparation/list/PendingPreparationList',
-    iconUrl: 'unitQuery',
-    getCountFun: getPendingPreparationListNum,
-    netWork: true,
-  },
-  {
-    title: '待审核',
-    id: 'pendingApproval',
-    description: '条待审核',
-    path: '/pages/pendingApproval/list/PendingApprovalList',
-    iconUrl: 'unitQuery',
-    getCountFun: getApprovalListNum,
-    netWork: true,
-  },
-  {
-    title: '待审批',
-    id: 'pendingRatify',
-    description: '条待审批',
-    path: '/pages/pendingRatify/list/PendingRatifyList',
-    iconUrl: 'unitQuery',
-    getCountFun: getRatifyListNum,
-    netWork: true,
-  },
-  {
-    title: '检验方案审核',
+    title: '检验方案审批',
     id: 'inspectionPlanAudit',
     description: '条待审批',
     path: '/pages/inspectionPlanAudit/list/InspectionPlanAuditList',
@@ -405,22 +405,22 @@ const cardList: CardObj[] = [
       }),
     netWork: true,
   },
-  {
-    title: '检验方案批准',
-    id: 'inspectionApproval',
-    description: '条待审批',
-    path: '/pages/inspectionApproval/list/inspectionApprovalList',
-    iconUrl: 'unitQuery',
-    getCountFun: () =>
-      getInspectionPlanAuditListNum({
-        reportType: 600,
-        secondStatus: 100,
-        flag: 1,
-        bpmUserId: userInfo.value?.id,
-        isInspectionSchemeRatify: true,
-      }),
-    netWork: true,
-  },
+  // {
+  //   title: '检验方案批准',
+  //   id: 'inspectionApproval',
+  //   description: '条待审批',
+  //   path: '/pages/inspectionApproval/list/inspectionApprovalList',
+  //   iconUrl: 'unitQuery',
+  //   getCountFun: () =>
+  //     getInspectionPlanAuditListNum({
+  //       reportType: 600,
+  //       secondStatus: 100,
+  //       flag: 1,
+  //       bpmUserId: userInfo.value?.id,
+  //       isInspectionSchemeRatify: true,
+  //     }),
+  //   netWork: true,
+  // },
   {
     title: '操作指导书批准',
     id: 'workInstructionAudit',

+ 1 - 1
src/pages/inspectionApproval/list/inspectionApprovalList.vue

@@ -35,7 +35,7 @@
     <!-- 列表 -->
     <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
       <view v-for="item in listData" :key="item.id" class="item-box">
-        <Item :item="item" :operate-text="'批准'" @handle-operation="pushAction" />
+        <Item :item="item" :operate-text="'批准'" @handle-operation="pushAction(item)" />
       </view>
 
       <view v-if="loading" class="loading-text">加载中...</view>

+ 33 - 17
src/pages/inspectionPlanAudit/components/Item.vue

@@ -2,30 +2,30 @@
   <view class="item-box">
     <view class="item-top">
       <view class="item-top-left">
-        <view class="row">
+        <view class="head-row">
           <view>
-            <text class="item-top-text">
-              任务单号:{{ item.orderNo }}
-            </text>
+            <text class="item-top-text">任务单号:{{ item.orderNo }}</text>
           </view>
+          <button v-show="item.status == '100'" class="main-btn" @click="handleOperation">
+            <text class="main-btn-text">{{ operateText }}</text>
+          </button>
         </view>
-
-        <button
-          v-if="item.status === '100'"
-          class="main-btn"
-          @click="handleOperation"
-        >
-          <text class="main-btn-text">{{ operateText }}</text>
-        </button>
       </view>
     </view>
 
     <view class="item-center">
       <view>
         <view class="row-center">
-          <text class="title">检验方案编号:<text class="text">{{ item.reportNo || '-' }}</text></text>
-          <text class="title">检验时间:<text class="text">{{ checkDate }}</text></text>
-
+          <text class="title">
+            检验方案编号:
+            <text class="text">{{ item.reportNo || '-' }}</text>
+          </text>
+          <text class="title">
+            检验时间:
+            <text class="text">{{ checkDate }}</text>
+          </text>
+        </view>
+        <view class="row-center">
           <view class="row">
             <text class="title">状态:</text>
             <view class="status-badge">
@@ -33,12 +33,21 @@
             </view>
           </view>
         </view>
-        <text class="title">
+        <view v-show="item.status == 100" class="row-center">
+          <view>
+            <text class="title">当前审批节点:</text>
+            <text class="text">{{ item?.currentNode }}</text>
+          </view>
+        </view>
+        <text class="row title">
           方案名称:
           <text class="text">{{ item?.reportName }}</text>
         </text>
 
-        <text class="title">使用单位:<text class="text">{{ item.useUnitName }}</text></text>
+        <text class="title">
+          使用单位:
+          <text class="text">{{ item.useUnitName }}</text>
+        </text>
         <view class="row-center">
           <text class="title">
             提交人:
@@ -104,6 +113,13 @@ const handleOperation = () => {
   flex-direction: row;
   align-items: center;
 }
+.head-row {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
 
 .item-top {
   display: flex;

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

@@ -35,7 +35,7 @@
     <!-- 列表 -->
     <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
       <view v-for="item in listData" :key="item.id" class="item-box">
-        <Item :item="item" :operate-text="'审核'" @handle-operation="pushAction" />
+        <Item :item="item" :operate-text="'审核'" @handle-operation="pushAction(item)" />
       </view>
 
       <view v-if="loading" class="loading-text">加载中...</view>
@@ -90,6 +90,13 @@ const rightTypeList = [
 const fetchList = async (refresh = false) => {
   if (loading.value) return
 
+  if (params.status && params.status !== '100') {
+    params.bpmUserId = null
+    params.approverId = userInfo.value?.id
+  } else{
+    params.bpmUserId = userInfo.value?.id || ''
+  }
+
   params.pageNo = refresh ? 1 : params.pageNo + 1
 
   loading.value = true
@@ -134,7 +141,7 @@ const handleRightRefresh = (id: string) => {
 // 跳转操作
 const pushAction = (item: any) => {
   uni.navigateTo({
-    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}`,
+    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}`,
   })
 }
 

+ 217 - 325
src/pages/inspectionPlanAudit/preViewReport/index.vue

@@ -4,436 +4,328 @@
   style: {
     navigationBarTitleText: '检验方案审核',
     navigationStyle: 'custom',
+    disableScroll: true,
   },
 }
 </route>
 
 <template>
   <view class="preview-container">
-    <!-- 导航栏 -->
-    <NavBar title="检验方案审核">
+    <ReportNavBar>
       <template #right>
-        <view class="action-btn" @click="showGoBackPopup">
-          <text class="btn-text reject">退回</text>
-        </view>
-        <view class="action-btn" @click="handleFinish">
-          <text class="btn-text pass">通过</text>
-        </view>
+        <button class="nav-btn btn-orange" @click="showGoBackPopupFn">退回</button>
+        <button class="nav-btn btn-blue" @click="showSelectUserPopupFn">通过</button>
       </template>
-    </NavBar>
-
-    <!-- PDF 预览区域 -->
-    <view class="pdf-container">
-      <view v-if="loading" class="loading-container">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <web-view v-else-if="pdfUrl" :src="pdfUrl" @error="onPdfError" />
-    </view>
-
-    <!-- 退回弹窗 -->
-    <view v-if="showGoBack" class="popup-overlay" @click="closeGoBackPopup">
-      <view class="popup-content" @click.stop>
-        <text class="popup-title">退回</text>
-        <view class="form-item">
-          <text class="form-label">退回原因:</text>
-          <textarea
-            v-model="goBackReason"
-            class="input-area"
-            placeholder="请输入退回原因"
-            maxlength="-1"
-          />
+    </ReportNavBar>
+
+    <SpreadPDFViewer
+      ref="spreadPdfViewerRef"
+      :businessConfig="businessConfig"
+      :templateData="templateData"
+      :templateBlob="templateBlob"
+    />
+
+    <wd-popup position="center" v-model="showGoBack">
+      <view class="popup-body">
+        <view class="row" style="margin-bottom: 20px">
+          <text class="center-text">退回原因:</text>
+          <textarea v-model="goBackReason" class="input-area" placeholder="请输入" />
         </view>
-        <view class="popup-actions">
-          <button class="action-btn cancel-btn" @click="closeGoBackPopup">取消</button>
-          <button class="action-btn confirm-btn" @click="goBackAction">确定</button>
+        <view class="popup-bottom-box">
+          <button class="bottom-btn white-btn" @click="closeGoBackPopup">取消</button>
+          <button class="bottom-btn" @click="confirmReject">确定</button>
         </view>
       </view>
-    </view>
-
-    <!-- 选择批准人弹窗 -->
-    <view v-if="showSelectUser" class="popup-overlay" @click="closeSelectUserPopup">
-      <view class="popup-content" @click.stop>
-        <text class="popup-title">选择批准人</text>
-        <picker
-          :range="auditUserGroupList"
-          range-key="label"
-          @change="onUserChange"
-        >
-          <view class="picker-value">
-            <text>{{ selectedUser?.label || '请选择' }}</text>
-          </view>
-        </picker>
-        <view class="popup-actions">
-          <button class="action-btn cancel-btn" @click="closeSelectUserPopup">取消</button>
-          <button class="action-btn confirm-btn" @click="confirmSelectUser">确定</button>
+    </wd-popup>
+
+    <wd-popup position="center" v-model="showSelectUser">
+      <view class="popup-body">
+        <view class="confirm-title">确认通过</view>
+        <view class="confirm-content">确定要通过检验方案吗?</view>
+        <view class="popup-bottom-box">
+          <button class="bottom-btn white-btn" @click="closeSelectUserPopup">取消</button>
+          <button class="bottom-btn" @click="confirmApprove">确定</button>
         </view>
       </view>
-    </view>
+    </wd-popup>
   </view>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue'
+import { ref, watch } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import { getApprovalDetail, getUserGroupUserList, passOpinionNoticeApproval, rejectOpinionNoticeApproval } from '@/api/task'
-import NavBar from '@/components/NavBar/NavBar.vue'
-
-interface UserItem {
-  id: string
-  nickname: string
-  label: string
-  value: string
-}
+import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
+import { useToast } from 'wot-design-uni'
+import SpreadPDFViewer from '@/components/SpreadDesigner/SpreadPDFViewer.vue'
 
-const loading = ref(false)
-const pdfUrl = ref('')
-const showGoBack = ref(false)
-const showSelectUser = ref(false)
-const goBackReason = ref('')
-const selectedUser = ref<UserItem | null>(null)
-const auditUserGroupList = ref<UserItem[]>([])
-const defaultSelect = ref<UserItem | null>(null)
+import { useConfigStore } from '@/store/config'
 
-let id = ''
+import { buildFileUrl } from '@/utils/index'
+import { getDynamicTbVal } from '@/api/task'
+import { getStandardTemplate } from '@/api/index'
+import { requestFunc, ApprovalFuncName } from '@/api/ApiRouter/approval'
 
-onLoad((options) => {
-  id = options.id || ''
+defineOptions({
+  name: 'InspectionPlanAuditPreview',
 })
 
-// 获取批准人列表
-const getAuditUserGroupList = async () => {
-  try {
-    const groupRes: any = await getApprovalDetail({})
-    const inspectionApproveId = groupRes?.data?.inspectionApproveId || ''
-    
-    const userRes: any = await getUserGroupUserList({ category: 700, status: 0 })
-    const list = (userRes?.data || []).map((item: any) => ({
-      ...item,
-      label: item.nickname,
-      value: item.id,
-    }))
-    
-    const defaultItem = list.find((item: UserItem) => item.value === inspectionApproveId) || null
-    defaultSelect.value = defaultItem
-    auditUserGroupList.value = list
-  } catch (error) {
-    console.error('获取批准人列表失败:', error)
-  }
-}
+const id = ref('')
+const templateIdFromRoute = ref('')
+
+const equipType = useConfigStore().getEquipType()
+
+const spreadPdfViewerRef = ref<any>(null)
+const templateBlob = ref<any>(null)
+const businessConfig = ref<any>({
+  businessType: 'JYFA',
+  title: '检验方案审核',
+  disableNavigate: true,
+  ui: {
+    title: '检验方案审核',
+    saveButtonText: '保存',
+    cancelButtonText: '取消',
+    showAdditionalToolbar: true,
+    customButtons: [],
+  },
+})
+const templateData = ref<any>({})
 
-// 获取 PDF 文件
-const getReportPdf = async () => {
-  loading.value = true
-  try {
-    // 这里需要根据实际 API 调整
-    // reportPreviewApi 返回的是 base64 或文件 URL
-    const params = {
-      reportId: id,
-      fileType: 200,
-      isBase64: true,
-    }
-    
-    // TODO: 调用报告预览 API
-    // const result = await reportPreviewApi(params)
-    // if (result) {
-    //   pdfUrl.value = `data:application/pdf;base64,${result}`
-    // }
-    
-    // 临时使用空 URL,实际使用时需要替换为真实 API
-    pdfUrl.value = ''
-  } catch (error) {
-    console.error('获取 PDF 失败:', error)
-    uni.showToast({ title: '加载报告失败', icon: 'error' })
-  } finally {
-    loading.value = false
-  }
-}
+onLoad((options: any) => {
+  id.value = options?.id || ''
+  templateIdFromRoute.value = options?.templateId || ''
+})
 
-// 初始化
-onMounted(() => {
-  getReportPdf()
-  getAuditUserGroupList()
+watch(id, (newVal) => {
+  if (newVal) {
+    loadPreviewData()
+  }
 })
 
-// 返回
-const goBack = () => {
-  uni.navigateBack()
+const loadPreviewData = async () => {
+  const tmplId = templateIdFromRoute.value
+  const refId = id.value
+
+  const res = await getStandardTemplate({ id: tmplId })
+  const resData = (res as any).data
+  const dataMap: any = {}
+  const dynamicTbResp = await getDynamicTbVal({ refId })
+  const dynamicTb: any = dynamicTbResp.data
+  for (let i = 0; i < dynamicTb.dynamicTbValRespVOList.length; i++) {
+    const item = dynamicTb.dynamicTbValRespVOList[i]
+    dataMap[item.colCode] = item.valValue
+  }
+  templateData.value = {
+    schema: resData.bindingPathSchema ? JSON.parse(resData.bindingPathSchema) : {},
+    data: {
+      ...dataMap,
+      templateId: tmplId,
+      templateUrl: resData.fileUrl,
+    },
+    pathNameMapping: JSON.parse(resData.bindingPathNameJson),
+    template: tmplId,
+    templateUrl: resData.fileUrl,
+  }
+
+  const fileUri = resData.fileUrl
+  const fileUrl = buildFileUrl(fileUri)
+  const fileBase64 = await spreadPdfViewerRef.value.downloadFileAsBase64(fileUrl)
+  templateBlob.value = fileBase64
 }
 
-// 显示退回弹窗
-const showGoBackPopup = () => {
+const showGoBack = ref(false)
+const goBackReason = ref('')
+
+const showGoBackPopupFn = () => {
+  goBackReason.value = ''
   showGoBack.value = true
 }
 
-// 关闭退回弹窗
 const closeGoBackPopup = () => {
-  showGoBack.value = false
   goBackReason.value = ''
+  showGoBack.value = false
 }
 
-// 显示选择批准人弹窗
-const handleFinish = () => {
-  showSelectUser.value = true
-}
-
-// 关闭选择批准人弹窗
-const closeSelectUserPopup = () => {
-  showSelectUser.value = false
-  selectedUser.value = null
-}
-
-// 用户选择变化
-const onUserChange = (e: any) => {
-  const index = e.detail.value
-  selectedUser.value = auditUserGroupList.value[index]
-}
-
-// 确认选择用户
-const confirmSelectUser = () => {
-  if (!selectedUser.value) {
-    uni.showToast({ title: '请选择批准人', icon: 'error' })
-    return
+const toast = useToast()
+const confirmReject = async () => {
+  if (!goBackReason.value.trim()) {
+    return toast.error('请输入退回原因')
   }
-  
-  uni.showModal({
-    title: '确认',
-    content: `确定通过吗?`,
-    success: (res) => {
-      if (res.confirm) {
-        handlePassFn(selectedUser.value)
-      } else {
-        closeSelectUserPopup()
-      }
-    }
-  })
-}
 
-// 通过操作
-const handlePassFn = async (select: UserItem) => {
-  const params = {
-    ids: [id],
-    ratifyId: select.value,
-    reportType: 600,
-  }
+  uni.showLoading({ title: '退回中...' })
 
   try {
-    const result: any = await passOpinionNoticeApproval(params)
+    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanReject, equipType, {
+      ids: [id.value],
+      reason: goBackReason.value.trim(),
+      reportType: 600,
+    })
+    uni.hideLoading()
+
     if (result?.code === 0) {
-      uni.showToast({ title: '审核成功', icon: 'success', duration: 3000 })
+      uni.showToast({ title: '退回成功', icon: 'success' })
+      uni.$emit('UpdateNum', ['inspectionPlanAudit', 'inspectionApproval'])
+      uni.$emit('onRefresh')
+      closeGoBackPopup()
       setTimeout(() => {
-        uni.$emit('UpdateNum', ['inspectionPlanAudit', 'inspectionApproval'])
-        uni.$emit('onRefresh')
         uni.navigateBack()
-      }, 1500)
+      }, 1000)
     } else {
-      uni.showToast({ title: result?.msg || '审核失败', icon: 'error' })
+      uni.showToast({ title: result?.msg || '退回失败', icon: 'error' })
     }
-  } catch (error: any) {
-    console.error('审核失败:', error)
-    uni.showToast({ title: error?.msg || '审核失败', icon: 'error' })
+  } catch (error) {
+    uni.hideLoading()
+    uni.showToast({ title: '退回失败,请重试', icon: 'error' })
   }
 }
 
-// 退回操作
-const goBackAction = async () => {
-  if (!goBackReason.value.trim()) {
-    uni.showToast({ title: '请输入退回原因', icon: 'error' })
-    return
-  }
+const showSelectUser = ref(false)
 
-  const params = {
-    ids: [id],
-    reason: goBackReason.value.trim(),
-    reportType: 600,
-  }
+const showSelectUserPopupFn = () => {
+  showSelectUser.value = true
+}
+
+const closeSelectUserPopup = () => {
+  showSelectUser.value = false
+}
+
+const confirmApprove = async () => {
+  uni.showLoading({ title: '提交中...' })
 
   try {
-    const result: any = await rejectOpinionNoticeApproval(params)
+    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanApprove, equipType, {
+      ids: [id.value],
+      reason: '',
+      reportType: 600,
+    })
+    uni.hideLoading()
+
     if (result?.code === 0) {
-      uni.showToast({ title: '退回成功', icon: 'success', duration: 3000 })
+      uni.showToast({ title: '审核成功', icon: 'success' })
+      uni.$emit('UpdateNum', ['inspectionPlanAudit', 'inspectionApproval'])
+      uni.$emit('onRefresh')
+      closeSelectUserPopup()
       setTimeout(() => {
-        uni.$emit('UpdateNum', ['inspectionPlanAudit', 'inspectionApproval'])
-        uni.$emit('onRefresh')
         uni.navigateBack()
-      }, 1500)
+      }, 1000)
     } else {
-      uni.showToast({ title: result?.msg || '退回失败', icon: 'error' })
+      uni.showToast({ title: result?.msg || '审核失败', icon: 'error' })
     }
-  } catch (error: any) {
-    console.error('退回失败:', error)
-    uni.showToast({ title: error?.msg || '退回失败', icon: 'error' })
-  } finally {
-    closeGoBackPopup()
+  } catch (error) {
+    uni.hideLoading()
+    uni.showToast({ title: '审核失败,请重试', icon: 'error' })
   }
 }
-
-// PDF 加载错误
-const onPdfError = (event: any) => {
-  console.error('PDF 加载失败:', event)
-  uni.showToast({ title: 'PDF 加载失败', icon: 'error' })
-}
 </script>
 
 <style lang="scss" scoped>
 .preview-container {
   display: flex;
   flex-direction: column;
-  height: 100vh;
+  min-height: 100vh;
   background-color: #f5f5f5;
 }
 
-.navigate-view {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  height: 44px;
-  background-color: #fff;
-  border-bottom: 1px solid #eee;
-  padding: 0 15px;
-}
-
-.navigate-left {
-  display: flex;
-  align-items: center;
-}
-
-.back-icon {
-  width: 20px;
-  height: 20px;
+.nav-btn {
+  padding: 4px 8px;
+  margin: 0 0 0 4px !important;
+  font-size: 12px;
+  line-height: 1.4;
+  color: #fff;
+  border: none;
+  border-radius: 3px;
 }
 
-.navigate-title {
-  font-size: 17px;
-  font-weight: 500;
-  color: #333;
+.btn-orange {
+  background-color: #f5a623;
 }
 
-.navigate-right {
-  display: flex;
-  flex-direction: row;
-  gap: 10px;
+.btn-blue {
+  background-color: #2f8eff;
 }
 
-.action-btn {
-  padding: 6px 12px;
-  border-radius: 3px;
+.popup-body {
+  width: 80vw;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 2vh 0 0 0;
 }
 
-.btn-text {
+.center-text {
   font-size: 14px;
+  color: rgb(51, 51, 51);
 }
 
-.reject {
-  color: #FF4445;
-}
-
-.pass {
-  color: rgb(47, 142, 255);
-}
-
-.pdf-container {
+.input-area {
   flex: 1;
-  background-color: #f5f5f5;
+  height: 80px;
+  border-radius: 6px;
+  border: 1px solid rgb(187, 187, 187);
+  padding: 8px;
+  font-size: 14px;
 }
 
-.loading-container {
+.popup-bottom-box {
   display: flex;
-  justify-content: center;
+  flex-direction: row;
+  justify-content: space-between;
   align-items: center;
-  height: 100%;
-}
-
-.loading-text {
-  color: #999;
-  font-size: 14px;
+  margin-bottom: 20px;
+  padding: 0 20px;
 }
 
-.popup-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: rgba(0, 0, 0, 0.5);
+.bottom-btn {
+  flex: 1;
+  height: 40px;
   display: flex;
   justify-content: center;
-  align-items: flex-start;
-  z-index: 999;
-  padding-top: 20%;
+  align-items: center;
+  border-radius: 4px;
+  background-color: rgb(47, 142, 255);
+  color: rgb(241, 247, 255);
+  font-size: 15px;
+  line-height: 40px;
+  margin: 0;
 }
 
-.popup-content {
-  width: 66.67%;
-  background-color: #fff;
-  border-radius: 10px;
-  padding: 0;
+.bottom-btn::after {
+  border: none;
 }
 
-.popup-title {
-  font-size: 21px;
+.white-btn {
+  border: 1px solid rgb(187, 187, 187);
+  background-color: rgb(255, 255, 255);
   color: rgb(51, 51, 51);
-  padding: 20px;
-  display: block;
+  margin-right: 20px;
 }
 
-.form-item {
+.row {
   display: flex;
-  flex-direction: column;
-  margin-bottom: 20px;
+  flex-direction: row;
+  align-items: flex-start;
   padding: 0 20px;
-}
-
-.form-label {
-  font-size: 16px;
-  color: rgb(51, 51, 51);
-}
-
-.input-area {
-  flex: 1;
-  min-height: 83px;
-  border: 1px solid rgb(187, 187, 187);
-  border-radius: 6px;
-  margin-top: 15px;
-  padding: 5px;
-  font-size: 14px;
+  margin-bottom: 2vh;
 }
 
 .picker-value {
-  padding: 15px 20px;
-  font-size: 16px;
-  color: #333;
-}
-
-.popup-actions {
   display: flex;
-  flex-direction: row;
-  justify-content: space-between;
   align-items: center;
-  margin-bottom: 20px;
-  padding: 0 20px;
-  gap: 10px;
-}
-
-.action-btn {
-  flex: 1;
-  height: 39px;
+  padding: 0 10px;
+  background-color: #f5f5f5;
   border-radius: 4px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  font-size: 16px;
-  border: none;
 }
-
-.cancel-btn {
-  background-color: #fff;
+.confirm-title {
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
   color: rgb(51, 51, 51);
-  border: 1px solid rgb(187, 187, 187);
+  margin-bottom: 20px;
 }
 
-.confirm-btn {
-  background-color: rgb(47, 142, 255);
-  color: rgb(241, 247, 255);
+.confirm-content {
+  font-size: 14px;
+  text-align: center;
+  color: rgb(102, 102, 102);
+  margin-bottom: 30px;
+  padding: 0 20px;
 }
 </style>

+ 0 - 6
src/pages/sign/index.vue

@@ -366,7 +366,6 @@ const getPreviewData = async () => {
       refId = orderReport?.acceptOrderId
       break
     case 'JYRS':
-      uni.showToast({ title: '检验结果告知报表暂未开发', icon: 'error' })
       const orderDetail = await taskOrderRequestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, { id: orderId.value })
       const notificationformReport = orderDetail.data?.notificationformReport
       if (!notificationformReport) {
@@ -607,11 +606,6 @@ const submitConfirm = async () => {
       params.receiverPhone = fwdInputPhone.value
     }
 
-    // if(true) {
-    //   console.log('params........', params)
-    //   return
-    // }
-
     const result: any = await requestFunc(SignFuncName.SubmitSign, equipType, params)
 
     if (result?.code === 0) {

+ 9 - 2
src/pages/workInstructionAudit/list/WorkInstructionAuditList.vue

@@ -32,7 +32,7 @@
 
     <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
       <view v-for="item in listData" :key="item.id" class="item-box">
-        <Item :item="item" :operate-text="'批准'" @handle-operation="pushAction" />
+        <Item :item="item" :operate-text="'批准'" @handle-operation="pushAction(item)" />
       </view>
 
       <view v-if="loading" class="loading-text">加载中...</view>
@@ -80,6 +80,13 @@ const rightTypeList = [
 
 const fetchList = async (refresh = false) => {
   if (loading.value) return
+
+  if (params.status && params.status !== '100') {
+    params.bpmUserId = null
+    params.approverId = userInfo.value?.id
+  } else {
+    params.bpmUserId = userInfo.value?.id || ''
+  }
   params.pageNo = refresh ? 1 : params.pageNo + 1
   loading.value = true
   try {
@@ -114,7 +121,7 @@ const handleRightRefresh = (id: string) => {
 
 const pushAction = (item: any) => {
   uni.navigateTo({
-    url: `/pages/workInstructionAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}`,
+    url: `/pages/workInstructionAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}`,
   })
 }
 

+ 217 - 317
src/pages/workInstructionAudit/preViewReport/index.vue

@@ -4,428 +4,328 @@
   style: {
     navigationBarTitleText: '操作指导书批准',
     navigationStyle: 'custom',
+    disableScroll: true,
   },
 }
 </route>
 
 <template>
   <view class="preview-container">
-    <!-- 导航栏 -->
-    <NavBar title="操作指导书批准">
+    <ReportNavBar>
       <template #right>
-        <view class="action-btn" @click="showGoBackPopup">
-          <text class="btn-text reject">退回</text>
-        </view>
-        <view class="action-btn" @click="handleFinish">
-          <text class="btn-text pass">通过</text>
-        </view>
+        <button class="nav-btn btn-orange" @click="showGoBackPopupFn">退回</button>
+        <button class="nav-btn btn-blue" @click="showApprovePopupFn">通过</button>
       </template>
-    </NavBar>
-
-    <!-- PDF 预览区域 -->
-    <view class="pdf-container">
-      <view v-if="loading" class="loading-container">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <web-view v-else-if="pdfUrl" :src="pdfUrl" @error="onPdfError" />
-    </view>
-
-    <!-- 退回弹窗 -->
-    <view v-if="showGoBack" class="popup-overlay" @click="closeGoBackPopup">
-      <view class="popup-content" @click.stop>
-        <text class="popup-title">退回</text>
-        <view class="form-item">
-          <text class="form-label">退回原因:</text>
-          <textarea
-            v-model="goBackReason"
-            class="input-area"
-            placeholder="请输入退回原因"
-            maxlength="-1"
-          />
+    </ReportNavBar>
+
+    <SpreadPDFViewer
+      ref="spreadPdfViewerRef"
+      :businessConfig="businessConfig"
+      :templateData="templateData"
+      :templateBlob="templateBlob"
+    />
+
+    <wd-popup position="center" v-model="showGoBack">
+      <view class="popup-body">
+        <view class="row" style="margin-bottom: 20px">
+          <text class="center-text">退回原因:</text>
+          <textarea v-model="goBackReason" class="input-area" placeholder="请输入" />
         </view>
-        <view class="popup-actions">
-          <button class="action-btn cancel-btn" @click="closeGoBackPopup">取消</button>
-          <button class="action-btn confirm-btn" @click="goBackAction">确定</button>
+        <view class="popup-bottom-box">
+          <button class="bottom-btn white-btn" @click="closeGoBackPopup">取消</button>
+          <button class="bottom-btn" @click="confirmReject">确定</button>
         </view>
       </view>
-    </view>
-
-    <!-- 选择批准人弹窗 -->
-    <view v-if="showSelectUser" class="popup-overlay" @click="closeSelectUserPopup">
-      <view class="popup-content" @click.stop>
-        <text class="popup-title">选择批准人</text>
-        <picker
-          :range="auditUserGroupList"
-          range-key="label"
-          @change="onUserChange"
-        >
-          <view class="picker-value">
-            <text>{{ selectedUser?.label || '请选择' }}</text>
-          </view>
-        </picker>
-        <view class="popup-actions">
-          <button class="action-btn cancel-btn" @click="closeSelectUserPopup">取消</button>
-          <button class="action-btn confirm-btn" @click="confirmSelectUser">确定</button>
+    </wd-popup>
+
+    <wd-popup position="center" v-model="showApprove">
+      <view class="popup-body">
+        <view class="confirm-title">确认通过</view>
+        <view class="confirm-content">确定要通过操作指导书吗?</view>
+        <view class="popup-bottom-box">
+          <button class="bottom-btn white-btn" @click="closeApprovePopup">取消</button>
+          <button class="bottom-btn" @click="confirmApprove">确定</button>
         </view>
       </view>
-    </view>
+    </wd-popup>
   </view>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue'
+import { ref, watch } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import { getApprovalDetail, getUserGroupUserList, passOpinionNoticeApproval, rejectOpinionNoticeApproval } from '@/api/task'
-import NavBar from '@/components/NavBar/NavBar.vue'
-
-interface UserItem {
-  id: string
-  nickname: string
-  label: string
-  value: string
-}
+import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
+import { useToast } from 'wot-design-uni'
+import SpreadPDFViewer from '@/components/SpreadDesigner/SpreadPDFViewer.vue'
 
-const loading = ref(false)
-const pdfUrl = ref('')
-const showGoBack = ref(false)
-const showSelectUser = ref(false)
-const goBackReason = ref('')
-const selectedUser = ref<UserItem | null>(null)
-const auditUserGroupList = ref<UserItem[]>([])
-const defaultSelect = ref<UserItem | null>(null)
+import { useConfigStore } from '@/store/config'
 
-let id = ''
+import { buildFileUrl } from '@/utils/index'
+import { getDynamicTbVal } from '@/api/task'
+import { getStandardTemplate } from '@/api/index'
+import { requestFunc, ApprovalFuncName } from '@/api/ApiRouter/approval'
 
-onLoad((options) => {
-  id = options.id || ''
+defineOptions({
+  name: 'WorkInstructionAuditPreview',
 })
 
-// 获取批准人列表
-const getAuditUserGroupList = async () => {
-  try {
-    const groupRes: any = await getApprovalDetail({})
-    const inspectionApproveId = groupRes?.data?.inspectionApproveId || ''
-    
-    const userRes: any = await getUserGroupUserList({ category: 700, status: 0 })
-    const list = (userRes?.data || []).map((item: any) => ({
-      ...item,
-      label: item.nickname,
-      value: item.id,
-    }))
-    
-    const defaultItem = list.find((item: UserItem) => item.value === inspectionApproveId) || null
-    defaultSelect.value = defaultItem
-    auditUserGroupList.value = list
-  } catch (error) {
-    console.error('获取批准人列表失败:', error)
-  }
-}
+const id = ref('')
+const templateIdFromRoute = ref('')
+
+const equipType = useConfigStore().getEquipType()
+
+const spreadPdfViewerRef = ref<any>(null)
+const templateBlob = ref<any>(null)
+const businessConfig = ref<any>({
+  businessType: 'ZDS',
+  title: '操作指导书批准',
+  disableNavigate: true,
+  ui: {
+    title: '操作指导书批准',
+    saveButtonText: '保存',
+    cancelButtonText: '取消',
+    showAdditionalToolbar: true,
+    customButtons: [],
+  },
+})
+const templateData = ref<any>({})
 
-// 获取 PDF 文件
-const getReportPdf = async () => {
-  loading.value = true
-  try {
-    const params = {
-      reportId: id,
-      fileType: 200,
-      isBase64: true,
-    }
-    
-    // TODO: 调用报告预览 API
-    pdfUrl.value = ''
-  } catch (error) {
-    console.error('获取 PDF 失败:', error)
-    uni.showToast({ title: '加载报告失败', icon: 'error' })
-  } finally {
-    loading.value = false
-  }
-}
+onLoad((options: any) => {
+  id.value = options?.id || ''
+  templateIdFromRoute.value = options?.templateId || ''
+})
 
-// 初始化
-onMounted(() => {
-  getReportPdf()
-  getAuditUserGroupList()
+watch(id, (newVal) => {
+  if (newVal) {
+    loadPreviewData()
+  }
 })
 
-// 返回
-const goBack = () => {
-  uni.navigateBack()
+const loadPreviewData = async () => {
+  const tmplId = templateIdFromRoute.value
+  const refId = id.value
+
+  const res = await getStandardTemplate({ id: tmplId })
+  const resData = (res as any).data
+  const dataMap: any = {}
+  const dynamicTbResp = await getDynamicTbVal({ refId })
+  const dynamicTb: any = dynamicTbResp.data
+  for (let i = 0; i < dynamicTb.dynamicTbValRespVOList.length; i++) {
+    const item = dynamicTb.dynamicTbValRespVOList[i]
+    dataMap[item.colCode] = item.valValue
+  }
+  templateData.value = {
+    schema: resData.bindingPathSchema ? JSON.parse(resData.bindingPathSchema) : {},
+    data: {
+      ...dataMap,
+      templateId: tmplId,
+      templateUrl: resData.fileUrl,
+    },
+    pathNameMapping: JSON.parse(resData.bindingPathNameJson),
+    template: tmplId,
+    templateUrl: resData.fileUrl,
+  }
+
+  const fileUri = resData.fileUrl
+  const fileUrl = buildFileUrl(fileUri)
+  const fileBase64 = await spreadPdfViewerRef.value.downloadFileAsBase64(fileUrl)
+  templateBlob.value = fileBase64
 }
 
-// 显示退回弹窗
-const showGoBackPopup = () => {
+const showGoBack = ref(false)
+const goBackReason = ref('')
+
+const showGoBackPopupFn = () => {
+  goBackReason.value = ''
   showGoBack.value = true
 }
 
-// 关闭退回弹窗
 const closeGoBackPopup = () => {
-  showGoBack.value = false
   goBackReason.value = ''
+  showGoBack.value = false
 }
 
-// 显示选择批准人弹窗
-const handleFinish = () => {
-  showSelectUser.value = true
-}
-
-// 关闭选择批准人弹窗
-const closeSelectUserPopup = () => {
-  showSelectUser.value = false
-  selectedUser.value = null
-}
-
-// 用户选择变化
-const onUserChange = (e: any) => {
-  const index = e.detail.value
-  selectedUser.value = auditUserGroupList.value[index]
-}
-
-// 确认选择用户
-const confirmSelectUser = () => {
-  if (!selectedUser.value) {
-    uni.showToast({ title: '请选择批准人', icon: 'error' })
-    return
+const toast = useToast()
+const confirmReject = async () => {
+  if (!goBackReason.value.trim()) {
+    return toast.error('请输入退回原因')
   }
-  
-  uni.showModal({
-    title: '确认',
-    content: `确定通过吗?`,
-    success: (res) => {
-      if (res.confirm) {
-        handlePassFn(selectedUser.value)
-      } else {
-        closeSelectUserPopup()
-      }
-    }
-  })
-}
 
-// 通过操作
-const handlePassFn = async (select: UserItem) => {
-  const params = {
-    ids: [id],
-    ratifyId: select.value,
-    reportType: 700,
-  }
+  uni.showLoading({ title: '退回中...' })
 
   try {
-    const result: any = await passOpinionNoticeApproval(params)
+    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanReject, equipType, {
+      ids: [id.value],
+      reason: goBackReason.value.trim(),
+      reportType: 700,
+    })
+    uni.hideLoading()
+
     if (result?.code === 0) {
-      uni.showToast({ title: '批准成功', icon: 'success', duration: 3000 })
+      uni.showToast({ title: '退回成功', icon: 'success' })
+      uni.$emit('UpdateNum', ['workInstructionAudit'])
+      uni.$emit('onRefresh')
+      closeGoBackPopup()
       setTimeout(() => {
-        uni.$emit('UpdateNum', ['workInstructionAudit'])
-        uni.$emit('onRefresh')
         uni.navigateBack()
-      }, 1500)
+      }, 1000)
     } else {
-      uni.showToast({ title: result?.msg || '批准失败', icon: 'error' })
+      uni.showToast({ title: result?.msg || '退回失败', icon: 'error' })
     }
-  } catch (error: any) {
-    console.error('批准失败:', error)
-    uni.showToast({ title: error?.msg || '批准失败', icon: 'error' })
+  } catch (error) {
+    uni.hideLoading()
+    uni.showToast({ title: '退回失败,请重试', icon: 'error' })
   }
 }
 
-// 退回操作
-const goBackAction = async () => {
-  if (!goBackReason.value.trim()) {
-    uni.showToast({ title: '请输入退回原因', icon: 'error' })
-    return
-  }
+const showApprove = ref(false)
 
-  const params = {
-    ids: [id],
-    reason: goBackReason.value.trim(),
-    reportType: 700,
-  }
+const showApprovePopupFn = () => {
+  showApprove.value = true
+}
+
+const closeApprovePopup = () => {
+  showApprove.value = false
+}
+
+const confirmApprove = async () => {
+  uni.showLoading({ title: '提交中...' })
 
   try {
-    const result: any = await rejectOpinionNoticeApproval(params)
+    const result: any = await requestFunc(ApprovalFuncName.InspectionPlanApprove, equipType, {
+      ids: [id.value],
+      reason: '',
+      reportType: 700,
+    })
+    uni.hideLoading()
+
     if (result?.code === 0) {
-      uni.showToast({ title: '退回成功', icon: 'success', duration: 3000 })
+      uni.showToast({ title: '批准成功', icon: 'success' })
+      uni.$emit('UpdateNum', ['workInstructionAudit'])
+      uni.$emit('onRefresh')
+      closeApprovePopup()
       setTimeout(() => {
-        uni.$emit('UpdateNum', ['workInstructionAudit'])
-        uni.$emit('onRefresh')
         uni.navigateBack()
-      }, 1500)
+      }, 1000)
     } else {
-      uni.showToast({ title: result?.msg || '退回失败', icon: 'error' })
+      uni.showToast({ title: result?.msg || '批准失败', icon: 'error' })
     }
-  } catch (error: any) {
-    console.error('退回失败:', error)
-    uni.showToast({ title: error?.msg || '退回失败', icon: 'error' })
-  } finally {
-    closeGoBackPopup()
+  } catch (error) {
+    uni.hideLoading()
+    uni.showToast({ title: '批准失败,请重试', icon: 'error' })
   }
 }
-
-// PDF 加载错误
-const onPdfError = (event: any) => {
-  console.error('PDF 加载失败:', event)
-  uni.showToast({ title: 'PDF 加载失败', icon: 'error' })
-}
 </script>
 
 <style lang="scss" scoped>
 .preview-container {
   display: flex;
   flex-direction: column;
-  height: 100vh;
+  min-height: 100vh;
   background-color: #f5f5f5;
 }
 
-.navigate-view {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  height: 44px;
-  background-color: #fff;
-  border-bottom: 1px solid #eee;
-  padding: 0 15px;
-}
-
-.navigate-left {
-  display: flex;
-  align-items: center;
-}
-
-.back-icon {
-  width: 20px;
-  height: 20px;
+.nav-btn {
+  padding: 4px 8px;
+  margin: 0 0 0 4px !important;
+  font-size: 12px;
+  line-height: 1.4;
+  color: #fff;
+  border: none;
+  border-radius: 3px;
 }
 
-.navigate-title {
-  font-size: 17px;
-  font-weight: 500;
-  color: #333;
+.btn-orange {
+  background-color: #f5a623;
 }
 
-.navigate-right {
-  display: flex;
-  flex-direction: row;
-  gap: 10px;
+.btn-blue {
+  background-color: #2f8eff;
 }
 
-.action-btn {
-  padding: 6px 12px;
-  border-radius: 3px;
+.popup-body {
+  width: 80vw;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 2vh 0 0 0;
 }
 
-.btn-text {
+.center-text {
   font-size: 14px;
+  color: rgb(51, 51, 51);
 }
 
-.reject {
-  color: #FF4445;
-}
-
-.pass {
-  color: rgb(47, 142, 255);
-}
-
-.pdf-container {
+.input-area {
   flex: 1;
-  background-color: #f5f5f5;
+  height: 80px;
+  border-radius: 6px;
+  border: 1px solid rgb(187, 187, 187);
+  padding: 8px;
+  font-size: 14px;
 }
 
-.loading-container {
+.popup-bottom-box {
   display: flex;
-  justify-content: center;
+  flex-direction: row;
+  justify-content: space-between;
   align-items: center;
-  height: 100%;
-}
-
-.loading-text {
-  color: #999;
-  font-size: 14px;
+  margin-bottom: 20px;
+  padding: 0 20px;
 }
 
-.popup-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: rgba(0, 0, 0, 0.5);
+.bottom-btn {
+  flex: 1;
+  height: 40px;
   display: flex;
   justify-content: center;
-  align-items: flex-start;
-  z-index: 999;
-  padding-top: 20%;
+  align-items: center;
+  border-radius: 4px;
+  background-color: rgb(47, 142, 255);
+  color: rgb(241, 247, 255);
+  font-size: 15px;
+  line-height: 40px;
+  margin: 0;
 }
 
-.popup-content {
-  width: 66.67%;
-  background-color: #fff;
-  border-radius: 10px;
-  padding: 0;
+.bottom-btn::after {
+  border: none;
 }
 
-.popup-title {
-  font-size: 21px;
+.white-btn {
+  border: 1px solid rgb(187, 187, 187);
+  background-color: rgb(255, 255, 255);
   color: rgb(51, 51, 51);
-  padding: 20px;
-  display: block;
+  margin-right: 20px;
 }
 
-.form-item {
+.row {
   display: flex;
-  flex-direction: column;
-  margin-bottom: 20px;
+  flex-direction: row;
+  align-items: flex-start;
   padding: 0 20px;
-}
-
-.form-label {
-  font-size: 16px;
-  color: rgb(51, 51, 51);
-}
-
-.input-area {
-  flex: 1;
-  min-height: 83px;
-  border: 1px solid rgb(187, 187, 187);
-  border-radius: 6px;
-  margin-top: 15px;
-  padding: 5px;
-  font-size: 14px;
+  margin-bottom: 2vh;
 }
 
 .picker-value {
-  padding: 15px 20px;
-  font-size: 16px;
-  color: #333;
-}
-
-.popup-actions {
   display: flex;
-  flex-direction: row;
-  justify-content: space-between;
   align-items: center;
-  margin-bottom: 20px;
-  padding: 0 20px;
-  gap: 10px;
-}
-
-.action-btn {
-  flex: 1;
-  height: 39px;
+  padding: 0 10px;
+  background-color: #f5f5f5;
   border-radius: 4px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  font-size: 16px;
-  border: none;
 }
-
-.cancel-btn {
-  background-color: #fff;
+.confirm-title {
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
   color: rgb(51, 51, 51);
-  border: 1px solid rgb(187, 187, 187);
+  margin-bottom: 20px;
 }
 
-.confirm-btn {
-  background-color: rgb(47, 142, 255);
-  color: rgb(241, 247, 255);
+.confirm-content {
+  font-size: 14px;
+  text-align: center;
+  color: rgb(102, 102, 102);
+  margin-bottom: 30px;
+  padding: 0 20px;
 }
 </style>