Просмотр исходного кода

Merge branch 'dev-bxy' into 'dev'

feat: 应急管理-应急处置(除指挥中心)

See merge request product-group-fe/sfy-safety-group/sfy-safety!131
陈昶 9 месяцев назад
Родитель
Сommit
1755aa334a
21 измененных файлов с 2272 добавлено и 58 удалено
  1. 296 0
      src/api/emergency-procedure/index.ts
  2. 1 0
      src/assets/svg/start-emergency-event.svg
  3. 0 5
      src/components/SvgIcon/SvgIcon.vue.d.ts
  4. 61 1
      src/router/routers/emergency.ts
  5. 96 52
      src/views/emergency/command-center/PageCommandCenter.vue
  6. 314 0
      src/views/emergency/emergency-procedure/PageProcedure.vue
  7. 239 0
      src/views/emergency/emergency-procedure/PageProcedureComplete.vue
  8. 165 0
      src/views/emergency/emergency-procedure/PageProcedureReport.vue
  9. 131 0
      src/views/emergency/emergency-procedure/components/AddLossItemRecord.vue
  10. 91 0
      src/views/emergency/emergency-procedure/components/AssociateLossReport.vue
  11. 167 0
      src/views/emergency/emergency-procedure/components/ChartOfDisasterLocation.vue
  12. 137 0
      src/views/emergency/emergency-procedure/components/ChartOfSafetyLevel.vue
  13. 125 0
      src/views/emergency/emergency-procedure/components/EmergencyEventManage.vue
  14. 119 0
      src/views/emergency/emergency-procedure/components/TimeLine.vue
  15. 74 0
      src/views/emergency/emergency-procedure/config/form.ts
  16. 25 0
      src/views/emergency/emergency-procedure/config/index.ts
  17. 40 0
      src/views/emergency/emergency-procedure/config/search.ts
  18. 126 0
      src/views/emergency/emergency-procedure/config/table.ts
  19. 25 0
      src/views/emergency/emergency-procedure/constant/index.ts
  20. 33 0
      src/views/emergency/emergency-procedure/hooks/index.ts
  21. 7 0
      src/views/emergency/src/constant.ts

+ 296 - 0
src/api/emergency-procedure/index.ts

@@ -0,0 +1,296 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ *
+ * @description: 应急处置
+ *
+ */
+export interface ProcedureListQuery {
+  eventType?: string;
+  eventName?: string;
+  startTimeStart?: string;
+  startTimeEnd?: string;
+  status?: string; // 状态: 1-启动中,2-已结束,3-已关闭
+}
+
+export interface EmergencyProcedureManageStruct {
+  id?: number; // 自增主键
+  eventType?: string; // 事件类型(字典)
+  eventLocation?: string; // 事件地点
+  eventName?: string; // 事件名称
+  emergencyPlanId?: number; // 应急预案id
+  startTime?: string; // 启动时间
+  status?: number; // 状态: 1-启动中,2-已结束,3-已关闭
+  completeTime?: string; // 处置完成时间
+  suggestion?: string; // 建议
+  createdAt?: string; // 创建时间
+  updatedAt?: string; // 更新时间
+  isDeleted?: number; // 0-未删除,大于0(时间戳)-已删除
+}
+
+/**
+ * @description: 分页查询应急处置任务列表
+ */
+export const getEmergencyProcedureList = (params: QueryPageRequest<ProcedureListQuery>) => {
+  return http.request<QueryPageResponse<EmergencyProcedureManageStruct>>({
+    url: '/emergencyHandleTask/queryEmergencyHandleTaskPage',
+    method: 'post',
+    params,
+  });
+};
+
+/**
+ * @description: 创建应急处置任务
+ */
+export const createEmergencyProcedure = (params: EmergencyProcedureManageStruct) => {
+  return http.request({
+    url: '/emergencyHandleTask/saveEmergencyHandleTask',
+    method: 'post',
+    params,
+  });
+};
+
+/**
+ * @description: 更新应急处置任务
+ */
+export const updateEmergencyProcedure = (params: EmergencyProcedureManageStruct) => {
+  return http.request({
+    url: '/emergencyHandleTask/updateEmergencyHandleTask',
+    method: 'put',
+    params,
+  });
+};
+
+export interface LossRecordItem {
+  id: number; // 自增主键
+  handleTaskId: number; // 应急处置任务id
+  isLoss: number; // 是否损失:0-否 1-是
+  buildingNo: string; // 损失楼号
+  floorNo: string; // 损失楼层
+  roomNo: string; // 损失房间号
+  affectedItems: string; // 受灾物品
+  images: string; // 受灾图片
+  description: string; // 损失描述
+  safetyLevel: string; // 影响安全评估
+  isAffectWork: number; // 是否影响正常工作:0-否,1-是
+  estimatedLoss: string; // 损失预估金额
+  priority: string; // 处置优先级
+  responsibleDeptId: number; // 整改责任部门id
+  responsibleDeptName: string; // 整改责任部门名称
+  remark: string; // 备注
+  createdBy: number; // 创建人
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+}
+export interface QueryHandleCompleteInfoRes {
+  completeTime: string; // 处置完成时间
+  lossRecords: LossRecordItem[]; // 损失记录列表
+  suggestion: string; // 总结与建议
+}
+/**
+ * @description: 查询处置完成信息
+ */
+export const getEmergencyProcedureCompleteInfo = (handleTaskId: number) => {
+  return http.request({
+    url: `/emergencyHandleTask/queryHandleCompleteInfo?handleTaskId=${handleTaskId}`,
+    method: 'get',
+  });
+};
+
+export interface LossItemRecordParams {
+  id?: number; // 自增主键
+  handleTaskId?: number; // 应急处置任务id
+  isLoss?: number; // 是否损失:0-否 1-是
+  buildingNo?: string; // 损失楼号
+  floorNo?: string; // 损失楼层
+  roomNo?: string; // 损失房间号
+  affectedItems?: string; // 受灾物品
+  images?: string; // 受灾图片
+  description?: string; // 损失描述
+  safetyLevel?: string; // 影响安全评估
+  isAffectWork?: number; // 是否影响正常工作:0-否,1-是
+  estimatedLoss?: string; // 损失预估金额
+  priority?: string; // 处置优先级
+  responsibleDeptId?: number; // 整改责任部门id
+  responsibleDeptName?: string; // 整改责任部门名称
+  remark?: string; // 备注
+  createdBy?: number; // 创建人
+  createdAt?: string; // 创建时间
+  updatedAt?: string; // 更新时间
+  isDeleted?: number; // 0-未删除,大于0(时间戳)-已删除
+}
+/**
+ * @description: 创建损失记录
+ */
+export const addLossItemRecord = (params: LossItemRecordParams) => {
+  return http.request({
+    url: '/emergencyHandleTask/saveLossRecord',
+    method: 'post',
+    params,
+  });
+};
+
+export interface QueryAllDisasterHandleTaskRes {
+  id: number; // 自增主键
+  taskName: string; // 任务名称
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+}
+/**
+ * @description: 查询全部灾害处置任务(即关联损失记录的选项)
+ */
+export const getDisasterHandleTaskList = () => {
+  return http.request({
+    url: '/disasterHandle/queryAllDisasterHandleTask',
+    method: 'get',
+  });
+};
+
+export interface AssociateLossItemRecordParams {
+  disasterHandleTaskId: number; // 灾害处置任务id
+  emergencyHandleTaskId: number; // 应急处置任务id
+}
+/**
+ * @description: 关联损失记录
+ */
+export const associateLossItemRecord = (params: AssociateLossItemRecordParams) => {
+  return http.request({
+    url: `/emergencyHandleTask/relateLossRecord?disasterHandleTaskId=${params.disasterHandleTaskId}&emergencyHandleTaskId=${params.emergencyHandleTaskId}`,
+    method: 'post',
+  });
+};
+
+/**
+ * @description: 删除损失记录
+ */
+export const deleteLossItemRecord = (lossRecordId: number) => {
+  return http.request({
+    url: `/emergencyHandleTask/deleteLossRecord?lossRecordId=${lossRecordId}`,
+    method: 'delete',
+  });
+};
+
+export interface QueryEmergencyHandleReportRes {
+  eventName: string; // 应急事件名称
+  startTime: string; // 事件启动时间
+  completeTime: string; // 事件结束时间
+  duration: string; // 时长
+  suggestion: string; // 建议
+  lossRecordCount: string; // 损失记录数量
+  safetyLevelDistributionList: SafetyLevelDistribution[]; // 影响安全分布列表
+  disasterLocationDistributionList: DisasterLocationDistribution[]; // 受灾位置分布列表
+}
+export interface SafetyLevelDistribution {
+  safetyLevel: string; // 安全等级
+  count: number; // 数量
+}
+export interface DisasterLocationDistribution {
+  buildingNo: number; // 楼号
+  count: number; // 数量
+}
+/**
+ * @description: 查询应急处置报表
+ */
+export const getEmergencyProcedureReport = (handleTaskId: number) => {
+  return http.request({
+    url: `/emergencyHandleTask/queryEmergencyHandleReport?handleTaskId=${handleTaskId}`,
+    method: 'get',
+  });
+};
+
+/**
+ *
+ * @description: 指挥中心
+ *
+ */
+export interface QueryEmergencySuppliesInfoListParams {
+  emergencyType?: string; // 应急类型
+  supplyType?: string; // 物资类型
+  supplyName?: string; // 应急物资名称
+  park?: string; // 园区
+  location?: string; // 地点
+  keeperName?: string; // 保管人name
+  status?: number; // 使用状态:0-完好 1-缺损
+}
+export interface QueryEmergencySuppliesInfoListRes {
+  id: number; // 自增主键
+  emergencyType: string; // 应急类型
+  supplyType: string; // 物资类型
+  supplyName: string; // 应急物资名称
+  requiredQuantity: number; // 应备数量
+  currentQuantity: number; // 当前数量
+  unit: string; // 数量单位
+  park: string; // 园区
+  location: string; // 地点
+  keeperId: number; // 保管人ID
+  expirationDate: string; // 使用期限
+  remark: string; // 备注
+  status: number; // 使用状态:0-完好 1-缺损
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  keeperName: string; // 保管人name
+  supplementQuantity: number; // 需补充数量
+}
+/**
+ * @description: 查询应急物资信息列表 TODO:
+ */
+export const getEmergencySuppliesInfoList = (params: QueryEmergencySuppliesInfoListParams) => {
+  return http.request({
+    url: '/emergencySupplies/queryEmergencySuppliesInfoList',
+    method: 'post',
+    params,
+  });
+};
+
+export interface QueryTeamListRes {
+  id: number; // 自增主键
+  teamName: string; // 队伍名称
+  memberCount: number; // 队伍人数
+  description: string; // 队伍职责
+  parentId: number; // 父队伍ID
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+}
+/**
+ * @description: 获取队伍列表 TODO:
+ */
+export const getEmergencyTeamList = () => {
+  return http.request({
+    url: '/emergencySystem/queryTeamList',
+    method: 'get',
+  });
+};
+
+export interface QueryTeamPersonnelListRes {
+  id: number; // 自增主键
+  userId: number; // 人员ID
+  realname: string; // 用户姓名
+  teamId: number; // 队伍ID
+  positionId: number; // 职位ID
+  jobTitle: string; // 职务
+  staffNo: string; // 员工号
+  department: string; // 部门
+  mobile: string; // 手机号
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  title: string; // 职位名称
+}
+/**
+ * @description: 获取队伍人员列表 TODO:
+ */
+export const getEmergencyTeamMemberList = (teamId: number) => {
+  return http.request({
+    url: `/emergencySystem/queryTeamPersonnelList?teamId=${teamId}`,
+    method: 'get',
+  });
+};
+
+/**
+ * @description: 获取当前应急事件对应的监控视频 TODO:
+ */

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/assets/svg/start-emergency-event.svg


