|
|
@@ -233,16 +233,22 @@
|
|
|
<div v-for="(channel, idx) in getDeviceChannels(props.row.deviceCode)" :key="idx">
|
|
|
<h5>{{ channel.name }} ({{ channel.points.length }}个测点)</h5>
|
|
|
<el-table :data="channel.points" size="mini" max-height="200">
|
|
|
- <el-table-column prop="name" label="测点名称"></el-table-column>
|
|
|
- <el-table-column prop="key" label="测点标识"></el-table-column>
|
|
|
- <el-table-column prop="value" label="当前值">
|
|
|
+ <el-table-column prop="name" label="测点名称" min-width="180"></el-table-column>
|
|
|
+ <el-table-column prop="key" label="测点标识" width="150"></el-table-column>
|
|
|
+ <el-table-column prop="location" label="位置" min-width="120"></el-table-column>
|
|
|
+ <el-table-column prop="desc" label="描述" min-width="120"></el-table-column>
|
|
|
+ <el-table-column label="当前值" width="120">
|
|
|
<template slot-scope="scope">
|
|
|
- {{ scope.row.value || '-' }}
|
|
|
+ <span>{{ scope.row.value || '-' }}</span>
|
|
|
+ <span v-if="scope.row.unit" style="margin-left: 4px; color: #909399; font-size: 12px;">
|
|
|
+ {{ scope.row.unit }}
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="updateTime" label="更新时间"></el-table-column>
|
|
|
+ <el-table-column prop="updateTime" label="更新时间" width="160"></el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
+ <el-empty v-if="getDeviceChannels(props.row.deviceCode).length === 0" description="暂无测点数据"></el-empty>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
@@ -561,7 +567,7 @@
|
|
|
style="flex: 0 0 120px;">
|
|
|
</el-input-number>
|
|
|
<span style="color: #909399; font-size: 13px;">
|
|
|
- 调节范围:{{ abilitySliderMin }} - {{ abilitySliderMax }}
|
|
|
+ 调节范围:{{ abilitySliderMin }} - {{ abilitySliderMax }}
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -627,6 +633,9 @@ export default {
|
|
|
},
|
|
|
// 所有模型代码(系统 + 设备)
|
|
|
allModelCodes: [],
|
|
|
+ // 子设备相关
|
|
|
+ subDeviceAttrs: {}, // 子设备属性缓存
|
|
|
+ subDeviceGatewayMap: {}, // 子设备到网关的映射
|
|
|
// 调用日志查询
|
|
|
callLogQuery: {
|
|
|
dateRange: [],
|
|
|
@@ -831,6 +840,7 @@ export default {
|
|
|
data: res.data
|
|
|
}
|
|
|
|
|
|
+ // 如果是抄表能力,刷新设备数据
|
|
|
if (ability.abilityKey === 'MeterReadingGw') {
|
|
|
this.loadDevices()
|
|
|
}
|
|
|
@@ -920,6 +930,7 @@ export default {
|
|
|
this.$message.success('执行成功')
|
|
|
this.abilityDialogVisible = false
|
|
|
|
|
|
+ // 如果是抄表相关能力,刷新数据
|
|
|
if (this.abilityForm.abilityKey.toLowerCase().includes('meter') ||
|
|
|
this.abilityForm.abilityKey.toLowerCase().includes('reading')) {
|
|
|
if (this.currentObjType === 3) {
|
|
|
@@ -1014,6 +1025,7 @@ export default {
|
|
|
detailTab: 'attrs'
|
|
|
}))
|
|
|
|
|
|
+ // 收集设备模型
|
|
|
const deviceModelCodes = new Set()
|
|
|
this.deviceList.forEach(device => {
|
|
|
if (device.deviceModel) {
|
|
|
@@ -1031,6 +1043,7 @@ export default {
|
|
|
this.deviceStats.online = this.deviceList.filter(d => d.deviceStatus === 1).length
|
|
|
this.deviceStats.offline = this.deviceList.filter(d => d.deviceStatus !== 1).length
|
|
|
|
|
|
+ // 按模型分组设备
|
|
|
const devicesByModel = {}
|
|
|
this.deviceList.forEach(device => {
|
|
|
if (device.deviceModel) {
|
|
|
@@ -1041,9 +1054,14 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ // 批量加载网关设备属性(包含subDev)
|
|
|
for (const modelCode in devicesByModel) {
|
|
|
await this.loadDeviceAttrsBatch(modelCode, devicesByModel[modelCode])
|
|
|
}
|
|
|
+
|
|
|
+ // 加载所有子设备的属性
|
|
|
+ await this.loadAllSubDeviceAttrs()
|
|
|
+
|
|
|
} catch (error) {
|
|
|
this.$message.error('加载设备列表失败')
|
|
|
} finally {
|
|
|
@@ -1051,19 +1069,18 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 批量加载设备属性
|
|
|
+ // 批量加载网关设备属性
|
|
|
async loadDeviceAttrsBatch(modelCode, devices) {
|
|
|
try {
|
|
|
const res = await getObjAttrBatch(2, modelCode)
|
|
|
const batchData = res.data || {}
|
|
|
|
|
|
- let totalPoints = 0
|
|
|
-
|
|
|
devices.forEach(device => {
|
|
|
const deviceCode = device.deviceCode
|
|
|
const attrData = batchData[deviceCode]
|
|
|
|
|
|
if (attrData) {
|
|
|
+ // 保存基础属性
|
|
|
const baseAttrs = {}
|
|
|
if (attrData.Base) {
|
|
|
attrData.Base.forEach(attr => {
|
|
|
@@ -1071,80 +1088,144 @@ export default {
|
|
|
})
|
|
|
}
|
|
|
this.deviceAttrs[deviceCode] = baseAttrs
|
|
|
-
|
|
|
- const channels = []
|
|
|
- if (attrData.Measure) {
|
|
|
- attrData.Measure.forEach(attr => {
|
|
|
- if (attr.attrKey.startsWith('interface')) {
|
|
|
- try {
|
|
|
- const points = JSON.parse(attr.attrValue || '[]')
|
|
|
- channels.push({
|
|
|
- name: attr.attrName,
|
|
|
- key: attr.attrKey,
|
|
|
- points: points
|
|
|
- })
|
|
|
- totalPoints += points.length
|
|
|
- } catch (e) {
|
|
|
- console.error(`解析设备 ${deviceCode} 的测点数据失败:`, e)
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- this.deviceChannels[deviceCode] = channels
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
- this.deviceStats.points += totalPoints
|
|
|
} catch (error) {
|
|
|
console.error(`批量加载模型 ${modelCode} 的设备属性失败:`, error)
|
|
|
- for (const device of devices) {
|
|
|
- await this.loadDeviceAttrs(device.deviceCode)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载所有子设备属性
|
|
|
+ async loadAllSubDeviceAttrs() {
|
|
|
+ // 收集所有子设备信息
|
|
|
+ const subDevicesByModel = {} // { modelCode: [deviceCodes] }
|
|
|
+
|
|
|
+ this.deviceList.forEach(device => {
|
|
|
+ const subDevAttr = this.deviceAttrs[device.deviceCode]?.subDev
|
|
|
+ if (subDevAttr) {
|
|
|
+ try {
|
|
|
+ const subDevices = JSON.parse(subDevAttr)
|
|
|
+ subDevices.forEach(subDev => {
|
|
|
+ if (!subDevicesByModel[subDev.modelCode]) {
|
|
|
+ subDevicesByModel[subDev.modelCode] = []
|
|
|
+ }
|
|
|
+ subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
|
|
|
+
|
|
|
+ // 保存子设备与网关的关联关系
|
|
|
+ this.subDeviceGatewayMap[subDev.deviceCode] = device.deviceCode
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ console.error(`解析设备 ${device.deviceCode} 的subDev失败:`, e)
|
|
|
+ }
|
|
|
}
|
|
|
+ })
|
|
|
+
|
|
|
+ // 批量查询各个模型的子设备属性
|
|
|
+ let totalPoints = 0
|
|
|
+ for (const modelCode in subDevicesByModel) {
|
|
|
+ const pointsCount = await this.loadSubDeviceAttrsBatch(modelCode)
|
|
|
+ totalPoints += pointsCount
|
|
|
}
|
|
|
+
|
|
|
+ this.deviceStats.points = totalPoints
|
|
|
+
|
|
|
+ // 组织成通道结构供展示
|
|
|
+ this.organizeChannelsFromSubDevices()
|
|
|
},
|
|
|
|
|
|
- // 单个加载设备属性
|
|
|
- async loadDeviceAttrs(deviceCode) {
|
|
|
+ // 批量加载子设备属性
|
|
|
+ async loadSubDeviceAttrsBatch(modelCode) {
|
|
|
try {
|
|
|
- const res = await getObjAttr(2, deviceCode)
|
|
|
- const attrData = res.data
|
|
|
+ const res = await getObjAttrBatch(2, modelCode)
|
|
|
+ const batchData = res.data || {}
|
|
|
|
|
|
- const baseAttrs = {}
|
|
|
- if (attrData.Base) {
|
|
|
- attrData.Base.forEach(attr => {
|
|
|
- baseAttrs[attr.attrKey] = attr.attrValue
|
|
|
- })
|
|
|
+ let pointsCount = 0
|
|
|
+
|
|
|
+ // 保存子设备属性
|
|
|
+ for (const deviceCode in batchData) {
|
|
|
+ const attrData = batchData[deviceCode]
|
|
|
+ this.subDeviceAttrs[deviceCode] = attrData
|
|
|
+ pointsCount++
|
|
|
}
|
|
|
- this.deviceAttrs[deviceCode] = baseAttrs
|
|
|
|
|
|
- const channels = []
|
|
|
- let pointsCount = 0
|
|
|
- if (attrData.Measure) {
|
|
|
- attrData.Measure.forEach(attr => {
|
|
|
- if (attr.attrKey.startsWith('interface')) {
|
|
|
- try {
|
|
|
- const points = JSON.parse(attr.attrValue || '[]')
|
|
|
- channels.push({
|
|
|
- name: attr.attrName,
|
|
|
- key: attr.attrKey,
|
|
|
- points: points
|
|
|
- })
|
|
|
- pointsCount += points.length
|
|
|
- } catch (e) {
|
|
|
- console.error(`解析测点数据失败:`, e)
|
|
|
+ return pointsCount
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`批量加载子设备模型 ${modelCode} 的属性失败:`, error)
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 将子设备组织成通道结构
|
|
|
+ organizeChannelsFromSubDevices() {
|
|
|
+ this.deviceList.forEach(device => {
|
|
|
+ const deviceCode = device.deviceCode
|
|
|
+ const subDevAttr = this.deviceAttrs[deviceCode]?.subDev
|
|
|
+
|
|
|
+ if (!subDevAttr) {
|
|
|
+ this.deviceChannels[deviceCode] = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const subDevices = JSON.parse(subDevAttr)
|
|
|
+
|
|
|
+ // 按通道分组
|
|
|
+ const channelMap = {}
|
|
|
+
|
|
|
+ subDevices.forEach(subDev => {
|
|
|
+ const subDevCode = subDev.deviceCode
|
|
|
+ const subDevAttrData = this.subDeviceAttrs?.[subDevCode]
|
|
|
+
|
|
|
+ if (!subDevAttrData) return
|
|
|
+
|
|
|
+ // 获取通道信息
|
|
|
+ const interfaceAttr = subDevAttrData.Base?.find(attr => attr.attrKey === 'interface')
|
|
|
+ const channelName = interfaceAttr?.attrValue || '未知通道'
|
|
|
+
|
|
|
+ if (!channelMap[channelName]) {
|
|
|
+ channelMap[channelName] = {
|
|
|
+ name: this.formatChannelName(channelName),
|
|
|
+ key: channelName,
|
|
|
+ points: []
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 构建测点信息
|
|
|
+ const point = {
|
|
|
+ key: subDevCode,
|
|
|
+ name: subDevAttrData.Base?.find(attr => attr.attrKey === 'deviceName')?.attrValue || subDevCode,
|
|
|
+ value: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrValue || '-',
|
|
|
+ updateTime: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.updateTime || '-',
|
|
|
+ unit: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrUnit || '',
|
|
|
+ location: subDevAttrData.Base?.find(attr => attr.attrKey === 'location')?.attrValue || '-',
|
|
|
+ desc: subDevAttrData.Base?.find(attr => attr.attrKey === 'desc')?.attrValue || '-'
|
|
|
+ }
|
|
|
+
|
|
|
+ channelMap[channelName].points.push(point)
|
|
|
})
|
|
|
- }
|
|
|
|
|
|
- if (!this.deviceChannels[deviceCode]) {
|
|
|
- this.deviceStats.points += pointsCount
|
|
|
+ // 转换为数组并排序
|
|
|
+ const channels = Object.values(channelMap).sort((a, b) => {
|
|
|
+ return a.key.localeCompare(b.key)
|
|
|
+ })
|
|
|
+
|
|
|
+ this.deviceChannels[deviceCode] = channels
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+ console.error(`组织设备 ${deviceCode} 的通道数据失败:`, e)
|
|
|
+ this.deviceChannels[deviceCode] = []
|
|
|
}
|
|
|
+ })
|
|
|
+ },
|
|
|
|
|
|
- this.deviceChannels[deviceCode] = channels
|
|
|
- } catch (error) {
|
|
|
- console.error(`加载设备 ${deviceCode} 属性失败:`, error)
|
|
|
+ // 格式化通道名称
|
|
|
+ formatChannelName(channelKey) {
|
|
|
+ // 将类似 "1003D1口" 转换为 "D1口通道"
|
|
|
+ const match = channelKey.match(/(\d+)(D\d+)口/)
|
|
|
+ if (match) {
|
|
|
+ return `${match[2]}口通道`
|
|
|
}
|
|
|
+ return channelKey
|
|
|
},
|
|
|
|
|
|
// 获取设备属性
|
|
|
@@ -1157,6 +1238,48 @@ export default {
|
|
|
return this.deviceChannels[deviceCode] || []
|
|
|
},
|
|
|
|
|
|
+ // 单个加载设备属性(用于刷新)
|
|
|
+ async loadDeviceAttrs(deviceCode) {
|
|
|
+ try {
|
|
|
+ // 加载网关属性
|
|
|
+ const res = await getObjAttr(2, deviceCode)
|
|
|
+ const attrData = res.data
|
|
|
+
|
|
|
+ const baseAttrs = {}
|
|
|
+ if (attrData.Base) {
|
|
|
+ attrData.Base.forEach(attr => {
|
|
|
+ baseAttrs[attr.attrKey] = attr.attrValue
|
|
|
+ })
|
|
|
+ }
|
|
|
+ this.deviceAttrs[deviceCode] = baseAttrs
|
|
|
+
|
|
|
+ // 重新加载该网关的所有子设备
|
|
|
+ const subDevAttr = baseAttrs.subDev
|
|
|
+ if (subDevAttr) {
|
|
|
+ const subDevices = JSON.parse(subDevAttr)
|
|
|
+ const subDevicesByModel = {}
|
|
|
+
|
|
|
+ subDevices.forEach(subDev => {
|
|
|
+ if (!subDevicesByModel[subDev.modelCode]) {
|
|
|
+ subDevicesByModel[subDev.modelCode] = []
|
|
|
+ }
|
|
|
+ subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 批量查询子设备属性
|
|
|
+ for (const modelCode in subDevicesByModel) {
|
|
|
+ await this.loadSubDeviceAttrsBatch(modelCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新组织该网关的通道数据
|
|
|
+ this.organizeChannelsFromSubDevices()
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`加载设备 ${deviceCode} 属性失败:`, error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
// 加载设备能力
|
|
|
async handleAbilityLoad(visible, device) {
|
|
|
if (visible && !this.deviceAbilities[device.deviceCode]) {
|
|
|
@@ -1191,6 +1314,7 @@ export default {
|
|
|
|
|
|
this.$message.success(`${ability.abilityName}执行成功`)
|
|
|
|
|
|
+ // 如果是抄表相关能力,刷新测点数据
|
|
|
if (ability.abilityKey.toLowerCase().includes('meter') ||
|
|
|
ability.abilityKey.toLowerCase().includes('reading')) {
|
|
|
await this.loadDeviceAttrs(device.deviceCode)
|
|
|
@@ -1217,6 +1341,7 @@ export default {
|
|
|
|
|
|
this.$message.success(`${ability.abilityName}执行成功`)
|
|
|
|
|
|
+ // 如果是抄表相关能力,刷新测点数据
|
|
|
if (ability.abilityKey.toLowerCase().includes('meter') ||
|
|
|
ability.abilityKey.toLowerCase().includes('reading')) {
|
|
|
await this.loadDeviceAttrs(device.deviceCode)
|