Bladeren bron

feat: 应急演练

wyf 9 maanden geleden
bovenliggende
commit
3e68f37af6
26 gewijzigde bestanden met toevoegingen van 1668 en 3 verwijderingen
  1. 7 0
      src/api/approval/approval.ts
  2. 87 0
      src/api/emergency-drill/emergency-drill.ts
  3. 3 0
      src/components/BasicForm.vue
  4. 89 3
      src/router/routers/emergency.ts
  5. 1 0
      src/router/routers/platform.ts
  6. 100 0
      src/views/emergency/emergency-drill/PageDrillPlanItem.vue
  7. 170 0
      src/views/emergency/emergency-drill/PageDrillPlanList.vue
  8. 70 0
      src/views/emergency/emergency-drill/PageDrillPlanView.vue
  9. 46 0
      src/views/emergency/emergency-drill/PageDrillSignItem.vue
  10. 22 0
      src/views/emergency/emergency-drill/PageDrillSignList.vue
  11. 110 0
      src/views/emergency/emergency-drill/components/DrillPlanCreateItem.vue
  12. 72 0
      src/views/emergency/emergency-drill/components/DrillPlanExecuteForm.vue
  13. 72 0
      src/views/emergency/emergency-drill/components/DrillPlanExecuteItem.vue
  14. 7 0
      src/views/emergency/emergency-drill/components/DrillPlanRecordItem.vue
  15. 95 0
      src/views/emergency/emergency-drill/components/DrillPlanViewActivities.vue
  16. 52 0
      src/views/emergency/emergency-drill/components/DrillPlanViewRecords.vue
  17. 0 0
      src/views/emergency/emergency-drill/components/DrillSignRecordItem.vue
  18. 0 0
      src/views/emergency/emergency-drill/components/DrillSignScriptItem.vue
  19. 165 0
      src/views/emergency/emergency-drill/configs/plan/form.ts
  20. 73 0
      src/views/emergency/emergency-drill/configs/plan/search.ts
  21. 104 0
      src/views/emergency/emergency-drill/configs/plan/table.ts
  22. 0 0
      src/views/emergency/emergency-drill/configs/sign/form.ts
  23. 0 0
      src/views/emergency/emergency-drill/configs/sign/search.ts
  24. 0 0
      src/views/emergency/emergency-drill/configs/sign/table.ts
  25. 132 0
      src/views/emergency/emergency-drill/constants.ts
  26. 191 0
      src/views/emergency/emergency-drill/types.ts

+ 7 - 0
src/api/approval/approval.ts

@@ -62,3 +62,10 @@ export function delApprovalNodeInstance(id: number) {
     method: 'delete',
   });
 }
+
+export function getAllApproval() {
+  return http.request<ApprovalInstanceType[]>({
+    url: '/approvalManagement/queryAllApprovalTemplate',
+    method: 'get',
+  });
+}

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

@@ -0,0 +1,87 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+import {
+  DrillPlanItem,
+  DrillPlanListSearch,
+  CreateEmergencyDrillRuleForm,
+  DrillPlanItemDetail,
+  DrillPlanRecord,
+  ExecuteDrillPlanRuleForm,
+} from '@/views/emergency/emergency-drill/types';
+
+export const queryEnergencyDrillPlanList = (params: QueryPageRequest<DrillPlanListSearch>) => {
+  return http.request<QueryPageResponse<DrillPlanItem>>({
+    url: '/emergencyDrill/queryEmergencyDrillPlanPage',
+    method: 'post',
+    data: params,
+  });
+};
+
+export const createEmergencyDrillPlan = (params: CreateEmergencyDrillRuleForm) => {
+  return http.request({
+    url: '/emergencyDrill/saveEmergencyDrillPlan',
+    method: 'post',
+    data: {
+      drillScope: params.drillScope,
+      drillContent: params.drillContent,
+      dueCompleteTime: params.dueCompleteTime,
+      responsibleDeptIdList: '[' + params.responsibleDeptIdList?.join(',') + ']',
+      coordinateDeptIdList: params.coordinateDeptIdList
+        ? '[' + params.coordinateDeptIdList?.join(',') + ']'
+        : undefined,
+      preplanId: params.preplanId,
+      approvalTemplateId: params.approvalTemplateId,
+    },
+  });
+};
+
+export const saveEmergencyDrillExecute = (params: ExecuteDrillPlanRuleForm) => {
+  return http.request({
+    url: '/emergencyDrill/saveEmergencyDrillPlan',
+    method: 'post',
+    data: {
+      id: params.id,
+      drillTime: params.drillTime,
+      drillLocation: params.drillLocation,
+      personInChargeId: params.personInChargeId,
+      drillDeptIdList: params.drillDeptIdList ? '[' + params.drillDeptIdList?.join(',') + ']' : null,
+      drillScript: params.drillScript || null,
+    },
+  });
+};
+
+export const deleteEmergencyDrillPlan = (id) => {
+  return http.request({
+    url: `/emergencyDrill/deleteEmergencyDrillPlan?drillPlanId=${id}`,
+    method: 'delete',
+  });
+};
+
+export const queryEmergencyDrillPlanDetail = (id) => {
+  return http.request<DrillPlanItemDetail>({
+    url: `/emergencyDrill/queryEmergencyDrillPlanDetail?drillPlanId=${id}`,
+    method: 'get',
+  });
+};
+
+export const queryEmergencyDrillRecord = (id) => {
+  return http.request<DrillPlanRecord>({
+    url: `/emergencyDrill/queryEmergencyDrillRecord?drillPlanId=${id}`,
+    method: 'get',
+  });
+};
+
+export const exportEmergencyDrillRecord = (id) => {
+  return http.request({
+    url: `/emergencyDrill/exportEmergencyDrillRecord?drillPlanId=${id}`,
+    method: 'post',
+  });
+};
+
+// 查询应急预案名
+export const queryEmergencyPlanDetail = (id) => {
+  return http.request<any>({
+    url: `/emergencyPlan/queryEmergencyPlanDetail?emergencyPlanId=${id}`,
+    method: 'get',
+  });
+};

