diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/src/views/monitor/deviceManage/listDevice.vue b/src/views/monitor/deviceManage/listDevice.vue index 8848009..1ec112e 100644 --- a/src/views/monitor/deviceManage/listDevice.vue +++ b/src/views/monitor/deviceManage/listDevice.vue @@ -47,6 +47,7 @@ // { text: 'NVR端口', value: 'nvrPort', align: 'center' }, { text: '所属单位', value: 'deptName', align: 'center' }, { text: '所属区域', value: 'areaName', align: 'center' }, + { text: '所属组织', value: 'groupName', align: 'center' }, { text: '经度', value: 'longitude', align: 'center' }, { text: '纬度', value: 'latitude', align: 'center' }, { text: '密级', value: 'secretLevelName', align: 'center', width: 100 }, diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/src/views/monitor/deviceManage/listDevice.vue b/src/views/monitor/deviceManage/listDevice.vue index 8848009..1ec112e 100644 --- a/src/views/monitor/deviceManage/listDevice.vue +++ b/src/views/monitor/deviceManage/listDevice.vue @@ -47,6 +47,7 @@ // { text: 'NVR端口', value: 'nvrPort', align: 'center' }, { text: '所属单位', value: 'deptName', align: 'center' }, { text: '所属区域', value: 'areaName', align: 'center' }, + { text: '所属组织', value: 'groupName', align: 'center' }, { text: '经度', value: 'longitude', align: 'center' }, { text: '纬度', value: 'latitude', align: 'center' }, { text: '密级', value: 'secretLevelName', align: 'center', width: 100 }, diff --git a/src/views/monitor/group/editDialog.vue b/src/views/monitor/group/editDialog.vue new file mode 100644 index 0000000..960f9be --- /dev/null +++ b/src/views/monitor/group/editDialog.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/src/views/monitor/deviceManage/listDevice.vue b/src/views/monitor/deviceManage/listDevice.vue index 8848009..1ec112e 100644 --- a/src/views/monitor/deviceManage/listDevice.vue +++ b/src/views/monitor/deviceManage/listDevice.vue @@ -47,6 +47,7 @@ // { text: 'NVR端口', value: 'nvrPort', align: 'center' }, { text: '所属单位', value: 'deptName', align: 'center' }, { text: '所属区域', value: 'areaName', align: 'center' }, + { text: '所属组织', value: 'groupName', align: 'center' }, { text: '经度', value: 'longitude', align: 'center' }, { text: '纬度', value: 'latitude', align: 'center' }, { text: '密级', value: 'secretLevelName', align: 'center', width: 100 }, diff --git a/src/views/monitor/group/editDialog.vue b/src/views/monitor/group/editDialog.vue new file mode 100644 index 0000000..960f9be --- /dev/null +++ b/src/views/monitor/group/editDialog.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/src/views/monitor/group/group-interface.ts b/src/views/monitor/group/group-interface.ts new file mode 100644 index 0000000..cf4558c --- /dev/null +++ b/src/views/monitor/group/group-interface.ts @@ -0,0 +1,23 @@ +export interface IListQuery { + deptId: string // 所属组织 + groupName: string // 分组名称 + offset: number + limit: number +} + +export interface IList { + id: string // 主键 + createId: string // 创建人 + createTime: string // 创建时间 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 + updateTime: string // 更新时间 +} + +export interface IForm { + id: string // 主键 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 +} diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/src/views/monitor/deviceManage/listDevice.vue b/src/views/monitor/deviceManage/listDevice.vue index 8848009..1ec112e 100644 --- a/src/views/monitor/deviceManage/listDevice.vue +++ b/src/views/monitor/deviceManage/listDevice.vue @@ -47,6 +47,7 @@ // { text: 'NVR端口', value: 'nvrPort', align: 'center' }, { text: '所属单位', value: 'deptName', align: 'center' }, { text: '所属区域', value: 'areaName', align: 'center' }, + { text: '所属组织', value: 'groupName', align: 'center' }, { text: '经度', value: 'longitude', align: 'center' }, { text: '纬度', value: 'latitude', align: 'center' }, { text: '密级', value: 'secretLevelName', align: 'center', width: 100 }, diff --git a/src/views/monitor/group/editDialog.vue b/src/views/monitor/group/editDialog.vue new file mode 100644 index 0000000..960f9be --- /dev/null +++ b/src/views/monitor/group/editDialog.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/src/views/monitor/group/group-interface.ts b/src/views/monitor/group/group-interface.ts new file mode 100644 index 0000000..cf4558c --- /dev/null +++ b/src/views/monitor/group/group-interface.ts @@ -0,0 +1,23 @@ +export interface IListQuery { + deptId: string // 所属组织 + groupName: string // 分组名称 + offset: number + limit: number +} + +export interface IList { + id: string // 主键 + createId: string // 创建人 + createTime: string // 创建时间 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 + updateTime: string // 更新时间 +} + +export interface IForm { + id: string // 主键 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 +} diff --git a/src/views/monitor/group/list.vue b/src/views/monitor/group/list.vue new file mode 100644 index 0000000..2ef4748 --- /dev/null +++ b/src/views/monitor/group/list.vue @@ -0,0 +1,140 @@ + + + + diff --git a/public/config/config.json b/public/config/config.json index eaba0a5..a1d00a8 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -53,7 +53,7 @@ "appStreamUrlDesc": "代理流播放地址", "appStreamUrl": "casic", "useApprovalDesc": "是否使用审批", - "useApproval": "true", + "useApproval": "false", "useGatewayDesc": "sm是否走网关", "useGateway": "false" } diff --git a/src/api/monitor/deviceGroup.ts b/src/api/monitor/deviceGroup.ts new file mode 100644 index 0000000..608d1d9 --- /dev/null +++ b/src/api/monitor/deviceGroup.ts @@ -0,0 +1,48 @@ +// 设备分组管理 +import request from '../index' +// 设备分组列表分页 +export function getGroupListPage(data: object) { + return request({ + url: 'device/group/listPage', + method: 'get', + params: data, + data, + }) +} + +// 设备分组列表分不分页 +export function getGroupList(data: object) { + return request({ + url: 'device/group/list', + method: 'get', + params: data, + data, + }) +} + +// 添加设备分组 +export function addDeviceGroup(data: object) { + return request({ + url: 'device/group/add', + method: 'post', + data, + }) +} + +// 编辑设备分组 +export function updateDeviceGroup(data: object) { + return request({ + url: 'device/group/update', + method: 'post', + data, + }) +} + +// 批量删除 +export function batchDeleteDeviceGroup(data: { ids: string[] }) { + return request({ + url: 'device/group/batchDelete', + method: 'post', + data, + }) +} diff --git a/src/assets/icons/icon-device-group.svg b/src/assets/icons/icon-device-group.svg new file mode 100644 index 0000000..fc04fe8 --- /dev/null +++ b/src/assets/icons/icon-device-group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/modules/monitor.ts b/src/router/modules/monitor.ts index f5f0190..b8ed151 100644 --- a/src/router/modules/monitor.ts +++ b/src/router/modules/monitor.ts @@ -128,6 +128,31 @@ ], }, { + path: '/deviceGroup', + component: Layout, + redirect: '/deviceGroup/list', + name: 'DeviceGroup', + meta: { + title: '设备分组管理', + icon: 'icon-device', + auth: '/deviceGroup', + }, + children: [ + { + path: 'list', + name: 'DeviceGroupList', + component: () => import('@/views/monitor/group/list.vue'), + meta: { + title: '设备分组管理', + auth: '/deviceGroup', + sidebar: false, + breadcrumb: false, + activeMenu: '/deviceGroup', + }, + }, + ], + }, + { path: '/groupEmpower', component: Layout, redirect: '/groupEmpower/list', diff --git a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue index 05fcc4a..5f6749b 100644 --- a/src/views/alarm/policyConfig/videoPreview/videoPreview.vue +++ b/src/views/alarm/policyConfig/videoPreview/videoPreview.vue @@ -509,60 +509,95 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } onMounted(() => { diff --git a/src/views/monitor/deviceManage/dev-interface.ts b/src/views/monitor/deviceManage/dev-interface.ts index cd58e98..7b5e05c 100644 --- a/src/views/monitor/deviceManage/dev-interface.ts +++ b/src/views/monitor/deviceManage/dev-interface.ts @@ -35,6 +35,7 @@ secretLevel: string // 密级 nvrIndexCode: string // NVR国标号 cameraIndexCode: string // 设备国标号 + groupId: string // 设备所属分组id } export interface DevConfigInfo { diff --git a/src/views/monitor/deviceManage/editDev.vue b/src/views/monitor/deviceManage/editDev.vue index b07a79e..b78486d 100644 --- a/src/views/monitor/deviceManage/editDev.vue +++ b/src/views/monitor/deviceManage/editDev.vue @@ -8,6 +8,7 @@ import { getDictByCode } from '@/api/system/dict' import { isInRange, isInRangeNum, isIp, isPort } from '@/utils/validate' import AreaSelectTree from '@/views/system/area/areaSelectTree.vue' +import { getGroupList } from '@/api/monitor/deviceGroup' const emits = defineEmits(['closeRefresh']) // 对话框类型:create,update @@ -53,6 +54,7 @@ secretLevel: '', // 密级 nvrIndexCode: '', // NVR国标号 cameraIndexCode: '', // 设备国标号 + groupId: '', // 设备所属分组id }) // 保存按钮加载状态 @@ -125,6 +127,7 @@ monitorName: [{ required: true, message: '设备名称必填', trigger: ['blur', 'change'] }], area: [{ required: true, message: '所属区域必选', trigger: ['blur', 'change'] }], deptId: [{ required: true, message: '所属单位必填', trigger: ['blur', 'change'] }], + groupId: [{ required: true, message: '所属分组必填', trigger: ['blur', 'change'] }], deviceIp: [{ required: true, message: '请输入合法ip地址', trigger: ['blur', 'change'], validator: isIpV }], devicePort: [{ required: true, message: '请输入合法端口号', trigger: ['blur', 'change'], validator: isPortV }], deviceUser: [{ required: true, message: '登录账号必填', trigger: ['blur', 'change'] }], @@ -209,6 +212,7 @@ formData.value = { id: '', deptId: '', + groupId: '', // 所属分组id area: '', areaName: '', monitorName: '', @@ -262,6 +266,7 @@ getDevInfo(id).then((response) => { formData.value.id = response.data.id formData.value.deptId = response.data.deptId + formData.value.groupId = response.data.groupId // 所属分组 formData.value.area = response.data.area formData.value.areaName = response.data.areaName formData.value.monitorName = response.data.monitorName @@ -323,6 +328,25 @@ getDict() systemType.value = window.localStorage.getItem('systemType') as string }) +// -------------------------------获取设备分组方法------------------------------------------------ +const groupList: any = ref([]) // 所属组织列表 +// 修改所属组织 +const changeDept = (val: string) => { + formData.value.groupId = '' + fetchGroupList(val) +} +// 获取所属组织列表 +function fetchGroupList(deptId = '') { + const params = { + deptId, // 所属组织 + groupName: '', // 分组名称 + offset: 1, + limit: 20, + } + getGroupList(params).then((res: any) => { + groupList.value = res.data + }) +} // ----------------------- 以下是暴露的方法内容 ---------------------------- defineExpose({ initDialog }) @@ -365,7 +389,17 @@ - + + + + + + + + diff --git a/src/views/monitor/deviceManage/listDevice.vue b/src/views/monitor/deviceManage/listDevice.vue index 8848009..1ec112e 100644 --- a/src/views/monitor/deviceManage/listDevice.vue +++ b/src/views/monitor/deviceManage/listDevice.vue @@ -47,6 +47,7 @@ // { text: 'NVR端口', value: 'nvrPort', align: 'center' }, { text: '所属单位', value: 'deptName', align: 'center' }, { text: '所属区域', value: 'areaName', align: 'center' }, + { text: '所属组织', value: 'groupName', align: 'center' }, { text: '经度', value: 'longitude', align: 'center' }, { text: '纬度', value: 'latitude', align: 'center' }, { text: '密级', value: 'secretLevelName', align: 'center', width: 100 }, diff --git a/src/views/monitor/group/editDialog.vue b/src/views/monitor/group/editDialog.vue new file mode 100644 index 0000000..960f9be --- /dev/null +++ b/src/views/monitor/group/editDialog.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/src/views/monitor/group/group-interface.ts b/src/views/monitor/group/group-interface.ts new file mode 100644 index 0000000..cf4558c --- /dev/null +++ b/src/views/monitor/group/group-interface.ts @@ -0,0 +1,23 @@ +export interface IListQuery { + deptId: string // 所属组织 + groupName: string // 分组名称 + offset: number + limit: number +} + +export interface IList { + id: string // 主键 + createId: string // 创建人 + createTime: string // 创建时间 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 + updateTime: string // 更新时间 +} + +export interface IForm { + id: string // 主键 + deptId: string // 所属组织 + deptName: string // 所属组织名称 + groupName: string // 分组名称 +} diff --git a/src/views/monitor/group/list.vue b/src/views/monitor/group/list.vue new file mode 100644 index 0000000..2ef4748 --- /dev/null +++ b/src/views/monitor/group/list.vue @@ -0,0 +1,140 @@ + + + + diff --git a/src/views/monitor/realTime/index-new-gm-plugin.vue b/src/views/monitor/realTime/index-new-gm-plugin.vue index 2c6c5ca..e65c4aa 100644 --- a/src/views/monitor/realTime/index-new-gm-plugin.vue +++ b/src/views/monitor/realTime/index-new-gm-plugin.vue @@ -322,61 +322,97 @@ } }) -/** - * 向 type 为 '1' 的组织对象中注入设备统计字段(仅统计叶子节点设备) - * @param {Array} data - 包含组织和设备节点的 JSON 数组 - * @returns {Array} 修改后的原始数据数组(直接修改对象引用) - */ -function injectDeviceCounts(data: any[]): any { - // ---------------------- 步骤 1:构建组织节点映射表 ---------------------- // - const orgMap = new Map() // 使用 Map 存储组织节点,便于快速查找 +function injectDeviceCounts(data: any[]) { + /** + * 向组织对象中注入设备统计字段 + * 仅统计叶子节点设备(type为2的节点) + * @param {Array} data - 包含组织和设备节点的JSON数组 + * @returns {Array} 修改后的原始数据数组 + */ - // 遍历所有节点,筛选出 type 为 '1' 的组织节点,并初始化统计字段 - data.forEach((node) => { - if (node.type === '1') { + // 步骤1:构建组织节点映射表(包括type为1和3的节点) + // 组织节点类型:1-部门,3-分组 + const orgMap = new Map() + + // 递归收集所有组织节点到映射表中 + function collectOrgNodes(node: { type: string; totalDevices: number; onlineDevices: number; id: any; children: any[] }) { + // 判断是否为组织节点(type为1或3) + if (node.type === '1' || node.type === '3') { + // 初始化统计字段 node.totalDevices = 0 // 设备总数 node.onlineDevices = 0 // 在线设备数 - orgMap.set(node.id, node) // 以 id 为键存入 Map - } - }) + orgMap.set(node.id, node) // 以id为键存入Map - // ---------------------- 步骤 2:递归统计叶子节点设备 ---------------------- // - /** - * 递归函数:遍历节点,仅当节点为叶子节点(无 children)时视为设备 - * @param {Object} node - 当前遍历的节点(组织或设备) - */ - function traverse(node: { children: any[]; device: { deviceStatus: number }; pid: any; nodeParentId: any; type: string }) { - // ---------------------- 判断是否为叶子节点设备 ---------------------- // - // 条件:无 children 或 children 为空数组,且存在 device 字段 - if ((!node.children || node.children.length === 0) && node.device) { - const parentOrgId = node.pid || node.nodeParentId // 获取父组织 ID(优先使用 pid) - const parentOrg = orgMap.get(parentOrgId) // 从映射表中查找父组织 - - if (parentOrg) { - parentOrg.totalDevices++ // 设备总数 +1 - if (node.device.deviceStatus === 1) { - parentOrg.onlineDevices++ // 在线设备数 +1(状态为 1 时) - } + // 递归处理子节点 + if (node.children && node.children.length > 0) { + node.children.forEach(collectOrgNodes) } } - - // ---------------------- 递归处理子节点(仅组织节点需要处理子节点) ---------------------- // - if (node.type === '1' && node.children) { // 仅当节点是组织且有子节点时递归 - node.children.forEach((child) => { - traverse(child) // 递归处理子节点 - }) - } } - // ---------------------- 步骤 3:从顶级组织开始递归统计 ---------------------- // - data.forEach((node) => { - if (node.type === '1') { // 仅从组织节点开始递归,避免直接处理设备节点 - traverse(node) - } - }) + // 从顶层开始收集所有组织节点 + data.forEach(collectOrgNodes) - return data // 返回修改后的原始数据 + // 步骤2:递归统计设备节点 + function countDevices(node: { type: string; device: { deviceStatus: number }; pid: any; nodeParentId: any; children: any[] }) { + // 如果是设备节点(type为2),增加统计 + if (node.type === '2' && node.device) { + // 获取设备的父组织ID(优先使用pid) + let currentParentId = node.pid || node.nodeParentId + + // 向上遍历所有上级组织节点,更新统计数据 + while (currentParentId && currentParentId !== '0') { + const parentOrg = orgMap.get(currentParentId) + if (parentOrg) { + // 设备总数 +1 + parentOrg.totalDevices++ + + // 如果设备在线(状态为1),在线设备数 +1 + if (node.device.deviceStatus === 1) { + parentOrg.onlineDevices++ + } + } + + // 向上查找父节点的父节点 + const parentNode = findNodeById(data, currentParentId) + currentParentId = parentNode ? (parentNode.pid || parentNode.nodeParentId) : null + } + + // 设备节点处理完毕,直接返回 + return + } + + // 如果是组织节点,递归处理子节点 + if ((node.type === '1' || node.type === '3') && node.children) { + node.children.forEach(countDevices) + } + } + + // 辅助函数:根据ID查找节点 + function findNodeById(nodes: any[], id: any) { + // 遍历当前层级的节点 + for (const node of nodes) { + // 如果找到匹配ID的节点,直接返回 + if (node.id === id) { return node } + + // 如果当前节点有子节点,递归查找 + if (node.children && node.children.length > 0) { + const found: any = findNodeById(node.children, id) + if (found) { return found } + } + } + + // 未找到匹配节点,返回null + return null + } + + // 从顶层开始统计设备 + data.forEach(countDevices) + + // 返回修改后的原始数据 + return data } + // ----------------------------------------------拖拽-------------------------------------------------- // 处理拖拽开始事件 const handleDragStart = (event: DragEvent, data: any) => {