+ 0 - 5
src/components/SvgIcon/SvgIcon.vue.d.ts

@@ -1,5 +0,0 @@
-declare module '*.vue' {
-  import { DefineComponent } from 'vue';
-  const component: DefineComponent<{}, {}, any>;
-  export default component;
-}

+ 61 - 1
src/router/routers/emergency.ts

@@ -63,7 +63,7 @@ const emergencyManagementRoute = {
       id: 2003,
       id: 2003,
       parentId: 2000,
       parentId: 2000,
       name: 'command-center',
       name: 'command-center',
-      path: 'command-center',
+      path: 'command-center/:id',
       component: '/emergency/command-center/PageCommandCenter',
       component: '/emergency/command-center/PageCommandCenter',
       redirect: '',
       redirect: '',
       meta: {
       meta: {
@@ -181,6 +181,66 @@ const emergencyManagementRoute = {
         },
         },
       ],
       ],
     },
     },
+    {
+      id: 2008,
+      parentId: 2000,
+      name: 'emergency-procedure',
+      path: 'emergency-procedure',
+      component: '/emergency/emergency-procedure/PageProcedure',
+      redirect: '',
+      meta: {
+        activeMenu: null,
+        alwaysShow: false,
+        frameSrc: '',
+        hidden: false,
+        icon: 'EmergencyResponseIcon',
+        isFrame: 0,
+        isRoot: false,
+        noCache: false,
+        query: '',
+        title: '应急处置',
+      },
+    },
+    {
+      id: 2009,
+      parentId: 2000,
+      name: 'emergency-procedure-complete',
+      path: 'emergency-procedure-complete/:id',
+      component: '/emergency/emergency-procedure/PageProcedureComplete',
+      redirect: '',
+      meta: {
+        activeMenu: '/emergency-management/emergency-procedure',
+        alwaysShow: false,
+        frameSrc: '',
+        hidden: false,
+        icon: '',
+        isFrame: 0,
+        isRoot: false,
+        noCache: false,
+        query: '',
+        title: '应急处置完成',
+      },
+    },
+    {
+      id: 2010,
+      parentId: 2000,
+      name: 'emergency-procedure-report',
+      path: 'emergency-procedure-report/:id',
+      component: '/emergency/emergency-procedure/PageProcedureReport',
+      redirect: '',
+      meta: {
+        activeMenu: '/emergency-management/emergency-procedure',
+        alwaysShow: false,
+        frameSrc: '',
+        hidden: false,
+        icon: '',
+        isFrame: 0,
+        isRoot: false,
+        noCache: false,
+        query: '',
+        title: '应急处置报表',
+      },
+    },
   ],
   ],
 };
 };
 
 

+ 96 - 52
src/views/emergency/command-center/PageCommandCenter.vue

@@ -1,66 +1,47 @@
 <template>
 <template>
-  <div class="command-center-container" ref="commandCenterContainer" :style="rootStyle">
-    <!-- <div class="large-screen-layout" :style="scaleStyle">
-      <header class="command-center__header"> </header>
-    </div> -->
+  <div class="command-center-container">
+    <div class="command-center__header">
+      <div class="date-time">
+        <span>{{ currentDate }}</span>
+        <span class="time">{{ currentTime }}</span>
+      </div>
+      <div class="title">XXXX应急处置指挥中心</div>
+    </div>
+    <div class="command-center__body">
+      <div class="emergency-info">
+        <div class="emergency-supplies"></div>
+        <div class="emergency-organization"></div>
+      </div>
+      <div class="monitor-cameras"></div>
+    </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-  import { ref, computed, onMounted, onUnmounted, CSSProperties } from 'vue';
+  import { ref, onMounted, onUnmounted } from 'vue';
+  import dayjs from 'dayjs';
 
 
-  const commandCenterContainer = ref<HTMLDivElement | null>(null);
+  let timer: NodeJS.Timeout;
+  const currentDate = ref('');
+  const currentTime = ref('');
 
 
-  const containerWidth = ref(0);
-  const containerHeight = ref(0);
-
-  // 基准尺寸
-  const BASE_WIDTH = 1920;
-  const BASE_HEIGHT = 1080;
-
-  // 更新容器尺寸
-  const updateSize = () => {
-    if (!commandCenterContainer.value) return;
-    containerWidth.value = commandCenterContainer.value.clientWidth;
-    containerHeight.value = commandCenterContainer.value.clientHeight;
+  const updateDateTime = () => {
+    const now = dayjs();
+    currentDate.value = now.format('YYYY年MM月DD日');
+    currentTime.value = now.format('HH:mm:ss');
   };
   };
 
 
-  // 计算缩放比例
-  const scale = computed(() => {
-    return Math.min(containerWidth.value / BASE_WIDTH, containerHeight.value / BASE_HEIGHT);
-  });
-
-  // 根元素样式,包含CSS变量
-  const rootStyle = computed((): CSSProperties => {
-    return {
-      '--base-width': `${BASE_WIDTH}px`,
-      '--base-height': `${BASE_HEIGHT}px`,
-    };
-  });
-
-  const scaleStyle = computed((): CSSProperties => {
-    return {
-      position: 'absolute',
-      left: '50%',
-      top: '50%',
-      transform: `translate(-50%, -50%) scale(${scale.value})`,
-    };
-  });
-
   onMounted(() => {
   onMounted(() => {
-    updateSize();
-    window.addEventListener('resize', updateSize);
+    updateDateTime();
+    timer = setInterval(updateDateTime, 1000);
   });
   });
 
 
   onUnmounted(() => {
   onUnmounted(() => {
-    window.removeEventListener('resize', updateSize);
+    clearInterval(timer);
   });
   });
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-  $base-width: var(--base-width);
-  $base-height: var(--base-height);
-
   .command-center-container {
   .command-center-container {
     position: absolute;
     position: absolute;
     left: 0;
     left: 0;
@@ -71,14 +52,77 @@
     z-index: 9999;
     z-index: 9999;
     overflow: hidden;
     overflow: hidden;
   }
   }
