Browse Source

增加安防设备接入

caoyang 6 months ago
parent
commit
17c9f63c05

+ 48 - 0
src/api/safe/camera/index.ts

@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+
+// 安防前端设备 VO
+export interface CameraVO {
+  id: number // 设备ID
+  name: string // 名称
+  code: string // 编号
+  sort: number // 排序
+  type: string // 类型
+  serverId: number // 后端设备
+  aiMode: string // AI模型
+  warn: string // 报警状态
+  warnApi: string // 报警接口
+  desc: string // 描述
+}
+
+// 安防前端设备 API
+export const CameraApi = {
+  // 查询安防前端设备分页
+  getCameraPage: async (params: any) => {
+    return await request.get({ url: `/safe/camera/page`, params })
+  },
+
+  // 查询安防前端设备详情
+  getCamera: async (id: number) => {
+    return await request.get({ url: `/safe/camera/get?id=` + id })
+  },
+
+  // 新增安防前端设备
+  createCamera: async (data: CameraVO) => {
+    return await request.post({ url: `/safe/camera/create`, data })
+  },
+
+  // 修改安防前端设备
+  updateCamera: async (data: CameraVO) => {
+    return await request.put({ url: `/safe/camera/update`, data })
+  },
+
+  // 删除安防前端设备
+  deleteCamera: async (id: number) => {
+    return await request.delete({ url: `/safe/camera/delete?id=` + id })
+  },
+
+  // 导出安防前端设备 Excel
+  exportCamera: async (params) => {
+    return await request.download({ url: `/safe/camera/export-excel`, params })
+  },
+}

+ 56 - 0
src/api/safe/server/index.ts

@@ -0,0 +1,56 @@
+import request from '@/config/axios'
+
+// 安防后端设备 VO
+export interface ServerVO {
+  id: number // 服务ID
+  name: string // 名称
+  type: string // 类型
+  brand: string // 品牌
+  model: string // 型号
+  orgId: number // 所属机构
+  protocol: string // 接入协议
+  ip: string // 地址
+  port: number // 端口
+  domain: string // 服务域
+  username: string // 用户名
+  password: string // 密码
+  desc: string // 描述
+}
+
+// 安防后端设备 API
+export const ServerApi = {
+  // 查询安防所有后端设备
+  getAllServer: async () => {
+    return await request.get({ url: `/safe/server/list`})
+  },
+
+  // 查询安防后端设备分页
+  getServerPage: async (params: any) => {
+    return await request.get({ url: `/safe/server/page`, params })
+  },
+
+  // 查询安防后端设备详情
+  getServer: async (id: number) => {
+    return await request.get({ url: `/safe/server/get?id=` + id })
+  },
+
+  // 新增安防后端设备
+  createServer: async (data: ServerVO) => {
+    return await request.post({ url: `/safe/server/create`, data })
+  },
+
+  // 修改安防后端设备
+  updateServer: async (data: ServerVO) => {
+    return await request.put({ url: `/safe/server/update`, data })
+  },
+
+  // 删除安防后端设备
+  deleteServer: async (id: number) => {
+    return await request.delete({ url: `/safe/server/delete?id=` + id })
+  },
+
+  // 导出安防后端设备 Excel
+  exportServer: async (params) => {
+    return await request.download({ url: `/safe/server/export-excel`, params })
+  },
+}

+ 8 - 1
src/utils/dict.ts

@@ -209,5 +209,12 @@ export enum DICT_TYPE {
 
   // ========== ERP - 企业资源计划模块  ==========
   ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态
-  ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type' // 库存明细的业务类型
+  ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型
+
+  // ========== SAFE - 安防接入模块  ==========
+  SAFE_DEVICE_TYPE = 'safe_device_type',//后端安防类型
+  SAFE_DEVICE_PROTOCOL = 'safe_device_protocol',//安防接入协议
+  SAFE_CAMERA_TYPE = 'safe_camera_type',//前端安防类型
+  SAFE_AI_MODE = 'safe_ai_mode',//AI大模型分类
+
 }

+ 178 - 0
src/views/safe/camera/CameraForm.vue