+ 3 - 0
src/components/BasicForm.vue

@@ -73,4 +73,7 @@
   :deep(.el-date-editor) {
     --el-date-editor-width: 100%;
   }
+  :deep(.el-cascader) {
+    width: 100%;
+  }
 </style>

+ 89 - 3
src/router/routers/emergency.ts

@@ -182,7 +182,93 @@ const emergencyManagementRoute = {
       ],
     },
     {
-      id: 2008,
+      id: 2009,
+      parentId: 2000,
+      name: 'emergency-drill',
+      path: 'emergency-drill',
+      component: '',
+      redirect: '',
+      meta: {
+        activeMenu: null,
+        alwaysShow: false,
+        frameSrc: '',
+        hidden: false,
+        icon: 'EmergencyDrillIcon',
+        isFrame: 0,
+        isRoot: false,
+        noCache: false,
+        query: '',
+        title: '应急演练',
+      },
+      children: [
+        {
+          id: 200901,
+          parentId: 2009,
+          component: '/emergency/emergency-drill/PageDrillPlanList',
+          name: 'emergency-drill-plan',
+          path: 'emergency-drill-plan',
+          meta: {
+            title: '演练计划',
+          },
+        },
+        {
+          id: 200902,
+          parentId: 2009,
+          component: '/emergency/emergency-drill/PageDrillPlanItem',
+          name: 'emergency-drill-plan-item',
+          path: 'emergency-drill-plan-item',
+          meta: {
+            activeMenu: '/emergency-drill/emergency-drill-plan',
+            title: '编辑演练计划',
+            hidden: false,
+            alwaysShow: false,
+            frameSrc: '',
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+          },
+        },
+        {
+          id: 200903,
+          parentId: 2009,
+          component: '/emergency/emergency-drill/PageDrillPlanView',
+          name: 'emergency-drill-plan-view',
+          path: 'emergency-drill-plan-view',
+          meta: {
+            activeMenu: '/emergency-drill/emergency-drill-plan',
+            title: '演练计划详情',
+            hidden: true,
+          },
+        },
+        {
+          id: 200904,
+          parentId: 2009,
+          component: '/emergency/emergency-drill/PageDrillSignList',
+          name: 'emergency-drill-sign',
+          path: 'emergency-drill-sign',
+          meta: {
+            activeMenu: '/emergency-drill/emergency-drill-sign',
+            title: '演练会签',
+          },
+        },
+        {
+          id: 200905,
+          parentId: 2009,
+          component: '/emergency/emergency-drill/PageDrillSignItem',
+          name: 'emergency-drill-sign-item',
+          path: 'emergency-drill-sign-item',
+          meta: {
+            activeMenu: '/emergency-drill/emergency-drill-sign',
+            title: '演练会签详情',
+            hidden: true,
+          },
+        },
+      ],
+    },
+    {
+      id: 2010,
       parentId: 2000,
       name: 'emergency-procedure',
       path: 'emergency-procedure',
@@ -202,7 +288,7 @@ const emergencyManagementRoute = {
       },
     },
     {
-      id: 2009,
+      id: 2011,
       parentId: 2000,
       name: 'emergency-procedure-complete',
       path: 'emergency-procedure-complete/:id',
@@ -222,7 +308,7 @@ const emergencyManagementRoute = {
       },
     },
     {
-      id: 2010,
+      id: 2012,
       parentId: 2000,
       name: 'emergency-procedure-report',
       path: 'emergency-procedure-report/:id',

+ 1 - 0
src/router/routers/platform.ts

@@ -102,6 +102,7 @@ const platformRoutes = {
       name: 'SystemApprovalNode',
       component: '/system/approval/PageApprovalNode',
       meta: {
+        activeMenu: '/approval',
         icon: 'UserOutlined',
         title: '编辑审批流程',
       },

+ 100 - 0
src/views/emergency/emergency-drill/PageDrillPlanItem.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" />
+    </main>
+    <footer class="safety-platform-container__footer">
+      <!-- 提交按钮 -->
+      <div v-if="operate === 'create'">
+        <el-button @click="router.back()">取消</el-button>
+        <el-button type="primary" @click="createSubmit">提交</el-button>
+      </div>
+      <div v-if="operate === 'execute'">
+        <el-button @click="executeSave">保存</el-button>
+        <el-button type="primary" @click="executeSubmit">提交</el-button>
+      </div>
+      <div v-if="operate === 'record'">
+        <el-button @click="recordSave">保存</el-button>
+        <el-button type="primary" @click="recordSubmit">提交审批</el-button>
+      </div>
+    </footer>
+    <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ElMessage } from 'element-plus';
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import { createEmergencyDrillPlan } from '@/api/emergency-drill/emergency-drill';
+
+  const formLoading = ref(false);
+  const router = useRouter();
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = route.query.id;
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'create':
+        return `创建演练计划`;
+      case 'view':
+        return `演练计划详情`;
+      case 'execute':
+        return `演练执行`;
+      case 'record':
+        return `演练记录`;
+      default:
+        return '未知操作';
+    }
+  });
+  const dynamicComponent = computed(() => {
+    switch (operate) {
+      case 'create':
+        return defineAsyncComponent(() => import('./components/DrillPlanCreateItem.vue'));
+      case 'execute':
+        return defineAsyncComponent(() => import('./components/DrillPlanExecuteItem.vue'));
+      case 'record':
+        return defineAsyncComponent(() => import('./components/DrillPlanRecordItem.vue'));
+    }
+  });
+
+  const dynamicComponentRef = ref();
+  const createSubmit = async () => {
+    if (!dynamicComponentRef.value) return;
+    const res = await dynamicComponentRef.value.handleValidate();
+    if (!res) return;
+    const formData = dynamicComponentRef.value.getFormData();
+    let message;
+    try {
+      formLoading.value = true;
+
+      await createEmergencyDrillPlan(formData);
+      message = '创建成功';
+
+      ElMessage.success(message);
+      router.back();
+    } finally {
+      formLoading.value = false;
+    }
+  };
+
+  const executeSave = async () => {};
+  const executeSubmit = async () => {};
+
+  const recordSave = async () => {};
+  const recordSubmit = async () => {};
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>