-  .large-screen-layout {
-    width: $base-width;
-    height: $base-height;
-    background: url('@/assets/images/exception/stay-tune.png') no-repeat center center;
-  }
+
   .command-center__header {
   .command-center__header {
     width: 100%;
     width: 100%;
-    height: 49px;
+    height: 50px;
     background: url('assets/images/home/nav-bg@1X.png') no-repeat center center / cover;
     background: url('assets/images/home/nav-bg@1X.png') no-repeat center center / cover;
+    position: relative;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .date-time {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 182px;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background: #ffffff;
+      opacity: 0.6;
+      font-size: 12px;
+      color: #666666;
+
+      .time {
+        font-weight: 500;
+        font-size: 14px;
+        color: #000000;
+        margin-left: 4px;
+      }
+    }
+
+    .title {
+      font-weight: 500;
+      font-size: 18px;
+      color: #000000;
+    }
+  }
+
+  .command-center__body {
+    width: 100%;
+    height: calc(100% - 50px);
+    padding: 10px;
+    display: flex;
+    justify-content: space-between;
+
+    .emergency-info {
+      width: 350px;
+      height: 100%;
+
+      .emergency-supplies {
+        width: 100%;
+        height: 55%;
+        background-color: #fff;
+        border-radius: 8px;
+      }
+
+      .emergency-organization {
+        width: 100%;
+        height: calc(45% - 10px);
+        margin-top: 10px;
+        background-color: #fff;
+        border-radius: 8px;
+      }
+    }
+
+    .monitor-cameras {
+      width: calc(100% - 360px);
+      height: 100%;
+      background: #ffffff;
+      border-radius: 8px;
+    }
   }
   }
 </style>
 </style>

+ 314 - 0
src/views/emergency/emergency-procedure/PageProcedure.vue

@@ -0,0 +1,314 @@
+<template>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <div class="breadcrumb-title">应急处置</div>
+    </div>
+    <div class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header class="disaster-precaution__header">
+          <el-button
+            type="danger"
+            size="large"
+            class="search-table-container--button start-event-btn"
+            v-if="emergencyProcedureManagePermission"
+            @click="handleAddEmergencyEvent"
+          >
+            <template #icon>
+              <SvgIcon iconName="start-emergency-event" color="#fff" />
+            </template>
+            <span class="start-event-btn-text">启动应急事件</span>
+          </el-button>
+          <BasicSearch
+            :searchConfig="PROCEDURE_LIST_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:search-data="handleSearch"
+          >
+            <template #emergencyType>
+              <el-select v-model="searchData.emergencyType" placeholder="全部" clearable>
+                <el-option
+                  v-for="item in emergencyEventDice"
+                  :key="item.itemCode"
+                  :label="item.itemValue"
+                  :value="item.itemValue"
+                />
+              </el-select>
+            </template>
+          </BasicSearch>
+        </header>
+        <BasicTable
+          :tableData="tableData"
+          :tableConfig="tableConfig"
+          @update:page-number="handleCurrentPageChange"
+          @update:page-size="handlePageSizeChange"
+        >
+          <template #status="scope">
+            <span :style="{ color: scope.row.status === EMERGENCY_PROCEDURE_STATUS.INPROGRESS ? 'red' : '' }">
+              {{ getEmergencyStatus(scope.row.status) }}
+            </span>
+          </template>
+          <template #action="scope">
+            <div class="action-container--div" v-if="scope.row.status === EMERGENCY_PROCEDURE_STATUS.INPROGRESS">
+              <ActionButton
+                text="指挥中心"
+                @click="handleOpenCommandCenter(scope.row)"
+                v-if="emergencyCommandCenterManagePermission"
+              />
+              <ActionButton
+                text="修改"
+                @click="handleEditEmergencyEvent(scope.row)"
+                v-if="emergencyProcedureManagePermission"
+              />
+              <ActionButton
+                text="处置完成"
+                @click="handleFinishEmergencyEvent(scope.row)"
+                v-if="emergencyProcedureCompletePermission"
+              />
+              <ActionButton
+                text="关闭"
+                :popconfirm="{
+                  title: '是否关闭应急事件?',
+                }"
+                @confirm="handleCloseEmergencyEvent(scope.row)"
+                v-if="emergencyProcedureManagePermission"
+              />
+            </div>
+            <div class="action-container--div" v-if="scope.row.status === EMERGENCY_PROCEDURE_STATUS.HASFINISHED">
+              <ActionButton text="应急报表" @click="handleOpenEmergencyReport(scope.row)" />
+            </div>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+  </div>
+  <EmergencyEventManage
+    v-if="emergencyEventManageDialog"
+    :dialog-type="emergencyEventManageType"
+    :event-type-options="emergencyEventDice"
+    :original-event-form="originalEventForm"
+    @close="handleCloseEmergencyEventManageDialog"
+    @confirm="handleConfirmEmergencyEventManageDialog"
+  />
+</template>
+
+<script setup lang="ts">
+  import { onMounted, reactive, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
+  import BasicSearch from '@/components/BasicSearch.vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
+  import { EMERGENCY_PERMISSIONS } from '@/views/emergency/src/constant';
+  import {
+    PROCEDURE_LIST_SEARCH_CONFIG,
+    PROCEDURE_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+    PROCEDURE_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+    PROCEDURE_LIST_TABLE_OPTIONS,
+    PROCEDURE_LIST_TABLE_COLUMNS,
+  } from './config';
+  import { useEmergencyProcedureHook } from './hooks';
+  import { EMERGENCY_PROCEDURE_STATUS } from './constant';
+  import {
+    ProcedureListQuery,
+    EmergencyProcedureManageStruct,
+    getEmergencyProcedureList,
+    createEmergencyProcedure,
+    updateEmergencyProcedure,
+  } from '@/api/emergency-procedure';
+  import EmergencyEventManage from './components/EmergencyEventManage.vue';
+
+  const { tableConfig, pagination } = useTableConfig(PROCEDURE_LIST_TABLE_COLUMNS, PROCEDURE_LIST_TABLE_OPTIONS);
+  const { emergencyEventDice, getEmergencyEventDict, getEmergencyStatus } = useEmergencyProcedureHook();
+
+  const router = useRouter();
+
+  const { permissions } = useUserInfoHook();
+  const emergencyProcedureManagePermission = ref<Boolean>(false);
+  const emergencyProcedureCompletePermission = ref<Boolean>(false);
+  const emergencyCommandCenterManagePermission = ref<Boolean>(false);
+
+  const searchData = reactive({
+    emergencyType: '',
+    procedureName: '',
+    procedureStartTime: ['', ''],
+    status: undefined,
+  });
+
+  const emergencyProcedureQuery: QueryPageRequest<ProcedureListQuery> = {
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  };
+  const tableData = ref<EmergencyProcedureManageStruct[]>([]);
+
+  const emergencyEventManageDialog = ref(false);
+  const emergencyEventManageType = ref('add');
+  const originalEventForm = ref<EmergencyProcedureManageStruct>();
+
+  const handleSearch = () => {
+    emergencyProcedureQuery.queryParam = {};
+    if (searchData.emergencyType) {
+      emergencyProcedureQuery.queryParam.eventType = searchData.emergencyType;
+    }
+    if (searchData.procedureName) {
+      emergencyProcedureQuery.queryParam.eventName = searchData.procedureName;
+    }
+    if (searchData.procedureStartTime) {
+      emergencyProcedureQuery.queryParam.startTimeStart = searchData.procedureStartTime[0];
+      emergencyProcedureQuery.queryParam.startTimeEnd = searchData.procedureStartTime[1];
+    }
+    if (searchData.status) {
+      emergencyProcedureQuery.queryParam.status = searchData.status;
+    }
+    getTableData();
+  };
+
+  // 启动应急事件
+  const handleAddEmergencyEvent = () => {
+    emergencyEventManageType.value = 'add';
+    emergencyEventManageDialog.value = true;
+  };
+
+  // 关闭应急事件管理弹窗
+  const handleCloseEmergencyEventManageDialog = () => {
+    emergencyEventManageDialog.value = false;
+    originalEventForm.value = undefined;
+  };
+
+  // 确认应急事件管理弹窗
+  const handleConfirmEmergencyEventManageDialog = (form) => {
+    emergencyEventManageDialog.value = false;
+    if (emergencyEventManageType.value === 'add') {
+      createEmergencyProcedure(form).then(() => {
+        ElMessage({
+          message: '应急事件启动成功',
+          type: 'success',
+        });
+        getTableData();
+        originalEventForm.value = undefined;
+      });
+    } else {
+      updateEmergencyProcedure({
+        id: originalEventForm.value?.id,
+        ...form,
+      }).then(() => {
+        ElMessage({
+          message: '应急事件修改成功',
+          type: 'success',
+        });
+        getTableData();
+        originalEventForm.value = undefined;
+      });
+    }
+  };
+
+  // 打开指挥中心
+  const handleOpenCommandCenter = (row) => {
+    window.open(`/#/emergency-management/command-center/${row.id}`, '_blank');
+  };
+
+  // 修改
+  const handleEditEmergencyEvent = (row) => {
+    emergencyEventManageType.value = 'edit';
+    emergencyEventManageDialog.value = true;
+    originalEventForm.value = row;
+  };
+
+  // 处置完成
+  const handleFinishEmergencyEvent = (row) => {
+    router.push({
+      name: 'emergency-procedure-complete',
+      params: {
+        id: row.id,
+      },
+    });
+  };
+
+  // 关闭
+  const handleCloseEmergencyEvent = (row) => {
+    updateEmergencyProcedure({
+      id: row.id,
+      status: EMERGENCY_PROCEDURE_STATUS.HASCLOSED,
+    }).then(() => {
+      ElMessage({
+        message: '应急事件已关闭',
+        type: 'success',
+      });
+      getTableData();
+    });
+  };
+
+  // 应急报表
+  const handleOpenEmergencyReport = (row) => {
+    router.push({
+      name: 'emergency-procedure-report',
+      params: {
+        id: row.id,
+      },
+    });
+  };
+
+  const handleCurrentPageChange = (pageNumber: number) => {
+    pagination.pageNumber = pageNumber;
+    emergencyProcedureQuery.pageNumber = pageNumber;
+    getTableData();
+  };
+
+  const handlePageSizeChange = (pageSize: number) => {
+    pagination.pageSize = pageSize;
+    emergencyProcedureQuery.pageSize = pageSize;
+    getTableData();
+  };
+
+  const getTableData = async () => {
+    tableConfig.loading = true;
+    const res = await getEmergencyProcedureList(emergencyProcedureQuery);
+    tableData.value = res?.records || [];
+    pagination.total = res?.totalRow || 0;
+    tableConfig.loading = false;
+  };
+
+  onMounted(() => {
+    getEmergencyEventDict();
+    getTableData();
+    // 应急处置-应急事件启动管理权限:增改关
+    emergencyProcedureManagePermission.value = Boolean(
+      permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.EMERGENCY_PROCEDURE_MANAGE),
+    );
+    // 应急处置-应急处置管理权限:完成
+    emergencyProcedureCompletePermission.value = Boolean(
+      permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.EMERGENCY_PROCEDURE_COMPLETE),
+    );
+    // 应急处置-应急指挥中心管理权限:打开、编辑
+    emergencyCommandCenterManagePermission.value = Boolean(
+      permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.EMERGENCY_COMMAND_CENTER_MANAGE),
+    );
+
+    tableConfig.maxHeight = emergencyProcedureManagePermission.value
+      ? PROCEDURE_LIST_TABLE_MAX_HEIGHT_PERMISSION
+      : PROCEDURE_LIST_TABLE_MAX_HEIGHT_DEFAULT;
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+
+  .el-select {
+    --el-select-width: 200px !important;
+  }
+
+  .start-event-btn {
+    padding: 30px 60px;
+    font-size: 40px;
+    background-color: #ec2828;
+
+    .start-event-btn-text {
+      font-size: 20px;
+    }
+  }
+</style>

