Quellcode durchsuchen

feat: 治安重点部位完成,新增部位相机编辑弹窗公共组件

bxy vor 7 Monaten
Ursprung
Commit
9ac7d0c10b

+ 68 - 0
src/api/security-confidentiality-position/index.ts

@@ -0,0 +1,68 @@
+import { http } from '@/utils/http/axios';
+import { GetCameraGroupListRes } from '@/api/disaster-overview';
+
+/**
+ * @description: 查询治安重点部位
+ */
+export interface GetPositionListParams {
+  groupName: string; // 部位名称
+  cameraName: string; // 相机名称
+}
+
+export interface PositionMonitorCameraListRes extends GetCameraGroupListRes {
+  orderNum: number; // 排序序号
+}
+export const getSecurityPositionList = (data: GetPositionListParams) => {
+  return http.request({
+    url: '/cameraGroup/queryHighSecurityAreas',
+    method: 'post',
+    data,
+  });
+};
+
+/**
+ * @description: 查询保密要害部位
+ */
+export const getConfidentialityPositionList = (data: GetPositionListParams) => {
+  return http.request({
+    url: '/cameraGroup/queryConfidentialKeyAreas',
+    method: 'post',
+    data,
+  });
+};
+
+/**
+ * @description: 新增或编辑治安重点部位/保密要害部位
+ */
+export interface AddOrUpdatePositionInfoParams {
+  id?: number; // 分组id
+  groupName: string; // 分组名称
+  type?: number; // 类型:3-治安重点部位,4-保密要害部位
+  cameraIdList?: number[]; // 相机id列表
+}
+export const addOrUpdatePositionInfo = (data: AddOrUpdatePositionInfoParams) => {
+  return http.request({
+    url: '/cameraGroup/saveOrUpdateCameraGroup',
+    method: 'post',
+    data,
+  });
+};
+
+/**
+ * @description: 删除治安重点部位/保密要害部位——deleteCameraGroupApi(/api/nine-square-grid/index.ts)
+ */
+
+/**
+ * @description: 修改相机分组顺序
+ */
+export interface UpdateCameraGroupOrderParam {
+  id: number; // 分组id
+  orderNum: number; // 排序序号
+}
+export const updateCameraGroupOrder = (data: UpdateCameraGroupOrderParam) => {
+  return http.request({
+    url: '/cameraGroup/updateCameraGroupOrder',
+    method: 'post',
+    data,
+  });
+};

+ 349 - 0
src/components/position-monitor-camera-edit/UpdatePositionMonitorCamera.vue

