Explorar o código

feat: 演练评估的审批

wyf hai 9 meses
pai
achega
428933d2a6

+ 11 - 0
src/api/emergency-drill/emergency-drill.ts

@@ -155,6 +155,17 @@ export const signDrillScript = (data: { drillPlanId: number; planToParticipateCo
   });
 };
 
+/**
+ * 演练记录审批
+ */
+export const approveDrillRecord = (data: { drillPlanId: number; approvalStatus: number; returnReason?: string }) => {
+  return http.request({
+    url: '/emergencyDrill/approveEmergencyDrillRecord',
+    method: 'post',
+    data,
+  });
+};
+
 /**
  * 查询审批模板
  */

+ 1 - 1
src/views/emergency/emergency-drill/PageDrillSignItem.vue

@@ -4,7 +4,7 @@
       <BreadcrumbBack />
       <span class="breadcrumb-title">{{ headerTitle }}</span>
     </header>
-    <component :is="dynamicComponent" :id="id" :status="status" />
+    <component :is="dynamicComponent" :id="Number(id)" :status="status" />
   </div>
 </template>
 

+ 6 - 17
src/views/emergency/emergency-drill/components/DrillApprovalDialog.vue

@@ -2,13 +2,13 @@
   <BasicDialog ref="basicDialogRef" title="提交审批" @refresh="refreshFromData">
     <template #form>
       <el-form ref="formRef" :model="formData" label-width="auto">
-        <el-form-item prop="approvalDescription" label="审批描述">
+        <el-form-item prop="approvalDescription" label="审批描述">
           <el-input v-model="formData.approvalDescription" placeholder="请输入审批描述" />
         </el-form-item>
         <el-form-item
           v-for="(item, index) in formData.approverListInfo"
           :key="item.approvalOrder"
-          :label="`第${item.approvalOrder + 1}步,${item.deptName}(${item.approvalType ? '或签' : '会签'})`"
+          :label="`第${item.approvalOrder}步:${item.nodeDescription}(${item.approvalType ? '或签' : '会签'})`"
           :prop="'approverListInfo.' + index + '.approverList'"
           :rules="{ required: true, message: '请选择审批人', trigger: 'change' }"
         >
