Преглед изворни кода

feat: 完成应急处置指挥中心,修改灾害防范总览天气信息

bxy пре 9 месеци
родитељ
комит
a19ad8628e

+ 96 - 0
src/api/command-center/index.ts

@@ -0,0 +1,96 @@
+import { http } from '@/utils/http/axios';
+
+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: 查询应急物资信息列表
+ */
+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: 获取队伍列表
+ */
+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: 获取队伍人员列表
+ */
+export const getEmergencyTeamMemberList = (teamId: number) => {
+  return http.request({
+    url: `/emergencySystem/queryTeamPersonnelList?teamId=${teamId}`,
+    method: 'get',
+  });
+};
+
+/**
+ * @description: 获取当前应急事件对应的监控视频,查询应急事件指挥中心相机分组
+ */
+export const getCommandCenterCameraLists = (taskId: number) => {
+  return http.request({
+    url: `/cameraGroup/queryCommandCenterCameraGroup?taskId=${taskId}`,
+    method: 'get',
+  });
+};

+ 1 - 1
src/api/disaster-overview/index.ts

@@ -192,7 +192,7 @@ export interface CameraInfo {
 }
 export const getMonitorCameraLists = () => {
   return http.request({
-    url: '/cameraGroup/queryCameraGroupList?isHomeDisplay=1',
+    url: '/cameraGroup/queryCameraGroupList?type=1',
     method: 'get',
   });
 };

+ 3 - 89
src/api/emergency-procedure/index.ts