@@ -0,0 +1,349 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="visible"
+      :title="titleOfUpdatePositionMonitorCamera"
+      width="705px"
+      align-center
+      @close="handleClose"
+      class="update-position-monitor-dialog"
+    >
+      <div v-loading="loading">
+        <div class="position-name">
+          <div class="position-name-title">部位名称:</div>
+          <el-input v-model="positionName" style="width: 294px" placeholder="请输入部位名称" />
+        </div>
+        <div class="relate-camera">关联相机:</div>
+        <div class="camera-select-section">
+          <div class="camera-items">
+            <el-input
+              class="search-bar"
+              v-model="searchQuery"
+              placeholder="请输入搜索内容"
+              clearable
+              :prefix-icon="Search"
+            />
+            <div class="tree-container">
+              <el-tree
+                class="camera-tree"
+                ref="treeRef"
+                :data="allCameraTree"
+                :props="defaultProps"
+                node-key="code"
+                default-expand-all
+                show-checkbox
+                :filter-node-method="filterNode"
+                @check-change="handleCheckChange"
+              >
+                <template #default="{ node, data }">
+                  <div
+                    class="treeNode"
+                    :class="{
+                      selectedCamera: isSelected(data),
+                    }"
+                  >
+                    <div v-if="data.nodeType === CameraTreeNodeType.camera" class="icons">
+                      <VideoCamera
+                        class="cameraIcon"
+                        :class="{
+                          selectedCameraIcon: isSelected(data),
+                        }"
+                      />
+                      <WarningFilled v-if="isInvalid(data)" class="invalidCamera" style="color: red" />
+                    </div>
+                    <div> {{ node.label }}</div>
+                    <Thumbnail
+                      v-if="data.nodeType === CameraTreeNodeType.camera"
+                      :imageUrl="data.imageUrl"
+                      :code="data.code"
+                      position="right"
+                    >
+                      <div class="mask"></div>
+                    </Thumbnail>
+                  </div>
+                </template>
+              </el-tree>
+            </div>
+          </div>
+          <div class="divider"></div>
+          <div class="selected-items">
+            <div class="selected-title">已选择:</div>
+            <div class="selected-items-tag">
+              <el-tag
+                v-for="item in selectedItems"
+                :key="item.id"
+                closable
+                type="primary"
+                @close="handleCloseTag(item)"
+              >
+                {{ item.name }}
+              </el-tag>
+            </div>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button type="primary" @click="handleConfirm">确 定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { nextTick, onMounted, ref, watch } from 'vue';
+  import { storeToRefs } from 'pinia';
+  import type { TreeInstance } from 'element-plus';
+  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 { usePositionMonitorCameraEdit } from '@/store/modules/usePositionMonitorCameraEdit';
+  import { CameraTreeNodeType } from '@/api/camera/camera-preview';
+  import { CameraInTree } from '@/api/disaster-overview';
+
+  const positionMonitorCameraEdit = usePositionMonitorCameraEdit();
+  const { titleOfUpdatePositionMonitorCamera, allCameraTree, nameOfPosition, selectedCameraIdsOfPosition } =
+    storeToRefs(positionMonitorCameraEdit);
+  const { getAllCameraTree } = positionMonitorCameraEdit;
+
+  interface Tree {
+    [key: string]: any;
+  }
+
+  interface SelectedCameraIds {
+    id: number;
+    name: string;
+    code: string;
+  }
+
+  const defaultProps = {
+    children: 'children',
+    label: 'name',
+  };
+
+  const emits = defineEmits(['close', 'confirm']);
+
+  const loading = ref(false);
+
+  const visible = ref(true);
+  const treeRef = ref<TreeInstance>();
+  const searchQuery = ref(''); // 搜索框内容
+  const selectedItems = ref<SelectedCameraIds[]>([]); // 已选择的相机列表
+  const positionName = ref(''); // 部位名称
+
+  const isSelected = (data: CameraInTree) => {
+    if (data.nodeType === 'camera') return selectedItems.value.find((x) => x.id === data.id);
+  };
+
+  const isInvalid = (data) => {
+    if (data.networkingState !== 0) data.disable = true;
+    return data.networkingState !== 0;
+  };
+
+  watch(searchQuery, (val) => {
+    treeRef.value!.filter(val);
+  });
+
+  const filterNode = (value: string, data: Tree) => {
+    if (!value) return true;
+    return data.name.includes(value);
+  };
+
+  // 处理树节点的勾选变化
+  function handleCheckChange(data: CameraInTree, checked: boolean) {
+    if (checked && data.nodeType === 'camera') {
+      if (!selectedItems.value.find((x) => x.id === data.id)) {
+        selectedItems.value.push({
+          id: data.id,
+          name: data.name,
+          code: data.code,
+        });
+      }
+    } else {
+      selectedItems.value = selectedItems.value.filter((x) => x.id !== data.id);
+    }
+  }
+
+  // 处理标签关闭
+  function handleCloseTag(camera: SelectedCameraIds) {
+    const index = selectedItems.value.find((x) => x.code === camera.code);
+    if (index) treeRef.value?.setChecked(camera, false, true);
+  }
+
+  // 确认按钮点击事件
+  function handleConfirm() {
+    selectedCameraIdsOfPosition.value = selectedItems.value;
+    nameOfPosition.value = positionName.value;
+    emits('confirm');
+    resetAll();
+  }
+
+  // 取消/关闭按钮点击事件
+  const handleClose = () => {
+    emits('close'); // 更新父组件的 visible 状态
+    resetAll();
+  };
+
+  function resetAll() {
+    searchQuery.value = ''; // 清空搜索框
+    selectedItems.value = []; // 清空已选择项
+    positionName.value = ''; // 清空部位名称
+  }
+
+  watch(
+    () => selectedCameraIdsOfPosition.value,
+    (newVal) => {
+      if (newVal.length === 0) return;
+      const selectedCodes = newVal.map((item) => item.code);
+      nextTick(() => {
+        if (!treeRef.value) return;
+        treeRef.value.setCheckedKeys(selectedCodes, true);
+      });
+    },
+    {
+      immediate: true,
+    },
+  );
+
+  onMounted(async () => {
+    loading.value = true;
+    await getAllCameraTree();
+    selectedItems.value = JSON.parse(JSON.stringify(selectedCameraIdsOfPosition.value));
+    positionName.value = nameOfPosition.value;
+    loading.value = false;
+  });
+</script>
+
+<style scoped lang="scss">
+  .position-name {
+    display: flex;
+    align-items: center;
+    margin-bottom: 25px;
+  }
+
+  .position-name-title,
+  .relate-camera {
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.85);
+  }
+
+  .position-name-title::before,
+  .relate-camera::before {
+    content: '*';
+    color: #ff4d4f;
+    margin-right: 4px;
+  }
+
+  .relate-camera {
+    margin-bottom: 12px;
+  }
+
+  .camera-select-section {
+    width: 673px;
+    height: 357px;
+    border-radius: 4px;
+    border: 1px solid #d9d9d9;
+    display: flex;
+    justify-content: space-evenly;
+  }
+
+  .camera-items {
+    width: 300px;
+
+    .search-bar {
+      margin: 20px 0;
+    }
+
+    .camera-tree {
+      max-height: 274px;
+      overflow: auto;
+    }
+  }
+
+  .selectedCamera {
+    color: #1777ff;
+  }
+
+  .treeNode {
+    display: flex;
+    align-items: center;
+
+    .icons {
+      display: flex;
+      align-items: center;
+      position: relative;
+
+      .cameraIcon {
+        height: 18px;
+        margin-right: 8px;
+        color: rgb(112, 112, 112);
+      }
+
+      .selectedCameraIcon {
+        color: #1777ff;
+      }
+
+      .invalidCamera {
+        height: 14px;
+        color: #dd5869;
+        position: absolute;
+        right: 0px;
+        top: -5px;
+      }
+    }
+  }
+
+  .mask {
+    height: 26px;
+    width: 100%;
+  }
+
+  :deep(.thumb-nail) {
+    display: block;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 99;
+  }
+
+  :deep(.el-tree-node__content) {
+    position: relative;
+  }
+
+  .divider {
+    width: 1px;
+    background-color: #e0e0e0;
+    margin: 0;
+  }
+
+  .selected-items {
+    width: 300px;
+
+    .selected-title {
+      font-size: 16px;
+      margin: 24px 0 25px 0;
+    }
+
+    .selected-items-tag {
+      max-height: 272px;
+      overflow: auto;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 10px;
+    }
+  }
+
+  :deep(.update-position-monitor-dialog) {
+    .el-dialog__header {
+      padding: 5px 5px 24px 4px;
+    }
+
+    .el-dialog__body {
+      min-height: 450px;
+    }
+  }
+</style>