+ 239 - 0
src/views/emergency/emergency-procedure/PageProcedureComplete.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ name }}</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="complete-time">
+        <div class="section-title">应急处置信息</div>
+        <span style="color: red">*</span>
+        <span>处置完成时间:</span>
+        <el-date-picker
+          v-model="formParam.completeTime"
+          type="datetime"
+          placeholder="选择处置完成时间"
+          :clearable="false"
+        />
+        <div class="required-field-tip" v-if="requiredFieldTipVisible">*请选择应急处置完成时间!</div>
+      </div>
+      <div class="loss-table">
+        <div class="section-title">损失情况</div>
+        <el-button type="primary" :icon="Plus" @click="addLossItemRecordVisible = true">添加损失物品</el-button>
+        <el-button type="primary" @click="linkLossReportVisible = true">关联损失上报</el-button>
+        <BasicTable :tableData="lossItemRecords" :tableConfig="tableConfig" class="loss-item-table">
+          <template #lossItemLocation="scope">
+            <span>{{ scope.row.buildingNo }}号楼{{ scope.row.floorNo }}层{{ scope.row.roomNo }}</span>
+          </template>
+          <template #safetyLevel="scope">
+            <span>{{ getSafetyLevel(scope.row.safetyLevel) }}</span>
+          </template>
+          <template #action="scope">
+            <ActionButton text="删除" @click="handleDeleteLossItemRecord(scope.row)" />
+          </template>
+        </BasicTable>
+      </div>
+      <div>
+        <div class="section-title">总结与改进建议</div>
+        <div class="suggestion-text">
+          <div class="suggestion-title">总结与改进建议:</div>
+          <el-input
+            v-model="formParam.suggestion"
+            style="min-width: 240px"
+            :autosize="{ minRows: 4 }"
+            type="textarea"
+            placeholder="请输入总结与改进建议"
+          />
+        </div>
+      </div>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="handleSaveInfo">保存</el-button>
+      <el-button type="primary" @click="handleUpdateStatus">提交</el-button>
+    </footer>
+  </div>
+  <AssociateLossReport
+    v-if="linkLossReportVisible"
+    @submit="handleLinkLossReport"
+    @cancel="linkLossReportVisible = false"
+  />
+  <AddLossItemRecord
+    v-if="addLossItemRecordVisible"
+    @submit="handleAddLossItemRecord"
+    @cancel="addLossItemRecordVisible = false"
+  />
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, reactive, ref, watch } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage, ElInput, ElButton, ElDatePicker } from 'element-plus';
+  import { Plus } from '@element-plus/icons-vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { useDisasterControlHook } from '@/views/disaster/disaster-control/src/hook';
+  import {
+    LossRecordItem,
+    getEmergencyProcedureCompleteInfo,
+    addLossItemRecord,
+    associateLossItemRecord,
+    deleteLossItemRecord,
+    updateEmergencyProcedure,
+  } from '@/api/emergency-procedure';
+  import { EMERGENCY_PROCEDURE_STATUS } from './constant';
+  import { PROCEDURE_COMPLETE_LOSS_ITEM_TABLE_COLUMNS, PROCEDURE_LIST_TABLE_OPTIONS } from './config';
+  import AssociateLossReport from './components/AssociateLossReport.vue';
+  import AddLossItemRecord from './components/AddLossItemRecord.vue';
+
+  const { tableConfig } = useTableConfig(PROCEDURE_COMPLETE_LOSS_ITEM_TABLE_COLUMNS, PROCEDURE_LIST_TABLE_OPTIONS);
+  const { getSafetyLevel, getSafetyLevelDict } = useDisasterControlHook();
+
+  const route = useRoute();
+  const router = useRouter();
+  const id = Number(route.params.id);
+  const name = ref('应急处置完成');
+
+  const requiredFieldTipVisible = ref(false);
+
+  const lossItemRecords = ref<LossRecordItem[]>([]);
+  const linkLossReportVisible = ref(false);
+  const addLossItemRecordVisible = ref(false);
+
+  const formParam = reactive({
+    completeTime: '',
+    suggestion: '',
+  });
+
+  watch(
+    () => formParam.completeTime,
+    (newValue) => {
+      if (newValue) requiredFieldTipVisible.value = false;
+    },
+  );
+
+  const handleAddLossItemRecord = (addItem, responsibleDeptName) => {
+    addLossItemRecord({ ...addItem, handleTaskId: id, responsibleDeptName: responsibleDeptName }).then(() => {
+      ElMessage({
+        message: '添加成功',
+        type: 'success',
+      });
+      addLossItemRecordVisible.value = false;
+      getLossItemRecords();
+    });
+  };
+
+  const handleLinkLossReport = (taskId: number) => {
+    associateLossItemRecord({ emergencyHandleTaskId: id, disasterHandleTaskId: taskId }).then(() => {
+      ElMessage({
+        message: '关联成功',
+        type: 'success',
+      });
+      linkLossReportVisible.value = false;
+      getLossItemRecords();
+    });
+  };
+
+  const handleDeleteLossItemRecord = (record: LossRecordItem) => {
+    deleteLossItemRecord(record.id).then(() => {
+      ElMessage({
+        message: '删除成功',
+        type: 'success',
+      });
+      getLossItemRecords();
+    });
+  };
+
+  const handleSaveInfo = () => {
+    if (!formParam.completeTime) {
+      requiredFieldTipVisible.value = true;
+      return;
+    }
+    updateEmergencyProcedure({
+      id: id,
+      completeTime: formParam.completeTime,
+      suggestion: formParam.suggestion,
+    }).then(() => {
+      ElMessage({
+        message: '信息已保存',
+        type: 'success',
+      });
+    });
+  };
+
+  const handleUpdateStatus = () => {
+    if (!formParam.completeTime) {
+      requiredFieldTipVisible.value = true;
+      return;
+    }
+    updateEmergencyProcedure({
+      id: id,
+      status: EMERGENCY_PROCEDURE_STATUS.HASFINISHED,
+      completeTime: formParam.completeTime,
+      suggestion: formParam.suggestion,
+    }).then(() => {
+      ElMessage({
+        message: '信息已提交',
+        type: 'success',
+      });
+      router.back();
+    });
+  };
+
+  const getLossItemRecords = async () => {
+    tableConfig.loading = true;
+    const response = await getEmergencyProcedureCompleteInfo(id);
+    formParam.completeTime = response.completeTime;
+    formParam.suggestion = response.suggestion;
+    lossItemRecords.value = response.lossRecords || [];
+    tableConfig.loading = false;
+  };
+
+  onMounted(() => {
+    getLossItemRecords();
+    getSafetyLevelDict();
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+
+  .safety-platform-container__main {
+    .section-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: #000;
+      margin-bottom: 16px;
+    }
+
+    .required-field-tip {
+      color: red;
+      margin: 10px 0 0 120px;
+      font-size: 12px;
+    }
+
+    .loss-item-table {
+      margin-top: 16px;
+    }
+
+    .complete-time,
+    .loss-table {
+      margin-bottom: 46px;
+    }
+
+    .suggestion-text {
+      display: flex;
+
+      .suggestion-title {
+        width: 150px;
+        margin-right: 8px;
+      }
+    }
+  }
+</style>

+ 165 - 0
src/views/emergency/emergency-procedure/PageProcedureReport.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ name }}</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="report-title">{{ procedureReport?.eventName }}应急处置</div>
+      <div class="duration-time">
+        <div class="container-title">
+          <span class="line"></span>
+          <span>应急处置时间</span>
+        </div>
+        <TimeLine
+          :startTime="procedureReport?.startTime"
+          :completeTime="procedureReport?.completeTime"
+          :duration="procedureReport?.duration"
+        />
+      </div>
+      <div class="suggestion-info">
+        <div class="container-title">
+          <span class="line"></span>
+          <span>事件总结与改进建议</span>
+        </div>
+        <div class="suggestion-content" v-if="procedureReport?.suggestion">{{ procedureReport?.suggestion }}</div>
+        <div class="suggestion-content" v-else>暂无内容</div>
+      </div>
+      <div class="loss-charts">
+        <div class="container-title">
+          <span class="line"></span>
+          <span>损失评估</span>
+        </div>
+        <div class="loss-num">
+          <span class="loss-num-title">共计受损物品</span>
+          <span class="loss-num-value">{{ procedureReport?.lossRecordCount || 0 }}</span>
+        </div>
+        <div v-if="!procedureReport?.lossRecordCount" class="no-loss-record">
+          <img src="@/assets/images/empty@1X.png" alt="" />
+          <span>本次事件没有损失</span>
+        </div>
+        <div class="charts-container" v-else>
+          <ChartOfSafetyLevel class="chart" :data="procedureReport?.safetyLevelDistributionList || []" />
+          <ChartOfDisasterLocation class="chart" :data="procedureReport?.disasterLocationDistributionList || []" />
+        </div>
+      </div>
+    </main>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import TimeLine from './components/TimeLine.vue';
+  import ChartOfSafetyLevel from './components/ChartOfSafetyLevel.vue';
+  import ChartOfDisasterLocation from './components/ChartOfDisasterLocation.vue';
+  import { QueryEmergencyHandleReportRes, getEmergencyProcedureReport } from '@/api/emergency-procedure';
+
+  const route = useRoute();
+  const id = Number(route.params.id);
+
+  const name = ref('应急处置报表');
+
+  const procedureReport = ref<QueryEmergencyHandleReportRes>();
+
+  onMounted(async () => {
+    procedureReport.value = await getEmergencyProcedureReport(id);
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+
+  .safety-platform-container__main {
+    padding: 20px 0;
+  }
+
+  .container-title {
+    display: flex;
+    align-items: center;
+    font-weight: 500;
+    font-size: 16px;
+    color: #000000;
+
+    .line {
+      width: 3px;
+      height: 16px;
+      background: #1777ff;
+      margin-right: 13px;
+    }
+  }
+
+  .report-title {
+    font-weight: 500;
+    font-size: 20px;
+    color: rgba(0, 0, 0, 0.85);
+    display: flex;
+    justify-content: center;
+    margin-bottom: 30px;
+  }
+
+  .duration-time,
+  .suggestion-info,
+  .loss-charts {
+    margin-bottom: 20px;
+  }
+
+  .suggestion-content {
+    margin: 10px 16px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #333333;
+    white-space: pre-line;
+  }
+
+  .loss-num {
+    width: 352px;
+    height: 42px;
+    background: #e7f1ff;
+    border-radius: 2px;
+    margin: 10px 16px;
+    font-weight: 400;
+    font-size: 14px;
+    color: #000000;
+    display: flex;
+    align-items: center;
+    padding: 0 16px;
+
+    .loss-num-title {
+      margin-right: 8px;
+    }
+
+    .loss-num-value {
+      color: #1777ff;
+    }
+  }
+
+  .no-loss-record {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 10px;
+    color: #bfbfbf;
+  }
+
+  .charts-container {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 40px 20px;
+
+    .chart {
+      width: 50%;
+      height: 400px;
+      min-width: 300px;
+      min-height: 300px;
+    }
+  }
+</style>

+ 131 - 0
src/views/emergency/emergency-procedure/components/AddLossItemRecord.vue

@@ -0,0 +1,131 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogVisible" width="700" align-center @close="handleCancel" class="add-loss-item-dialog">
+      <template #header>
+        <div class="dialog-header">
+          <span>添加损失物品</span>
+        </div>
+      </template>
+      <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+        <template #affectedRoom>
+          <div class="affected-room-container">
+            <el-form-item prop="buildingNo">
+              <el-input v-model="ruleFormData.buildingNo" placeholder="楼号" />
+            </el-form-item>
+            <el-form-item prop="floorNo">
+              <el-input v-model="ruleFormData.floorNo" placeholder="楼层" />
+            </el-form-item>
+            <el-form-item prop="roomNo">
+              <el-input v-model="ruleFormData.roomNo" placeholder="房间号" />
+            </el-form-item>
+          </div>
+        </template>
+        <template #safetyLevel>
+          <el-select v-model="ruleFormData.safetyLevel" placeholder="请选择影响安全评估">
+            <el-option
+              v-for="item in safetyLevelDice"
+              :key="item.itemCode"
+              :label="item.itemValue"
+              :value="item.itemCode"
+            />
+          </el-select>
+        </template>
+        <template #responsibleDeptId>
+          <el-select v-model="ruleFormData.responsibleDeptId" placeholder="请选择整改责任部门" filterable>
+            <el-option v-for="item in firstLevelDepts" :key="item.id" :label="item.deptName" :value="item.id" />
+          </el-select>
+        </template>
+      </BasicForm>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">提交</el-button>
+          <el-button @click="handleCancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import type { LossReportItemFormData } from '@/types/disaster-control';
+  import { useDeptInfoHook } from '@/views/disaster/hooks';
+  import { useDisasterControlHook } from '@/views/disaster/disaster-control/src/hook';
+
+  import {
+    LOSS_ITEM_FORM_OF_EMERGENCY_PROCEDURE,
+    LOSS_ITEM_FORM_RULES_OF_EMERGENCY_PROCEDURE,
+    LOSS_ITEM_PARAM_OF_EMERGENCY_PROCEDURE,
+  } from '../config';
+
+  const { safetyLevelDice, getSafetyLevelDict } = useDisasterControlHook();
+  const { firstLevelDepts, getFirstLevelDepts } = useDeptInfoHook();
+
+  const { ruleFormConfig, ruleFormData, formRules } = useFormConfigHook<LossReportItemFormData>(
+    LOSS_ITEM_FORM_OF_EMERGENCY_PROCEDURE,
+    LOSS_ITEM_PARAM_OF_EMERGENCY_PROCEDURE,
+    LOSS_ITEM_FORM_RULES_OF_EMERGENCY_PROCEDURE,
+  );
+
+  const emits = defineEmits(['submit', 'cancel']);
+
+  const dialogVisible = ref(true);
+
+  const handleSubmit = () => {
+    const responsibleDeptName = firstLevelDepts.value.find(
+      (dept) => dept.id === ruleFormData.responsibleDeptId,
+    )?.deptName;
+    emits('submit', { ...ruleFormData }, responsibleDeptName);
+  };
+
+  const handleCancel = () => {
+    emits('cancel');
+  };
+
+  onMounted(() => {
+    getSafetyLevelDict();
+    getFirstLevelDepts();
+  });
+</script>
+
+<style scoped lang="scss">
+  :deep(.add-loss-item-dialog) {
+    min-height: 500px;
+    padding: 20px 30px;
+
+    .el-dialog__header {
+      padding-right: 0;
+      padding-bottom: 40px;
+    }
+    .dialog-header {
+      font-size: 20px;
+      font-weight: bold;
+      display: flex;
+      justify-content: center;
+    }
+
+    .el-dialog__headerbtn {
+      font-size: 30px;
+      width: 60px;
+      height: 60px;
+      top: 8px;
+      right: 8px;
+    }
+
+    .el-dialog__body {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    .el-dialog__footer {
+      padding-top: 40px;
+    }
+  }
+
+  .affected-room-container {
+    display: flex;
+  }
+</style>

+ 91 - 0
src/views/emergency/emergency-procedure/components/AssociateLossReport.vue

@@ -0,0 +1,91 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="dialogVisible"
+      width="500"
+      align-center
+      @close="handleCancel"
+      class="associate-loss-report-dialog"
+    >
+      <template #header>
+        <div class="dialog-header">
+          <span>关联损失上报记录</span>
+        </div>
+      </template>
+      <el-select v-model="taskId" clearable placeholder="请选择需关联的损失上报记录" style="width: 400px">
+        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">提交</el-button>
+          <el-button @click="handleCancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { ElDialog, ElSelect, ElOption, ElButton } from 'element-plus';
+  import { QueryAllDisasterHandleTaskRes, getDisasterHandleTaskList } from '@/api/emergency-procedure';
+
+  const emits = defineEmits(['submit', 'cancel']);
+
+  const dialogVisible = ref(true);
+
+  const taskId = ref<number>();
+  const options = ref<Array<{ label: string; value: number }>>([]);
+
+  const handleSubmit = () => {
+    emits('submit', taskId.value);
+  };
+
+  const handleCancel = () => {
+    emits('cancel');
+  };
+
+  onMounted(async () => {
+    const response = await getDisasterHandleTaskList();
+    options.value = response.map((item: QueryAllDisasterHandleTaskRes) => ({
+      label: item.taskName,
+      value: item.id,
+    }));
+  });
+</script>
+
+<style scoped lang="scss">
+  :deep(.associate-loss-report-dialog) {
+    min-height: 200px;
+    padding: 20px 30px;
+
+    .el-dialog__header {
+      padding-right: 0;
+      padding-bottom: 40px;
+    }
+    .dialog-header {
+      font-size: 20px;
+      font-weight: bold;
+      display: flex;
+      justify-content: center;
+    }
+
+    .el-dialog__headerbtn {
+      font-size: 30px;
+      width: 60px;
+      height: 60px;
+      top: 8px;
+      right: 8px;
+    }
+
+    .el-dialog__body {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    .el-dialog__footer {
+      padding-top: 40px;
+    }
+  }
+</style>

+ 167 - 0
src/views/emergency/emergency-procedure/components/ChartOfDisasterLocation.vue

@@ -0,0 +1,167 @@
+<template>
+  <div ref="chartRef" style="width: 100%; height: 400px"></div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, watch, onUnmounted } from 'vue';
+  import * as echarts from 'echarts';
+  import type { EChartsOption } from 'echarts';
+
+  // 定义接收的数据类型
+  interface BuildingData {
+    buildingNo: number | string;
+    count: number;
+  }
+
+  // 定义 props
+  const props = defineProps<{
+    data: BuildingData[];
+  }>();
+
+  // 图表容器引用
+  const chartRef = ref<HTMLElement | null>(null);
+  let chartInstance: echarts.ECharts | null = null;
+
+  // 渲染图表
+  const renderChart = () => {
+    if (!chartRef.value) return;
+
+    // 销毁旧实例
+    if (chartInstance) {
+      chartInstance.dispose();
+    }
+
+    // 初始化图表实例
+    chartInstance = echarts.init(chartRef.value);
+
+    // 处理数据
+    const categories = props.data.map((item) => `${item.buildingNo}号楼`);
+    const values = props.data.map((item) => item.count);
+
+    // 图表配置项
+    const option: EChartsOption = {
+      title: {
+        text: '受灾位置分布',
+        left: 'center',
+      },
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          type: 'shadow',
+        },
+        formatter: (params) => {
+          const param = params[0];
+          return `${param.name}<br/>数量: ${param.value}`;
+        },
+      },
+      grid: {
+        left: '8%',
+        right: '10%',
+        top: '14%',
+        bottom: '6%',
+        containLabel: true,
+      },
+      xAxis: {
+        type: 'value',
+        name: '数量',
+        nameGap: 16,
+        axisLine: {
+          show: true,
+          lineStyle: {
+            color: '#bbbbbb',
+          },
+        },
+        axisTick: {
+          show: true,
+        },
+        splitLine: {
+          lineStyle: {
+            color: '#dfdfdf',
+            type: 'dashed',
+          },
+        },
+      },
+      yAxis: {
+        type: 'category',
+        data: categories,
+        axisLine: {
+          show: true,
+          lineStyle: {
+            color: '#bbbbbb',
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      series: [
+        {
+          name: '楼宇数量统计',
+          type: 'bar',
+          data: values,
+          barWidth: '18px',
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+              { offset: 0, color: '#5ab1ef' },
+              { offset: 1, color: '#87cfff' },
+            ]),
+          },
+          label: {
+            show: true,
+            position: 'right',
+            color: '#000',
+            fontSize: 12,
+          },
+          emphasis: {
+            focus: 'series',
+          },
+        },
+      ],
+    };
+
+    // 应用配置
+    chartInstance.setOption(option);
+
+    // 初始渲染后立即 resize 一次,确保尺寸正确
+    chartInstance.resize();
+  };
+
+  // 处理窗口尺寸变化
+  const handleResize = () => {
+    if (chartInstance) {
+      chartInstance.resize({
+        animation: {
+          duration: 100,
+        },
+      });
+    }
+  };
+
+  // 组件挂载后初始化图表
+  onMounted(() => {
+    renderChart();
+    window.addEventListener('resize', handleResize);
+  });
+
+  // 监听数据变化,重新渲染
+  watch(
+    () => props.data,
+    () => {
+      renderChart();
+    },
+    { deep: true },
+  );
+
+  // 组件卸载前清理
+  onUnmounted(() => {
+    // 移除 resize 监听
+    window.removeEventListener('resize', handleResize);
+    // 销毁图表实例
+    if (chartInstance) {
+      chartInstance.dispose();
+      chartInstance = null;
+    }
+  });
+</script>
+
+<style scoped lang="scss"></style>

+ 137 - 0
src/views/emergency/emergency-procedure/components/ChartOfSafetyLevel.vue

@@ -0,0 +1,137 @@
+<template>
+  <div ref="chartRef" style="width: 100%; height: 400px"></div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, watch, defineProps, onUnmounted } from 'vue';
+  import * as echarts from 'echarts';
+  import type { EChartsOption } from 'echarts';
+  import { useDisasterControlHook } from '@/views/disaster/disaster-control/src/hook';
+
+  const { getSafetyLevel, getSafetyLevelDict } = useDisasterControlHook();
+
+  // 定义 props 接收父组件传来的数据
+  interface DataItem {
+    count: number;
+    safetyLevel: string;
+  }
+
+  const props = defineProps<{
+    data: DataItem[];
+  }>();
+
+  // 图表容器的引用
+  const chartRef = ref<HTMLElement | null>(null);
+  let chartInstance: echarts.ECharts | null = null;
+
+  // 颜色配置(可根据视觉设计调整)
+  const colorPalette = ['#5ab1ef', '#67e0e3', '#ffd53e', '#f0853a', '#c0343d'];
+
+  // 处理数据并生成图表
+  const renderChart = () => {
+    if (!chartRef.value) return;
+
+    // 销毁旧实例
+    if (chartInstance) {
+      chartInstance.dispose();
+    }
+
+    // 初始化 ECharts 实例
+    chartInstance = echarts.init(chartRef.value);
+
+    // 转换数据为 ECharts 所需格式
+    const seriesData = props.data.map((item) => ({
+      value: item.count,
+      name: getSafetyLevel(item.safetyLevel) || item.safetyLevel,
+    }));
+
+    // 配置项
+    const option: EChartsOption = {
+      title: {
+        text: '影响安全评估分布',
+        left: 'center',
+      },
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b}: {c} ({d}%)',
+      },
+      legend: {
+        bottom: 0,
+        left: 'center',
+        data: seriesData.map((item) => item.name),
+      },
+      series: [
+        {
+          name: '安全等级分布',
+          type: 'pie',
+          radius: ['40%', '70%'],
+          avoidLabelOverlap: false,
+          itemStyle: {
+            borderRadius: 8,
+            borderColor: '#fff',
+            borderWidth: 2,
+          },
+          label: {
+            show: true,
+            formatter: '{b}: {c} ({d}%)',
+          },
+          emphasis: {
+            label: {
+              show: true,
+              fontSize: 16,
+              fontWeight: 'bold',
+            },
+          },
+          data: seriesData,
+        },
+      ],
+      color: colorPalette,
+    };
+
+    // 应用配置
+    chartInstance.setOption(option);
+
+    // 初始渲染后立即 resize 一次,确保尺寸正确
+    chartInstance.resize();
+  };
+
+  // 处理窗口尺寸变化
+  const handleResize = () => {
+    if (chartInstance) {
+      chartInstance.resize({
+        animation: {
+          duration: 100,
+        },
+      });
+    }
+  };
+
+  // 组件挂载后初始化图表
+  onMounted(async () => {
+    await getSafetyLevelDict();
+    renderChart();
+    window.addEventListener('resize', handleResize);
+  });
+
+  // 监听数据变化,重新渲染图表
+  watch(
+    () => props.data,
+    () => {
+      renderChart();
+    },
+    { deep: true },
+  );
+
+  // 组件卸载前清理
+  onUnmounted(() => {
+    // 移除 resize 监听
+    window.removeEventListener('resize', handleResize);
+    // 销毁图表实例
+    if (chartInstance) {
+      chartInstance.dispose();
+      chartInstance = null;
+    }
+  });
+</script>
+
+<style scoped lang="scss"></style>