+ 170 - 0
src/views/emergency/emergency-drill/PageDrillPlanList.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 演练计划 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <!-- 按钮 -->
+          <el-button type="primary" class="search-table-container--button" :icon="Plus" @click="handleCreateDrillPlan">
+            创建演练计划
+          </el-button>
+          <!-- 搜索栏 -->
+          <BasicSearch
+            :searchConfig="DRILL_PLAN_LIST_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:searchData="handleSearch"
+          ></BasicSearch>
+        </header>
+        <!-- 表格 -->
+        <BasicTable
+          :tableConfig="tableConfig"
+          :tableData="tableData"
+          @update:pageSize="handleSizeChange"
+          @update:pageNumber="handleCurrentChange"
+        >
+          <template #action="scope">
+            <ActionButton text="查看" @click="handleViewDrillPlan(scope.row.id)"></ActionButton>
+            <ActionButton
+              v-if="scope.row.status > 0 && scope.row.status <= 3"
+              text="演练执行"
+              @click="handleToExecute(scope.row.id)"
+            ></ActionButton>
+            <ActionButton
+              v-else-if="scope.row.status < 6"
+              text="演练记录"
+              @click="handleToRecord(scope.row.id)"
+            ></ActionButton>
+            <ActionButton
+              text="删除"
+              :popconfirm="{
+                title: '确定要删除?',
+              }"
+              @confirm="handleDeleteDrillPlan(scope.row.id)"
+            />
+          </template>
+        </BasicTable>
+      </div>
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ElButton } from 'element-plus';
+  import { Plus } from '@element-plus/icons-vue';
+  import { reactive, ref, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import BasicSearch from '@/components/BasicSearch.vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+
+  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';
+
+  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(() => {
+      getTabelData();
+    });
+  }
+
+  // 搜索栏
+  const searchData = reactive<DrillPlanListSearch>({});
+  function handleSearch() {
+    tabelQuery.value.queryParam = searchData;
+    getTabelData();
+  }
+
+  // 表格
+  const { tableConfig, pagination } = useTableConfig(DRILL_PLAN_LIST_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tabelQuery = ref({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  });
+
+  const tableData = ref<DrillPlanItem[]>([]);
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tabelQuery.value.pageSize = value;
+    getTabelData();
+  };
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tabelQuery.value.pageSize = value;
+    getTabelData();
+  };
+
+  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;
+    tableConfig.loading = false;
+  }
+
+  // 初始化
+  onMounted(() => {
+    getTabelData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+</style>

+ 70 - 0
src/views/emergency/emergency-drill/PageDrillPlanView.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div>
+        <BreadcrumbBack />
+        <span class="breadcrumb-title" style="margin-left: 8px">{{ '演练记录' }}</span>
+      </div>
+      <el-tabs v-model="activeName">
+        <el-tab-pane
+          v-for="item in EMERGENCY_DRILL_DETAIL_SUBPAGE"
+          :key="item.value"
+          :label="item.label"
+          :name="item.value"
+        />
+      </el-tabs>
+    </header>
+    <main class="safety-platform-container__main">
+      <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" />
+    </main>
+    <footer class="safety-platform-container__footer" v-if="activeName === EMERGENCY_DRILL_DETAIL_SUBPAGE[1].value">
+      <!-- 按钮 -->
+      <el-button type="primary" @click="downloadPDF">导出</el-button>
+    </footer>
+    <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import { EMERGENCY_DRILL_DETAIL_SUBPAGE } from './constants';
+  import { exportEmergencyDrillRecord } from '@/api/emergency-drill/emergency-drill';
+
+  const formLoading = ref(false);
+  const activeName = ref(EMERGENCY_DRILL_DETAIL_SUBPAGE[0].value);
+  const router = useRouter();
+  const route = useRoute();
+  const id = route.query.id;
+
+  const dynamicComponent = computed(() => {
+    switch (activeName.value) {
+      case 'activities':
+        return defineAsyncComponent(() => import('./components/DrillPlanViewActivities.vue'));
+      case 'records':
+        return defineAsyncComponent(() => import('./components/DrillPlanViewRecords.vue'));
+    }
+  });
+
+  const downloadPDF = async () => {
+    try {
+      await exportEmergencyDrillRecord(id);
+    } catch (e) {
+      console.log(e);
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+  :deep(.el-tabs__item) {
+    font-size: 14px !important;
+  }
+</style>

+ 46 - 0
src/views/emergency/emergency-drill/PageDrillSignItem.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" />
+    </main>
+    <footer class="safety-platform-container__footer" v-if="operate">
+      <!-- 提交按钮 -->
+    </footer>
+    <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { useRoute } from 'vue-router';
+
+  const formLoading = ref(false);
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = route.query.id;
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'script':
+        return `演练脚本确认`;
+      case 'record':
+        return `演练记录确认`;
+      default:
+        return '未知操作';
+    }
+  });
+  const dynamicComponent = computed(() => {
+    switch (operate) {
+      case 'script':
+        return defineAsyncComponent(() => import('./components/DrillSignScriptItem.vue'));
+      case 'record':
+        return defineAsyncComponent(() => import('./components/DrillSignRecordItem.vue'));
+    }
+  });
+</script>
+
+<style scoped></style>

+ 22 - 0
src/views/emergency/emergency-drill/PageDrillSignList.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 演练会签 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <!-- 搜索栏 -->
+        </header>
+        <!-- 表格 -->
+      </div>
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+</style>

+ 110 - 0
src/views/emergency/emergency-drill/components/DrillPlanCreateItem.vue

@@ -0,0 +1,110 @@
+<template>
+  <div>
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #responsibleDeptIdList>
+        <el-cascader
+          v-model="ruleFormData.responsibleDeptIdList"
+          :options="deptTree"
+          :props="cascaderProp"
+          clearable
+          collapse-tags
+          :show-all-levels="false"
+          :max-collapse-tags="3"
+          placeholder="请选择责任部门"
+        >
+        </el-cascader>
+      </template>
+      <template #coordinateDeptIdList>
+        <el-cascader
+          v-model="ruleFormData.coordinateDeptIdList"
+          :options="deptTree"
+          :props="cascaderProp"
+          clearable
+          collapse-tags
+          :show-all-levels="false"
+          :max-collapse-tags="3"
+          placeholder="请选择配合部门"
+        >
+        </el-cascader>
+      </template>
+      <template #preplanId>
+        <el-select
+          v-model="ruleFormData.preplanId"
+          multiple
+          collapse-tags
+          collapse-tags-tooltip
+          :max-collapse-tags="1"
+          placeholder="请选择应急预案"
+          filterable
+          @change=""
+          class="custom-select"
+        >
+          <!-- <el-option v-for="item in firstLevelDepts" :key="item.id" :label="item.deptName" :value="item.id" /> -->
+        </el-select>
+      </template>
+      <template #approvalTemplateId>
+        <el-select v-model="ruleFormData.approvalTemplateId" placeholder="请选择审批流程" class="custom-select">
+          <el-option v-for="item in allApprovals" :key="item.id" :label="item.templateName" :value="item.id" />
+        </el-select>
+      </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { DRILL_CREATE_FORM_CONFIG, DRILL_CREATE_FORM_DATA, DRILL_CREATE_FORM_RULES } from '../configs/plan/form';
+  import { CreateEmergencyDrillRuleForm } from '../types';
+  import { onMounted, ref } from 'vue';
+  import { DeptTree } from '@/types/dept/type';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { getAllApproval } from '@/api/approval/approval';
+  import { ApprovalInstanceType } from '@/views/system/approval/types';
+
+  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;
+  };
+
+  onMounted(() => {
+    loadDeptTreeData();
+    loadApprovalData();
+  });
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const parentValidateResult = await basicFormRef.value.validateForm();
+    return parentValidateResult;
+  };
+
+  const getFormData = () => {
+    cloneRuleFormData();
+    return ruleFormData;
+  };
+
+  defineExpose({
+    handleValidate,
+    getFormData,
+  });
+</script>
+
+<style scoped></style>