+ 4 - 4
src/store/modules/useMonitorCameraEdit.ts

@@ -2,14 +2,14 @@
  * @description: 由于总览页面和应急处置页面都需要使用到相机列表,为了避免重复请求,将相机列表存储在pinia中
  * @return {
  *  idOfOverview: 总览页面相机分组id
- *  selectedCameras: 总览页面已选择的相机列表
- *  selectedCameraIds: 总览页面已选择的相机id列表
+ *  selectedCamerasOfOverview: 总览页面已选择的相机列表
+ *  selectedCameraIdsOfOverview: 总览页面已选择的相机id列表
  * }
  * @return {
  *  idOfEmergencyEvent: 应急处置页面当前应急处置事件id
  *  idOfCommandCenter: 应急处置页面当前应急处置事件相机分组id
- *  selectedCameras: 应急处置页面已选择的相机列表
- *  selectedCameraIds: 应急处置页面已选择的相机id列表
+ *  selectedCamerasOfCommandCenter: 应急处置页面已选择的相机列表
+ *  selectedCameraIdsOfCommandCenter: 应急处置页面已选择的相机id列表
  * }
  */
 

+ 80 - 0
src/store/modules/usePositionMonitorCameraEdit.ts

@@ -0,0 +1,80 @@
+/**
+ * @description: 治安重点部位+保密要害部位监控相机编辑(二者返回结构一致)
+ * @return {
+ *  titleOfUpdatePositionMonitorCamera: 编辑重点监控部位/编辑要害监控部位
+ *  allCameraTree: 所有相机列表
+ *  dataOfPosition: 当前分组数据(即治安重点部位或保密要害部位数据)
+ *  idOfPosition: 当前分组id
+ *  nameOfPosition: 当前分组名称
+ *  selectedCameraIdsOfPosition: 当前分组已选择的相机id列表
+ * }
+ */
+
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+import { getCameraTreeList } from '@/api/disaster-overview';
+import { PositionMonitorCameraListRes } from '@/api/security-confidentiality-position';
+
+export const usePositionMonitorCameraEdit = defineStore('positionMonitorCameraEdit', () => {
+  // 编辑重点监控部位/编辑要害监控部位(dialog标题)
+  const titleOfUpdatePositionMonitorCamera = ref('编辑重点监控部位');
+
+  // 所有相机列表
+  const allCameraTree = ref();
+  // 初始化所有相机列表
+  const getAllCameraTree = async () => {
+    const res = await getCameraTreeList();
+    allCameraTree.value = res;
+  };
+
+  const dataOfPosition = ref<PositionMonitorCameraListRes>();
+  const idOfPosition = ref<number>();
+  const nameOfPosition = ref<string>('');
+
+  interface SelectedCameraIds {
+    id: number;
+    name: string;
+    code: string;
+  }
+  const selectedCameraIdsOfPosition = ref<SelectedCameraIds[]>([]);
+
+  const initDataOfPosition = () => {
+    // 添加
+    if (!dataOfPosition.value) {
+      idOfPosition.value = undefined;
+      nameOfPosition.value = '';
+      selectedCameraIdsOfPosition.value = [];
+      return;
+    }
+    // 编辑
+    idOfPosition.value = dataOfPosition.value.id;
+    nameOfPosition.value = dataOfPosition.value.groupName;
+    selectedCameraIdsOfPosition.value = dataOfPosition.value.children.map((item) => ({
+      id: item.id,
+      name: item.name,
+      code: item.code,
+    }));
+  };
+
+  const resetPositionMonitorCameraEdit = () => {
+    titleOfUpdatePositionMonitorCamera.value = '编辑重点监控部位';
+    dataOfPosition.value = undefined;
+    idOfPosition.value = undefined;
+    nameOfPosition.value = '';
+    selectedCameraIdsOfPosition.value = [];
+  };
+
+  return {
+    titleOfUpdatePositionMonitorCamera,
+    allCameraTree,
+    getAllCameraTree,
+    initDataOfPosition,
+    dataOfPosition,
+    idOfPosition,
+    nameOfPosition,
+    selectedCameraIdsOfPosition,
+    resetPositionMonitorCameraEdit,
+  };
+});
+
+export default usePositionMonitorCameraEdit;