@@ -0,0 +1,178 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入名称" />
+      </el-form-item>
+      <el-form-item label="编号" prop="code">
+        <el-input v-model="formData.code" placeholder="请输入编号" />
+      </el-form-item>
+      <el-form-item label="排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="1" :max="1000" controls-position="right" />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select v-model="formData.type" placeholder="请选择类型">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_CAMERA_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="后端设备" prop="serverId">
+        <el-select v-model="formData.serverId" placeholder="请选择后端设备">
+          <el-option
+            v-for="server in serverList"
+            :key="server.id"
+            :label="server.name"
+            :value="server.id"
+        >
+            <span style="float: left">{{ server.name }}</span>
+            <span
+              style="
+                float: right;
+                color: var(--el-text-color-secondary);
+                font-size: 10px;
+              "
+            >
+              {{ server.protocol +'://'+server.ip+':'+server.port+'/'+server.domain }}
+            </span>
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="AI模型" prop="aiMode">
+        <el-select v-model="formData.aiMode" placeholder="请选择AI模型">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_AI_MODE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="报警状态" prop="warn">
+        <el-select v-model="formData.warn" placeholder="请选择报警状态">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="报警接口" prop="warnApi">
+        <el-input v-model="formData.warnApi" placeholder="请输入报警接口" />
+      </el-form-item>
+      <el-form-item label="描述" prop="des">
+        <el-input v-model="formData.des" type="textarea" placeholder="请输入描述" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
+import { CameraApi, CameraVO } from '@/api/safe/camera'
+import { ServerApi } from '@/api/safe/server'
+
+/** 安防前端设备 表单 */
+defineOptions({ name: 'CameraForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+let serverList = ref([])//后端设备列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: undefined,
+  code: undefined,
+  sort: undefined,
+  type: undefined,
+  serverId: undefined,
+  aiMode: undefined,
+  warn: undefined,
+  warnApi: undefined,
+  des: undefined,
+})
+const formRules = reactive({
+  name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+  code: [{ required: true, message: '编号不能为空', trigger: 'blur' }],
+  serverId: [{ required: true, message: '后端设备不能为空', trigger: 'change' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  serverList.value = await ServerApi.getAllServer()
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await CameraApi.getCamera(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+
+defineExpose({ open}) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as CameraVO
+    if (formType.value === 'create') {
+      await CameraApi.createCamera(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await CameraApi.updateCamera(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: undefined,
+    code: undefined,
+    sort: undefined,
+    type: undefined,
+    serverId: undefined,
+    aiMode: undefined,
+    warn: undefined,
+    warnApi: undefined,
+    des: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 255 - 0
src/views/safe/camera/index.vue

@@ -0,0 +1,255 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="编号" prop="code">
+        <el-input
+          v-model="queryParams.code"
+          placeholder="请输入编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select
+          v-model="queryParams.type"
+          placeholder="请选择类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_CAMERA_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="后端设备" prop="serverId">
+        <el-select
+          v-model="queryParams.serverId"
+          placeholder="请选择后端设备"
+          clearable
+          class="!w-300px"
+        >
+          <el-option
+            v-for="server in serverList"
+            :key="server.id"
+            :label="server.name"
+            :value="server.id"
+        >
+            <span style="float: left">{{ server.name }}</span>
+            <span
+              style="
+                float: right;
+                color: var(--el-text-color-secondary);
+                font-size: 10px;
+              "
+            >
+              {{ server.protocol +'://'+server.ip+':'+server.port+'/'+server.domain }}
+            </span>
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['safe:camera:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['safe:camera:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="名称" align="center" prop="name" />
+      <el-table-column label="编号" align="center" prop="code" />
+      <el-table-column label="类型" align="center" prop="type">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SAFE_CAMERA_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column label="后端设备" align="center" prop="serverId" >
+        <template #default="scope">
+          {{ serverMap.get(scope.row.serverId) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="AI模型" align="center" prop="aiMode">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SAFE_AI_MODE" :value="scope.row.aiMode" />
+        </template>
+      </el-table-column>
+      <el-table-column label="报警状态" align="center" prop="warn">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.warn" />
+        </template>
+      </el-table-column>
+      <el-table-column label="报警接口" align="center" prop="warnApi" />
+      <el-table-column label="描述" align="center" prop="des" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['safe:camera:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['safe:camera:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <CameraForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
+import download from '@/utils/download'
+import { CameraApi, CameraVO } from '@/api/safe/camera'
+import { ServerApi } from '@/api/safe/server'
+import CameraForm from './CameraForm.vue'
+
+/** 安防前端设备 列表 */
+defineOptions({ name: 'SafeCamera' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<CameraVO[]>([]) // 列表的数据
+let serverList = ref([])//后端设备列表
+const serverMap = new Map()//后端设备映射字典
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  code: undefined,
+  type: undefined,
+  serverId: undefined,
+  des: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await CameraApi.getCameraPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await CameraApi.deleteCamera(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await CameraApi.exportCamera(queryParams)
+    download.excel(data, '安防前端设备.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 查询流程分类列表
+  serverList.value = await ServerApi.getAllServer()
+  serverMap.clear
+  if(serverList){
+    serverList.value.forEach(it =>{
+      serverMap.set(it.id, it.name)
+    })
+  }
+})
+</script>

src/views/camera/pull.vue → src/views/safe/demo/pull.vue


src/views/camera/push.vue → src/views/safe/demo/push.vue


+ 178 - 0
src/views/safe/server/ServerForm.vue

@@ -0,0 +1,178 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入名称" />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select v-model="formData.type" placeholder="请选择类型">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_DEVICE_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="品牌" prop="brand">
+        <el-input v-model="formData.brand" placeholder="请输入品牌" />
+      </el-form-item>
+      <el-form-item label="型号" prop="model">
+        <el-input v-model="formData.model" placeholder="请输入型号" />
+      </el-form-item>
+      <el-form-item label="所属机构" prop="orgId">
+        <el-tree-select
+              v-model="formData.orgId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              placeholder="请选择归属机构"
+            />
+      </el-form-item>
+      <el-form-item label="接入协议" prop="protocol">
+        <el-select v-model="formData.protocol" placeholder="请选择接入协议">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_DEVICE_PROTOCOL)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="SRS地址" prop="ip">
+        <el-input v-model="formData.ip" placeholder="请输入SRS地址" />
+      </el-form-item>
+      <el-form-item label="SRS端口" prop="port">
+        <el-input v-model="formData.port" placeholder="请输入SRS端口" />
+      </el-form-item>
+      <el-form-item label="SRS服务域" prop="domain">
+        <el-input v-model="formData.domain" placeholder="请输入SRS服务域" />
+      </el-form-item>
+      <el-form-item label="用户名" prop="username">
+        <el-input v-model="formData.username" placeholder="请输入用户名" />
+      </el-form-item>
+      <el-form-item label="密码" prop="password">
+        <el-input type="password" v-model="formData.password" placeholder="请输入密码" show-password/>
+      </el-form-item>
+      <el-form-item label="描述" prop="des">
+        <el-input v-model="formData.des" type="textarea" placeholder="请输入描述" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
+import { ServerApi, ServerVO } from '@/api/safe/server'
+import * as DeptApi from '@/api/system/dept'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+/** 安防后端设备 表单 */
+defineOptions({ name: 'ServerForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const deptList = ref<Tree[]>([]) // 树形结构
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: undefined,
+  type: undefined,
+  brand: undefined,
+  model: undefined,
+  orgId: undefined,
+  protocol: undefined,
+  ip: undefined,
+  port: undefined,
+  domain: undefined,
+  username: undefined,
+  password: undefined,
+  des: undefined,
+})
+const formRules = reactive({
+  name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+  protocol: [{ required: true, message: '接入协议不能为空', trigger: 'change' }],
+  ip: [{ required: true, message: 'SRS地址不能为空', trigger: 'blur' }],
+  port: [{ required: true, message: 'SRS端口不能为空', trigger: 'blur' }],
+  domain: [{ required: true, message: 'SRS服务域不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ServerApi.getServer(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载部门树
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ServerVO
+    if (formType.value === 'create') {
+      await ServerApi.createServer(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServerApi.updateServer(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: undefined,
+    type: undefined,
+    brand: undefined,
+    model: undefined,
+    orgId: undefined,
+    protocol: undefined,
+    ip: undefined,
+    port: undefined,
+    domain: undefined,
+    username: undefined,
+    password: undefined,
+    des: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 254 - 0
src/views/safe/server/index.vue

@@ -0,0 +1,254 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select
+          v-model="queryParams.type"
+          placeholder="请选择类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.SAFE_DEVICE_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="品牌" prop="brand">
+        <el-input
+          v-model="queryParams.brand"
+          placeholder="请输入品牌"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="型号" prop="model">
+        <el-input
+          v-model="queryParams.model"
+          placeholder="请输入型号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="所属机构" prop="orgId">
+        <el-tree-select
+          v-model="queryParams.orgId"
+          :data="deptList"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          placeholder="请选择所属机构"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['safe:server:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['safe:server:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="名称" align="center" prop="name" />
+      <el-table-column label="类型" align="center" prop="type">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SAFE_DEVICE_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column label="品牌" align="center" prop="brand" />
+      <el-table-column label="型号" align="center" prop="model" />
+      <el-table-column label="所属机构" align="center" prop="orgId" >
+        <template #default="scope">
+          {{ orgMap.get(scope.row.orgId) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="接入协议" align="center" prop="protocol">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SAFE_DEVICE_PROTOCOL" :value="scope.row.protocol" />
+        </template>
+      </el-table-column>
+      <el-table-column label="SRS地址" align="center" prop="ip" />
+      <el-table-column label="SRS端口" align="center" prop="port" />
+      <el-table-column label="SRS服务域" align="center" prop="domain" />
+      <el-table-column label="描述" align="center" prop="des" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['safe:server:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['safe:server:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ServerForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
+import download from '@/utils/download'
+import { ServerApi, ServerVO } from '@/api/safe/server'
+import ServerForm from './ServerForm.vue'
+import * as DeptApi from '@/api/system/dept'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+/** 安防后端设备 列表 */
+defineOptions({ name: 'SafeServer' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const deptList = ref<Tree[]>([]) // 树形结构
+const orgMap= new Map()
+const loading = ref(true) // 列表的加载中
+const list = ref<ServerVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  type: undefined,
+  brand: undefined,
+  model: undefined,
+  orgId: undefined,
+  des: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // 加载部门树
+    deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+    if(deptList.value){
+      orgMap.clear//更新机构列表
+      const deptlist = await DeptApi.getSimpleDeptList();//获得若依部门原始结构
+      if(deptlist){
+        deptlist.forEach(it =>{
+          orgMap.set(it.id,it.name)//放入map中,做id和name映射,模拟多对一,否则还要在后台写,好费劲
+        })
+      }
+    }
+    const data = await ServerApi.getServerPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ServerApi.deleteServer(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await ServerApi.exportServer(queryParams)
+    download.excel(data, '安防后端设备.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>