+ 72 - 0
src/views/emergency/emergency-drill/components/DrillPlanExecuteForm.vue

@@ -0,0 +1,72 @@
+<template>
+  <div>
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #personInChargeId> </template>
+      <template #drillDeptIdList>
+        <el-cascader
+          v-model="ruleFormData.drillDeptIdList"
+          :options="deptTree"
+          :props="cascaderProp"
+          clearable
+          collapse-tags
+          :show-all-levels="false"
+          :max-collapse-tags="3"
+          placeholder="请选择配合部门"
+        >
+        </el-cascader>
+      </template>
+      <template #drillScript> </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { DrillPlanItemDetail, ExecuteDrillPlanRuleForm } from '../types';
+  import {
+    DRILL_EXECUTE_FORM_CONFIG,
+    INS_DRILL_EXECUTE_FORM_RULES,
+    DEP_DRILL_EXECUTE_FORM_RULES,
+    DRILL_EXECUTE_FORM_DATA,
+  } from '../configs/plan/form';
+  import { DeptTree } from '@/types/dept/type';
+  import { getAllDepartments } from '@/api/auth/dept';
+
+  const props = defineProps<{
+    drillData: DrillPlanItemDetail;
+  }>();
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<ExecuteDrillPlanRuleForm>(
+      DRILL_EXECUTE_FORM_CONFIG,
+      props.drillData
+        ? {
+            id: props.drillData.id,
+            drillTime: props.drillData.drillTime,
+            drillLocation: props.drillData.drillLocation,
+            personInChargeId: props.drillData.personInChargeId,
+            drillDeptIdList: undefined,
+            drillScript: props.drillData.drillScript,
+          }
+        : DRILL_EXECUTE_FORM_DATA,
+      props.drillData.drillScope === 'institute_level' ? INS_DRILL_EXECUTE_FORM_RULES : DEP_DRILL_EXECUTE_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;
+  };
+
+  onMounted(() => {
+    loadDeptTreeData();
+  });
+</script>
+
+<style scoped></style>