+ 0 - 0
src/views/security-confidentiality/confidentiality-position/position-management/config/index.ts


+ 0 - 0
src/views/security-confidentiality/confidentiality-position/position-management/config/search.ts


+ 0 - 0
src/views/security-confidentiality/confidentiality-position/position-management/config/table.ts


+ 0 - 0
src/views/security-confidentiality/confidentiality-position/position-management/constant/index.ts


+ 9 - 0
src/views/security-confidentiality/constant/index.ts

@@ -0,0 +1,9 @@
+// 保卫保密权限
+export const SECURITY_CONFIDENTIALITY_PERMISSIONS = {
+  // 治安重点部位管理
+  SECURITY_POSITION_MANAGEMENT: 'security_business_module:security_position_management',
+  // 保密要害部位管理
+  CONFIDENTIALITY_POSITION_MANAGEMENT: 'security_business_module:confidentiality_position_management',
+  // 保密要害部位监测抓拍管理
+  CONFIDENTIALITY_POSITION_MONITOR_MANAGEMENT: 'security_business_module:confidentiality_position_monitor_management',
+};

+ 250 - 3
src/views/security-confidentiality/security-position/SecurityPosition.vue

@@ -1,7 +1,254 @@
 <template>
-  <div> overview </div>
+  <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
+            v-if="securityPositionManagePermission"
+            class="search-table-container--button"
+            type="primary"
+            :icon="Plus"
+            @click="handleAddSecurityPosition"
+          >
+            新建治安重点部位
+          </el-button>
+          <BasicSearch
+            :searchConfig="SECURITY_POSITION_LIST_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:search-data="handleSearch"
+          >
+            <template #securityPosition>
+              <el-input
+                v-model="searchKeyword"
+                :placeholder="`请输入${curSearchTypeLabel}进行搜索`"
+                clearable
+                @input="handleSearch"
+                @keyup.enter="handleSearch"
+                style="width: 380px"
+              >
+                <template #prefix>
+                  <el-icon color="#1777ff"><Search /></el-icon>
+                </template>
+                <template #prepend>
+                  <el-select
+                    v-model="searchSelectedType"
+                    placeholder="选择搜索项"
+                    @change="handleSelectedTypeChange"
+                    style="width: 100px"
+                  >
+                    <el-option
+                      v-for="item in securityPositionQueryOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                    />
+                  </el-select>
+                </template>
+              </el-input>
+            </template>
+          </BasicSearch>
+        </header>
+        <BasicTable ref="basicTableRef" :tableData="tableData" :tableConfig="tableConfig">
+          <template #cameraName="scope">
+            <div class="camera-name-container">
+              <div v-for="item in scope.row.children" :key="item.id">
+                {{ item.name }}
+              </div>
+            </div>
+          </template>
+          <template #action="scope">
+            <div class="action-container--div">
+              <ActionButton text="上移" @click="handleUpOne(scope.row.id, scope.row.orderNum)" />
+              <ActionButton text="下移" @click="handleDownOne(scope.row.id, scope.row.orderNum)" />
+              <ActionButton
+                text="编辑"
+                v-if="securityPositionManagePermission"
+                @click="handleEditSecurityPosition(scope.row)"
+              />
+              <ActionButton
+                v-if="securityPositionManagePermission"
+                text="删除"
+                :popconfirm="{
+                  title: '是否删除该治安重点部位?',
+                }"
+                @confirm="handleDeleteSecurityPosition(scope.row.id)"
+              />
+            </div>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+    <UpdatePositionMonitorCamera
+      v-if="updatePositionMonitorCameraVisible"
+      @confirm="handleConfirmPositionMonitorCamera"
+      @close="handleClosePositionMonitorCamera"
+    />
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { ref, onMounted, reactive, computed } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import { Plus, Search } from '@element-plus/icons-vue';
+  import { storeToRefs } from 'pinia';
+  import BasicSearch from '@/components/BasicSearch.vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import UpdatePositionMonitorCamera from '@/components/position-monitor-camera-edit/UpdatePositionMonitorCamera.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
+  import { usePositionMonitorCameraEdit } from '@/store/modules/usePositionMonitorCameraEdit';
+  import { SECURITY_CONFIDENTIALITY_PERMISSIONS } from '../constant';
+  import { FIELDTYPE, FIELD_CONTENT, POSITION_TYPE, securityPositionQueryOptions } from './constant';
+  import {
+    SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+    SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+    SECURITY_POSITION_LIST_TABLE_OPTIONS,
+    SECURITY_POSITION_LIST_TABLE_COLUMNS,
+    SECURITY_POSITION_LIST_SEARCH_CONFIG,
+  } from './config';
+  import {
+    GetPositionListParams,
+    PositionMonitorCameraListRes,
+    getSecurityPositionList,
+    AddOrUpdatePositionInfoParams,
+    addOrUpdatePositionInfo,
+    updateCameraGroupOrder,
+  } from '@/api/security-confidentiality-position';
+  import { deleteCameraGroupApi } from '@/api/nine-square-grid';
 