@@ -2,9 +2,7 @@ import { http } from '@/utils/http/axios';
 import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
 
 /**
- *
  * @description: 应急处置
- *
  */
 export interface ProcedureListQuery {
   eventType?: string;
@@ -202,95 +200,11 @@ export const getEmergencyProcedureReport = (handleTaskId: number) => {
 };
 
 /**
- *
- * @description: 指挥中心
- *
+ * @description: 根据id查询应急处置任务详情
  */
-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) => {
+export const getEmergencyProcedureById = (handleTaskId: number) => {
   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}`,
+    url: `/emergencyHandleTask/queryEmergencyHandleTaskById?handleTaskId=${handleTaskId}`,
     method: 'get',
   });
 };
-
-/**
- * @description: 获取当前应急事件对应的监控视频 TODO:
- */

+ 1 - 1
src/components/live/LiveVideoFlv.vue

@@ -171,7 +171,7 @@
     width: 100%;
     height: 100%;
     background-color: transparent !important;
-    object-fit: fill;
+    object-fit: contain; // 0729fill->contain: 应急处置-指挥中心按需修改,若有其余地方使用出错再行修改
   }
 
   .videoWrapper {

+ 5 - 4
src/views/disaster/overview/components/key-monitor-area/UpdateMonitorArea.vue

@@ -5,6 +5,7 @@
     width="700px"
     @close="handleClose"
     class="update-monitor-dialog"
+    append-to-body
   >
     <div class="camera-items">
       <el-input class="search-bar" v-model="searchQuery" placeholder="请输入搜索内容" clearable :prefix-icon="Search" />
@@ -75,13 +76,13 @@
   import { ElDialog, ElInput, ElTree, ElTag, ElButton } from 'element-plus';
   import { VideoCamera, WarningFilled, Search } from '@element-plus/icons-vue';
   import Thumbnail from '@/components/thumbnail/Thumbnail.vue';
-  import { useDisasterOverviewMonitor } from '@/store/modules/useDisasterOverviewMonitor';
+  import { useMonitorCameraEdit } from '@/store/modules/useMonitorCameraEdit';
   import { CameraTreeNodeType } from '@/api/camera/camera-preview';
   import { CameraInTree } from '@/api/disaster-overview';
 
-  const disasterOverviewMonitor = useDisasterOverviewMonitor();
-  const { allCameraTree, selectedCameraIds } = storeToRefs(disasterOverviewMonitor);
-  const { getAllCameraTree } = disasterOverviewMonitor;
+  const monitorCameraEdit = useMonitorCameraEdit();
+  const { allCameraTree, selectedCameraIds } = storeToRefs(monitorCameraEdit);
+  const { getAllCameraTree } = monitorCameraEdit;
 
   interface Tree {
     [key: string]: any;

+ 2 - 1
src/router/full-routes.ts

@@ -8,7 +8,7 @@ import { AppRouteRecordRaw } from './types';
 import { getTreeItem } from '@/utils';
 import { cloneDeep } from 'lodash-es';
 import { PageEnum } from '@/enums/pageEnum';
-import { disasterPreventionRoute, emergencyManagementRoute, platformRoutes } from './routers';
+import { disasterPreventionRoute, emergencyManagementRoute, platformRoutes, largeScreenRoutes } from './routers';
 
 type RouteRecordString = Omit<AppRouteRecordRaw, 'component'> & { component?: string };
 
@@ -36,6 +36,7 @@ export const fullRoutes: AppRouteRecordRaw[] = [
   disasterPreventionRoute,
   emergencyManagementRoute,
   platformRoutes,
+  largeScreenRoutes,
 ] as const;
 
 /**

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

@@ -59,26 +59,7 @@ const emergencyManagementRoute = {
         title: '应急架构体系',
       },
     },
-    {
-      id: 2003,
-      parentId: 2000,
-      name: 'command-center',
-      path: 'command-center/:id',
-      component: '/emergency/command-center/PageCommandCenter',
-      redirect: '',
-      meta: {
-        activeMenu: null,
-        alwaysShow: false,
-        frameSrc: '',
-        hidden: false,
-        icon: '',
-        isFrame: 0,
-        isRoot: false,
-        noCache: false,
-        query: '',
-        title: '应急指挥中心',
-      },
-    },
+
     {
       id: 2004,
       parentId: 2000,

+ 2 - 1
src/router/routers/index.ts

@@ -2,5 +2,6 @@ import platformRoutes from './platform';
 import disasterPreventionRoute from './disaster';
 import emergencyManagementRoute from './emergency';
 import exceptionRouters from './exception';
+import largeScreenRoutes from './large-screen';
 
-export { platformRoutes, disasterPreventionRoute, emergencyManagementRoute, exceptionRouters };
+export { platformRoutes, disasterPreventionRoute, emergencyManagementRoute, exceptionRouters, largeScreenRoutes };

+ 36 - 0
src/router/routers/large-screen.ts

@@ -0,0 +1,36 @@
+/** 大屏 */
+const largeScreenRoutes = {
+  id: 3000,
+  parentId: -1,
+  name: 'LargeScreen',
+  path: '/large-screen',
+  component: 'ParentLayout',
+  redirect: '',
+  meta: {
+    title: '大屏路由',
+  },
+  children: [
+    {
+      id: 3001,
+      parentId: 3000,
+      name: 'command-center',
+      path: 'command-center/:id',
+      component: '/emergency/command-center/PageCommandCenter',
+      redirect: '',
+      meta: {
+        activeMenu: null,
+        alwaysShow: false,
+        frameSrc: '',
+        hidden: false,
+        icon: '',
+        isFrame: 0,
+        isRoot: false,
+        noCache: false,
+        query: '',
+        title: '应急指挥中心',
+      },
+    },
+  ],
+};
+
+export default largeScreenRoutes;

+ 0 - 48
src/store/modules/useDisasterOverviewMonitor.ts

@@ -1,48 +0,0 @@
-import { ref } from 'vue';
-import { defineStore } from 'pinia';
-import { getCameraTreeList, CameraInfo, getMonitorCameraLists } from '@/api/disaster-overview';
-
-export const useDisasterOverviewMonitor = defineStore('disasterOverviewMonitor', () => {
-  interface SelectedCameraIds {
-    id: number;
-    name: string;
-    code: string;
-  }
-  // 总览页面相机分组id
-  const idOfOverview = ref<number>();
-  // 所有相机列表
-  const allCameraTree = ref();
-  // 记录当前已选择的相机列表
-  const selectedCameras = ref<CameraInfo[]>([]);
-  const selectedCameraIds = ref<SelectedCameraIds[]>([]);
-
-  // 初始化所有相机列表
-  const getAllCameraTree = async () => {
-    const res = await getCameraTreeList();
-    allCameraTree.value = res;
-  };
-
-  // 初始化总览页面相机分组id + 当前已选择的相机列表
-  const getSelectedCameraIds = async () => {
-    await getMonitorCameraLists().then((res) => {
-      idOfOverview.value = res[0].id; // 总览相机分组有且只有一个,取res[0].id
-      selectedCameras.value = res[0].children;
-      selectedCameraIds.value = selectedCameras.value.map((item) => ({
-        id: item.id,
-        name: item.name,
-        code: item.code,
-      }));
-    });
-  };
-
-  return {
-    idOfOverview,
-    allCameraTree,
-    selectedCameras,
-    selectedCameraIds,
-    getAllCameraTree,
-    getSelectedCameraIds,
-  };
-});
-
-export default useDisasterOverviewMonitor;

+ 100 - 0
src/store/modules/useMonitorCameraEdit.ts

@@ -0,0 +1,100 @@
+/**
+ * @description: 由于总览页面和应急处置页面都需要使用到相机列表,为了避免重复请求,将相机列表存储在pinia中
+ * @return {
+ *  idOfOverview: 总览页面相机分组id
+ *  selectedCameras: 总览页面已选择的相机列表
+ *  selectedCameraIds: 总览页面已选择的相机id列表
+ * }
+ * @return {
+ *  idOfEmergencyEvent: 应急处置页面当前应急处置事件id
+ *  idOfCommandCenter: 应急处置页面当前应急处置事件相机分组id
+ *  selectedCameras: 应急处置页面已选择的相机列表
+ *  selectedCameraIds: 应急处置页面已选择的相机id列表
+ * }
+ */
+
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+import { getCameraTreeList, CameraInfo, getMonitorCameraLists } from '@/api/disaster-overview';
+import { getCommandCenterCameraLists } from '@/api/command-center';
+
+export const useMonitorCameraEdit = defineStore('monitorCameraEdit', () => {
+  // 所有相机列表
+  const allCameraTree = ref();
+  // 初始化所有相机列表
+  const getAllCameraTree = async () => {
+    const res = await getCameraTreeList();
+    allCameraTree.value = res;
+  };
+
+  interface SelectedCameraIds {
+    id: number;
+    name: string;
+    code: string;
+  }
+  // 用于UpdateMonitorArea.vue组件中,记录当前已选择的相机id列表
+  const selectedCameraIds = ref<SelectedCameraIds[]>([]);
+
+  /**
+   * @description: 灾害防范-总览
+   */
+  const idOfOverview = ref<number>(); // 总览页面相机分组id
+  const selectedCamerasOfOverview = ref<CameraInfo[]>([]); // 记录当前已选择的相机列表
+  const selectedCameraIdsOfOverview = ref<SelectedCameraIds[]>([]);
+
+  // 初始化总览页面相机分组id + 当前已选择的相机列表
+  const getSelectedCameraIdsOfOverview = async () => {
+    await getMonitorCameraLists().then((res) => {
+      idOfOverview.value = res[0].id; // 总览相机分组有且只有一个,取res[0].id
+      selectedCamerasOfOverview.value = res[0].children;
+      selectedCameraIdsOfOverview.value = selectedCamerasOfOverview.value.map((item) => ({
+        id: item.id,
+        name: item.name,
+        code: item.code,
+      }));
+      selectedCameraIds.value = selectedCameraIdsOfOverview.value;
+    });
+  };
+
+  /**
+   * @description: 应急管理-应急处置-指挥中心
+   */
+
+  const idOfEmergencyEvent = ref<number>(); // 当前应急处置事件id
+  const idOfCommandCenter = ref<number>(); // 相机分组id
+  // 记录当前已选择的相机列表
+  const selectedCamerasOfCommandCenter = ref<CameraInfo[]>([]);
+  const selectedCameraIdsOfCommandCenter = ref<SelectedCameraIds[]>([]);
+
+  // 初始化总览页面相机分组id + 当前已选择的相机列表
+  const getSelectedCameraIdsOfCommandCenter = async () => {
+    if (!idOfEmergencyEvent.value) return;
+    await getCommandCenterCameraLists(idOfEmergencyEvent.value).then((res) => {
+      idOfCommandCenter.value = res.id;
+      selectedCamerasOfCommandCenter.value = res.children;
+      selectedCameraIdsOfCommandCenter.value = selectedCamerasOfCommandCenter.value.map((item) => ({
+        id: item.id,
+        name: item.name,
+        code: item.code,
+      }));
+      selectedCameraIds.value = selectedCameraIdsOfCommandCenter.value;
+    });
+  };
+
+  return {
+    allCameraTree,
+    getAllCameraTree,
+    idOfOverview,
+    selectedCamerasOfOverview,
+    selectedCameraIdsOfOverview,
+    getSelectedCameraIdsOfOverview,
+    idOfEmergencyEvent,
+    idOfCommandCenter,
+    selectedCamerasOfCommandCenter,
+    selectedCameraIdsOfCommandCenter,
+    getSelectedCameraIdsOfCommandCenter,
+    selectedCameraIds,
+  };
+});
+
+export default useMonitorCameraEdit;

+ 19 - 20
src/views/disaster/overview/components/KeyMonitorArea.vue

@@ -5,16 +5,16 @@
       <span class="title">重点监测区域</span>
     </div>
     <div class="monitor-areas">
-      <div v-if="monitorEditPermission" class="add-area" @click="addMonitorVisible = true">
+      <div v-if="monitorEditPermission" class="add-area" @click="updateMonitorVisible = true">
         <span class="add-icon">+</span>
         <span>编辑监测区域</span>
       </div>
       <div class="monitor-area">
-        <div v-if="selectedCameras.length === 0" class="empty-style">
+        <div v-if="selectedCamerasOfOverview.length === 0" class="empty-style">
           <img class="empty-img" src="@/assets/images/empty-2.png" alt="" />
           <span>暂无监测区域</span>
         </div>
-        <div v-for="(item, index) in selectedCameras" :key="index" class="monitor-item">
+        <div v-for="(item, index) in selectedCamerasOfOverview" :key="index" class="monitor-item">
           <img
             v-if="!getCameraUrl(item)"
             src="@/assets/images/disaster-overview/no-camera-url.png"
@@ -37,34 +37,36 @@
         </div>
       </div>
     </div>
-    <UpdateMonitorArea v-if="addMonitorVisible" @confirm="handleConfirmAddMonitor" @close="handleCloseAddMonitor" />
+    <UpdateMonitorArea v-if="updateMonitorVisible" @confirm="handleConfirmAddMonitor" @close="handleCloseAddMonitor" />
   </div>
 </template>
 
 <script setup lang="ts">
-  import { onMounted, onUnmounted, ref } from 'vue';
+  import { computed, onMounted, onUnmounted, ref } from 'vue';
   import screenfull from 'screenfull';
   import urlJoin from 'url-join';
   import { storeToRefs } from 'pinia';
   import { ElMessage } from 'element-plus';
   import LiveVideo from '@/components/live/LiveVideo.vue';
-  import UpdateMonitorArea from './key-monitor-area/UpdateMonitorArea.vue';
+  import UpdateMonitorArea from '@/components/monitor-camera-edit/UpdateMonitorArea.vue';
   import { useUserInfoHook } from '@/views/disaster/hooks/index';
   import { DISASTER_PERMISSIONS } from '@/views/disaster/constant/index';
-  import { useDisasterOverviewMonitor } from '@/store/modules/useDisasterOverviewMonitor';
+  import { useMonitorCameraEdit } from '@/store/modules/useMonitorCameraEdit';
   import { userSplitScreenFullScreen } from '@/store/modules/userSplitScreenFullScreen';
   import { CameraInfo, CameraInTree, updateCameraToOverviewGroup } from '@/api/disaster-overview';
 
-  const disasterOverviewMonitor = useDisasterOverviewMonitor();
-  const { idOfOverview, selectedCameras } = storeToRefs(disasterOverviewMonitor);
-  const { getSelectedCameraIds } = disasterOverviewMonitor;
+  const monitorCameraEdit = useMonitorCameraEdit();
+  const { idOfOverview, selectedCamerasOfOverview } = storeToRefs(monitorCameraEdit);
+  const { getSelectedCameraIdsOfOverview } = monitorCameraEdit;
 
   const { isFullScreen, curFullScreenType } = storeToRefs(userSplitScreenFullScreen());
   const { fullScreen, exitFullscreen } = userSplitScreenFullScreen();
   const { permissions } = useUserInfoHook();
 
-  const monitorEditPermission = ref(false);
-  const addMonitorVisible = ref(false);
+  const monitorEditPermission = computed(() => {
+    return Boolean(permissions.find((item: { code: string }) => item.code === DISASTER_PERMISSIONS.OVERVIEW_MONITOR));
+  });
+  const updateMonitorVisible = ref(false);
 
   const isHttps = () => {
     return window.location.protocol.startsWith('https');
@@ -98,10 +100,10 @@
   };
 
   const handleConfirmAddMonitor = (data: CameraInTree[]) => {
-    addMonitorVisible.value = false;
+    updateMonitorVisible.value = false;
     const cameraAddIds: number[] = data.map((item) => item.id);
     updateCameraToOverviewGroup({ groupId: idOfOverview.value, cameraIdList: cameraAddIds }).then(() => {
-      getSelectedCameraIds();
+      getSelectedCameraIdsOfOverview();
       ElMessage({
         message: '监测区域编辑成功',
         type: 'success',
@@ -110,14 +112,11 @@
   };
 
   const handleCloseAddMonitor = () => {
-    addMonitorVisible.value = false;
+    updateMonitorVisible.value = false;
   };
 
-  onMounted(async () => {
-    await getSelectedCameraIds();
-    monitorEditPermission.value = Boolean(
-      permissions.find((item: { code: string }) => item.code === DISASTER_PERMISSIONS.OVERVIEW_MONITOR),
-    );
+  onMounted(() => {
+    getSelectedCameraIdsOfOverview();
   });
 
   window.onresize = () => {

+ 4 - 4
src/views/disaster/overview/components/WeatherCard.vue

@@ -42,10 +42,10 @@
   const measureInfo = ref<string | undefined>('');
 
   const weatherInfo = ref({
-    type: 'sunny',
-    temperature: 30,
-    humidity: 56,
-    windSpeed: '西南风2级',
+    type: 'storm',
+    temperature: 26,
+    humidity: 91,
+    windSpeed: '东北风3级',
     warning: '',
   });
 

+ 23 - 8
src/views/emergency/command-center/PageCommandCenter.vue

@@ -5,21 +5,31 @@
         <span>{{ currentDate }}</span>
         <span class="time">{{ currentTime }}</span>
       </div>
-      <div class="title">XXXX应急处置指挥中心</div>
+      <div class="title">{{ eventTitle }}应急处置指挥中心</div>
     </div>
     <div class="command-center__body">
       <div class="emergency-info">
-        <div class="emergency-supplies"></div>
-        <div class="emergency-organization"></div>
+        <EmergencySupply class="emergency-supplies" />
+        <EmergencyOrganization class="emergency-organization" />
       </div>
-      <div class="monitor-cameras"></div>
+      <MonitorCameras class="monitor-cameras" />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
   import { ref, onMounted, onUnmounted } from 'vue';
+  import { useRoute } from 'vue-router';
   import dayjs from 'dayjs';
+  import EmergencySupply from './components/EmergencySupply.vue';
+  import EmergencyOrganization from './components/EmergencyOrganization.vue';
+  import MonitorCameras from './components/MonitorCameras.vue';
+  import { getEmergencyProcedureById } from '@/api/emergency-procedure';
+
+  const route = useRoute();
+  const emergencyEventId = Number(route.params.id);
+
+  const eventTitle = ref('');
 
   let timer: NodeJS.Timeout;
   const currentDate = ref('');
@@ -34,6 +44,9 @@
   onMounted(() => {
     updateDateTime();
     timer = setInterval(updateDateTime, 1000);
+    getEmergencyProcedureById(emergencyEventId).then((res) => {
+      eventTitle.value = res.eventName;
+    });
   });
 
   onUnmounted(() => {
@@ -49,14 +62,14 @@
     width: 100vw;
     height: 100vh;
     background-color: #d8e3f4;
-    z-index: 9999;
+    z-index: 3;
     overflow: hidden;
   }
 
   .command-center__header {
     width: 100%;
     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');
     position: relative;
     display: flex;
     justify-content: center;
@@ -99,8 +112,10 @@
     justify-content: space-between;
 
     .emergency-info {
-      width: 350px;
+      width: 400px;
       height: 100%;
+      display: flex;
+      flex-direction: column;
 
       .emergency-supplies {
         width: 100%;
@@ -119,7 +134,7 @@
     }
 
     .monitor-cameras {
-      width: calc(100% - 360px);
+      width: calc(100% - 410px);
       height: 100%;
       background: #ffffff;
       border-radius: 8px;

+ 157 - 0
src/views/emergency/command-center/components/EmergencyOrganization.vue

@@ -0,0 +1,157 @@
+<template>
+  <div>
+    <div class="container-title">
+      <span class="line"></span>
+      <span class="title">应急队伍</span>
+    </div>
+    <div class="organization-container">
+      <el-tabs v-model="activeTeamId" tab-position="left" @tab-change="fetchTeamMembers">
+        <el-tab-pane v-for="team in teamList" :key="team.id" :label="team.teamName" :name="team.id">
+          <div v-if="teamMembers.length === 0" class="no-team-member">暂无人员信息</div>
+          <div class="team-member" v-for="member in teamMembers" :key="member.id">
+            <div class="member-info">
+              <div class="member-name">{{ member.realname }}</div>
+              <div class="mobile">{{ member.mobile }}</div>
+            </div>
+            <div class="member-job">{{ member.title }}</div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import {
+    QueryTeamListRes,
+    getEmergencyTeamList,
+    QueryTeamPersonnelListRes,
+    getEmergencyTeamMemberList,
+  } from '@/api/command-center';
+
+  const activeTeamId = ref<number>();
+  const teamList = ref<QueryTeamListRes[]>([]);
+  const teamMembers = ref<QueryTeamPersonnelListRes[]>([]);
+
+  const fetchTeamMembers = async () => {
+    if (!activeTeamId.value) return;
+    teamMembers.value = await getEmergencyTeamMemberList(activeTeamId.value);
+  };
+
+  onMounted(async () => {
+    teamList.value = await getEmergencyTeamList();
+    if (teamList.value.length > 0) {
+      activeTeamId.value = teamList.value[0].id;
+      fetchTeamMembers();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  .container-title {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    font-weight: 500;
+    font-size: 16px;
+    color: #000000;
+
+    .line {
+      width: 3px;
+      height: 16px;
+      background: #1777ff;
+      margin-right: 10px;
+    }
+
+    .title {
+      margin-right: 12px;
+    }
+  }
+
+  .organization-container {
+    height: calc(100% - 49px);
+    margin-top: 15px;
+    padding-bottom: 10px;
+
+    .team-member {
+      width: 100%;
+      height: 51px;
+      border-bottom: 1px solid rgba(151, 151, 151, 0.13);
+      padding: 0 10px 0 0;
+      margin-bottom: 10px;
+
+      .member-info {
+        display: flex;
+        justify-content: space-between;
+        font-weight: 500;
+        font-size: 16px;
+        color: #333333;
+
+        .member-name {
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .mobile {
+          font-weight: 400;
+        }
+      }
+
+      .member-job {
+        font-weight: 400;
+        font-size: 14px;
+        color: #666666;
+        margin-top: 5px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+
+    .no-team-member {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  :deep(.el-tabs) {
+    width: 100%;
+    height: 100%;
+
+    .el-tabs__header {
+      width: 130px;
+      border-right: 1px solid #e9e9e9;
+    }
+
+    .el-tabs__nav-wrap {
+      width: 100%;
+    }
+
+    .el-tabs__nav {
+      white-space: unset;
+    }
+
+    .el-tabs__item {
+      height: auto;
+      font-weight: 400;
+      font-size: 14px;
+      color: rgba(0, 0, 0, 0.65);
+      padding: 10px;
+    }
+
+    .el-tabs__item.is-active {
+      font-weight: 500;
+      font-size: 14px;
+      color: #1890ff;
+      background: #e7f7ff;
+    }
+
+    .el-tabs__content {
+      overflow: auto;
+    }
+  }
+</style>

+ 130 - 0
src/views/emergency/command-center/components/EmergencySupply.vue

@@ -0,0 +1,130 @@
+<template>
+  <div>
+    <div class="container-title">
+      <span class="line"></span>
+      <span class="title">应急物资</span>
+    </div>
+    <div class="supplies-container">
+      <div class="supply-tabs">
+        <span
+          v-for="item in emergencySupplyDice"
+          :key="item.itemCode"
+          :class="activeTab === item.itemCode ? 'active-tab' : 'normal-tab'"
+          @click="activeTab = item.itemCode"
+        >
+          {{ item.itemValue }}
+        </span>
+      </div>
+      <div class="supply-list" v-loading="loading">
+        <el-table :data="emergencySuppliesList" style="width: 100%; height: 100%">
+          <el-table-column prop="supplyName" label="物资名称" width="105" show-overflow-tooltip />
+          <el-table-column prop="location" label="位置" show-overflow-tooltip />
+          <el-table-column prop="currentQuantity" label="数量" width="80" show-overflow-tooltip />
+          <el-table-column prop="keeperName" label="保管人" show-overflow-tooltip />
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref, watch } from 'vue';
+  import { useEmergencySuppliesHook } from '@/views/emergency/emergency-supplies/src/hook';
+  import { QueryEmergencySuppliesInfoListRes, getEmergencySuppliesInfoList } from '@/api/command-center';
+
+  const { emergencySupplyDice, getEmergencySupplyDict } = useEmergencySuppliesHook();
+
+  const activeTab = ref('');
+  const loading = ref(true);
+  const emergencySuppliesList = ref<QueryEmergencySuppliesInfoListRes[]>([]);
+
+  const getEmergencySuppliesList = async () => {
+    emergencySuppliesList.value = await getEmergencySuppliesInfoList({
+      supplyType: activeTab.value,
+    });
+    loading.value = false;
+  };
+
+  watch(
+    () => activeTab.value,
+    async () => {
+      loading.value = true;
+      await getEmergencySuppliesList();
+      loading.value = false;
+    },
+  );
+
+  onMounted(async () => {
+    await getEmergencySupplyDict();
+    activeTab.value = emergencySupplyDice.value[0]?.itemCode || '';
+  });
+</script>
+
+<style scoped lang="scss">
+  .container-title {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    font-weight: 500;
+    font-size: 16px;
+    color: #000000;
+
+    .line {
+      width: 3px;
+      height: 16px;
+      background: #1777ff;
+      margin-right: 10px;
+    }
+
+    .title {
+      margin-right: 12px;
+    }
+  }
+
+  .supplies-container {
+    height: calc(100% - 49px);
+    padding-bottom: 10px;
+
+    .supply-tabs {
+      font-size: 14px;
+      cursor: pointer;
+      display: flex;
+      justify-content: space-evenly;
+      margin: 15px 10px 25px 10px;
+
+      .normal-tab {
+        color: rgba(0, 0, 0, 0.65);
+        margin: 0 5px;
+      }
+
+      .normal-tab:hover {
+        color: #1777ff;
+      }
+
+      .active-tab {
+        color: #1777ff;
+        font-weight: 600;
+        position: relative;
+        margin: 0 5px;
+      }
+
+      .active-tab::after {
+        content: '';
+        display: block;
+        width: 40px;
+        height: 2px;
+        background-color: #1777ff;
+        border-radius: 2px;
+        margin-top: 8px;
+        position: absolute;
+        left: 50%;
+        transform: translateX(-50%);
+      }
+    }
+
+    .supply-list {
+      height: calc(100% - 45px);
+      overflow: auto;
+    }
+  }
+</style>

+ 297 - 0
src/views/emergency/command-center/components/MonitorCameras.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="monitor-cameras-container">
+    <div class="main-camera">
+      <LiveVideo
+        v-if="curPlayingCamera && getCameraUrl(curPlayingCamera)"
+        :url="getCameraUrl(curPlayingCamera)"
+        :poster="getCameraImg(curPlayingCamera)"
+        :id="`monitor-livevideo`"
+        class="main-video"
+      />
+      <img
+        v-if="curPlayingCamera && !getCameraUrl(curPlayingCamera)"
+        src="@/assets/images/disaster-overview/full-screen.png"
+        alt=""
+        class="full-screen"
+        @click="isFullScreen ? exitFullscreen() : fullScreen(`monitor-livevideo`, 'overview-monitor')"
+      />
+      <div class="no-main-camera" v-if="!curPlayingCamera || !getCameraUrl(curPlayingCamera)">
+        <img class="cameraEmptyImg" src="@/assets/icons/nine-square-grid/cameraEmpty.png" />
+        <span>暂无监控相机画面</span>
+      </div>
+    </div>
+    <div class="all-cameras">
+      <div class="camera-edit">
+        <div>
+          <img src="@/assets/images/disaster-overview/camera.png" alt="" />
+          <span class="title">监控相机</span>
+        </div>
+        <div class="add-area" @click="updateMonitorVisible = true">
+          <span class="add-icon">+</span>
+          <span>编辑监控相机</span>
+        </div>
+      </div>
+      <div class="camera-list">
+        <div v-if="selectedCamerasOfCommandCenter.length === 0" class="no-camera-list">
+          <img class="empty-img" src="@/assets/images/empty.png" alt="" />
+          <span>暂无监控相机</span>
+        </div>
+        <div
+          v-for="(item, index) in selectedCamerasOfCommandCenter"
+          :key="index"
+          class="monitor-item"
+          :class="{ 'cur-playing': curPlayingCamera?.id === item.id }"
+          @click="curPlayingCamera = item"
+        >
+          <img
+            v-if="!getCameraUrl(item)"
+            src="@/assets/images/disaster-overview/no-camera-url.png"
+            alt=""
+            class="monitor-no-url"
+          />
+          <LiveVideo
+            :url="getCameraUrl(item)"
+            :poster="getCameraImg(item)"
+            :id="`monitor-livevideo-${index}`"
+            class="live-video"
+          />
+          <div>{{ item.name }}</div>
+        </div>
+      </div>
+    </div>
+    <UpdateMonitorArea v-if="updateMonitorVisible" @confirm="handleConfirmAddMonitor" @close="handleCloseAddMonitor" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, onUnmounted, ref, watch } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { storeToRefs } from 'pinia';
+  import screenfull from 'screenfull';
+  import urlJoin from 'url-join';
+  import { ElMessage } from 'element-plus';
+  import LiveVideo from '@/components/live/LiveVideo.vue';
+  import UpdateMonitorArea from '@/components/monitor-camera-edit/UpdateMonitorArea.vue';
+  import { useMonitorCameraEdit } from '@/store/modules/useMonitorCameraEdit';
+  import { userSplitScreenFullScreen } from '@/store/modules/userSplitScreenFullScreen';
+  import { CameraInfo, CameraInTree, updateCameraToOverviewGroup } from '@/api/disaster-overview';
+
+  const monitorCameraEdit = useMonitorCameraEdit();
+  const { idOfEmergencyEvent, idOfCommandCenter, selectedCamerasOfCommandCenter } = storeToRefs(monitorCameraEdit);
+  const { getSelectedCameraIdsOfCommandCenter } = monitorCameraEdit;
+
+  const { isFullScreen, curFullScreenType } = storeToRefs(userSplitScreenFullScreen());
+  const { fullScreen, exitFullscreen } = userSplitScreenFullScreen();
+
+  const route = useRoute();
+  const emergencyEventId = Number(route.params.id);
+
+  const curPlayingCamera = ref<CameraInfo>();
+
+  const updateMonitorVisible = ref(false);
+
+  const isHttps = () => {
+    return window.location.protocol.startsWith('https');
+  };
+
+  const getCameraUrl = (val: CameraInfo | undefined) => {
+    if (val?.pushStreamDTO && val?.pushStreamDTO.videoUrls) {
+      const videoUrl = val?.pushStreamDTO.videoUrls.pushstreamIp;
+      const protocol = isHttps() ? 'wss' : 'ws';
+      // 如果是绝对地址
+      if (videoUrl.startsWith('http')) {
+        // 如果是https的话,websocket要用wss
+        return videoUrl.replace('http', protocol);
+      }
+      const u = urlJoin(
+        `${protocol}://`,
+        window.location.host,
+        window.location.pathname === '/' ? '' : window.location.pathname,
+        videoUrl,
+      );
+      return u;
+    }
+    return '';
+  };
+
+  const getCameraImg = (val: CameraInfo | undefined) => {
+    if (val?.pushStreamDTO && val?.pushStreamDTO.imageUrl) {
+      return val?.pushStreamDTO.imageUrl;
+    }
+    return '';
+  };
+
+  const handleConfirmAddMonitor = (data: CameraInTree[]) => {
+    updateMonitorVisible.value = false;
+    const cameraAddIds: number[] = data.map((item) => item.id);
+    updateCameraToOverviewGroup({ groupId: idOfCommandCenter.value, cameraIdList: cameraAddIds }).then(() => {
+      getSelectedCameraIdsOfCommandCenter();
+      ElMessage({
+        message: '监测区域编辑成功',
+        type: 'success',
+      });
+    });
+  };
+
+  const handleCloseAddMonitor = () => {
+    updateMonitorVisible.value = false;
+  };
+
+  watch(
+    () => emergencyEventId,
+    (newVal) => {
+      idOfEmergencyEvent.value = newVal;
+    },
+  );
+
+  watch(
+    () => selectedCamerasOfCommandCenter.value,
+    (newVal) => {
+      curPlayingCamera.value = newVal[0];
+    },
+  );
+
+  onMounted(() => {
+    idOfEmergencyEvent.value = emergencyEventId;
+    getSelectedCameraIdsOfCommandCenter();
+  });
+
+  window.onresize = () => {
+    if (!screenfull.isFullscreen) {
+      isFullScreen.value = false; //判断退出全屏,进行赋值
+      curFullScreenType.value = 'single';
+    }
+  };
+
+  onUnmounted(() => {
+    window.onresize = null;
+  });
+</script>
+
+<style scoped lang="scss">
+  .monitor-cameras-container {
+    padding: 10px;
+
+    .main-camera {
+      width: 100%;
+      height: calc(100% - 220px);
+      position: relative;
+
+      .main-video {
+        height: 100%;
+      }
+
+      .full-screen {
+        position: absolute;
+        bottom: 0px;
+        right: 0px;
+        cursor: pointer;
+        padding: 12px;
+        background: rgba(0, 0, 0, 0.6);
+        border-radius: 8px 0px 0px 8px;
+      }
+    }
+
+    .no-main-camera {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 20px;
+      color: #999;
+    }
+
+    .all-cameras {
+      width: 100%;
+      height: 200px;
+      margin-top: 20px;
+    }
+  }
+
+  .camera-edit {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .title {
+      font-weight: 500;
+      font-size: 16px;
+      color: #000000;
+      margin-left: 5px;
+    }
+
+    .add-area {
+      padding: 0 16px;
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #333333;
+      cursor: pointer;
+
+      .add-icon {
+        width: 24px;
+        height: 24px;
+        border: 1px dashed #1777ff;
+        color: #1777ff;
+        font-size: 20px;
+        line-height: 24px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 12px;
+      }
+    }
+
+    .add-area:hover {
+      color: #1777ff;
+    }
+  }
+
+  .camera-list {
+    width: 100%;
+    height: calc(100% - 40px);
+    margin-top: 10px;
+    overflow: auto;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+
+    .monitor-item {
+      width: 235px;
+      height: 100%;
+      cursor: pointer;
+    }
+
+    .cur-playing .live-video {
+      border: 2px solid #1777ff;
+    }
+
+    .monitor-no-url {
+      width: 100%;
+      height: calc(100% - 26px);
+      object-fit: contain;
+    }
+
+    .live-video {
+      width: 100%;
+      height: calc(100% - 26px);
+      border: 1px solid #ddd;
+    }
+  }
+
+  .no-camera-list {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #999;
+
+    .empty-img {
+      height: 80%;
+      object-fit: contain;
+    }
+  }
+</style>

+ 1 - 1
src/views/emergency/emergency-procedure/PageProcedure.vue

@@ -207,7 +207,7 @@
 
   // 打开指挥中心
   const handleOpenCommandCenter = (row) => {
-    window.open(`/#/emergency-management/command-center/${row.id}`, '_blank');
+    window.open(`/#/large-screen/command-center/${row.id}`, '_blank');
   };
 
   // 修改