@@ -71,10 +71,10 @@
   function initForm(originalData: DrillApprovalItem[]) {
     // 初始化转换结果
     const result: {
+      nodeDescription: string;
       approvalOrder: number;
       approvalType: number;
       approverType: number;
-      deptName: string;
       approverList: any[];
       approverIds?: number[];
       // approverName: string[];
@@ -87,24 +87,19 @@
         // 收集当前审批环节的所有审批人ID和名称
         const approverIds: number[] = [];
         const approverNames: string[] = [];
-        // 默认为第一个流程项的部门名称
-        let deptName = '';
+
         // 处理流程列表
         item.processInfoList.forEach((process, index) => {
           // 收集审批人信息
           approverIds.push(process.approverId);
           approverNames.push(process.approverName);
-          // 设置部门名称(取第一个流程项的部门名称)
-          if (index === 0) {
-            deptName = process.approvalDeptName;
-          }
         });
         // 添加到转换结果中
         result.push({
+          nodeDescription: item.nodeDescription,
           approvalOrder: item.approvalOrder,
           approverType: item.approverType,
           approvalType: item.processInfoList[0].approvalType,
-          deptName,
           approverList: approverNames,
           approverIds: approverIds,
           // approverName: approverNames,
@@ -112,10 +107,10 @@
       } else {
         // 自选
         result.push({
+          nodeDescription: item.nodeDescription,
           approvalOrder: item.approvalOrder,
           approverType: item.approverType,
           approvalType: item.processInfoList[0].approvalType,
-          deptName: '',
           approverList: [],
           // approverId: [],
           // approverName: [],
@@ -141,12 +136,6 @@
     emits('filled', formData);
   };
 
-  // const handleCancel = () => {
-  //   if (!basicDialogRef.value) return;
-  //   clearForm();
-  //   basicDialogRef.value.closeDialog();
-  // };
-
   // 人员选择
   const userOptions = ref<QueryUserInfoByUserNameRes[]>([]);
   const loading = ref<boolean>(false);

+ 9 - 2
src/views/emergency/emergency-drill/components/DrillPlanCreateItem.vue

@@ -86,13 +86,20 @@
       DRILL_CREATE_FORM_DATA,
       DRILL_CREATE_FORM_RULES,
     );
-  const cascaderProp = { multiple: true, expandTrigger: 'hover', emitPath: false, value: 'id', label: 'deptName' };
+  const cascaderProp = {
+    multiple: true,
+    expandTrigger: 'hover',
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
 
   // 获取级联部门数据
   const deptTree = ref<DeptTree[]>();
   const loadDeptTreeData = async () => {
     const result = await getAllDepartments();
-    deptTree.value = result;
+    deptTree.value = result[0].children;
   };
 
   // 获取所有审批流程

+ 10 - 2
src/views/emergency/emergency-drill/components/DrillPlanExecuteForm.vue

@@ -38,6 +38,7 @@
           ref="uploadFilesRef"
           label="上传材料"
           :fileList="unformatAttachment(props.drillData.drillScript)"
+          :maxCount="1"
           @uploadSuccess="
             (files) => {
               fileList = files;
@@ -91,12 +92,19 @@
       props.drillData.drillScope === props.insName ? INS_DRILL_EXECUTE_FORM_RULES : DEP_DRILL_EXECUTE_FORM_RULES,
     );
 
-  const cascaderProp = { multiple: true, expandTrigger: 'hover', emitPath: false, value: 'id', label: 'deptName' };
+  const cascaderProp = {
+    multiple: true,
+    expandTrigger: 'hover',
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
 
   const deptTree = ref<DeptTree[]>();
   const loadDeptTreeData = async () => {
     const result = await getAllDepartments();
-    deptTree.value = result;
+    deptTree.value = result[0].children;
   };
 
   const userOptions = ref<QueryUserInfoByUserNameRes[]>([]);

+ 1 - 1
src/views/emergency/emergency-drill/components/DrillPlanExecuteItem.vue

@@ -76,7 +76,7 @@
         </el-row>
       </div>
     </div>
-    <div class="drill-execute-form">
+    <div class="drill-execute-form" style="margin-top: 20px">
       <div class="drill-container__title">
         <div class="drill-container--line"></div>
         <span>演练实施</span>

+ 6 - 6
src/views/emergency/emergency-drill/components/DrillPlanRecordForm.vue

@@ -13,9 +13,9 @@
       </template>
     </BasicForm>
   </div>
-  <DrillApprovalDialog
+  <DrillPlanApprovalDialog
     v-if="approvalInfo"
-    ref="drillApprovalDialogRef"
+    ref="drillPlanApprovalDialogRef"
     :approval-data="approvalInfo"
     @filled="handleSubmitRecord"
   />
@@ -25,7 +25,7 @@
   import BasicForm from '@/components/BasicForm.vue';
   import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
   import ShowImages from './ShowImages.vue';
-  import DrillApprovalDialog from './DrillApprovalDialog.vue';
+  import DrillPlanApprovalDialog from './DrillPlanApprovalDialog.vue';
   import { useFormConfigHook } from '@/hooks/useFormConfigHook';
   import { DRILL_RECORD_FORM_CONFIG, DRILL_RECORD_FORM_RULES, DRILL_RECORD_FORM_DATA } from '../configs/plan/form';
   import { DrillApprovalItem, DrillApprovalRuleForm, DrillPlanRecord, DrillRecordRuleForm } from '../types';
@@ -122,14 +122,14 @@
   });
 
   // 弹窗
-  const drillApprovalDialogRef = ref();
+  const drillPlanApprovalDialogRef = ref();
 
   function openApprovalDialog() {
-    drillApprovalDialogRef.value.openDialog();
+    drillPlanApprovalDialogRef.value.openDialog();
   }
 
   function closeApprovalDialog() {
-    drillApprovalDialogRef.value.closeDialog();
+    drillPlanApprovalDialogRef.value.closeDialog();
   }
 
   function handleSubmitRecord(approverData: DrillApprovalRuleForm) {

+ 42 - 14
src/views/emergency/emergency-drill/components/DrillPlanViewRecords.vue

@@ -1,20 +1,35 @@
 <template>
   <div v-if="recordData" class="drill-plan-record">
-    <div v-for="item in DRILL_VIEW_RECORD" :key="item.value" class="item">
-      <span class="label">{{ item.label }}</span>
-      <span class="text"> {{ recordData![item.value] }}</span>
+    <div class="item">
+      <span class="label">演练内容:</span>
+      <span class="text">{{ recordData.drillContent }}</span>
+    </div>
+    <div class="item">
+      <span class="label">演练时间:</span>
+      <span class="text">{{ recordData.drillTime }}</span>
+    </div>
+    <div class="item">
+      <span class="label">演练地点:</span>
+      <span class="text">{{ recordData.drillLocation }}</span>
+    </div>
+    <div class="item">
+      <span class="label">参与人员描述:</span>
+      <span class="text">{{ recordData.participants }}</span>
+    </div>
+    <div class="item">
+      <span class="label">演练描述:</span>
+      <span class="text">{{ recordData.drillDescription }}</span>
+    </div>
+    <div class="item">
+      <span class="label">演练照片:</span>
+      <ShowImages style="min-height: 100px; flex: 1" :image-list="getImageUrls(recordData.drillImage)" />
+    </div>
+    <div class="item">
+      <span class="label">演练效果评估:</span>
+      <span class="text">{{ recordData.drillEffectAssess }}</span>
     </div>
   </div>
-  <div
-    v-else
-    style="
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      flex-direction: column;
-      margin-top: calc(70vh - 500px);
-    "
-  >
+  <div v-else class="empty">
     <img :src="EmptyImg" alt="empty" />
     <span>暂无数据</span>
   </div>
@@ -24,8 +39,8 @@
   import { onMounted, ref } from 'vue';
   import { useRoute } from 'vue-router';
   import { DrillPlanRecord } from '../types';
-  import { DRILL_VIEW_RECORD } from '../constants';
   import { queryEmergencyDrillRecordInView } from '@/api/emergency-drill/emergency-drill';
+  import ShowImages from './ShowImages.vue';
   import EmptyImg from 'assets/images/empty@1X.png';
 
   const route = useRoute();
@@ -43,6 +58,11 @@
   onMounted(() => {
     getData();
   });
+
+  function getImageUrls(imgs: string) {
+    if (!imgs) return [];
+    return JSON.parse(imgs);
+  }
 </script>
 
 <style scoped>
@@ -62,5 +82,13 @@
   .text {
     flex: 1;
     word-break: break-all;
+    white-space: pre;
+  }
+  .empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    margin-top: calc(70vh - 500px);
   }
 </style>

+ 116 - 0
src/views/emergency/emergency-drill/components/DrillSignApprovalDialog.vue

@@ -0,0 +1,116 @@
+<template>
+  <BasicDialog ref="basicDialogRef" title="提交审批" @refresh="refreshFromData">
+    <template #form>
+      <el-form ref="formRef" :model="formData" label-width="auto">
+        <el-form-item
+          label="审批:"
+          prop="approvalStatus"
+          :rules="{ required: true, message: '请选择', trigger: 'change' }"
+        >
+          <el-radio-group v-model="formData.approvalStatus">
+            <el-radio :value="2">通过</el-radio>
+            <el-radio :value="3">退回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item
+          v-if="formData.approvalStatus === 3"
+          label="退回原因:"
+          prop="returnReason"
+          :rules="{ required: true, message: '请选择', trigger: 'change' }"
+        >
+          <el-input v-model="formData.returnReason" placeholder="请输入退回原因" />
+        </el-form-item>
+      </el-form>
+    </template>
+    <template #footer>
+      <el-button type="primary" @click="handleSumbit">提交</el-button>
+      <el-button @click="closeDialog">取消</el-button>
+    </template>
+  </BasicDialog>
+</template>
+
+<script setup lang="ts">
+  import { approveDrillRecord } from '@/api/emergency-drill/emergency-drill';
+  import BasicDialog from '@/components/BasicDialog.vue';
+  import { ElMessage } from 'element-plus';
+  import { reactive, ref } from 'vue';
+
+  const props = defineProps<{
+    id: number;
+    status: number;
+  }>();
+  const emits = defineEmits<{
+    (e: 'success'): void;
+  }>();
+
+  const formRef = ref();
+
+  const formData = reactive<{ approvalStatus: number | null; returnReason: string }>({
+    approvalStatus: null,
+    returnReason: '',
+  });
+
+  const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
+  const openDialog = () => {
+    basicDialogRef.value?.openDialog();
+  };
+
+  const closeDialog = () => {
+    clearForm();
+    basicDialogRef.value?.closeDialog();
+  };
+
+  function clearForm() {
+    formData.approvalStatus = null;
+    formData.returnReason = '';
+  }
+
+  const refreshFromData = () => {
+    formRef.value?.clearValidate();
+  };
+  const handleSumbit = async () => {
+    const validate = await new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+    if (!validate) return;
+    try {
+      if (!formData.approvalStatus) throw new Error('请选择审批状态');
+      await approveDrillRecord({
+        drillPlanId: props.id,
+        approvalStatus: formData.approvalStatus,
+        returnReason: formData.returnReason,
+      });
+      ElMessage.success('审批成功');
+      emits('success');
+    } catch (e) {
+      ElMessage.error('审批失败');
+    }
+  };
+
+  defineExpose({
+    openDialog,
+    closeDialog,
+  });
+</script>
+
+<style scoped lang="scss">
+  .el-form {
+    display: flex;
+    flex-direction: column;
+    width: 600px;
+    min-height: 100px;
+    height: 100%;
+    gap: 32px;
+
+    .el-form-item {
+      margin-bottom: 0;
+    }
+
+    :deep(.el-form-item__label) {
+      padding: 0;
+      min-width: 80px;
+    }
+  }
+</style>

+ 103 - 4
src/views/emergency/emergency-drill/components/DrillSignRecordItem.vue

@@ -1,20 +1,119 @@
 <template>
   <main class="safety-platform-container__main">
-    this is record page
+    <div v-if="recordData" class="drill-plan-record">
+      <div class="item">
+        <span class="label">演练内容:</span>
+        <span class="text">{{ recordData.drillContent }}</span>
+      </div>
+      <div class="item">
+        <span class="label">演练时间:</span>
+        <span class="text">{{ recordData.drillTime }}</span>
+      </div>
+      <div class="item">
+        <span class="label">演练地点:</span>
+        <span class="text">{{ recordData.drillLocation }}</span>
+      </div>
+      <div class="item">
+        <span class="label">参与人员描述:</span>
+        <span class="text">{{ recordData.participants }}</span>
+      </div>
+      <div class="item">
+        <span class="label">演练描述:</span>
+        <span class="text">{{ recordData.drillDescription }}</span>
+      </div>
+      <div class="item">
+        <span class="label">演练照片:</span>
+        <ShowImages style="min-height: 100px; flex: 1" :image-list="getImageUrls(recordData.drillImage)" />
+      </div>
+      <div class="item">
+        <span class="label">演练效果评估:</span>
+        <span class="text">{{ recordData.drillEffectAssess }}</span>
+      </div>
+    </div>
+    <!-- <div v-else class="empty">
+      <img :src="EmptyImg" alt="empty" />
+      <span>暂无数据</span>
+    </div> -->
   </main>
-  <footer class="safety-platform-container__footer" v-if="status === CONFIRM_STATUS_TYPE.WAIT_CONFIRM">
-    <el-button type="primary">审批</el-button>
+  <footer
+    class="safety-platform-container__footer"
+    v-if="status === CONFIRM_STATUS_TYPE.WAIT_CONFIRM && recordData?.approvalStatus === 0"
+  >
+    <el-button type="primary" @click="drillSignApprovalDialogRef?.openDialog">审批</el-button>
   </footer>
+  <DrillSignApprovalDialog
+    ref="drillSignApprovalDialogRef"
+    :id="id"
+    :status="status"
+    @success="handleApprovalSuccess"
+  />
 </template>
 
 <script lang="ts" setup>
+  import { onMounted, ref } from 'vue';
   import { CONFIRM_STATUS_TYPE } from '../constants';
-  defineProps<{
+  import { DrillPlanRecord } from '../types';
+  import { queryEmergencyDrillRecordInEdit } from '@/api/emergency-drill/emergency-drill';
+  import ShowImages from './ShowImages.vue';
+  // import EmptyImg from 'assets/images/empty@1X.png';
+  import DrillSignApprovalDialog from './DrillSignApprovalDialog.vue';
+
+  const props = defineProps<{
     id: number;
     status: number;
   }>();
+
+  const recordData = ref<DrillPlanRecord>();
+  const drillSignApprovalDialogRef = ref();
+
+  async function getData() {
+    try {
+      recordData.value = await queryEmergencyDrillRecordInEdit(props.id);
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  function handleApprovalSuccess() {
+    drillSignApprovalDialogRef.value.closeDialog();
+    getData();
+  }
+
+  onMounted(() => {
+    getData();
+  });
+
+  function getImageUrls(imgs: string) {
+    if (!imgs) return [];
+    return JSON.parse(imgs);
+  }
 </script>
 
 <style lang="scss" scoped>
   @use '@/styles/page-details-layout.scss' as *;
+  .data {
+    display: flex;
+  }
+  .item {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+    padding: 20px 10px;
+  }
+  .label {
+    width: 150px;
+    text-align: end;
+  }
+  .text {
+    flex: 1;
+    word-break: break-all;
+    white-space: pre;
+  }
+  .empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    margin-top: calc(70vh - 500px);
+  }
 </style>

+ 0 - 31
src/views/emergency/emergency-drill/constants.ts

@@ -28,37 +28,6 @@ export const EMERGENCY_DRILL_DETAIL_SUBPAGE = [
   },
 ];
 
-export const DRILL_VIEW_RECORD = [
-  {
-    label: '演练内容:',
-    value: 'drillContent',
-  },
-  {
-    label: '演练时间:',
-    value: 'drillTime',
-  },
-  {
-    label: '演练地点:',
-    value: 'drillLocation',
-  },
-  {
-    label: '参与人员描述:',
-    value: 'participants',
-  },
-  {
-    label: '演练描述:',
-    value: 'drillDescription',
-  },
-  {
-    label: '演练照片:',
-    value: 'drillImage',
-  },
-  {
-    label: '演练效果评估:',
-    value: 'drillEffectAssess',
-  },
-];
-
 export enum DRILL_SIGN_TYPE {
   SCRIPT = 1,
   RECORD,

+ 3 - 2
src/views/emergency/emergency-drill/types.ts

@@ -157,7 +157,7 @@ export interface DrillPlanRecord {
   /*演练描述 */
   drillDescription?: string;
   /*演练照片 */
-  drillImage?: string;
+  drillImage: string;
   /*演练效果评估 */
   drillEffectAssess?: string;
   /*审批状态?: 0-审批中,1-已审批,2-已退回 */
@@ -208,6 +208,7 @@ export interface DrillRecordRuleForm {
 }
 
 export interface DrillApprovalItem {
+  nodeDescription: string;
   /*审批顺序 */
   approvalOrder: number;
   /*审批人选择方式: 0-固定,1-自选 */
@@ -236,10 +237,10 @@ export interface DrillApprovalItem {
 export interface DrillApprovalRuleForm {
   approvalDescription?: string;
   approverListInfo: {
+    nodeDescription: string;
     approvalOrder: number;
     approvalType: number;
     approverType: number;
-    deptName: string;
     approverList: any[];
     approverIds?: number[];
     // approverName: string[];

+ 0 - 8
src/views/system/approval/PageApprovalNode.vue

@@ -58,7 +58,6 @@
       :order-list="approvalOrderList"
       @success="getTabelData"
     />
-    <UploadLoading loading-text="加载中" :form-loading="formLoading" v-if="formLoading" />
   </div>
 </template>
 
@@ -68,7 +67,6 @@
   import { Plus } from '@element-plus/icons-vue';
   import { computed, onMounted, ref } from 'vue';
   import { useFormConfigHook } from '@/hooks/useFormConfigHook';
-  import UploadLoading from '@/components/UploadLoading.vue';
   import BasicForm from '@/components/BasicForm.vue';
   import ActionButton from '@/components/ActionButton.vue';
   import BasicTable from '@/components/BasicTable.vue';
@@ -94,7 +92,6 @@
   const curNodeId = ref();
   const approvalInstance = ref<ApprovalInstanceRuleForm>();
 
-  const formLoading = ref(false);
   const basicFormRef = ref<InstanceType<typeof BasicForm>>();
   const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
     useFormConfigHook<ApprovalInstanceRuleForm>(APPROVAL_FORM_CONFIG, APPROVAL_FORM_DATA, APPROVAL_FORM_RULES);
@@ -127,10 +124,8 @@
   }
 
   onMounted(async () => {
-    formLoading.value = true;
     await getTabelData();
     initForm(approvalInstance.value!);
-    formLoading.value = false;
     cloneRuleFormData();
     beforeRouteLeave();
   });
@@ -140,15 +135,12 @@
     if (!validate) return;
     // 提交表单接口
     try {
-      formLoading.value = true;
       await updateApprovalInstance(ruleFormData);
-      formLoading.value = false;
       ElMessage.success('提交成功');
       cloneRuleFormData();
     } catch (e) {
       console.log(e);
     } finally {
-      formLoading.value = false;
     }
   };