-<style scoped></style>
+  const { tableConfig } = useTableConfig(SECURITY_POSITION_LIST_TABLE_COLUMNS, SECURITY_POSITION_LIST_TABLE_OPTIONS);
+
+  const { permissions } = useUserInfoHook();
+  const securityPositionManagePermission = ref<boolean>(false);
+
+  const positionMonitorCameraEdit = usePositionMonitorCameraEdit();
+  const {
+    titleOfUpdatePositionMonitorCamera,
+    dataOfPosition,
+    idOfPosition,
+    nameOfPosition,
+    selectedCameraIdsOfPosition,
+  } = storeToRefs(positionMonitorCameraEdit);
+  const { initDataOfPosition } = positionMonitorCameraEdit;
+
+  const searchData = reactive<GetPositionListParams>({
+    groupName: '',
+    cameraName: '',
+  });
+
+  const searchSelectedType = ref(FIELDTYPE.POSITION_NAME);
+  const searchKeyword = ref('');
+  const curSearchTypeLabel = computed(() => {
+    const option = securityPositionQueryOptions.find((item) => item.value === searchSelectedType.value);
+    return option ? option.label : FIELD_CONTENT[searchSelectedType.value];
+  });
+
+  const tableData = ref<PositionMonitorCameraListRes[]>([]);
+
+  const updatePositionMonitorCameraVisible = ref(false);
+
+  const handleAddSecurityPosition = () => {
+    titleOfUpdatePositionMonitorCamera.value = '编辑重点监控部位';
+    dataOfPosition.value = undefined;
+    updatePositionMonitorCameraVisible.value = true;
+  };
+
+  const handleSearch = () => {
+    console.log('查询治安重点部位');
+    if (searchSelectedType.value === FIELDTYPE.POSITION_NAME) searchData.groupName = searchKeyword.value;
+    else searchData.cameraName = searchKeyword.value;
+    getTableData();
+  };
+
+  const handleSelectedTypeChange = () => {
+    searchKeyword.value = '';
+  };
+
+  const handleUpOne = (id: number, orderNum: number) => {
+    console.log('上移治安重点部位', id);
+    updateCameraGroupOrder({
+      id: id,
+      orderNum: orderNum - 1,
+    }).then((res) => {
+      console.log('上移治安重点部位', res);
+      getTableData();
+    });
+  };
+
+  const handleDownOne = (id: number, orderNum: number) => {
+    console.log('下移治安重点部位', id);
+    updateCameraGroupOrder({
+      id: id,
+      orderNum: orderNum + 1,
+    }).then((res) => {
+      console.log('下移治安重点部位', res);
+      getTableData();
+    });
+  };
+
+  const handleEditSecurityPosition = (row: PositionMonitorCameraListRes) => {
+    console.log('编辑治安重点部位', row);
+    titleOfUpdatePositionMonitorCamera.value = '编辑重点监控部位';
+    dataOfPosition.value = row;
+    initDataOfPosition();
+    updatePositionMonitorCameraVisible.value = true;
+  };
+
+  const handleDeleteSecurityPosition = (id: number) => {
+    console.log('删除治安重点部位', id);
+    deleteCameraGroupApi(id).then((res) => {
+      console.log('删除治安重点部位', res);
+      getTableData();
+    });
+  };
+
+  const handleConfirmPositionMonitorCamera = () => {
+    const params: AddOrUpdatePositionInfoParams = {
+      id: idOfPosition.value ?? undefined,
+      groupName: nameOfPosition.value,
+      type: POSITION_TYPE.SECURITY_POSITION,
+      cameraIdList: selectedCameraIdsOfPosition.value.map((item) => item.id),
+    };
+    addOrUpdatePositionInfo(params).then(() => {
+      ElMessage.success(idOfPosition.value ? '治安重点部位编辑成功' : '治安重点部位新建成功');
+      getTableData();
+    });
+    updatePositionMonitorCameraVisible.value = false;
+  };
+
+  const handleClosePositionMonitorCamera = () => {
+    ElMessage.info('取消操作');
+    updatePositionMonitorCameraVisible.value = false;
+  };
+
+  const getTableData = () => {
+    tableConfig.loading = true;
+    getSecurityPositionList(searchData).then((res) => {
+      console.log('获取治安重点部位列表', res);
+      tableData.value = res;
+    });
+    tableConfig.loading = false;
+  };
+
+  onMounted(() => {
+    securityPositionManagePermission.value = Boolean(
+      permissions.find(
+        (item: { code: string }) => item.code === SECURITY_CONFIDENTIALITY_PERMISSIONS.SECURITY_POSITION_MANAGEMENT,
+      ),
+    );
+    tableConfig.maxHeight = securityPositionManagePermission.value
+      ? SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_PERMISSION
+      : SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_DEFAULT;
+  });
+  getTableData();
+</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 *;
+</style>