+ 72 - 0
src/views/emergency/emergency-drill/components/DrillPlanExecuteItem.vue

@@ -0,0 +1,72 @@
+<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>
+    </div>
+    <div>
+      <div class="name">演练实施</div>
+      <DrillPlanExecuteForm v-if="drillData" :drill-data="drillData" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { DrillPlanItemDetail } from '../types';
+  import { DRILL_VIEW_CONTENT } from '../constants';
+  import DrillPlanExecuteForm from './DrillPlanExecuteForm.vue';
+  import { queryEmergencyDrillPlanDetail, queryEmergencyPlanDetail } from '@/api/emergency-drill/emergency-drill';
+  import { getAllApproval } from '@/api/approval/approval';
+
+  const route = useRoute();
+  const id = route.query.id;
+
+  const drillData = ref<DrillPlanItemDetail>();
+  async function getDrillData() {
+    try {
+      drillData.value = await queryEmergencyDrillPlanDetail(id);
+
+      drillData.value.responsibleDeptNameList = drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '');
+      drillData.value.coordinateDeptNameList = drillData.value.coordinateDeptNameList
+        ? drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '')
+        : undefined;
+
+      const emergencyPlan = await queryEmergencyPlanDetail(drillData.value.emergencyPlanId);
+      drillData.value.emergencyPlanFile = emergencyPlan.planName + emergencyPlan.appendix;
+
+      const allApprovals = await getAllApproval();
+      drillData.value.approvalTemplateName = allApprovals.find(
+        (x) => x.id === drillData.value!.approvalTemplateId,
+      )?.templateName;
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  onMounted(() => {
+    getDrillData();
+  });
+</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>

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

@@ -0,0 +1,7 @@
+<template>
+  <div> 记录 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped></style>

+ 95 - 0
src/views/emergency/emergency-drill/components/DrillPlanViewActivities.vue

