Sfoglia il codice sorgente

演练记录和执行展示数据修改&提交审批表单

wyf 9 mesi fa
parent
commit
b2b75b9782

+ 8 - 3
src/api/emergency-drill/emergency-drill.ts

@@ -150,9 +150,14 @@ export const signDrillScript = (data: { drillPlanId: number; planToParticipateCo
 /**
  * 查询审批模板
  */
-export const queryDrillApproval = (id: number) => {
+export const queryDrillApproval = (approvalTemplateId: number, drillId: number) => {
   return http.request<any>({
-    url: `/approvalManagement/queryApprovalProcess?drillPlanId=${id}`,
-    method: 'get',
+    url: `/approvalManagement/queryApprovalProcess`,
+    method: 'post',
+    data: {
+      approvalTemplateId,
+      sourceType: 2,
+      sourceId: drillId,
+    },
   });
 };

+ 9 - 2
src/views/emergency/emergency-drill/PageDrillPlanItem.vue

@@ -66,9 +66,10 @@
   });
 
   const dynamicComponentRef = ref();
+  // 创建演练计划页 提交按钮
   const createSubmit = async () => {
     if (!dynamicComponentRef.value) return;
-    const res = await dynamicComponentRef.value.handleValidate();
+    const res = await dynamicComponentRef.value.formValidate();
     if (!res) return;
     const formData = dynamicComponentRef.value.getFormData();
     let message;
@@ -85,24 +86,30 @@
     }
   };
 
+  // 演练执行页 保存按钮
   const executeSave = async () => {
     formLoading.value = true;
     await dynamicComponentRef.value.executeSaveOrSubmit('save');
     formLoading.value = false;
   };
+
+  // 演练执行页 提交按钮
   const executeSubmit = async () => {
     formLoading.value = true;
     await dynamicComponentRef.value.executeSaveOrSubmit('submit');
     formLoading.value = false;
   };
 
+  // 演练记录页 保存按钮
   const recordSave = async () => {
     formLoading.value = true;
     await dynamicComponentRef.value.saveDrillRecord();
     formLoading.value = false;
   };
+
+  // 演练记录页 提交按钮
   const recordSubmit = async () => {
-    await dynamicComponentRef.value.submitDrillRecord();
+    await dynamicComponentRef.value.startSubmitDrillRecord();
   };
 </script>
 

+ 72 - 48
src/views/emergency/emergency-drill/PageDrillPlanList.vue

@@ -15,7 +15,18 @@
             :searchConfig="DRILL_PLAN_LIST_SEARCH_CONFIG"
             :searchData="searchData"
             @update:searchData="handleSearch"
-          ></BasicSearch>
+          >
+            <template #drillScope>
+              <el-select v-model="searchData.drillScope" placeholder="全部" filterable>
+                <el-option
+                  v-for="item in [{ itemCode: undefined, itemValue: '全部' }, ...drillScopeDice]"
+                  :key="item.itemCode"
+                  :label="item.itemValue"
+                  :value="item.itemCode"
+                />
+              </el-select>
+            </template>
+          </BasicSearch>
         </header>
         <!-- 表格 -->
         <BasicTable
@@ -24,6 +35,12 @@
           @update:pageSize="handleSizeChange"
           @update:pageNumber="handleCurrentChange"
         >
+          <template #drillScope="scope">
+            <div>{{ decodeScope(scope.row.drillScope) }}</div>
+          </template>
+          <template #status="scope">
+            <div>{{ decodeStatus(scope.row.status) }}</div>
+          </template>
           <template #action="scope">
             <div class="action-container--div">
               <ActionButton text="查看" @click="handleViewDrillPlan(scope.row.id)"></ActionButton>
@@ -61,54 +78,14 @@
   import BasicTable from '@/components/BasicTable.vue';
   import ActionButton from '@/components/ActionButton.vue';
   import useTableConfig from '@/hooks/useTableConfigHook';
-
+  import { queryEnergencyDrillPlanList, deleteEmergencyDrillPlan } from '@/api/emergency-drill/emergency-drill';
   import { DRILL_PLAN_LIST_SEARCH_CONFIG } from './configs/plan/search';
   import { TABLE_OPTIONS, DRILL_PLAN_LIST_TABLE_COLUMNS } from './configs/plan/table';
-
   import { DrillPlanListSearch, DrillPlanItem } from './types';
-
-  import { queryEnergencyDrillPlanList, deleteEmergencyDrillPlan } from '@/api/emergency-drill/emergency-drill';
-  import { EMERGENCY_DRILL_STATUS_DICT, EMERGENCY_DRILL_SCOPE_LABEL } from './constants';
+  import { EMERGENCY_DRILL_STATUS_DICT } from './constants';
+  import { useEmergencyDrillHook } from './hook';
 
   const router = useRouter();
