Pārlūkot izejas kodu

feat(api): 添加场站、设备、规则管理相关接口和摄像机管理页面

yusheng 5 dienas atpakaļ
vecāks
revīzija
805ef4c785

+ 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));
+}

+ 23 - 0
src/api/basic/stationCarAccess/index.js

@@ -0,0 +1,23 @@
+
+
+import request from '@/utils/request';
+// 批量删除车辆进出
+
+export async function deleteBatchStationCarAccess(ids) {
+  const res = await request.get(`/basic/station-car-access/deleteBatch?ids=` + ids);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得车辆进出分页
+export async function getStationCarAccessPage(data) {
+  const res = await request.get('/basic/station-car-access/page', {
+    params: data
+  });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 22 - 0
src/api/basic/stationCarPresent/index.js

@@ -0,0 +1,22 @@
+import request from '@/utils/request';
+
+// 批量删除车辆在场
+
+export async function deleteBatchStationCarPresent(ids) {
+  const res = await request.get(`/basic/station-car-present/deleteBatch?ids=` + ids);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获得车辆在场分页
+export async function getStationCarPresentPage(data) {
+  const res = await request.get('/basic/station-car-present/page', {
+    params: 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));
+}

+ 131 - 0
src/api/ledgerAssets/robot.js

@@ -0,0 +1,131 @@
+import request from '@/utils/request'
+
+// 获得设备管理分页
+export async function getDevicePage(params) {
+  const res = await request.get('/main/basic/device/pageRobert', { params })
+  if (res.data.code == 0) {
+    return res.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 获得其他设备管理列表
+export async function getOtherDeviceList(params) {
+  const res = await request.get('/isp/device/listOtherDevice', { params })
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 获得其他未绑定的设备列表
+export async function getNonLoadDeviceList(params) {
+  const res = await request.get('/isp/device/listOtherNonLoadDevice', { 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('/isp/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/get?id=' + id)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 摄像头截图
+export async function catchPic(code) {
+  const res = await request.get({
+    url: '/isp/camera/catchPicBin?cameraCode=' + code,
+    responseType: 'blob',
+    returnType: true
+  })
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 摄像头开关灯
+export async function playControl(params) {
+  const res = await request.get('/isp/camera/playControl', { params })
+  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/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('/isp/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('/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) {
+    return res
+  }
+  return Promise.reject(new Error(res.message))
+}
+
+// 机器人运动控制
+export async function robotCommandControl(data) {
+  const res = await request.post('/isp/robot/Command/CommandControl', data)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 机器人运动暂停
+export async function robotStop(data) {
+  const res = await request.post('/isp/robot/Command/Stop', data)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}
+
+// 机器人状态查询
+export async function GetWorkState(data) {
+  const res = await request.post('/isp/robot/Command/GetWorkState', data)
+  if (res.data.code == 0) {
+    return res.data.data
+  }
+  return Promise.reject(new Error(res.data.message))
+}

+ 61 - 0
src/api/ruleManagement/matter.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request';
+
+// 更新或保存
+export async function saveOrUpdate (data) {
+  const res = await request.post('/main/ruleinfo/saveOrEdit', data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 生成编码
+export async function getCode (code) {
+  const res = await request.get(`/main/codemanage/getCode/` + code, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取规则列表页
+export async function getList (data) {
+	let par = new URLSearchParams(data);
+  const res = await request.get(`/main/ruleinfo/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取规则详情
+export async function getDetail (id) {
+  const res = await request.get(`/main/ruleinfo/getById/` + id, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+
+/**
+ * 删除事项
+ */
+export async function removeRule(data) {
+  const res = await request.delete('/main/ruleinfo/delete', { data } );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+
+// 获取仓库列表 
+export async function getWarehouseList (params) {
+  const res = await request.get('/wms/warehouse/page',  params );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+

+ 148 - 0
src/api/ruleManagement/plan.js

@@ -0,0 +1,148 @@
+import request from '@/utils/request';
+
+// 获取规则名称下拉
+export async function getRule(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/ruleinfo/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取类别信息列表
+export async function getCategory(data) {
+  const res = await request.post(`/main/category/getList`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取实体列表分页
+export async function getAssetList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/asset/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 巡点检计划配置新增或更新
+export async function saveOrUpdate(data) {
+  const res = await request.post('/eam/planconfig/saveOrUpdate', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取巡点检分页数据
+export async function planConfigPage(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/eam/planconfig/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取详情
+export async function getInfoById(id) {
+  const res = await request.get(`/eam/planconfig/getById/` + id, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 删除事项
+ */
+export async function removeRule(data) {
+  const res = await request.delete('/eam/planconfig/delete', { data });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// // 更新或保存
+// export async function saveOrUpdate (data) {
+//   const res = await request.post('/main/ruleinfo/saveOrEdit', data);
+//   if (res.data.code == 0) {
+//     return res.data;
+//   }
+//   return Promise.reject(new Error(res.data.message));
+// }
+
+// // 生成编码
+// export async function getCode (code) {
+//   const res = await request.get(`/main/codemanage/getCode/` + code, {});
+//   if (res.data.code == 0) {
+//     return res.data.data;
+//   }
+//   return Promise.reject(new Error(res.data.message));
+// }
+
+// // 获取规则列表页
+// export async function getList (data) {
+// 	let par = new URLSearchParams(data);
+//   const res = await request.get(`/main/ruleinfo/page?` + par, {});
+//   if (res.data.code == 0) {
+//     return res.data.data;
+//   }
+//   return Promise.reject(new Error(res.data.message));
+// }
+
+// // 获取规则详情
+// export async function getDetail (id) {
+//   const res = await request.get(`/main/ruleinfo/getById/` + id, {});
+//   if (res.data.code == 0) {
+//     return res.data.data;
+//   }
+//   return Promise.reject(new Error(res.data.message));
+// }
+
+export async function getWarehouseList() {
+  const res = await request.post(`/wms/warehouse/select/warehouseList`, {
+    params: { status: 1 }
+  });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取实体列表分页
+export async function bomSubstance(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/bomSubstance/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+export async function bindByMeter(data) {
+  const res = await request.post(`/main/bomSubstance/bindByMeter`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+export async function batchUnbindByMeter(data) {
+  const res = await request.post(`/main/bomSubstance/batchUnbindByMeter`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+export async function queryBindSubstanceList(params) {
+  const res = await request.get(`/main/asset/queryBindSubstanceList`, {params});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 40 - 0
src/components/area-select.vue

@@ -0,0 +1,40 @@
+<!-- 机构选择下拉框 -->
+<template>
+  <ele-tree-select
+    ref="tree"
+    clearable
+    :value="value || ''"
+    :data="data"
+    label-key="name"
+    value-key="id"
+    default-expand-all
+    :placeholder="placeholder"
+    @input="updateValue"
+    v-bind="$attrs"
+    v-on="$listeners"
+  />
+
+</template>
+
+<script>
+  export default {
+    props: {
+      // 选中的数据(v-model)
+      value: [Number,String],
+      // 提示信息
+      placeholder: {
+        type: String,
+        default: '请选择'
+      },
+      // 机构数据
+      data: Array
+    },
+    methods: {
+      /* 更新选中数据 */
+      updateValue(value,node) {
+        this.$emit('input', value);
+        this.$emit('checkedKeys', this.$refs.tree.getNodeByValue(value))
+      },
+    }
+  };
+</script>

+ 54 - 0
src/views/camera/components/camera-search.vue

@@ -0,0 +1,54 @@
+<!-- 搜索表单 -->
+<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',
+            labelWidth: '120px',
+
+            placeholder: '请输入'
+          },
+          {
+            label: '设备品牌:',
+            value: 'brandCode',
+            type: 'DictSelection',
+            dictName: '摄像头品牌'
+          },
+          {
+            label: '摄像机类型:',
+            value: 'classifyCode',
+            labelWidth: '120px',
+            type: 'DictSelection',
+            dictName: '摄像机类型'
+          },
+          {
+            label: '摄像机状态:',
+            labelWidth: '120px',
+            value: 'activityStatus',
+            type: 'DictSelection',
+            dictName: '设备管理状态'
+          }
+        ];
+      }
+    },
+    methods: {
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', { ...e });
+      }
+    }
+  };
+</script>

+ 214 - 0
src/views/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>

+ 532 - 0
src/views/camera/components/formModel.vue

@@ -0,0 +1,532 @@
+<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="areaId">
+            <area-select
+              v-model="form.areaId"
+              @checkedKeys="getAreaInfo"
+              :data="areaTreeList"
+              ref="areaTree"
+            />
+          </el-form-item>
+        </el-col>
+        <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"
+              @itemChange="classifyCodeChange"
+            ></DictSelection>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="15">
+        <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-col :span="8">
+          <el-form-item label="品牌" prop="brandCode">
+            <DictSelection
+              dictName="摄像头品牌"
+              v-model="form.brandCode"
+              @itemChange="brandCodeChange"
+            ></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 { basicAreaPageAPI } from '@/api/factoryModel';
+  import { getCode } from '@/api/ruleManagement/matter';
+  import areaSelect from '@/components/area-select.vue';
+  // 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],
+    components: {
+      areaSelect
+    },
+    data() {
+      return {
+        visible: false,
+        title: '',
+        loading: false,
+        saving: false,
+        type: '',
+        areaTree: [],
+        areaTreeList: [],
+        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: {
+      brandCodeChange(e) {
+        console.log('brandCodeChange', e);
+        this.form.brandName = e.dictValue;
+      },
+      classifyCodeChange(e) {
+        console.log('classifyCodeChange', e);
+        this.form.classifyName = e.dictValue;
+      },
+      /** 加载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'
+          });
+        });
+      },
+
+      /** 获取区域树列表 */
+      getBasicAreaList() {
+        basicAreaPageAPI({
+          pageNum: 1,
+          size: 9999
+        }).then((list) => {
+          this.areaTreeList = this.$util.toTreeData({
+            data: list || [],
+            idField: 'id',
+            parentIdField: 'parentId'
+          });
+        });
+      },
+
+      /** 区域选择回调 */
+      getAreaInfo(nodeInfo) {
+        if (nodeInfo) {
+          this.form.areaId = nodeInfo.id;
+          this.form.areaName = nodeInfo.name;
+          this.form.areaCode = nodeInfo.areaCode;
+        }
+      },
+
+      /** 品牌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, areaInfo) {
+        this.type = type;
+        this.title = type === 'create' ? '新增摄像机' : '编辑摄像机';
+
+        // 重置表单
+        this.form = { ...defForm };
+        // this.loadNvrDeviceData()
+
+        // 加载区域树
+        this.getBasicAreaList();
+
+        // 从列表页左侧区域树带入所选区域
+        if (areaInfo && areaInfo.id) {
+          this.form.areaId = areaInfo.id;
+          this.form.areaName = areaInfo.name;
+          this.form.areaCode = areaInfo.areaCode;
+        }
+
+        // 编辑时,加载数据
+        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/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/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/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>

+ 365 - 0
src/views/camera/index.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <ele-split-layout
+        width="266px"
+        allow-collapse
+        :right-style="{ overflow: 'hidden' }"
+      >
+        <div>
+          <div class="ele-border-lighter sys-organization-list">
+            <div class="tree-title">区域列表</div>
+            <el-tree
+              ref="areaTree"
+              :data="areaTreeList"
+              :props="defaultProps"
+              node-key="id"
+              highlight-current
+              default-expand-all
+              :expand-on-click-node="false"
+              @node-click="handleAreaClick"
+            >
+            </el-tree>
+          </div>
+        </div>
+        <template v-slot:content>
+          <!-- 搜索表单 -->
+          <camera-search ref="searchRef" @search="reload" />
+          <!-- 数据表格 -->
+          <ele-pro-table
+            :page-size="defPageSize"
+            ref="table"
+            :columns="columns"
+            :datasource="datasource"
+            :selection.sync="selection"
+            :cache-key="cacheKeyUrl"
+            :pageSizes="tablePageSizes"
+            height="calc(100vh - 320px)"
+            full-height="calc(100vh - 115px)"
+            :response="{
+              dataName: 'data.records', // 数据列表的字段名称,支持嵌套,例如:data.rows
+              countName: 'data.total' // 数据总数的字段名称,例如:data.total
+            }"
+          >
+            <!-- 表头工具栏 -->
+            <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>
+        </template>
+      </ele-split-layout>
+    </el-card>
+
+    <!-- 表单弹窗:添加/修改 -->
+    <formModel ref="formRef" @success="reload" />
+  </div>
+</template>
+
+<script>
+  import * as CameraApi from '@/api/ledgerAssets/camera';
+  import { basicAreaPageAPI } from '@/api/factoryModel';
+  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: [],
+        loading: false,
+        // 当前选中的区域
+        currentArea: null,
+        // 区域树数据
+        areaTreeList: [],
+        defaultProps: {
+          children: 'children',
+          label: 'name'
+        },
+        // 表格列配置
+        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: 180
+          },
+          {
+            prop: 'name',
+            label: '摄像机名称',
+            headerAlign: 'center',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          },
+          {
+            prop: 'areaName',
+            label: '所属区域',
+            headerAlign: 'center',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 160
+          },
+          {
+            prop: 'brandName',
+            label: '设备品牌',
+            headerAlign: 'center',
+            align: 'center',
+            showOverflowTooltip: true,
+            width: 180
+          },
+          {
+            prop: 'modelName',
+            label: '设备型号',
+            headerAlign: 'center',
+            align: 'center',
+            showOverflowTooltip: true,
+            width: 180
+          },
+          {
+            prop: 'classifyName',
+            label: '摄像机类型',
+            headerAlign: 'center',
+            align: 'center',
+            showOverflowTooltip: true,
+            width: 180
+          },
+          // {
+          //   prop: 'loadDeviceName',
+          //   label: '关联设备',
+          //   headerAlign: 'center',
+          //   align: 'center',
+          //   showOverflowTooltip: true,
+          //   width: 150
+          // },
+          {
+            prop: 'activityStatus',
+            label: '摄像机状态',
+            align: 'center',
+            showOverflowTooltip: true,
+            width: 120,
+            slot: 'activityStatus'
+          },
+          {
+            columnKey: 'action',
+            fixed: 'right',
+            label: '操作',
+            width: 160,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true
+          }
+        ]
+      };
+    },
+    created() {
+      this.getAreaTreeList();
+    },
+    methods: {
+      /* 获取区域树 */
+      getAreaTreeList() {
+        this.loading = true;
+        basicAreaPageAPI({
+          pageNum: 1,
+          size: 9999
+        })
+          .then((list) => {
+            this.areaTreeList = this.$util.toTreeData({
+              data: list || [],
+              idField: 'id',
+              parentIdField: 'parentId'
+            });
+          })
+          .finally(() => {
+            this.loading = false;
+          });
+      },
+
+      /* 点击区域节点查询 */
+      handleAreaClick(data) {
+        this.currentArea = data;
+        // 记录所选区域到搜索条件并刷新
+        this.reload({
+          areaId: data.id
+        });
+      },
+
+      /* 表格数据源 */
+      async datasource({ page, limit, where }) {
+        return await CameraApi.getDevicePage({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+
+      /* 刷新表格 */
+      reload(where) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      /** 添加/修改操作 */
+      openForm(type, id) {
+        this.$refs.formRef.open(type, id, this.currentArea);
+      },
+
+      /** 状态切换 */
+      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>
+  .sys-organization-list {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+  .tree-title {
+    padding: 10px 12px;
+    font-weight: bold;
+    border-bottom: 1px solid #ebeef5;
+  }
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 36px;
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+</style>

+ 1 - 1
vue.config.js

@@ -36,7 +36,7 @@ module.exports = {
     proxy: {
       // 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
       '/api': {
-        target: 'http://192.168.1.102:18086', //开发
+        target: 'http://192.168.1.251:18086', //开发
         // target: 'http://192.168.1.251:18186', //测试
         // target: 'http://192.168.1.23:18086',//罗
         // target: 'http://192.168.1.144:18086',//付