@@ -0,0 +1,95 @@
+<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>
+    </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>
+          <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>
+    </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!">
+      </BasicTable>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  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';
+
+  const route = useRoute();
+  const id = route.query.id;
+  const drillData = ref<DrillPlanItemDetail>();
+
+  async function getData() {
+    try {
+      tableConfig.loading = true;
+      drillData.value = await queryEmergencyDrillPlanDetail(id);
+
+      drillData.value.responsibleDeptNameList = drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '');
+      drillData.value.coordinateDeptNameList = drillData.value.coordinateDeptNameList
+        ? drillData.value.responsibleDeptNameList!.replace(/^\[|\]$/g, '')
+        : undefined;
+
+      const emergencyPlan = await queryEmergencyPlanDetail(drillData.value.emergencyPlanId);
+      drillData.value.emergencyPlanFile = emergencyPlan.planName + emergencyPlan.appendix;
+
+      const allApprovals = await getAllApproval();
+      drillData.value.approvalTemplateName = allApprovals.find(
+        (x) => x.id === drillData.value!.approvalTemplateId,
+      )?.templateName;
+      tableConfig.loading = false;
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  const { tableConfig } = useTableConfig(
+    DRILL_PLAN_ACTIVITIES_TABLE_COLUMNS,
+    DRILL_PLAN_ACTIVITIES_TABLE_OPTIONS,
+    false,
+  );
+
+  onMounted(() => {
+    getData();
+  });
+</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>

+ 52 - 0
src/views/emergency/emergency-drill/components/DrillPlanViewRecords.vue

@@ -0,0 +1,52 @@
+<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>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { DrillPlanRecord } from '../types';
+  import { DRILL_VIEW_RECORD } from '../constants';
+  import { queryEmergencyDrillRecord } from '@/api/emergency-drill/emergency-drill';
+
+  const route = useRoute();
+  const id = route.query.id;
+  const recordData = ref<DrillPlanRecord>();
+
+  async function getData() {
+    try {
+      recordData.value = await queryEmergencyDrillRecord(id);
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  onMounted(() => {
+    getData();
+  });
+</script>
+
+<style scoped>
+  .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;
+  }
+</style>

+ 0 - 0
src/views/emergency/emergency-drill/components/DrillSignRecordItem.vue


+ 0 - 0
src/views/emergency/emergency-drill/components/DrillSignScriptItem.vue


+ 165 - 0
src/views/emergency/emergency-drill/configs/plan/form.ts

@@ -0,0 +1,165 @@
+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,
+      },
+    ],
+  },
+  {
+    prop: 'drillContent',
+    label: '演练内容:',
+    component: 'el-input',
+    componentProps: { placeholder: '请输入演练内容' },
+  },
+  {
+    prop: 'dueCompleteTime',
+    label: '计划完成日期:',
+    component: 'el-date-picker',
+    componentProps: {
+      type: 'date',
+      placeholder: '请选择计划完成日期',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    prop: 'responsibleDeptIdList',
+    label: '责任部门:',
+    slot: 'responsibleDeptIdList',
+  },
+  {
+    prop: 'coordinateDeptIdList',
+    label: '配合部门:',
+    slot: 'coordinateDeptIdList',
+  },
+  {
+    prop: 'preplanId',
+    label: '应急预案:',
+    slot: 'preplanId',
+  },
+  {
+    prop: 'approvalTemplateId',
+    label: '审批流程:',
+    slot: 'approvalTemplateId',
+  },
+];
+
+export const DRILL_CREATE_FORM_RULES = {
+  drillScope: [
+    {
+      required: true,
+      message: '请选择演练规模',
+      trigger: 'change',
+    },
+  ],
+  drillContent: [
+    {
+      required: true,
+      message: '请输入演练内容',
+      trigger: 'blur',
+    },
+  ],
+  dueCompleteTime: [
+    {
+      required: true,
+      message: '请选择计划完成时间',
+      trigger: 'change',
+    },
+  ],
+  responsibleDeptIdList: [
+    {
+      required: true,
+      message: '请选择责任部门',
+      trigger: 'change',
+    },
+  ],
+  approvalTemplateId: [
+    {
+      required: true,
+      message: '请选择审批流程',
+      trigger: 'change',
+    },
+  ],
+};
+
+export const DRILL_CREATE_FORM_DATA = {
+  drillScope: '',
+  drillContent: '',
+  dueCompleteTime: '',
+  responsibleDeptIdList: undefined,
+  coordinateDeptIdList: undefined,
+  preplanId: undefined,
+  approvalTemplateId: undefined,
+};
+
+export const DRILL_EXECUTE_FORM_CONFIG = [
+  {
+    prop: 'drillTime',
+    label: '演练时间:',
+    component: 'el-date-picker',
+    componentProps: {
+      type: 'date',
+      placeholder: '请选择演练时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    prop: 'drillLocation',
+    label: '演练地点:',
+    component: 'el-input',
+    componentProps: { placeholder: '请输入演练地点' },
+  },
+  {
+    prop: 'personInChargeId',
+    label: '负责人:',
+    slot: 'personInChargeId',
+  },
+  {
+    prop: 'drillDeptIdList',
+    label: '演练部门:',
+    slot: 'drillDeptIdList',
+  },
+  {
+    prop: 'drillScript',
+    label: '演练脚本:',
+    slot: 'drillScript',
+  },
+];
+
+export const INS_DRILL_EXECUTE_FORM_RULES = {
+  drillTime: [{ required: true, message: '请选择演练时间', trigger: 'change' }],
+  drillLocation: [{ required: true, message: '请输入演练地点', trigger: 'blur' }],
+  personInChargeId: [{ required: true, message: '请选择演练负责人', trigger: 'change' }],
+  drillDeptIdList: [{ required: true, message: '请选择演练部门', trigger: 'change' }],
+  drillScript: [{ required: true, message: '请上传演练脚本', trigger: 'change' }],
+};
+
+export const DEP_DRILL_EXECUTE_FORM_RULES = {
+  drillTime: [{ required: true, message: '请选择演练时间', trigger: 'change' }],
+  drillLocation: [{ required: true, message: '请输入演练地点', trigger: 'blur' }],
+  personInChargeId: [{ required: true, message: '请选择演练负责人', trigger: 'change' }],
+};
+
+export const DRILL_EXECUTE_FORM_DATA = {
+  drillTime: undefined,
+  drillLocation: '',
+  personInChargeId: undefined,
+  drillDeptIdList: undefined,
+  drillScript: undefined,
+};

+ 73 - 0
src/views/emergency/emergency-drill/configs/plan/search.ts

@@ -0,0 +1,73 @@
+import type { SearchConfig } from '@/types/basic-search';
+import {
+  EMERGENCY_DRILL_SCOPE,
+  EMERGENCY_DRILL_SCOPE_DICT,
+  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: '全部' },
+  },
+  {
+    label: '演练内容:',
+    prop: 'drillContent',
+    component: 'ElInput',
+  },
+  {
+    label: '责任部门:',
+    prop: 'responsibleDept',
+    component: 'ElInput',
+  },
+  {
+    label: '演练时间:',
+    prop: 'daterange',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'daterange',
+      startPlaceholder: '开始时间',
+      endPlaceholder: '结束时间',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '状态:',
+    prop: 'status',
+    component: 'ElSelect',
+    selectOptions: [
+      { label: '选择全部', value: EMERGENCY_DRILL_STATUS.TOTAL },
+      {
+        label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.WAIT_SCRIPT],
+        value: EMERGENCY_DRILL_STATUS.WAIT_SCRIPT,
+      },
+      { label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.WAIT_SIGN], value: EMERGENCY_DRILL_STATUS.WAIT_SIGN },
+      {
+        label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.WAIT_EXECUTE],
+        value: EMERGENCY_DRILL_STATUS.WAIT_EXECUTE,
+      },
+      {
+        label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.WAIT_RECORD],
+        value: EMERGENCY_DRILL_STATUS.WAIT_RECORD,
+      },
+      {
+        label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.WAIT_CHECK],
+        value: EMERGENCY_DRILL_STATUS.WAIT_CHECK,
+      },
+      { label: EMERGENCY_DRILL_STATUS_DICT[EMERGENCY_DRILL_STATUS.COMPLETE], value: EMERGENCY_DRILL_STATUS.COMPLETE },
+    ],
+    componentProps: { placeholder: '选择全部' },
+  },
+];