-  // 按钮操作
-  function handleCreateDrillPlan() {
-    router.push({
-      name: 'emergency-drill-plan-item',
-      query: {
-        operate: 'create',
-      },
-    });
-  }
-
-  function handleViewDrillPlan(id: number) {
-    router.push({
-      name: 'emergency-drill-plan-view',
-      query: {
-        id: id,
-      },
-    });
-  }
-
-  function handleToExecute(id: number) {
-    router.push({
-      name: 'emergency-drill-plan-item',
-      query: {
-        id: id,
-        operate: 'execute',
-      },
-    });
-  }
-
-  function handleToRecord(id: number) {
-    router.push({
-      name: 'emergency-drill-plan-item',
-      query: {
-        id: id,
-        operate: 'record',
-      },
-    });
-  }
 
   function handleDeleteDrillPlan(id: number) {
     deleteEmergencyDrillPlan(id).then(() => {
@@ -145,15 +122,22 @@
     getTabelData();
   };
 
+  // 解析演练规模字典
+  const { drillScopeDice, getDrillScopeDict } = useEmergencyDrillHook();
+  function decodeScope(code: string) {
+    return drillScopeDice.value.find((x) => x.itemCode === code)?.itemValue;
+  }
+  // 解析演练状态
+  function decodeStatus(status: number) {
+    return EMERGENCY_DRILL_STATUS_DICT[status];
+  }
+
   async function getTabelData() {
     tableConfig.loading = true;
-
     const res = await queryEnergencyDrillPlanList(tabelQuery.value);
     res.records.forEach((item) => {
-      item.drillScope = EMERGENCY_DRILL_SCOPE_LABEL[item.drillScope];
       item.responsibleDeptNameList = item.responsibleDeptNameList.replace(/^\[|\]$/g, '');
       item.coordinateDeptNameList = item.coordinateDeptNameList?.replace(/^\[|\]$/g, '');
-      item.statusLabel = EMERGENCY_DRILL_STATUS_DICT[item.status];
     });
     tableData.value = res.records;
     pagination.total = res.totalRow;
@@ -161,9 +145,49 @@
   }
 
   // 初始化
-  onMounted(() => {
+  onMounted(async () => {
+    await getDrillScopeDict();
     getTabelData();
   });
+
+  // 按钮操作
+  function handleCreateDrillPlan() {
+    router.push({
+      name: 'emergency-drill-plan-item',
+      query: {
+        operate: 'create',
+      },
+    });
+  }
+
+  function handleViewDrillPlan(id: number) {
+    router.push({
+      name: 'emergency-drill-plan-view',
+      query: {
+        id: id,
+      },
+    });
+  }
+
+  function handleToExecute(id: number) {
+    router.push({
+      name: 'emergency-drill-plan-item',
+      query: {
+        id: id,
+        operate: 'execute',
+      },
+    });
+  }
+
+  function handleToRecord(id: number) {
+    router.push({
+      name: 'emergency-drill-plan-item',
+      query: {
+        id: id,
+        operate: 'record',
+      },
+    });
+  }
 </script>
 
 <style scoped lang="scss">

+ 135 - 0
src/views/emergency/emergency-drill/components/DrillApprovalDialog.vue

@@ -0,0 +1,135 @@
+<template>
+  <BasicDialog ref="basicDialogRef" title="提交审批" @refresh="refreshFromData">
+    <template #form>
+      <el-form ref="formRef" :model="formData" label-width="auto">
+        <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}`"
+          :prop="'approverListInfo.' + index + '.approverName'"
+          :rules="{ required: true, message: '请选择审批人', trigger: 'change' }"
+        >
+          <el-select
+            v-model="item.approverName"
+            placeholder="请输入审批人"
+            value-key="id"
+            filterable
+            remote
+            :disabled="formDisabled"
+            :remote-method="remoteMethod"
+            :loading="loading"
+            @change="(value) => selectKeeper(value, index)"
+          >
+            <el-option
+              v-for="item in userOptions"
+              :key="item.id"
+              :label="`${item.realname}(${item.username})${item.deptName}`"
+              :value="item"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </template>
+    <template #footer>
+      <el-button type="primary" @click="handleSumbit">提交</el-button>
+      <el-button @click="handleCancel">取消</el-button>
+    </template>
+  </BasicDialog>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted } from 'vue';
+  import BasicDialog from '@/components/BasicDialog.vue';
+  import { DrillApprovalItem, DrillApprovalRuleForm } from '../types';
+  import type { QueryUserInfoByUserNameRes } from '@/types/person-group/type';
+  import { queryUserInfoByUserName } from '@/api/system/person-group';
+  import { FormInstance } from 'element-plus';
+  import { rules } from '@/types/camera/constant';
+
+  const props = defineProps<{
+    approvalData: DrillApprovalItem;
+  }>();
+  const emits = defineEmits<{
+    (e: 'success'): void;
+  }>();
+
+  const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
+  const formRef = ref<FormInstance>();
+
+  const openDialog = () => {
+    initForm();
+    basicDialogRef.value?.openDialog();
+  };
+
+  const formDisabled = ref(props.approvalData.approverType === 0);
+
+  const formData = reactive<DrillApprovalRuleForm>({
+    approvalDescription: '',
+    approverListInfo: [],
+  });
+
+  function initForm() {
+    props.approvalData.processInfoList.forEach((x, index) => {
+      formData.approverListInfo.push({
+        approvalOrder: index,
+        deptName: x.approvalDeptName,
+        approverId: x.approverId,
+        approverName: x.approverName,
+      });
+    });
+  }
+
+  function clearForm() {
+    formData.approvalDescription = '';
+    formData.approverListInfo = [];
+  }
+
+  const handleSumbit = async () => {
+    const validate = await new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+    if (!validate) return;
+    console.log(formData);
+    // 提交表单接口
+    // emits('success');
+  };
+
+  const handleCancel = () => {
+    if (!basicDialogRef.value) return;
+    clearForm();
+    basicDialogRef.value.closeDialog();
+  };
+
+  // 人员选择
+  const userOptions = ref<QueryUserInfoByUserNameRes[]>([]);
+  const loading = ref<boolean>(false);
+  const remoteMethod = async (query: string) => {
+    if (!query) {
+      userOptions.value = [];
+      return;
+    }
+    loading.value = true;
+    userOptions.value = await queryUserInfoByUserName(query);
+    loading.value = false;
+  };
+  function selectKeeper(value, index) {
+    if (!value) return;
+    formData.approverListInfo[index].approverId = value.id;
+    formData.approverListInfo[index].approverName = value.realname;
+  }
+
+  const refreshFromData = () => {
+    formRef.value?.clearValidate();
+  };
+
+  defineExpose({
+    openDialog,
+  });
+</script>
+
+<style scoped></style>

+ 22 - 4
src/views/emergency/emergency-drill/components/DrillPlanCreateItem.vue

@@ -1,6 +1,16 @@
 <template>
   <div>
     <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #drillScope>
+        <el-select v-model="ruleFormData.drillScope" placeholder="请选择演练规模" filterable>
+          <el-option
+            v-for="item in drillScopeDice"
+            :key="item.itemCode"
+            :label="item.itemValue"
+            :value="item.itemCode"
+          />
+        </el-select>
+      </template>
       <template #responsibleDeptIdList>
         <el-cascader
           v-model="ruleFormData.responsibleDeptIdList"
@@ -66,48 +76,56 @@
   import { getAllDepartments } from '@/api/auth/dept';
   import { getAllApproval } from '@/api/approval/approval';
   import { ApprovalInstanceType } from '@/views/system/approval/types';
+  import { useEmergencyDrillHook } from '../hook';
 
+  // 表单配置初始化
   const basicFormRef = ref<InstanceType<typeof BasicForm>>();
-
   const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
     useFormConfigHook<CreateEmergencyDrillRuleForm>(
       DRILL_CREATE_FORM_CONFIG,
       DRILL_CREATE_FORM_DATA,
       DRILL_CREATE_FORM_RULES,
     );
-
   const cascaderProp = { multiple: true, expandTrigger: 'hover', emitPath: false, value: 'id', label: 'deptName' };
 
+  // 获取级联部门数据
   const deptTree = ref<DeptTree[]>();
   const loadDeptTreeData = async () => {
     const result = await getAllDepartments();
     deptTree.value = result;
   };
 
+  // 获取所有审批流程
   const allApprovals = ref<ApprovalInstanceType[]>();
   const loadApprovalData = async () => {
     const result = await getAllApproval();
     allApprovals.value = result;
   };
 
+  // 获取演练规模字典
+  const { drillScopeDice, getDrillScopeDict } = useEmergencyDrillHook();
+
   onMounted(() => {
+    getDrillScopeDict();
     loadDeptTreeData();
     loadApprovalData();
   });
 
-  const handleValidate = async () => {
+  // 表单校验
+  const formValidate = async () => {
     if (!basicFormRef.value) return;
     const parentValidateResult = await basicFormRef.value.validateForm();
     return parentValidateResult;
   };
 
+  // 表单数据获取
   const getFormData = () => {
     cloneRuleFormData();
     return ruleFormData;
   };
 
   defineExpose({
-    handleValidate,
+    formValidate,
     getFormData,
   });
 </script>

+ 96 - 49
src/views/emergency/emergency-drill/components/DrillPlanExecuteItem.vue

@@ -1,17 +1,78 @@
 <template>
   <div v-if="drillData">
-    <div>
-      <div class="name">演练活动</div>
-      <div class="data">
-        <div v-for="item in DRILL_VIEW_CONTENT" :key="item.value" class="item">
-          <span class="label">{{ item.label }}</span>
-          <a v-if="item.link" class="link" :href="item.link">{{ drillData![item.value] }}</a>
-          <span v-else class="value"> {{ drillData![item.value] }}</span>
-        </div>
+    <div class="drill-activity-container">
+      <div class="drill-container__title">
+        <div class="drill-container--line"></div>
+        <span>演练活动</span>
+      </div>
+      <div class="drill-container__content">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练规模:</span>
+              <span class="value">{{ getDrillScope(drillData.drillScope) }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练内容:</span>
+              <span class="value">{{ drillData.drillContent }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">计划完成时间:</span>
+              <span class="value">{{ drillData.dueCompleteTime }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">责任部门:</span>
+              <template v-for="(dept, index) in safatyJsonParse(drillData.responsibleDeptNameList)" :key="index">
+                <span class="value">
+                  {{ dept }}
+                  <span v-if="index !== safatyJsonParse(drillData.responsibleDeptNameList).length - 1">、</span>
+                </span>
+              </template>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item" v-if="drillData.coordinateDeptNameList">
+              <span class="label">配合部门:</span>
+              <template v-for="(dept, index) in safatyJsonParse(drillData.coordinateDeptNameList)" :key="index">
+                <span class="value">
+                  {{ dept }}
+                  <span v-if="index !== safatyJsonParse(drillData.coordinateDeptNameList).length - 1">、</span>
+                </span>
+              </template>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">关联应急预案:</span>
+              <a v-if="emergencyPlanDetail" class="value font-primary" :href="emergencyPlanDetail.appendix">{{
+                emergencyPlanDetail.planName
+              }}</a>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col>
+            <div class="drill-container__content--item">
+              <span class="label">审批流程:</span>
+              <span class="value">{{ getApprovalName(drillData.approvalTemplateId) }}</span>
+            </div>
+          </el-col>
+        </el-row>
       </div>
     </div>
-    <div>
-      <div class="name">演练实施</div>
+    <div class="drill-execute-form">
+      <div class="drill-container__title">
+        <div class="drill-container--line"></div>
+        <span>演练实施</span>
+      </div>
       <DrillPlanExecuteForm
         v-if="drillData"
         ref="drillPlanExecuteFormRef"
@@ -28,7 +89,6 @@
   import { ElMessage } from 'element-plus';
   import { useRoute } from 'vue-router';
   import { DrillPlanItemDetail } from '../types';
-  import { DRILL_VIEW_CONTENT } from '../constants';
   import DrillPlanExecuteForm from './DrillPlanExecuteForm.vue';
   import {
     queryEmergencyDrillPlanDetail,
@@ -41,40 +101,43 @@
   import { uploadFileApi, UPLOAD_BIZ_TYPE } from '@/api/minio';
   import { useEmergencyDrillHook } from '../hook';
 
-  const { drillScopeDice, getDrillScopeDict } = useEmergencyDrillHook();
-
   const route = useRoute();
   const id = route.query.id;
-
+  const approvalList = ref();
+  const emergencyPlanDetail = ref();
   const drillData = ref<DrillPlanItemDetail>();
+
+  const { drillScopeDice, getDrillScopeDict, getDrillScope } = useEmergencyDrillHook();
+
+  const getApprovalList = async () => {
+    approvalList.value = await getAllApproval();
+  };
   async function getDrillData() {
     try {
       drillData.value = await queryEmergencyDrillPlanDetail(id);
 
-      // 解析详情数据
-      drillData.value.drillScope = drillScopeDice.value.find(
-        (x) => x.itemCode === drillData.value!.drillScope,
-      )?.itemValue;
-
-      drillData.value.responsibleDeptNameList = drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '');
-      drillData.value.coordinateDeptNameList = drillData.value.coordinateDeptNameList
-        ? drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '')
-        : undefined;
-
+      // 获取应急预案名
       if (drillData.value.emergencyPlanId) {
-        const emergencyPlan = await queryEmergencyPlanDetail(drillData.value.emergencyPlanId);
-        drillData.value.emergencyPlanFile = emergencyPlan.planName + emergencyPlan.appendix;
+        emergencyPlanDetail.value = await queryEmergencyPlanDetail(id);
       }
-
-      const allApprovals = await getAllApproval();
-      drillData.value.approvalTemplateName = allApprovals.find(
-        (x) => x.id === drillData.value!.approvalTemplateId,
-      )?.templateName;
     } catch (e) {
       console.log(e);
     }
   }
 
+  onMounted(async () => {
+    await getApprovalList();
+    await getDrillScopeDict();
+    getDrillData();
+  });
+
+  const getApprovalName = (id: number) => {
+    return approvalList.value.find((item) => item.id === id)?.templateName;
+  };
+  const safatyJsonParse = (str: string) => {
+    return str.slice(1, -1).split(',');
+  };
+
   const drillPlanExecuteFormRef = ref();
 
   const formatAttachmentList = async (data: FileItem[]) => {
@@ -145,27 +208,11 @@
     }
   }
 
-  onMounted(() => {
-    getDrillScopeDict();
-    getDrillData();
-  });
-
   defineExpose({
     executeSaveOrSubmit,
   });
 </script>
 
-<style scoped>
-  .name {
-    margin-top: 10px;
-    padding-left: 12px;
-    border-left: 3px #1890ff solid;
-  }
-  .data {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, 33%);
-  }
-  .item {
-    padding: 25px 16px;
-  }
+<style scoped lang="scss">
+  @use '../style/common.scss' as *;
 </style>

+ 67 - 10
src/views/emergency/emergency-drill/components/DrillPlanRecordForm.vue

@@ -12,26 +12,79 @@
       </template>
     </BasicForm>
   </div>
+  <DrillApprovalDialog v-if="approvalInfo" ref="drillApprovalDialogRef" :approval-data="approvalInfo" />
 </template>
 
 <script setup lang="ts">
   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 { useFormConfigHook } from '@/hooks/useFormConfigHook';
   import { DRILL_RECORD_FORM_CONFIG, DRILL_RECORD_FORM_RULES, DRILL_RECORD_FORM_DATA } from '../configs/plan/form';
-  import { DrillPlanRecord, DrillRecordRuleForm } from '../types';
-  import { ref } from 'vue';
+  import { DrillApprovalItem, DrillPlanRecord, DrillRecordRuleForm } from '../types';
+  import { onMounted, ref } from 'vue';
+  import { queryDrillApproval } from '@/api/emergency-drill/emergency-drill';
 
   const props = defineProps<{
     drillRecord?: DrillPlanRecord;
   }>();
 
+  const handleUploadChange = (files: File[]) => {};
+
+  const approvalInfo = ref();
+  const approvalDataExample: DrillApprovalItem = {
+    // 审批顺序为第1步
+    approvalOrder: 1,
+    // 审批人选择方式为固定(0-固定)
+    approverType: 0,
+    // 审批流程列表,包含两个审批节点
+    processInfoList: [
+      {
+        approvalDeptId: 101,
+        approvalDeptName: '技术部',
+        approverId: 1001,
+        approverName: '张明',
+        approvalType: 0, // 会签
+        approvalStatus: 1, // 已审批
+        approvalContent: '同意该方案,建议补充安全预案',
+      },
+      {
+        approvalDeptId: 102,
+        approvalDeptName: '安全管理部',
+        approverId: 1002,
+        approverName: '李华',
+        approvalType: 1, // 或签
+        approvalStatus: 2, // 待审批
+        approvalContent: '', // 待审批时无内容
+      },
+    ],
+  };
+
+  async function getApprovalInfo() {
+    // if (!props.drillRecord) return;
+    try {
+      // approvalInfo.value = await queryDrillApproval(props.drillRecord.approvalTemplateId, props.drillRecord.drillPlanId);
+      approvalInfo.value = approvalDataExample;
+      console.log(approvalInfo.value);
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  onMounted(async () => {
+    getApprovalInfo();
+  });
+
+  // 表单
+  const basicFormRef = ref<typeof BasicForm>();
+
   const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
     useFormConfigHook<DrillRecordRuleForm>(
       DRILL_RECORD_FORM_CONFIG,
       props.drillRecord
         ? {
+            drillPlanId: props.drillRecord.drillPlanId,
             participants: props.drillRecord.participants,
             drillDescription: props.drillRecord.drillDescription,
             drillImage: props.drillRecord.drillImage,
@@ -41,17 +94,13 @@
       DRILL_RECORD_FORM_RULES,
     );
 
-  const handleUploadChange = (files: File[]) => {};
-
-  const basicFormRef = ref<typeof BasicForm>();
-
-  const FormValidate = async () => {
+  const formValidate = async () => {
     if (!basicFormRef.value) return;
     const validateResult = await basicFormRef.value.validateForm();
     return validateResult;
   };
 
-  const FormClearValidate = () => {
+  const formClearValidate = () => {
     if (!basicFormRef.value) return;
     basicFormRef.value.clearValidate();
   };
@@ -74,12 +123,20 @@
     });
   }
 
+  // 弹窗
+  const drillApprovalDialogRef = ref();
+
+  function openApprovalDialog() {
+    drillApprovalDialogRef.value.openDialog();
+  }
+
   defineExpose({
-    FormValidate,
-    FormClearValidate,
+    formValidate,
+    formClearValidate,
     getFormData,
     disableFormInput,
     enableFormInput,
+    openApprovalDialog,
   });
 </script>
 

+ 17 - 7
src/views/emergency/emergency-drill/components/DrillPlanRecordItem.vue

@@ -9,24 +9,23 @@
   import { onMounted, ref } from 'vue';
   import { useRoute } from 'vue-router';
   import { DrillPlanRecord } from '../types';
-  import { queryEmergencyDrillRecord } from '@/api/emergency-drill/emergency-drill';
+  import { queryEmergencyDrillRecord, saveEmergencyDrillRecord } from '@/api/emergency-drill/emergency-drill';
+  import { ElMessage } from 'element-plus';
 
   const route = useRoute();
   const id = route.query.id;
   const recordData = ref<DrillPlanRecord>();
 
-  async function getData() {
+  async function getDrillData() {
     try {
       recordData.value = await queryEmergencyDrillRecord(id);
-
-      // 演练记录数据处理
     } catch (e) {
       console.log(e);
     }
   }
 
   onMounted(() => {
-    getData();
+    getDrillData();
   });
 
   const drillPlanRecordFormRef = ref();
@@ -34,17 +33,28 @@
   async function saveDrillRecord() {
     if (!drillPlanRecordFormRef) return;
     const formData = drillPlanRecordFormRef.value.getFormData();
+    if (!formData.drillPlanId) formData.drillPlanId = Number(id);
+    try {
+      await saveEmergencyDrillRecord(formData);
+      ElMessage.success('保存成功');
+      drillPlanRecordFormRef.value.formClearValidate();
+      recordData.value = undefined;
+      getDrillData();
+    } catch (e) {
+      console.log(e);
+    }
   }
 
-  async function submitDrillRecord() {
+  async function startSubmitDrillRecord() {
     if (!drillPlanRecordFormRef) return;
     const res = await drillPlanRecordFormRef.value.formValidate();
     if (!res) return;
+    drillPlanRecordFormRef.value.openApprovalDialog();
   }
 
   defineExpose({
     saveDrillRecord,
-    submitDrillRecord,
+    startSubmitDrillRecord,
   });
 </script>
 

+ 159 - 76
src/views/emergency/emergency-drill/components/DrillPlanViewActivities.vue

@@ -1,40 +1,133 @@
 <template>
   <div v-if="drillData" class="drill-plan-detail">
-    <div>
-      <div class="name">演练活动</div>
-      <div class="data">
-        <div v-for="item in DRILL_VIEW_CONTENT" :key="item.value" class="item">
-          <span class="label">{{ item.label }}</span>
-          <a v-if="item.link" class="link" :href="item.link">{{ drillData![item.value] }}</a>
-          <span v-else class="value"> {{ drillData![item.value] }}</span>
-        </div>
+    <div class="drill-activity-container">
+      <div class="drill-container__title">
+        <div class="drill-container--line"></div>
+        <span>演练活动</span>
+      </div>
+      <div class="drill-container__content">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练规模:</span>
+              <span class="value">{{ getDrillScope(drillData.drillScope) }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练内容:</span>
+              <span class="value">{{ drillData.drillContent }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">计划完成时间:</span>
+              <span class="value">{{ drillData.dueCompleteTime }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">责任部门:</span>
+              <template v-for="(dept, index) in safatyJsonParse(drillData.responsibleDeptNameList)" :key="index">
+                <span class="value">
+                  {{ dept }}
+                  <span v-if="index !== safatyJsonParse(drillData.responsibleDeptNameList).length - 1">、</span>
+                </span>
+              </template>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item" v-if="drillData.coordinateDeptNameList">
+              <span class="label">配合部门:</span>
+              <template v-for="(dept, index) in safatyJsonParse(drillData.coordinateDeptNameList)" :key="index">
+                <span class="value">
+                  {{ dept }}
+                  <span v-if="index !== safatyJsonParse(drillData.coordinateDeptNameList).length - 1">、</span>
+                </span>
+              </template>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">关联应急预案:</span>
+              <a v-if="emergencyPlanDetail" class="value font-primary" :href="emergencyPlanDetail.appendix">{{
+                emergencyPlanDetail.planName
+              }}</a>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col>
+            <div class="drill-container__content--item">
+              <span class="label">审批流程:</span>
+              <span class="value">{{ getApprovalName(drillData.approvalTemplateId) }}</span>
+            </div>
+          </el-col>
+        </el-row>
       </div>
     </div>
-    <div>
-      <div class="name">演练实施</div>
-      <div class="data">
-        <div v-for="item in DRILL_VIEW_EXECUTE" :key="item.value" class="item">
-          <span class="label">{{ item.label }}</span>
-          <el-popover v-if="item.popover" placement="bottom" trigger="hover" width="224">
-            <template #reference>
-              <span style="cursor: pointer">查看签到码</span>
-            </template>
-            <QrCode :value="drillData![item.popover]" :width="200" />
-          </el-popover>
-          <a v-else-if="item.link" class="link" :href="drillData![item.link]">{{ drillData![item.value] }}</a>
-          <span v-else class="value"> {{ drillData![item.value] }}</span>
-        </div>
+    <div class="drill-activity-container">
+      <div class="drill-container__title">
+        <div class="drill-container--line"></div>
+        <span>演练实施</span>
+      </div>
+      <div class="drill-container__content">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练时间:</span>
+              <span class="value">{{ drillData.drillTime }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练地点:</span>
+              <span class="value">{{ drillData.drillLocation }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">演练负责人:</span>
+              <span class="value">{{ drillData.personInChargeName }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <div class="drill-container__content--item" v-if="drillData.drillScript">
+              <span class="label">演练脚本:</span>
+              <span class="value font-primary link" @click="handlePreviewScript(drillData.drillScript)">{{
+                JSON.parse(drillData.drillScript).fileName
+              }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="drill-container__content--item">
+              <span class="label">签到码:</span>
+              <el-popover placement="bottom" trigger="hover" width="224">
+                <template #reference>
+                  <span style="cursor: pointer">查看签到码</span>
+                </template>
+                <QrCode :value="qrCode" :width="200" />
+              </el-popover>
+            </div>
+          </el-col>
+        </el-row>
       </div>
     </div>
-    <div>
-      <div class="name">演练参与部门</div>
-      <el-button style="margin-top: 20px" type="primary" :icon="Download" @click=""> 下载签到名单 </el-button>
-      <BasicTable style="margin-top: 20px" :tableConfig="tableConfig" :tableData="drillData.planDetailList!">
-        <template #drillScriptStatus="scope">
-          <span>{{ scope.row.drillScriptStatus === 1 ? '未会签' : '已会签' }}</span>
-        </template>
-      </BasicTable>
+
+    <div class="drill-container__title">
+      <div class="drill-container--line"></div>
+      <span>演练参与部门</span>
     </div>
+    <el-button style="margin-top: 20px" type="primary" :icon="Download" @click=""> 下载签到名单 </el-button>
+    <BasicTable style="margin-top: 20px" :tableConfig="tableConfig" :tableData="drillData.planDetailList!">
+      <template #drillScriptStatus="scope">
+        <span>{{ scope.row.drillScriptStatus === 1 ? '未会签' : '已会签' }}</span>
+      </template>
+    </BasicTable>
   </div>
 </template>
 
@@ -45,53 +138,41 @@
   import { Download } from '@element-plus/icons-vue';
   import { useRoute } from 'vue-router';
   import { DrillPlanItemDetail } from '../types';
-  import { DRILL_VIEW_CONTENT, DRILL_VIEW_EXECUTE } from '../constants';
   import useTableConfig from '@/hooks/useTableConfigHook';
   import { getAllApproval } from '@/api/approval/approval';
   import { queryEmergencyDrillPlanDetail, queryEmergencyPlanDetail } from '@/api/emergency-drill/emergency-drill';
   import { DRILL_PLAN_ACTIVITIES_TABLE_OPTIONS, DRILL_PLAN_ACTIVITIES_TABLE_COLUMNS } from '../configs/plan/table';
   import { useEmergencyDrillHook } from '../hook';
   import QrCode from '@/components/Qrcode/src/Qrcode.vue';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { FILE_TYPE_ICON } from '@/views/disaster/constant';
 
   const route = useRoute();
   const id = route.query.id;
+  const approvalList = ref();
+  const emergencyPlanDetail = ref();
   const drillData = ref<DrillPlanItemDetail>();
+  const qrCode = ref();
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+
+  const { getDrillScopeDict, getDrillScope } = useEmergencyDrillHook();
 
-  const { drillScopeDice, getDrillScopeDict } = useEmergencyDrillHook();
+  const getApprovalList = async () => {
+    approvalList.value = await getAllApproval();
+  };
 
-  async function getData() {
+  async function getDrillData() {
     try {
       tableConfig.loading = true;
       drillData.value = await queryEmergencyDrillPlanDetail(id);
 
-      // 解析详情数据
-      drillData.value.drillScope = drillScopeDice.value.find(
-        (x) => x.itemCode === drillData.value!.drillScope,
-      )!.itemValue;
-
-      drillData.value.responsibleDeptNameList = drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '');
-      drillData.value.coordinateDeptNameList = drillData.value.coordinateDeptNameList
-        ? drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '')
-        : undefined;
-
+      // 获取应急预案名
       if (drillData.value.emergencyPlanId) {
-        const emergencyPlan = await queryEmergencyPlanDetail(drillData.value.emergencyPlanId);
-        drillData.value.emergencyPlanFile = emergencyPlan.planName + emergencyPlan.appendix;
+        emergencyPlanDetail.value = await queryEmergencyPlanDetail(id);
       }
 
-      const allApprovals = await getAllApproval();
-      drillData.value.approvalTemplateName = allApprovals.find(
-        (x) => x.id === drillData.value!.approvalTemplateId,
-      )?.templateName;
-
-      if (drillData.value.drillScript && drillData.value.drillScript.length > 0) {
-        const scriptFile = unformatAttachment(drillData.value.drillScript);
-        // console.log(scriptFile);
-        drillData.value.drillScriptName = scriptFile.fileName;
-        drillData.value.drillScriptUrl = scriptFile.fileUrl;
-      }
-
-      drillData.value.qrCode = 'https://cn.bing.com/?id=' + id + '&type=test';
+      // 获取二维码
+      qrCode.value = 'https://cn.bing.com/?id=' + id + '&type=test';
 
       tableConfig.loading = false;
     } catch (e) {
@@ -106,28 +187,30 @@
   );
 
   onMounted(async () => {
+    await getApprovalList();
     await getDrillScopeDict();
-    getData();
+    getDrillData();
   });
 
-  function unformatAttachment(file?: string) {
-    if (!file) return undefined;
-    const fileData = JSON.parse(file);
-    return fileData;
-  }
+  const getApprovalName = (id: number) => {
+    return approvalList.value.find((item) => item.id === id)?.templateName;
+  };
+  const safatyJsonParse = (str: string) => {
+    return str.slice(1, -1).split(',');
+  };
+
+  const handlePreviewScript = (str: string) => {
+    const file = JSON.parse(str);
+    const url = file.fileUrl;
+    const type = file.fileType as keyof typeof FILE_TYPE_ICON;
+    if (!url) return;
+    previewOnlineRef.value?.open(url, type);
+  };
 </script>
 
-<style scoped>
-  .name {
-    margin-top: 10px;
-    padding-left: 12px;
-    border-left: 3px #1890ff solid;
-  }
-  .data {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, 33%);
-  }
-  .item {
-    padding: 25px 16px;
+<style scoped lang="scss">
+  @use '../style/common.scss' as *;
+  .link {
+    cursor: pointer;
   }
 </style>

+ 4 - 18
src/views/emergency/emergency-drill/configs/plan/form.ts

@@ -1,24 +1,10 @@
 import { FormConfig } from '@/types/basic-form';
-import { EMERGENCY_DRILL_SCOPE, EMERGENCY_DRILL_SCOPE_DICT } from '../../constants';
 
 export const DRILL_CREATE_FORM_CONFIG: FormConfig[] = [
   {
     prop: 'drillScope',
     label: '演练规模:',
-    component: 'el-select',
-    componentProps: {
-      placeholder: '请选择演练规模',
-    },
-    selectOptions: [
-      {
-        label: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.INSTITUTE].label,
-        value: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.INSTITUTE].dict,
-      },
-      {
-        label: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.DEPT].label,
-        value: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.DEPT].dict,
-      },
-    ],
+    slot: 'drillScope',
   },
   {
     prop: 'drillContent',
@@ -171,7 +157,7 @@ export const DRILL_RECORD_FORM_CONFIG: FormConfig[] = [
     component: 'el-input',
     componentProps: {
       type: 'textarea',
-      maxLength: 1000,
+      maxlength: 1000,
       autosize: { minRows: 3 },
       showWordLimit: true,
       placeholder: '请描述演练人员,不超过1000字',
@@ -183,7 +169,7 @@ export const DRILL_RECORD_FORM_CONFIG: FormConfig[] = [
     component: 'el-input',
     componentProps: {
       type: 'textarea',
-      maxLength: 2000,
+      maxlength: 2000,
       showWordLimit: true,
       autosize: { minRows: 8 },
       placeholder: '请描述演练过程,不超过2000字',
@@ -200,7 +186,7 @@ export const DRILL_RECORD_FORM_CONFIG: FormConfig[] = [
     component: 'el-input',
     componentProps: {
       type: 'textarea',
-      maxLength: 2000,
+      maxlength: 2000,
       showWordLimit: true,
       autosize: { minRows: 5 },
       placeholder: '请描述演练效果评估,不超过2000字',

+ 2 - 16
src/views/emergency/emergency-drill/configs/plan/search.ts

@@ -1,26 +1,12 @@
 import type { SearchConfig } from '@/types/basic-search';
-import {
-  EMERGENCY_DRILL_SCOPE,
-  EMERGENCY_DRILL_SCOPE_DICT,
-  EMERGENCY_DRILL_STATUS,
-  EMERGENCY_DRILL_STATUS_DICT,
-} from '../../constants';
+import { EMERGENCY_DRILL_STATUS, EMERGENCY_DRILL_STATUS_DICT } from '../../constants';
 
 // drillplan表格搜索 演练规模 演练内容 责任部门 计划完成日期 状态
 export const DRILL_PLAN_LIST_SEARCH_CONFIG: SearchConfig[] = [
   {
     label: '演练规模:',
     prop: 'drillScope',
-    component: 'ElSelect',
-    selectOptions: [
-      { label: '全部', value: undefined },
-      {
-        label: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.INSTITUTE].label,
-        value: EMERGENCY_DRILL_SCOPE.INSTITUTE,
-      },
-      { label: EMERGENCY_DRILL_SCOPE_DICT[EMERGENCY_DRILL_SCOPE.DEPT].label, value: EMERGENCY_DRILL_SCOPE.DEPT },
-    ],
-    componentProps: { placeholder: '全部' },
+    slot: 'drillScope',
   },
   {
     label: '演练内容:',

+ 3 - 1
src/views/emergency/emergency-drill/configs/plan/table.ts

@@ -17,6 +17,7 @@ export const DRILL_PLAN_LIST_TABLE_COLUMNS: TableColumnProps[] = [
   {
     prop: 'drillScope',
     label: '演练规模',
+    slot: 'drillScope',
     align: 'center',
     minWidth: '120px',
   },
@@ -46,8 +47,9 @@ export const DRILL_PLAN_LIST_TABLE_COLUMNS: TableColumnProps[] = [
     minWidth: '120px',
   },
   {
-    prop: 'statusLabel',
+    prop: 'status',
     label: '状态',
+    slot: 'status',
     align: 'center',
     minWidth: '120px',
   },

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

@@ -1,18 +1,3 @@
-export const EMERGENCY_DRILL_SCOPE = {
-  TOTAL: 0,
-  INSTITUTE: 1,
-  DEPT: 2,
-};
-
-export const EMERGENCY_DRILL_SCOPE_DICT = {
-  1: { dict: 'institute_level', label: '院级演练' },
-  2: { dict: 'dept_level', label: '部门级演练' },
-};
-export const EMERGENCY_DRILL_SCOPE_LABEL = {
-  institute_level: '院级演练',
-  dept_level: '部门级演练',
-};
-
 export const EMERGENCY_DRILL_STATUS = {
   TOTAL: undefined,
   WAIT_SCRIPT: 1, // 待传脚本
@@ -43,63 +28,6 @@ export const EMERGENCY_DRILL_DETAIL_SUBPAGE = [
   },
 ];
 
-export const DRILL_VIEW_CONTENT = [
-  {
-    label: '演练规模:',
-    value: 'drillScope',
-  },
-  {
-    label: '演练内容:',
-    value: 'drillContent',
-  },
-  {
-    label: '计划完成日期:',
-    value: 'dueCompleteTime',
-  },
-  {
-    label: '责任部门:',
-    value: 'responsibleDeptNameList',
-  },
-  {
-    label: '配合部门:',
-    value: 'coordinateDeptNameList',
-  },
-  {
-    label: '关联应急预案:',
-    value: 'emergencyPlanFile',
-    link: 'emergencyPlanFile',
-  },
-  {
-    label: '审批流程:',
-    value: 'approvalTemplateName',
-  },
-];
-
-export const DRILL_VIEW_EXECUTE = [
-  {
-    label: '演练时间:',
-    value: 'drillTime',
-  },
-  {
-    label: '演练地点:',
-    value: 'drillLocation',
-  },
-  {
-    label: '演练负责人:',
-    value: 'personInChargeName',
-  },
-  {
-    label: '演练脚本:',
-    value: 'drillScriptName',
-    link: 'drillScriptUrl',
-  },
-  {
-    label: '签到码:',
-    value: 'qrCode',
-    popover: 'qrCode',
-  },
-];
-
 export const DRILL_VIEW_RECORD = [
   {
     label: '演练内容:',

+ 37 - 12
src/views/emergency/emergency-drill/types.ts

@@ -21,7 +21,6 @@ export interface DrillPlanItem {
   approvalTemplateId: number;
   /*状态: 1-待传脚本,2-脚本会签,3-待执行,4-待记录,5-记录待审批,6-已完成 */
   status: number;
-  statusLabel: string;
   /*演练时间 */
   drillTime: string;
   /*演练地点 */
@@ -82,10 +81,8 @@ export interface DrillPlanItemDetail {
   coordinateDeptNameList?: string;
   /*应急预案id */
   emergencyPlanId?: number;
-  emergencyPlanFile?: string;
   /*审批模板id */
   approvalTemplateId: number;
-  approvalTemplateName?: string;
   /*状态?: 1-待传脚本,2-脚本会签,3-待执行,4-待记录,5-记录待审批,6-已完成 */
   status?: number;
   /*演练时间 */
@@ -98,8 +95,6 @@ export interface DrillPlanItemDetail {
   drillDeptIdList?: string;
   /*演练脚本 */
   drillScript?: string;
-  drillScriptName?: string;
-  drillScriptUrl?: string;
   /*提交人 */
   createdBy?: number;
   /*创建时间 */
@@ -133,8 +128,6 @@ export interface DrillPlanItemDetail {
     /*0-未删除,大于0(时间戳)-已删除 */
     isDeleted?: number;
   }[];
-  /*签到码*/
-  qrCode?: string;
 }
 
 export interface ExecuteDrillPlanRuleForm {
@@ -154,9 +147,9 @@ export interface ExecuteDrillPlanRuleForm {
 
 export interface DrillPlanRecord {
   /*自增主键 */
-  id?: number;
+  id: number;
   /*演练计划id */
-  drillPlanId?: number;
+  drillPlanId: number;
   /*参与人员 */
   participants?: string;
   /*演练描述 */
@@ -192,7 +185,7 @@ export interface DrillPlanRecord {
   /*审批通过时间 */
   approvalTime?: string;
   /*审批模板id */
-  approvalTemplateId?: number;
+  approvalTemplateId: number;
 }
 
 export interface DrillRecordRuleForm {
@@ -211,6 +204,38 @@ export interface DrillRecordRuleForm {
   approvalInfoList?: { approvalOrder?: number; approverIdList?: string }[];
 }
 
-// export interface DrillApprovalItem {
+export interface DrillApprovalItem {
+  /*审批顺序 */
+  approvalOrder: number;
+  /*审批人选择方式: 0-固定,1-自选 */
+  approverType: number;
+  /*审批流程列表 */
+  processInfoList: {
+    /*审批部门id */
+    approvalDeptId: number;
+    /*审批部门名称 */
+    approvalDeptName: string;
+    /*审批人id */
+    approverId: number;
+    /*审批人名称 */
+    approverName: string;
+    /*审批方式: 0-会签,1-或签 */
+    approvalType: number;
+    /*审批状态: 1-已审批,2-待审批,3-退回 */
+    approvalStatus: number;
+    /*审批时间 */
+    approvalTime?: string;
+    /*审批内容 */
+    approvalContent: string;
+  }[];
+}
 
-// }
+export interface DrillApprovalRuleForm {
+  approvalDescription?: string;
+  approverListInfo: {
+    approvalOrder: number;
+    deptName: string;
+    approverId: number;
+    approverName: string;
+  }[];
+}