Jelajahi Sumber

feat:摄像头菜单

liujt 2 bulan lalu
induk
melakukan
54f02a9de6

+ 127 - 0
src/api/basic/area/index.js

@@ -0,0 +1,127 @@
+import request from '@/utils/request';
+
+// 查询场站区域分页列表
+export async function getAreaPage(params) {
+  const res = await request.get('/basic/area/page', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询isp区域列表
+export async function getIspAreaList(params) {
+  const res = await request.get('/basic/area/pageList', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询场站区域分页列表(isp)
+export async function getAreaPageList(params) {
+  const res = await request.get('/basic/area/pageList', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询场站区域列表
+export async function getAreaList(params) {
+  const res = await request.post('/main/basicarea/page', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询场站区域详情
+export async function getArea(id) {
+  const res = await request.get(`/basic/area/get?id=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 新增场站区域
+export async function createArea(data) {
+  const res = await request.post('/basic/area/create', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 修改场站区域
+export async function updateArea(data) {
+  const res = await request.put('/basic/area/update', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 删除场站区域
+export async function deleteArea(id) {
+  const res = await request.delete(`/basic/area/delete?id=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 导出场站区域 Excel
+export async function exportArea(params) {
+  const res = await request.download('/basic/area/export-excel', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取监控系统物联列表详细数据
+export async function getDeviceDataPage(data) {
+  const res = await request.post('/isp/alarm/iot/page', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取设备详细数据
+export async function getDeviceDataInfo(data) {
+  const res = await request.post('/ccs/data-query/queryDeviceInitData', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取树形结构数据
+export async function getDeviceTree(params) {
+  const res = await request.get('/ccs/data-query/queryAreaTreeData', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取编码
+export async function getCode() {
+  const res = await request.get('/basic/code/getCode?prefix=QYGL');
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取物联页统计数据
+export async function getWarningDataList(data) {
+  const res = await request.post('/isp/alarm/statistics', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 64 - 0
src/api/ledgerAssets/camera.js

@@ -0,0 +1,64 @@
+import request from '@/utils/request'
+
+// 获得设备管理分页
+export async function getDevicePage(params) {
+  const res = await request.get('/main/basic/device/pageCamera', {params})
+  if (res.data.code == 0) {
+    return res.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 获得设备管理列表
+export async function getDeviceList(params) {
+  const res = await request.get('/main/basic/device/list', {params})
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 获得设备管理
+export async function getDevice(id) {
+  const res = await request.get('/main/basic/device/get?id=' + id)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 创建设备管理
+export async function createDevice(data) {
+  const res = await request.post('/main/basic/device/create', data)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 更新设备管理
+export async function updateDevice(data) {
+  const res = await request.put('/main/basic/device/update', data)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 删除设备管理
+export async function deletedDevice(id) {
+  const res = await request.delete('/main/basic/device/delete?ids=' + id)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 导出设备管理 Excel
+export async function exportDevice(params) {
+  const res = await request.get('/isp/device/export-excel', { params })
+  if (res) {
+    return res
+  }
+  return Promise.reject(new Error(res.message))
+}

+ 100 - 0
src/api/ledgerAssets/device/index.js

@@ -0,0 +1,100 @@
+import request from '@/utils/request';
+
+// 获得设备管理分页
+export async function getDevicePage(params) {
+  const res = await request.get('isp/device/page', {params});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得设备管理列表
+export async function getDeviceList(params) {
+  const res = await request.get('main/basic/device/list', {params});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得设备管理
+export async function getDevice(id) {
+  const res = await request.get(`isp/device/getDevice?id=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 创建设备管理
+export async function createDevice(data) {
+  const res = await request.post('isp/device/createLoadDevice', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 更新设备管理
+export async function updateDevice(data) {
+  const res = await request.put('isp/device/updateLoadDevice', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 删除设备管理
+export async function deleteDevice(id) {
+  const res = await request.delete(`isp/device/delete?ids=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 导出设备管理 Excel
+export async function exportDevice(params) {
+  const res = await request.download('isp/device/export-excel', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 根据code查询摄像头信息
+export async function getByCode(params) {
+  const res = await request.get('isp/camera-info/getByCode', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 创建摄像头信息
+export async function createInfo(data) {
+  const res = await request.post('isp/camera-info/create', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 更新摄像头信息
+export async function updateInfo(data) {
+  const res = await request.put('isp/camera-info/update', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 删除摄像头流代理
+export async function delStreamProxy(data) {
+  const res = await request.get(`isp/camera/delStreamProxy?cameraCode=${data}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 73 - 0
src/api/ledgerAssets/device/presetInfo/index.js

@@ -0,0 +1,73 @@
+import request from '@/utils/request';
+
+// 获得设备预置位分页
+export async function getDevicePointPage(params) {
+  const res = await request.get('isp/device-preset-point/page', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得设备预置位列表
+export async function getDevicePointList(params) {
+  const res = await request.get('isp/device-preset-point/list', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得设备预置位
+export async function getDevicePoint(id) {
+  const res = await request.get(`isp/device-preset-point/get?id=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 创建设备预置位
+export async function createDevicePoint(data) {
+  const res = await request.post('isp/device-preset-point/create', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 更新设备预置位
+export async function updateDevicePoint(data) {
+  const res = await request.put('isp/device-preset-point/update', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 删除设备预置位
+export async function deleteDevicePoint(id) {
+  const res = await request.delete(`isp/device-preset-point/delete?id=${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 批量删除设备预置位
+export async function batchDeleteDevicePoint(data) {
+  const res = await request.delete('isp/device-preset-point/batchDelete', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 导出设备预置位 Excel
+export async function exportDevicePoint(params) {
+  const res = await request.download('isp/device-preset-point/export-excel', params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 5 - 1
src/enum/dict.js

@@ -43,7 +43,11 @@ export default {
   工作流任务分配自定义脚本: 'bpm_task_assign_script',
   流程实例的结果: 'bpm_process_instance_result',
   表计类型: 'meter_type',
-  定时任务状态: 'infra_job_status'
+  定时任务状态: 'infra_job_status',
+  设备管理状态:'activity_status',
+  摄像机类型: 'isp_camera_type',
+  摄像头品牌: 'camera_brand',
+  NVR设备类型:'device_type_nvr'
 
 };
 

+ 1 - 0
src/utils/request.js

@@ -57,6 +57,7 @@ service.interceptors.response.use(
     return res;
   },
   (error) => {
+    console.log('error~~~', error)
     // 登录过期处理
     if (error?.response?.status === 401) {
       const currentPath = router.currentRoute.path;

+ 50 - 0
src/views/ledgerAssets/camera/components/camera-search.vue

@@ -0,0 +1,50 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="4" @search="search"></seekPage>
+</template>
+
+<script>
+export default {
+  name: 'CameraSearch',
+  components: {},
+  data() {
+    return {}
+  },
+  computed: {
+    seekList() {
+      return [
+        {
+          label: '摄像机名称:',
+          value: 'name',
+          type: 'input',
+          placeholder: '请输入'
+        },
+        {
+          label: '设备品牌:',
+          value: 'brandCode',
+          type: 'DictSelection',
+          dictName: '摄像头品牌'
+        },
+        {
+          label: '摄像机类型:',
+          value: 'classifyCode',
+          type: 'DictSelection',
+          dictName: '摄像机类型'
+        },
+        {
+          label: '摄像机状态:',
+          value: 'activityStatus',
+          type: 'DictSelection',
+          dictName: '设备管理状态'
+        }
+      ]
+    }
+  },
+  methods: {
+    /* 搜索 */
+    search(e) {
+      this.$emit('search', { ...e })
+    }
+  }
+}
+</script>

+ 214 - 0
src/views/ledgerAssets/camera/components/detail.vue

@@ -0,0 +1,214 @@
+<template>
+  <ele-modal
+    :visible.sync="visible"
+    title="摄像机详情"
+    width="1100px"
+    append-to-body
+    @close="cancel"
+  >
+    <el-tabs type="border-card" @tab-change="handleTabChange">
+      <el-tab-pane label="基础信息">
+        <el-form ref="form" :model="form" disabled label-width="120px" v-loading="loading">
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="摄像机编号">
+                <span>{{ form.deviceCode }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="摄像机类型">
+                <span>{{ form.classifyName }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="摄像机名称">
+                <span>{{ form.name }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="品牌">
+                <span>{{ form.brandName }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="型号">
+                <span>{{ form.modelName }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="排序号">
+                <span>{{ form.deviceSort }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="状态">
+                <span :style="{ color: form.activityStatus == 1 ? '#0F83FF' : 'red' }">
+                  {{ form.activityStatus == 1 ? '启用' : '停用' }}
+                </span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <div class="tit">连接配置</div>
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="IP">
+                <span>{{ form.extraInfo?.ip }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="端口">
+                <span>{{ form.extraInfo?.port }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="账号">
+                <span>{{ form.extraInfo?.account }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="密码">
+                <span>{{ form.extraInfo?.password }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="16">
+              <el-form-item label="RTSP流">
+                <span>{{ form.extraInfo?.stream }}</span>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="支持云台控制">
+                <span :style="{ color: form.extraInfo?.ptzControl == 1 ? '#0F83FF' : 'red' }">
+                  {{ form.extraInfo?.ptzControl == 1 ? '是' : '否' }}
+                </span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <div class="tit">关联配置</div>
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="关联NVR设备">
+                <span>{{ form.byNvrName }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </el-tab-pane>
+      <el-tab-pane label="预置位信息">
+        <presetInfo ref="presetInfoRef" :deviceId="form.id" />
+      </el-tab-pane>
+    </el-tabs>
+
+    <template #footer>
+      <el-button @click="cancel">关 闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script>
+import presetInfo from './presetInfo/index.vue'
+import * as CameraApi from '@/api/ledgerAssets/camera'
+
+export default {
+  name: 'CameraDetail',
+  components: {
+    presetInfo
+  },
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      form: {
+        name: undefined,
+        deviceCode: undefined,
+        classifyName: undefined,
+        brandName: undefined,
+        modelName: undefined,
+        deviceSort: undefined,
+        activityStatus: undefined,
+        byNvrName: undefined,
+        extraInfo: {
+          ip: undefined,
+          port: undefined,
+          account: undefined,
+          password: undefined,
+          ptzControl: 0,
+          stream: undefined
+        }
+      }
+    }
+  },
+  methods: {
+    /** 打开弹窗 */
+    open(id) {
+      this.visible = true
+      this.loading = true
+
+      CameraApi.getDevice(id)
+        .then((res) => {
+          this.form = res
+        })
+        .catch((e) => {
+          this.$message.error(e.message)
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+
+    /** 取消 */
+    cancel() {
+      this.resetForm()
+      this.visible = false
+    },
+
+    /** Tab切换 */
+    handleTabChange(index) {
+      if (index == 1) {
+        this.$refs.presetInfoRef?.preSetSuccess()
+      }
+    },
+
+    /** 重置表单 */
+    resetForm() {
+      this.form = {
+        name: undefined,
+        deviceCode: undefined,
+        classifyName: undefined,
+        brandName: undefined,
+        modelName: undefined,
+        deviceSort: undefined,
+        activityStatus: undefined,
+        byNvrName: undefined,
+        extraInfo: {
+          ip: undefined,
+          port: undefined,
+          account: undefined,
+          password: undefined,
+          ptzControl: 0,
+          stream: undefined
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tit {
+  margin-left: 10px;
+  font-weight: bold;
+  border-left: 3px solid #9266f9;
+  padding-left: 10px;
+  margin-bottom: 14px;
+}
+</style>

+ 437 - 0
src/views/ledgerAssets/camera/components/formModel.vue

@@ -0,0 +1,437 @@
+<template>
+  <ele-modal
+    :visible.sync="visible"
+    :title="title"
+    width="1100px"
+    append-to-body
+    @close="cancel"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="120px"
+      class="create-form"
+      v-loading="loading"
+    >
+      <!-- <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="所属场站" prop="stationName">
+            <el-input
+              v-model="form.stationName"
+              placeholder="请输入"
+              :maxlength="20"
+              class="!w-100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="所属区域" prop="areaId">
+            <ele-tree-select
+              clearable
+              :data="areaTree"
+              v-model="form.areaId"
+              valueKey="id"
+              labelKey="name"
+              placeholder="请选择"
+              @change="areaIdChange"
+              default-expand-all
+            />
+          </el-form-item>
+        </el-col>
+      </el-row> -->
+
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="摄像机编号" prop="deviceCode">
+            <el-input
+              v-model="form.deviceCode"
+              placeholder="请输入"
+              :maxlength="50"
+              class="!w-100%"
+              disabled
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="摄像机类型" prop="classifyCode">
+            <DictSelection
+              dictName="摄像机类型"
+              v-model="form.classifyCode"
+            ></DictSelection>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="摄像机名称" prop="name">
+            <el-input v-model="form.name" placeholder="请输入" :maxlength="20" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="品牌" prop="brandCode">
+            <DictSelection
+              dictName="摄像头品牌"
+              v-model="form.brandCode"
+            ></DictSelection>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="型号" prop="modelName">
+            <el-input
+              v-model="form.modelName"
+              placeholder="请输入"
+              :maxlength="20"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="排序号" prop="deviceSort">
+            <el-input-number
+              v-model="form.deviceSort"
+              :min="1"
+              :max="99999"
+              class="!w-100%"
+              controls-position="right"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="状态" prop="activityStatus">
+            <el-radio-group v-model="form.activityStatus" class="ml-4" style="flex-wrap: nowrap">
+              <el-radio :label="1">启用</el-radio>
+              <el-radio :label="0">停用</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <div class="tit">连接配置</div>
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="IP" prop="extraInfo.ip">
+            <el-input v-model="form.extraInfo.ip" placeholder="请输入" :maxlength="20" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="端口" prop="extraInfo.port">
+            <el-input-number
+              v-model="form.extraInfo.port"
+              :min="1"
+              :max="65535"
+              :maxlength="5"
+              controls-position="right"
+              placeholder="请输入"
+              class="!w-100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="账号" prop="extraInfo.account">
+            <el-input v-model="form.extraInfo.account" placeholder="请输入" :maxlength="30" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="密码" prop="extraInfo.password">
+            <el-input v-model="form.extraInfo.password" placeholder="请输入" :maxlength="30" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="通道号" prop="extraInfo.channel">
+            <el-input v-model="form.extraInfo.channel" placeholder="请输入" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="RTSP流" prop="extraInfo.stream">
+            <el-input v-model="form.extraInfo.stream" placeholder="请输入" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="支持云台控制" prop="extraInfo.ptzControl">
+            <el-radio-group v-model="form.extraInfo.ptzControl" class="ml-4" style="flex-wrap: nowrap">
+              <el-radio :label="0">支持</el-radio>
+              <el-radio :label="1">不支持</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- <div class="tit">关联配置</div>
+      <el-row :gutter="15">
+        <el-col :span="8">
+          <el-form-item label="关联NVR设备" prop="byNvrCode">
+            <el-select
+              @change="handleNvr"
+              placeholder="请选择"
+              v-model="form.byNvrCode"
+              clearable
+              class="!w-100%"
+            >
+              <el-option
+                v-for="(item, index) in loadNvrDeviceList"
+                :key="index"
+                :label="item.name"
+                :value="item.deviceCode"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row> -->
+    </el-form>
+
+    <template #footer>
+      <el-button @click="cancel">取消</el-button>
+      <el-button type="primary" @click="save" :loading="saving">
+        确定
+      </el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script>
+import dictMixins from '@/mixins/dictMixins';
+import * as CameraApi from '@/api/ledgerAssets/camera'
+import * as DeviceApi from '@/api/ledgerAssets/device'
+import * as AreaApi from '@/api/basic/area'
+import { getCode } from '@/api/ruleManagement/matter';
+// import { handleTree } from '@/utils/tree'
+// import { getStationData } from '@/utils/auth'
+
+const defForm = {
+  name: '',
+  deviceCode: '',
+  classifyCode: '',
+  classifyName: '',
+  modelId: '',
+  modelName: '',
+  brandCode: '',
+  brandName: '',
+  // stationName: '',
+  // stationId: '',
+  // areaId: '',
+  // areaCode: '',
+  // areaName: '',
+  // byNvrCode: '',
+  // byNvrName: '',
+  deviceSort: 100,
+  activityStatus: 1,
+  relDeviceType: '',
+  relDeviceId: '',
+  delayTime: '',
+  listLoad: [],
+  extInfo: '{}',
+  extraInfo: {
+    ip: '',
+    port: 8000,
+    account: 'admin',
+    password: 'admin',
+    ptzControl: 0,
+    stream: '',
+    channel: '',
+    // nvr: ''
+  }
+}
+
+export default {
+  name: 'CameraForm',
+  mixins: [dictMixins],
+  data() {
+    return {
+      visible: false,
+      title: '',
+      loading: false,
+      saving: false,
+      type: '',
+      areaTree: [],
+      loadNvrDeviceList: [],
+      loadDeviceList: [],
+      defaultProps: {
+        children: 'children',
+        label: 'name'
+      },
+      form: { ...defForm },
+      rules: {
+        name: [{ required: true, message: '摄像机名称不能为空', trigger: 'blur' }],
+        deviceCode: [{ required: true, message: '摄像机编号不能为空', trigger: 'blur' }],
+        brandCode: [{ required: true, message: '品牌不能为空', trigger: 'change' }],
+        classifyCode: [{ required: true, message: '摄像机类型不能为空', trigger: 'change' }],
+        deviceSort: [{ required: true, message: '排序号不能为空', trigger: 'change' }],
+        stationName: [{ required: true, message: '所属场站不能为空', trigger: 'change' }],
+        areaId: [{ required: true, message: '所属区域不能为空', trigger: 'change' }],
+        extraInfo: {
+          ip: [{ required: true, message: 'IP不能为空', trigger: 'blur' }],
+          port: [{ required: true, message: '端口不能为空', trigger: 'blur' }],
+          account: [{ required: true, message: '账号不能为空', trigger: 'blur' }],
+          password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
+          channel: [{ required: true, message: '通道号不能为空', trigger: 'blur' }],
+          stream: [{ required: true, message: 'RTSP流不能为空', trigger: 'blur' }]
+        }
+      }
+    }
+  },
+  created() {
+    this.requestDict('摄像头品牌');
+    // this.requestDict('NVR设备类型');
+    this.requestDict('摄像机类型');
+  },
+  methods: {
+    /** 加载NVR设备列表 */
+    loadNvrDeviceData() {
+      DeviceApi.getDeviceList({ classifyCode: '' }).then((res) => {
+        this.loadNvrDeviceList = res
+      })
+    },
+
+    /** 获得区域树 */
+    getAreaTree(stationId) {
+      AreaApi.getAreaList({ stationId: stationId }).then((res) => {
+        // this.areaTree = handleTree(res)
+        console.log('getAreaTree', res)
+        this.areaTree = this.$util.toTreeData({
+              data: res.list,
+              idField: 'id',
+              parentIdField: 'parentId'
+            });
+      })
+    },
+
+    /** 品牌change事件 */
+    brandChange(e) {
+      this.form.brandName = this.getDictValue('摄像头品牌', e+'')
+    },
+
+    /** 设备分类Change事件 */
+    handleClassifyId(e) {
+      this.form.classifyName = this.getDictValue('摄像机类型', e)
+    },
+
+    /** 关联NVR设备Change事件 */
+    handleNvr(val) {
+      const deviceObj = this.loadNvrDeviceList.find((item) => item.deviceCode == val)
+      if (deviceObj) {
+        this.form.byNvrName = deviceObj.name
+      } else {
+        this.form.byNvrName = ''
+      }
+    },
+
+    /** 区域Change事件 */
+    areaIdChange(e) {
+      this.processingAreaId(e, this.areaTree)
+    },
+
+    processingAreaId(id, data) {
+      for (let i = 0; i < data.length; i++) {
+        if (data[i].id == id) {
+          this.form.areaCode = data[i].areaCode
+          this.form.areaName = data[i].name
+          return
+        } else {
+          if (data[i].children && data[i].children.length) {
+            this.processingAreaId(id, data[i].children)
+          }
+        }
+      }
+    },
+
+    /** 打开弹窗 */
+    open(type, id, station) {
+      this.type = type
+      this.title = type === 'create' ? '新增摄像机' : '编辑摄像机'
+
+      // 重置表单
+      this.form = { ...defForm }
+      // this.loadNvrDeviceData()
+
+      // 设置场站信息
+      // const stationData = getStationData()
+      // this.getAreaTree(this.form.stationId)
+      if (station) {
+        this.form.stationName = station.stationName
+        this.form.stationId = station.stationId || station.id
+        this.getAreaTree(this.form.stationId)
+        if (station.stationId) {
+          this.form.areaCode = station.areaCode
+          this.form.areaName = station.areaName
+          this.form.areaId = station.id
+        }
+      }
+      //  else if (stationData) {
+      //   this.form.stationName = stationData.stationName
+      //   this.form.stationId = stationData.stationId || stationData.id
+      // }
+
+      // 编辑时,加载数据
+      if (id) {
+        this.loading = true
+        CameraApi.getDevice(id)
+          .then((res) => {
+            this.form = res
+          })
+          .finally(() => {
+            this.loading = false
+          })
+      } else {
+        // 新增时生成编号
+        getCode('camera_code').then((code) => {
+          this.form.deviceCode = code
+        })
+      }
+
+      this.visible = true
+    },
+
+    /** 取消 */
+    cancel() {
+      this.form = { ...defForm }
+      this.visible = false
+    },
+
+    /** 保存 */
+    save() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          this.saving = true
+          const data = { ...this.form }
+          const action =
+            this.type === 'create' ? CameraApi.createDevice(data) : CameraApi.updateDevice(data)
+
+          action
+            .then(() => {
+              this.$message.success(this.type === 'create' ? '新增成功' : '修改成功')
+              this.cancel()
+              this.$emit('success')
+            })
+            .catch((e) => {
+              this.$message.error(e.message)
+            })
+            .finally(() => {
+              this.saving = false
+            })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tit {
+  margin-left: 10px;
+  font-weight: bold;
+  border-left: 3px solid #9266f9;
+  padding-left: 10px;
+  margin-bottom: 14px;
+}
+</style>

+ 173 - 0
src/views/ledgerAssets/camera/components/presetInfo/detail.vue

@@ -0,0 +1,173 @@
+<template>
+  <ele-modal
+    :visible.sync="visible"
+    title="预置位详情"
+    width="50%"
+    append-to-body
+    @close="cancel"
+  >
+    <el-form ref="form" :model="form" label-width="120px" v-loading="loading" disabled>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="预置位名称">
+            <span>{{ form.pointName }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="聚焦调整">
+            <el-progress :percentage="focal">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item>
+            <span>{{ form.focal }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="光圈调整">
+            <el-progress :percentage="perture">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item>
+            <span>{{ form.perture }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="上下方向">
+            <el-progress :percentage="upDown">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item>
+            <span>{{ form.upDown }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="左右方向">
+            <el-progress :percentage="leftRight">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item>
+            <span>{{ form.leftRight }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="排序号">
+            <span>{{ form.indexNo }}</span>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="cancel">关闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script>
+import * as DevicePointApi from '@/api/ledgerAssets/device/presetInfo'
+
+const defForm = {
+  pointName: '',
+  focal: 0,
+  perture: 0,
+  upDown: 0,
+  leftRight: 0,
+  indexNo: 0
+}
+
+export default {
+  name: 'PresetInfoDetail',
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      focal: 0,
+      perture: 0,
+      upDown: 0,
+      leftRight: 0,
+      form: { ...defForm }
+    }
+  },
+  methods: {
+    /** 打开弹窗 */
+    open(id) {
+      this.visible = true
+      this.loading = true
+
+      DevicePointApi.getDevicePoint(id)
+        .then((res) => {
+          const pointJson = JSON.parse(res.pointJson || '{}')
+          this.form = {
+            ...res,
+            focal: pointJson.focal || 0,
+            perture: pointJson.perture || 0,
+            upDown: pointJson.upDown || 0,
+            leftRight: pointJson.leftRight || 0
+          }
+          this.focal = Math.round((pointJson.focal || 0) / 35)
+          this.perture = Math.round((pointJson.perture || 0) / 35)
+          this.upDown = Math.round((pointJson.upDown || 0) / 35)
+          this.leftRight = Math.round((pointJson.leftRight || 0) / 35)
+        })
+        .catch((e) => {
+          this.$message.error(e.message)
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+
+    /** 取消 */
+    cancel() {
+      this.resetForm()
+      this.visible = false
+    },
+
+    /** 重置表单 */
+    resetForm() {
+      this.form = { ...defForm }
+      this.focal = 0
+      this.perture = 0
+      this.upDown = 0
+      this.leftRight = 0
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-col-4 {
+  .el-form-item {
+    :deep(.el-form-item__content) {
+      margin-left: 20px !important;
+    }
+  }
+}
+</style>

+ 274 - 0
src/views/ledgerAssets/camera/components/presetInfo/formModel.vue

@@ -0,0 +1,274 @@
+<template>
+  <ele-modal
+    :visible.sync="visible"
+    :title="title"
+    width="50%"
+    append-to-body
+    @close="cancel"
+  >
+    <el-form ref="form" :model="form" :rules="rules" label-width="120px" v-loading="loading">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="预置位名称" prop="pointName">
+            <el-input v-model="form.pointName" placeholder="请输入" :maxlength="20" class="!w-100%" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="聚焦调整">
+            <el-progress :percentage="focal">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item prop="focal">
+            <el-input
+              v-model="form.focal"
+              @input="progressUpdate($event, 'focal')"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="光圈调整">
+            <el-progress :percentage="perture">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item prop="perture">
+            <el-input
+              v-model="form.perture"
+              @input="progressUpdate($event, 'perture')"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="上下方向">
+            <el-progress :percentage="upDown">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item prop="upDown">
+            <el-input
+              v-model="form.upDown"
+              @input="progressUpdate($event, 'upDown')"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="16">
+          <el-form-item label="左右方向">
+            <el-progress :percentage="leftRight">
+              <el-button text>3500</el-button>
+            </el-progress>
+          </el-form-item>
+        </el-col>
+        <el-col :span="4">
+          <el-form-item prop="leftRight">
+            <el-input
+              v-model="form.leftRight"
+              @input="progressUpdate($event, 'leftRight')"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="排序号" prop="indexNo">
+            <el-input-number
+              v-model="form.indexNo"
+              class="!w-100%"
+              placeholder="请输入"
+              :min="1"
+              :max="99999"
+              controls-position="right"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="cancel">取消</el-button>
+      <el-button type="primary" @click="save" :loading="saving">确定</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script>
+import * as DevicePointApi from '@/api/ledgerAssets/device/presetInfo'
+
+const defForm = {
+  pointName: '',
+  pointJson: '',
+  focal: 2000,
+  perture: 2000,
+  upDown: 2000,
+  leftRight: 2000,
+  indexNo: 100
+}
+
+export default {
+  name: 'PresetInfoForm',
+  props: {
+    dId: ''
+  },
+  data() {
+    return {
+      visible: false,
+      title: '',
+      loading: false,
+      saving: false,
+      type: '',
+      focal: 57,
+      perture: 57,
+      upDown: 57,
+      leftRight: 57,
+      form: { ...defForm },
+      rules: {
+        pointName: [{ required: true, message: '预置位名称不能为空', trigger: 'blur' }],
+        indexNo: [{ required: true, message: '排序号不能为空', trigger: 'change' }]
+      }
+    }
+  },
+  methods: {
+    /** 进度更新 */
+    progressUpdate(value, type) {
+      let num = parseInt(value) || 0
+      if (num > 3500) {
+        num = 3500
+        this.form[type] = num
+      }
+      this[type] = Math.round(num / 35)
+    },
+
+    /** 打开弹窗 */
+    open(type, id) {
+      this.type = type
+      this.title = type === 'create' ? '新增预置位' : '编辑预置位'
+      this.resetForm()
+
+      if (id) {
+        this.loading = true
+        DevicePointApi.getDevicePoint(id)
+          .then((res) => {
+            const pointJson = JSON.parse(res.pointJson || '{}')
+            this.form = {
+              ...res,
+              focal: pointJson.focal || 0,
+              perture: pointJson.perture || 0,
+              upDown: pointJson.upDown || 0,
+              leftRight: pointJson.leftRight || 0
+            }
+            this.progressUpdate(this.form.focal, 'focal')
+            this.progressUpdate(this.form.perture, 'perture')
+            this.progressUpdate(this.form.upDown, 'upDown')
+            this.progressUpdate(this.form.leftRight, 'leftRight')
+          })
+          .catch((e) => {
+            this.$message.error(e.message)
+          })
+          .finally(() => {
+            this.loading = false
+          })
+      }
+
+      this.visible = true
+    },
+
+    /** 取消 */
+    cancel() {
+      this.resetForm()
+      this.visible = false
+    },
+
+    /** 重置表单 */
+    resetForm() {
+      this.form = { ...defForm }
+      this.focal = 57
+      this.perture = 57
+      this.upDown = 57
+      this.leftRight = 57
+      this.$refs.form?.resetFields()
+    },
+
+    /** 保存 */
+    save() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          this.saving = true
+          const data = {
+            ...this.form,
+            deviceId: this.dId,
+            pointJson: JSON.stringify({
+              focal: this.form.focal,
+              perture: this.form.perture,
+              upDown: this.form.upDown,
+              leftRight: this.form.leftRight
+            })
+          }
+          delete data.focal
+          delete data.perture
+          delete data.upDown
+          delete data.leftRight
+
+          const action =
+            this.type === 'create'
+              ? DevicePointApi.createDevicePoint(data)
+              : DevicePointApi.updateDevicePoint(data)
+
+          action
+            .then(() => {
+              this.$message.success(this.type === 'create' ? '新增成功' : '修改成功')
+              this.cancel()
+              this.$emit('success')
+            })
+            .catch((e) => {
+              this.$message.error(e.message)
+            })
+            .finally(() => {
+              this.saving = false
+            })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-col-4 {
+  .el-form-item {
+    :deep(.el-form-item__content) {
+      margin-left: 20px !important;
+    }
+  }
+}
+
+.tit {
+  margin-left: 10px;
+  font-weight: bold;
+  border-left: 3px solid #9266f9;
+  padding-left: 10px;
+  margin-bottom: 14px;
+}
+</style>

+ 170 - 0
src/views/ledgerAssets/camera/components/presetInfo/index.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never">
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        :selection.sync="selection"
+      >
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            type="danger"
+            icon="el-icon-delete"
+            class="ele-btn-icon"
+            v-if="selection.length"
+            @click="handleBatchDelete"
+          >
+            批量删除
+          </el-button>
+        </template>
+
+        <!-- 操作列 -->
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-delete"
+            :style="{ color: delColor }"
+            @click="handleDelete(row.pointId)"
+          >
+            删除
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <!-- 新增编辑弹窗 -->
+    <formModel ref="formRef" :dId="deviceId" @success="reload" />
+  </div>
+</template>
+
+<script>
+import * as DevicePointApi from '@/api/ledgerAssets/device/presetInfo'
+import formModel from './formModel.vue'
+
+export default {
+  name: 'CameraPresetInfo',
+  components: {
+    formModel
+  },
+  props: {
+    deviceId: ''
+  },
+  data() {
+    return {
+      delColor: '#F56C6C',
+      selection: [],
+      // 表格列配置
+      columns: [
+        {
+          width: 45,
+          type: 'selection',
+          columnKey: 'selection',
+          align: 'center',
+          fixed: 'left'
+        },
+        {
+          columnKey: 'index',
+          label: '序号',
+          type: 'index',
+          width: 60,
+          align: 'center'
+        },
+        {
+          prop: 'pointName',
+          label: '预置位名称',
+          align: 'left',
+          minWidth: 200,
+          showOverflowTooltip: true
+        },
+        {
+          prop: 'indexNo',
+          label: '排序号',
+          align: 'center',
+          width: 100
+        },
+        {
+          columnKey: 'action',
+          fixed: 'right',
+          label: '操作',
+          width: 80,
+          align: 'center',
+          slot: 'action'
+        }
+      ]
+    }
+  },
+  methods: {
+    /* 表格数据源 */
+    datasource({ page, limit }) {
+      return DevicePointApi.getDevicePointPage({
+        pageNo: page,
+        pageSize: limit,
+        deviceId: this.deviceId
+      }).then((res) => {
+        return {
+          list: res.list.map((item) => ({
+            ...item,
+            pointJson: JSON.parse(item.pointJson || '{}')
+          })),
+          total: res.total
+        }
+      })
+    },
+
+    /* 刷新表格 */
+    reload() {
+      this.$refs.table.reload()
+    },
+
+    /** 预置位初始化 */
+    preSetSuccess() {
+      this.reload()
+    },
+
+    /** 打开表单 */
+    openForm(type, id) {
+      this.$refs.formRef.open(type, id)
+    },
+
+    /** 删除按钮操作 */
+    handleDelete(id) {
+      this.$confirm('是否确认删除该预置位?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          await DevicePointApi.deletedDevicePoint(id)
+          this.$message.success('删除成功')
+          this.reload()
+        })
+        .catch(() => {})
+    },
+
+    /** 批量删除按钮操作 */
+    handleBatchDelete() {
+      if (this.selection.length === 0) {
+        this.$message.warning('请选择要删除的数据项')
+        return
+      }
+      const ids = this.selection.map((item) => item.pointId)
+      this.$confirm('是否确认删除选中的预置位?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          await DevicePointApi.batchDeleteDevicePoint(ids)
+          this.$message.success('删除成功')
+          this.reload()
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 263 - 0
src/views/ledgerAssets/camera/index.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never">
+      <!-- 搜索表单 -->
+      <camera-search ref="searchRef" @search="reload" />
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        :selection.sync="selection"
+        :cache-key="cacheKeyUrl"
+      >
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            v-if="$hasPermission('ledgerAssets:camera:create')"
+            @click="openForm('create')"
+          >
+            新建
+          </el-button>
+          <el-button
+            type="danger"
+            icon="el-icon-delete"
+            class="ele-btn-icon"
+            v-if="$hasPermission('ledgerAssets:camera:delete') && selection.length"
+            @click="handleBatchDelete"
+          >
+            批量删除
+          </el-button>
+        </template>
+
+        <!-- 摄像机状态列 -->
+        <template v-slot:activityStatus="{ row }">
+          <el-switch
+            v-model="row.activityStatus"
+            :active-value="1"
+            :inactive-value="0"
+            @change="handleStatusChange(row)"
+          />
+        </template>
+
+        <!-- 操作列 -->
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            v-if="$hasPermission('ledgerAssets:camera:update')"
+            @click="openForm('update', row.id)"
+          >
+            编辑
+          </el-link>
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-delete"
+            :style="{ color: delColor }"
+            v-if="$hasPermission('ledgerAssets:camera:delete')"
+            @click="handleDelete(row.id)"
+          >
+            删除
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <!-- 表单弹窗:添加/修改 -->
+    <formModel ref="formRef" @success="reload" />
+  </div>
+</template>
+
+<script>
+import * as CameraApi from '@/api/ledgerAssets/camera'
+import formModel from './components/formModel.vue'
+import cameraSearch from './components/camera-search.vue'
+
+export default {
+  name: 'LedgerCamera',
+  components: {
+    formModel,
+    cameraSearch
+  },
+  data() {
+    return {
+      cacheKeyUrl: 'ledgerCameraTable',
+      delColor: '#F56C6C',
+      selection: [],
+      // 表格列配置
+      columns: [
+        {
+          width: 45,
+          type: 'selection',
+          columnKey: 'selection',
+          align: 'center',
+          fixed: 'left'
+        },
+        {
+          columnKey: 'index',
+          label: '序号',
+          type: 'index',
+          width: 55,
+          align: 'center',
+          showOverflowTooltip: true,
+          fixed: 'left'
+        },
+        {
+          prop: 'deviceCode',
+          label: '摄像机编号',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 150
+        },
+        {
+          prop: 'name',
+          label: '摄像机名称',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          minWidth: 200
+        },
+        {
+          prop: 'brandName',
+          label: '设备品牌',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 150
+        },
+        {
+          prop: 'modelName',
+          label: '设备型号',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 150
+        },
+        {
+          prop: 'classifyName',
+          label: '摄像机类型',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 150
+        },
+        {
+          prop: 'loadDeviceName',
+          label: '关联设备',
+          headerAlign: 'center',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 150
+        },
+        {
+          prop: 'activityStatus',
+          label: '摄像机状态',
+          align: 'center',
+          showOverflowTooltip: true,
+          width: 100,
+          slot: 'activityStatus'
+        },
+        {
+          columnKey: 'action',
+          fixed: 'right',
+          label: '操作',
+          width: 150,
+          align: 'center',
+          resizable: false,
+          slot: 'action',
+          showOverflowTooltip: true
+        }
+      ],
+      loading: false
+    }
+  },
+  methods: {
+    /* 表格数据源 */
+    async datasource({ page, limit, where }) {
+      const res = await CameraApi.getDevicePage({
+        pageNo: page,
+        pageSize: limit,
+        ...where
+      })
+
+      console.log('获取摄像机分页数据', res)
+
+      return res.data.records
+    },
+
+    /* 刷新表格 */
+    reload(where) {
+      this.$refs.table.reload({ page: 1, where })
+    },
+
+    /** 添加/修改操作 */
+    openForm(type, id) {
+      this.$refs.formRef.open(type, id)
+    },
+
+    /** 状态切换 */
+    handleStatusChange(row) {
+      const text = row.activityStatus === 1 ? '启用' : '停用'
+      this.$confirm('确认要' + text + '摄像机"' + row.name + '"吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          row.extInfo = row.extInfo || '{}'
+          await CameraApi.updateDevice(row)
+          this.$message.success(text + '成功')
+          this.reload()
+        })
+        .catch(() => {
+          row.activityStatus = row.activityStatus === 1 ? 0 : 1
+        })
+    },
+
+    /** 删除按钮操作 */
+    handleDelete(id) {
+      this.$confirm('是否确认删除该数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          await CameraApi.deletedDevice(id)
+          this.$message.success('删除成功')
+          this.reload()
+        })
+        .catch(() => {})
+    },
+
+    /** 批量删除按钮操作 */
+    handleBatchDelete() {
+      if (this.selection.length === 0) {
+        this.$message.warning('请选择要删除的数据项')
+        return
+      }
+      this.$confirm('是否确认删除选中的数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(async () => {
+          const ids = this.selection.map((item) => item.id)
+          for (const id of ids) {
+            await CameraApi.deletedDevice(id)
+          }
+          this.$message.success('删除成功')
+          this.reload()
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>