+ 104 - 0
src/views/emergency/emergency-drill/configs/plan/table.ts

@@ -0,0 +1,104 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  height: 'calc(70vh - 150px)',
+};
+
+export const DRILL_PLAN_LIST_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    prop: 'id',
+    label: '序号',
+    align: 'center',
+    // width: '150px',
+  },
+  {
+    prop: 'drillScope',
+    label: '演练规模',
+    align: 'center',
+    // minWidth: '200px',
+  },
+  {
+    prop: 'drillContent',
+    label: '演练内容',
+    align: 'center',
+    // minWidth: '800px',
+  },
+  {
+    prop: 'dueCompleteTime',
+    label: '计划完成日期',
+    align: 'center',
+    // minWidth: '800px',
+  },
+
+  {
+    prop: 'responsibleDeptNameList',
+    label: '责任部门',
+    align: 'center',
+    // minWidth: '800px',
+  },
+  {
+    prop: 'coordinateDeptNameList',
+    label: '配合部门',
+    align: 'center',
+    // minWidth: '800px',
+  },
+  {
+    prop: 'statusLabel',
+    label: '状态',
+    align: 'center',
+    // minWidth: '800px',
+  },
+  {
+    prop: 'drillTime',
+    label: '演练时间',
+    align: 'center',
+    // minWidth: '800px',
+  },
+
+  {
+    prop: 'action',
+    label: '操作',
+    slot: 'action',
+    fixed: 'right',
+    width: '200px',
+    align: 'center',
+  },
+];
+
+export const DRILL_PLAN_ACTIVITIES_TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  height: 'calc(50vh - 100px)',
+};
+
+export const DRILL_PLAN_ACTIVITIES_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    prop: 'id',
+    label: '序号',
+    align: 'center',
+  },
+  {
+    prop: 'drillDeptName',
+    label: '部门',
+    align: 'center',
+  },
+  {
+    prop: 'drillScriptStatus',
+    label: '脚本确认状态',
+    slot: 'drillScriptStatus',
+    align: 'center',
+  },
+  {
+    prop: 'planToParticipateCount',
+    label: '计划参与人数',
+    align: 'center',
+  },
+  {
+    prop: 'actualSignCount',
+    label: '实际签到人数',
+    align: 'center',
+  },
+];

+ 0 - 0
src/views/emergency/emergency-drill/configs/sign/form.ts


+ 0 - 0
src/views/emergency/emergency-drill/configs/sign/search.ts


+ 0 - 0
src/views/emergency/emergency-drill/configs/sign/table.ts


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

@@ -0,0 +1,132 @@
+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, // 待传脚本
+  WAIT_SIGN: 2, // 脚本会签
+  WAIT_EXECUTE: 3, // 待执行
+  WAIT_RECORD: 4, // 待记录
+  WAIT_CHECK: 5, // 记录待审批
+  COMPLETE: 6, // 已完成
+};
+
+export const EMERGENCY_DRILL_STATUS_DICT = {
+  1: '待传脚本', // 待传脚本
+  2: '脚本会签', // 脚本会签
+  3: '待执行', // 待执行
+  4: '待记录', // 待记录
+  5: '记录待审批', // 待审批
+  6: '已完成', // 已完成
+};
+
+export const EMERGENCY_DRILL_DETAIL_SUBPAGE = [
+  {
+    label: '演练活动',
+    value: 'activities',
+  },
+  {
+    label: '演练记录',
+    value: 'records',
+  },
+];
+
+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: 'drillScript',
+    link: 'drillScript',
+  },
+  {
+    label: '签到码:',
+    value: 'qr',
+    link: 'qr',
+  },
+];
+
+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',
+  },
+];

+ 191 - 0
src/views/emergency/emergency-drill/types.ts