+ 125 - 0
src/views/emergency/emergency-procedure/components/EmergencyEventManage.vue

@@ -0,0 +1,125 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="emergencyEventManageDialog"
+      width="500"
+      align-center
+      @close="handleCloseDialog"
+      @keyup.enter="handleConfirmDialog(ruleFormRef)"
+      class="emergency-event-manage-dialog"
+    >
+      <template #header>
+        <div class="dialog-header">
+          <span>{{ dialogTitle }}应急事件</span>
+        </div>
+      </template>
+      <el-form ref="ruleFormRef" :model="eventForm" :rules="rules">
+        <el-form-item label="事件类型" prop="eventType">
+          <el-select v-model="eventForm.eventType" placeholder="请选择事件类型" clearable>
+            <el-option
+              v-for="item in eventTypeOptions"
+              :key="item.itemCode"
+              :label="item.itemValue"
+              :value="item.itemValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件地点" prop="eventLocation">
+          <el-input v-model="eventForm.eventLocation" placeholder="请输入事件地点" clearable />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="danger" @click="handleConfirmDialog(ruleFormRef)">
+            {{ dialogBtn }}
+          </el-button>
+          <el-button @click="handleCloseDialog">返回</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, reactive, ref } from 'vue';
+  import { ElDialog, ElForm, ElFormItem } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import type { SysDictDataDetail } from '@/api/dict';
+  import type { EmergencyProcedureManageStruct } from '@/api/emergency-procedure';
+
+  const props = defineProps<{
+    dialogType: string; // 新增add/修改edit
+    eventTypeOptions: Array<SysDictDataDetail>; // 事件类型选项
+    originalEventForm?: EmergencyProcedureManageStruct; // 事件表单数据
+  }>();
+
+  const emits = defineEmits(['close', 'confirm']);
+
+  const emergencyEventManageDialog = ref(true);
+  const dialogTitle = computed(() => {
+    return props.dialogType === 'add' ? '启动' : '修改';
+  });
+  const dialogBtn = computed(() => {
+    return props.dialogType === 'add' ? '立即启动' : '确认修改';
+  });
+
+  const ruleFormRef = ref<FormInstance>();
+  const eventForm = reactive({
+    eventType: '',
+    eventLocation: '',
+  });
+
+  const rules = reactive({
+    eventType: [{ required: true, message: '请选择事件类型', trigger: 'change' }],
+    eventLocation: [{ required: true, message: '请输入事件地点', trigger: 'blur' }],
+  });
+
+  const handleCloseDialog = () => {
+    emits('close');
+  };
+
+  const handleConfirmDialog = async (formEl: FormInstance | undefined) => {
+    if (!formEl) return;
+    await formEl.validate((valid) => {
+      if (valid) emits('confirm', eventForm);
+      else return;
+    });
+  };
+
+  onMounted(() => {
+    if (props.originalEventForm) {
+      eventForm.eventType = props.originalEventForm.eventType ?? '';
+      eventForm.eventLocation = props.originalEventForm.eventLocation ?? '';
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  :deep(.emergency-event-manage-dialog) {
+    min-height: 300px;
+    padding: 20px 30px;
+
+    .el-dialog__header {
+      padding-right: 0;
+      padding-bottom: 50px;
+    }
+    .dialog-header {
+      font-size: 20px;
+      font-weight: bold;
+      display: flex;
+      justify-content: center;
+    }
+
+    .el-dialog__headerbtn {
+      font-size: 30px;
+      width: 60px;
+      height: 60px;
+      top: 8px;
+      right: 8px;
+    }
+
+    .el-dialog__footer {
+      padding-top: 40px;
+    }
+  }
+</style>

+ 119 - 0
src/views/emergency/emergency-procedure/components/TimeLine.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="timeline-container">
+    <div class="timeline" ref="timelineRef">
+      <div class="timeline-dot start"></div>
+      <div class="timeline-line" :style="{ width: timelineWidth }"></div>
+      <div class="timeline-dot end"></div>
+    </div>
+    <div class="timeline-info">
+      <div class="start-time">
+        <p class="timeline-info-title">事件启动时间</p>
+        <p>{{ props.startTime }}</p>
+      </div>
+      <div class="duration">
+        <p>{{ props.duration }}</p>
+      </div>
+      <div class="end-time">
+        <p class="timeline-info-title">处置完成时间</p>
+        <p>{{ props.completeTime }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, onMounted } from 'vue';
+
+  const props = defineProps<{
+    startTime?: string;
+    completeTime?: string;
+    duration?: string;
+  }>();
+
+  const timelineRef = ref<HTMLDivElement | null>(null);
+  const timelineWidth = ref('0%');
+
+  // 在组件挂载后,设置动画宽度
+  onMounted(() => {
+    if (timelineRef.value) {
+      setTimeout(() => {
+        timelineWidth.value = '100%';
+      }, 100); // 短暂延迟以确保动画生效
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  .timeline-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin: 30px;
+  }
+
+  .timeline {
+    width: 100%;
+    max-width: 680px;
+    height: 20px;
+    position: relative;
+  }
+
+  .timeline-dot {
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background-color: #1777ff;
+    position: absolute;
+    top: 4px;
+  }
+
+  .timeline-dot.start {
+    left: 0;
+  }
+
+  .timeline-dot.end {
+    right: 0;
+  }
+
+  .timeline-line {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    border-top: 2px dashed #1777ff;
+    transition: width 2s ease-in-out; /* 添加过渡效果 */
+  }
+
+  .timeline-info {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    max-width: 800px;
+    margin-top: 10px;
+    position: relative;
+  }
+
+  .start-time,
+  .end-time {
+    text-align: center;
+    font-weight: 400;
+    font-size: 14px;
+    color: #999999;
+  }
+
+  .timeline-info-title {
+    font-weight: 400;
+    font-size: 14px;
+    color: #000000;
+    margin-bottom: 6px;
+  }
+
+  .duration {
+    position: absolute;
+    top: -50px;
+    left: 50%;
+    transform: translateX(-50%);
+    font-weight: 400;
+    font-size: 14px;
+    color: #1777ff;
+  }
+</style>

+ 74 - 0
src/views/emergency/emergency-procedure/config/form.ts

@@ -0,0 +1,74 @@
+import type { FormConfig } from '@/types/basic-form';
+
+// 创建损失记录表单信息(应急处置事件)
+export const LOSS_ITEM_FORM_OF_EMERGENCY_PROCEDURE: FormConfig[] = [
+  {
+    label: '损失楼号、楼层、房间号:',
+    prop: 'affectedRoom',
+    slot: 'affectedRoom',
+  },
+  {
+    label: '受灾物品:',
+    prop: 'affectedItems',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入受灾物品',
+    },
+  },
+  {
+    label: '损失描述:',
+    prop: 'description',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入损失描述',
+      type: 'textarea',
+      rows: 4,
+      maxlength: 1000,
+      showWordLimit: true,
+    },
+  },
+  {
+    label: '影响安全评估:',
+    prop: 'safetyLevel',
+    slot: 'safetyLevel',
+  },
+  {
+    label: '整改责任部门:',
+    prop: 'responsibleDeptId',
+    slot: 'responsibleDeptId',
+  },
+];
+
+// 创建损失记录表单规则(应急处置事件)
+export const LOSS_ITEM_FORM_RULES_OF_EMERGENCY_PROCEDURE = {
+  affectedRoom: [{ required: true, message: '', trigger: 'blur' }],
+  buildingNo: [{ required: true, message: '请输入楼号', trigger: 'blur' }],
+  floorNo: [{ required: true, message: '请输入楼层', trigger: 'blur' }],
+  roomNo: [{ required: true, message: '请输入房间号', trigger: 'blur' }],
+  affectedItems: [{ required: true, message: '请输入受灾物品', trigger: 'blur' }],
+  description: [{ required: true, message: '请输入损失描述', trigger: 'blur' }],
+  safetyLevel: [{ required: true, message: '请选择影响安全评估', trigger: 'change' }],
+  responsibleDeptId: [{ required: true, message: '请选择整改责任部门', trigger: 'change' }],
+};
+
+export const LOSS_ITEM_PARAM_OF_EMERGENCY_PROCEDURE = {
+  reportTaskName: '',
+  isLoss: 1,
+  buildingNo: '',
+  floorNo: '',
+  roomNo: '',
+  affectedItems: '',
+  images: [],
+  description: '',
+  safetyLevel: '',
+  isAffectWork: null,
+  estimatedLoss: null,
+  responsibleDeptId: null,
+  userGroupList: [],
+  isPush: 1,
+  priority: '',
+  createdBy: '',
+  remark: '',
+  affectedRoom: '永远存在', //自己创造的一个数值,不要传给后端 这个数值永远存在
+  uploadImages: [],
+};

+ 25 - 0
src/views/emergency/emergency-procedure/config/index.ts

@@ -0,0 +1,25 @@
+import { PROCEDURE_LIST_SEARCH_CONFIG } from './search';
+import {
+  PROCEDURE_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  PROCEDURE_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  PROCEDURE_LIST_TABLE_OPTIONS,
+  PROCEDURE_LIST_TABLE_COLUMNS,
+  PROCEDURE_COMPLETE_LOSS_ITEM_TABLE_COLUMNS,
+} from './table';
+import {
+  LOSS_ITEM_FORM_OF_EMERGENCY_PROCEDURE,
+  LOSS_ITEM_FORM_RULES_OF_EMERGENCY_PROCEDURE,
+  LOSS_ITEM_PARAM_OF_EMERGENCY_PROCEDURE,
+} from './form';
+
+export {
+  PROCEDURE_LIST_SEARCH_CONFIG,
+  PROCEDURE_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  PROCEDURE_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  PROCEDURE_LIST_TABLE_OPTIONS,
+  PROCEDURE_LIST_TABLE_COLUMNS,
+  PROCEDURE_COMPLETE_LOSS_ITEM_TABLE_COLUMNS,
+  LOSS_ITEM_FORM_OF_EMERGENCY_PROCEDURE,
+  LOSS_ITEM_FORM_RULES_OF_EMERGENCY_PROCEDURE,
+  LOSS_ITEM_PARAM_OF_EMERGENCY_PROCEDURE,
+};

+ 40 - 0
src/views/emergency/emergency-procedure/config/search.ts

@@ -0,0 +1,40 @@
+import type { SearchConfig } from '@/types/basic-search';
+import { EMERGENCY_PROCEDURE_STATUS_OPTIONS } from '../constant';
+
+export const PROCEDURE_LIST_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '应急事件类型:',
+    prop: 'emergencyType',
+    slot: 'emergencyType',
+  },
+  {
+    label: '事件名称:',
+    prop: 'procedureName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入事件名称',
+    },
+  },
+  {
+    label: '事件启动时间:',
+    prop: 'procedureStartTime',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'datetimerange',
+      format: 'YYYY-MM-DD HH:mm:ss',
+      rangeSeparator: '-',
+      startPlaceholder: '开始时间',
+      endPlaceholder: '结束时间',
+    },
+  },
+  {
+    label: '状态:',
+    prop: 'status',
+    component: 'ElSelect',
+    selectOptions: EMERGENCY_PROCEDURE_STATUS_OPTIONS,
+    componentProps: {
+      placeholder: '全部',
+      clearable: true,
+    },
+  },
+];