+ 15 - 0
src/views/security-confidentiality/security-position/config/index.ts

@@ -0,0 +1,15 @@
+import {
+  SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  SECURITY_POSITION_LIST_TABLE_OPTIONS,
+  SECURITY_POSITION_LIST_TABLE_COLUMNS,
+} from './table';
+import { SECURITY_POSITION_LIST_SEARCH_CONFIG } from './search';
+
+export {
+  SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  SECURITY_POSITION_LIST_TABLE_OPTIONS,
+  SECURITY_POSITION_LIST_TABLE_COLUMNS,
+  SECURITY_POSITION_LIST_SEARCH_CONFIG,
+};

+ 9 - 0
src/views/security-confidentiality/security-position/config/search.ts

@@ -0,0 +1,9 @@
+import type { SearchConfig } from '@/types/basic-search';
+
+export const SECURITY_POSITION_LIST_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '',
+    prop: 'securityPosition',
+    slot: 'securityPosition',
+  },
+];

+ 56 - 0
src/views/security-confidentiality/security-position/config/table.ts

@@ -0,0 +1,56 @@
+/**
+ * 治安重点部位表格配置
+ */
+import type { TableColumnProps } from '@/types/basic-table';
+
+export const SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_DEFAULT = 'calc(100vh - 80px)';
+export const SECURITY_POSITION_LIST_TABLE_MAX_HEIGHT_PERMISSION = 'calc(100vh - 130px)';
+
+// 基础表格样式配置
+const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+};
+
+// 应急处置表格样式配置
+export const SECURITY_POSITION_LIST_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
+};
+
+// 应急处置表格列配置
+export const SECURITY_POSITION_LIST_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '序号',
+    prop: 'index',
+    width: '80px',
+    type: 'index',
+    align: 'center',
+  },
+  {
+    label: '重点部位名称',
+    prop: 'groupName',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '关联相机',
+    prop: 'cameraName',
+    slot: 'cameraName',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '创建时间',
+    prop: 'updatedAt',
+    align: 'center',
+    width: '250px',
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    align: 'center',
+    slot: 'action',
+    fixed: 'right',
+    width: '400px',
+  },
+];

+ 31 - 0
src/views/security-confidentiality/security-position/constant/index.ts

@@ -0,0 +1,31 @@
+/**
+ * @description: 治安重点部位/保密要害部位 常量对应字段
+ */
+
+// 查询字段对应:1-部位名称,2-相机名称
+export enum FIELDTYPE {
+  POSITION_NAME = 'groupName',
+  CAMERA_NAME = 'cameraName',
+}
+
+export const FIELD_CONTENT = {
+  [FIELDTYPE.POSITION_NAME]: '部位名称',
+  [FIELDTYPE.CAMERA_NAME]: '相机名称',
+};
+
+export const securityPositionQueryOptions = [
+  {
+    label: FIELD_CONTENT[FIELDTYPE.POSITION_NAME],
+    value: FIELDTYPE.POSITION_NAME,
+  },
+  {
+    label: FIELD_CONTENT[FIELDTYPE.CAMERA_NAME],
+    value: FIELDTYPE.CAMERA_NAME,
+  },
+];
+
+// 新增或编辑治安重点部位/保密要害部位type:3-治安重点部位,4-保密要害部位
+export enum POSITION_TYPE {
+  SECURITY_POSITION = 3,
+  CONFIDENTIALITY_POSITION = 4,
+}