@@ -0,0 +1,191 @@
+export interface DrillPlanItem {
+  /*自增主键 */
+  id: number;
+  /*演练规模(字典) */
+  drillScope: string;
+  /*演练内容 */
+  drillContent: string;
+  /*计划完成时间 */
+  dueCompleteTime: string;
+  /*责任部门id列表 */
+  responsibleDeptIdList: string;
+  /*责任部门名称列表 */
+  responsibleDeptNameList: string;
+  /*配合部门id列表 */
+  coordinateDeptIdList: string;
+  /*配合部门名称列表 */
+  coordinateDeptNameList: string;
+  /*应急预案id */
+  emergencyPlanId: number;
+  /*审批模板id */
+  approvalTemplateId: number;
+  /*状态: 1-待传脚本,2-脚本会签,3-待执行,4-待记录,5-记录待审批,6-已完成 */
+  status: number;
+  statusLabel: string;
+  /*演练时间 */
+  drillTime: string;
+  /*演练地点 */
+  drillLocation: string;
+  /*演练负责人id */
+  personInChargeId: number;
+  /*演练部门id列表 */
+  drillDeptIdList: string;
+  /*演练脚本 */
+  drillScript: string;
+  /*提交人 */
+  createdBy: number;
+}
+
+export interface DrillPlanListSearch {
+  drillScope?: number;
+  drillContent?: string;
+  responsibleDept?: string;
+  status?: number;
+  daterange?: string[];
+  dueCompleteTimeStart?: string;
+  dueCompleteTimeEnd?: string;
+}
+
+export interface CreateEmergencyDrillRuleForm {
+  /*演练规模(字典) */
+  drillScope?: string;
+  /*演练内容 */
+  drillContent?: string;
+  /*计划完成时间 */
+  dueCompleteTime?: string;
+  /*责任部门id列表 */
+  responsibleDeptIdList?: number[];
+  /*配合部门id列表 */
+  coordinateDeptIdList?: number[];
+  /*应急预案 */
+  preplanId?: number;
+  /*审批流程 */
+  approvalTemplateId?: number;
+}
+
+export interface DrillPlanItemDetail {
+  /*自增主键 */
+  id?: number;
+  /*演练规模(字典) */
+  drillScope?: string;
+  /*演练内容 */
+  drillContent?: string;
+  /*计划完成时间 */
+  dueCompleteTime?: string;
+  /*责任部门id列表 */
+  responsibleDeptIdList?: string;
+  /*责任部门名称列表 */
+  responsibleDeptNameList?: string;
+  /*配合部门id列表 */
+  coordinateDeptIdList?: string;
+  /*配合部门名称列表 */
+  coordinateDeptNameList?: string;
+  /*应急预案id */
+  emergencyPlanId?: number;
+  emergencyPlanFile?: string;
+  /*审批模板id */
+  approvalTemplateId?: number;
+  approvalTemplateName?: string;
+  /*状态?: 1-待传脚本,2-脚本会签,3-待执行,4-待记录,5-记录待审批,6-已完成 */
+  status?: number;
+  /*演练时间 */
+  drillTime?: string;
+  /*演练地点 */
+  drillLocation?: string;
+  /*演练负责人id */
+  personInChargeId?: number;
+  /*演练部门id列表 */
+  drillDeptIdList?: string;
+  /*演练脚本 */
+  drillScript?: string;
+  /*提交人 */
+  createdBy?: number;
+  /*创建时间 */
+  createdAt?: string;
+  /*更新时间 */
+  updatedAt?: string;
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted?: number;
+  /*演练负责人名称 */
+  personInChargeName?: string;
+  /*计划详情列表 */
+  planDetailList?: {
+    /*自增主键 */
+    id?: number;
+    /*演练计划id */
+    drillPlanId?: number;
+    /*参与部门id */
+    deptId?: number;
+    /*参与部门名称 */
+    deptName?: number;
+    /*演练脚本状态?: 1-未会签,2-已会签 */
+    drillScriptStatus?: number;
+    /*计划参与人数 */
+    planToParticipateCount?: number;
+    /*实际签到人数 */
+    actualSignCount?: number;
+    /*创建时间 */
+    createdAt?: string;
+    /*更新时间 */
+    updatedAt?: string;
+    /*0-未删除,大于0(时间戳)-已删除 */
+    isDeleted?: number;
+  }[];
+}
+
+export interface DrillPlanRecord {
+  /*自增主键 */
+  id?: number;
+  /*演练计划id */
+  drillPlanId?: number;
+  /*参与人员 */
+  participants?: string;
+  /*演练描述 */
+  drillDescription?: string;
+  /*演练照片 */
+  drillImage?: string;
+  /*演练效果评估 */
+  drillEffectAssess?: string;
+  /*审批状态?: 0-审批中,1-已审批,2-已退回 */
+  approvalStatus?: number;
+  /*创建时间 */
+  createdAt?: string;
+  /*更新时间 */
+  updatedAt?: string;
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted?: number;
+  /*演练内容 */
+  drillContent?: string;
+  /*演练时间 */
+  drillTime?: string;
+  /*演练地点 */
+  drillLocation?: string;
+  /*提交人id */
+  createdById?: number;
+  /*提交人姓名 */
+  createdByName?: string;
+  /*提交时间 */
+  createdTime?: string;
+  /*演练负责人id */
+  personInChargeId?: number;
+  /*演练负责人姓名 */
+  personInChargeName?: string;
+  /*审批通过时间 */
+  approvalTime?: string;
+  /*审批模板id */
+  approvalTemplateId?: number;
+}
+
+export interface ExecuteDrillPlanRuleForm {
+  id?: number;
+  /*演练时间 */
+  drillTime?: string;
+  /*演练地点 */
+  drillLocation?: string;
+  /*演练负责人id */
+  personInChargeId?: number;
+  /*演练部门id列表 */
+  drillDeptIdList?: number[];
+  /*演练脚本 */
+  drillScript?: any;
+}