+ 126 - 0
src/views/emergency/emergency-procedure/config/table.ts

@@ -0,0 +1,126 @@
+/**
+ * 应急处置表格配置
+ */
+import type { TableColumnProps } from '@/types/basic-table';
+
+export const PROCEDURE_LIST_TABLE_MAX_HEIGHT_DEFAULT = 'calc(70vh - 0px)';
+export const PROCEDURE_LIST_TABLE_MAX_HEIGHT_PERMISSION = 'calc(70vh - 60px)';
+
+// 基础表格样式配置
+const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+};
+
+// 应急处置表格样式配置
+export const PROCEDURE_LIST_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
+};
+
+// 应急处置表格列配置
+export const PROCEDURE_LIST_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '序号',
+    prop: 'index',
+    width: '80px',
+    type: 'index',
+    align: 'center',
+  },
+  {
+    label: '应急事件类型',
+    prop: 'eventType',
+    // slot: 'eventType',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '应急事件地点',
+    prop: 'eventLocation',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '应急事件名称',
+    prop: 'eventName',
+    // slot: 'eventName',
+    align: 'center',
+    minWidth: '360px',
+  },
+  // {
+  //   label: '关联预案',
+  //   prop: 'emergencyPlanId',
+  //   align: 'center',
+  // },
+  {
+    label: '启动时间',
+    prop: 'startTime',
+    align: 'center',
+    width: '180px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    width: '120px',
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    align: 'center',
+    slot: 'action',
+    fixed: 'right',
+    width: '350px',
+  },
+];
+
+// 应急处置完成-损失记录表格
+export const PROCEDURE_COMPLETE_LOSS_ITEM_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '序号',
+    prop: 'index',
+    width: '80px',
+    type: 'index',
+    align: 'center',
+  },
+  {
+    label: '受灾地点',
+    prop: 'lossItemLocation',
+    slot: 'lossItemLocation',
+    align: 'center',
+    minWidth: '160px',
+  },
+  {
+    label: '受灾物品',
+    prop: 'affectedItems',
+    align: 'center',
+    minWidth: '160px',
+  },
+  {
+    label: '损失描述',
+    prop: 'description',
+    align: 'center',
+    minWidth: '250px',
+  },
+  {
+    label: '影响安全评估',
+    prop: 'safetyLevel',
+    slot: 'safetyLevel',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '灾害损失整改责任部门',
+    prop: 'responsibleDeptName',
+    align: 'center',
+    minWidth: '160px',
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    align: 'center',
+    slot: 'action',
+    fixed: 'right',
+    width: '80px',
+  },
+];

+ 25 - 0
src/views/emergency/emergency-procedure/constant/index.ts

@@ -0,0 +1,25 @@
+/**
+ * @description: 应急处置常量
+ */
+
+// 应急处置-事件状态:1-启动中,2-已结束,3-已关闭
+export enum EMERGENCY_PROCEDURE_STATUS {
+  INPROGRESS = 1,
+  HASFINISHED = 2,
+  HASCLOSED = 3,
+}
+
+export const EMERGENCY_PROCEDURE_STATUS_OPTIONS = [
+  {
+    label: '启动中',
+    value: EMERGENCY_PROCEDURE_STATUS.INPROGRESS,
+  },
+  {
+    label: '已结束',
+    value: EMERGENCY_PROCEDURE_STATUS.HASFINISHED,
+  },
+  {
+    label: '已关闭',
+    value: EMERGENCY_PROCEDURE_STATUS.HASCLOSED,
+  },
+];

+ 33 - 0
src/views/emergency/emergency-procedure/hooks/index.ts

@@ -0,0 +1,33 @@
+/**
+ * @description: 应急处置
+ */
+import { ref } from 'vue';
+import type { SysDictDataDetail } from '@/api/dict';
+import { queryDictTypeDetail } from '@/api/dict';
+import { DICT_CODE } from '@/constant/dict';
+import { EMERGENCY_PROCEDURE_STATUS_OPTIONS } from '../constant';
+
+export const useEmergencyProcedureHook = () => {
+  // 应急事件类型
+  const emergencyEventDice = ref<SysDictDataDetail[]>([]);
+  const getEmergencyEventDict = async () => {
+    const emergencyEventRes = await queryDictTypeDetail(DICT_CODE.EMERGENCY_EVENT);
+    emergencyEventDice.value = emergencyEventRes.sysDictDataList;
+  };
+
+  // 获取应急事件类型字典
+  const getEmergencyEvent = (emergencyType: string) => {
+    return emergencyEventDice.value.find((item) => item.itemCode === emergencyType)?.itemValue;
+  };
+
+  // 状态映射
+  const getEmergencyStatus = (status: number) => {
+    return EMERGENCY_PROCEDURE_STATUS_OPTIONS.find((item) => item.value === status)?.label;
+  };
+  return {
+    emergencyEventDice,
+    getEmergencyEventDict,
+    getEmergencyEvent,
+    getEmergencyStatus,
+  };
+};

+ 7 - 0
src/views/emergency/src/constant.ts

@@ -1,4 +1,11 @@
 //管理权限
 //管理权限
 export const EMERGENCY_PERMISSIONS = {
 export const EMERGENCY_PERMISSIONS = {
   SUPPLY_LIST: 'emergency_business_module:supplies_list',
   SUPPLY_LIST: 'emergency_business_module:supplies_list',
+
+  // 应急处置-应急事件启动管理权限:增改关
+  EMERGENCY_PROCEDURE_MANAGE: 'emergency_business_module:emergency_procedure_manage',
+  // 应急处置-应急处置管理权限:完成
+  EMERGENCY_PROCEDURE_COMPLETE: 'emergency_business_module:emergency_procedure_complete',
+  // 应急处置-应急指挥中心管理权限:打开、编辑
+  EMERGENCY_COMMAND_CENTER_MANAGE: 'emergency_business_module:emergency_command_center_manage',
 };
 };