数据修复工具:完善设备检索

This commit is contained in:
zhugongping 2026-10-05 20:51:26 +08:00
parent 4599221606
commit 4cc487c00f
2 changed files with 134 additions and 32 deletions

View File

@ -159,7 +159,7 @@ public class DeviceController extends BaseController {
/** /**
* 查询树形设备列表(用于报表生成等场景,网关设备可展开显示子设备) * 查询树形设备列表(用于报表生成等场景,网关设备可展开显示子设备)
*/ */
@PreAuthorize("@ss.hasAnyPermi('iot:device:list,iot:report:generate')") @PreAuthorize("@ss.hasAnyPermi('iot:device:list,iot:report:generate,dataCenter:repair:list')")
@GetMapping("/treeList") @GetMapping("/treeList")
@ApiOperation("查询树形设备列表(网关可展开子设备)") @ApiOperation("查询树形设备列表(网关可展开子设备)")
public AjaxResult treeList(Device device) { public AjaxResult treeList(Device device) {

View File

@ -47,16 +47,34 @@
<el-form-item label="设备"> <el-form-item label="设备">
<el-select <el-select
v-model="queryParams.deviceId" v-model="queryParams.deviceId"
placeholder="请选择设备" placeholder="请选择设备(可输入名称/编号检索)"
filterable filterable
clearable clearable
@change="handleDeviceChange" @change="handleDeviceChange"
style="width: 220px" style="width: 280px"
> >
<el-option <el-option
v-for="item in deviceList" v-for="item in deviceList"
:key="item.deviceId" :key="item.deviceId"
:label="`${item.deviceName} (${item.serialNumber})`" :label="item.label"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<!-- 选中网关且其下挂有子设备时动态出现;不选则修复网关本体的物模型 -->
<el-form-item v-if="gatewayChildren.length > 0" label="子设备">
<el-select
v-model="queryParams.subDeviceId"
placeholder="不选则修复网关本体"
filterable
clearable
@change="handleSubDeviceChange"
style="width: 240px"
>
<el-option
v-for="item in gatewayChildren"
:key="item.deviceId"
:label="item.label"
:value="item.deviceId" :value="item.deviceId"
></el-option> ></el-option>
</el-select> </el-select>
@ -124,7 +142,7 @@
</div> </div>
<!-- 自动校准配置弹窗 --> <!-- 自动校准配置弹窗 -->
<el-dialog title="自动校准配置" :visible.sync="calibrationConfigVisible" width="600px" append-to-body v-dialogDrag> <el-dialog title="自动校准配置" :visible.sync="calibrationConfigVisible" width="720px" append-to-body v-dialogDrag>
<el-alert <el-alert
type="info" type="info"
:closable="false" :closable="false"
@ -170,7 +188,24 @@
<el-option <el-option
v-for="item in deviceList" v-for="item in deviceList"
:key="item.deviceId" :key="item.deviceId"
:label="`${item.deviceName} (${item.serialNumber})`" :label="item.label"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="configGatewayChildren.length > 0" label="子设备">
<el-select
v-model="newConfig.subDeviceId"
placeholder="不选则配置网关本体"
filterable
clearable
@change="handleConfigSubDeviceChange"
style="width: 200px"
>
<el-option
v-for="item in configGatewayChildren"
:key="item.deviceId"
:label="item.label"
:value="item.deviceId" :value="item.deviceId"
></el-option> ></el-option>
</el-select> </el-select>
@ -337,7 +372,7 @@
<script> <script>
import moment from 'moment'; import moment from 'moment';
import { listDeviceShort, listThingsModel } from '@/api/iot/device.js'; import { listDeviceTree, listThingsModel } from '@/api/iot/device.js';
import { import {
getRepairDataList, getRepairDataList,
addDeviceLog, addDeviceLog,
@ -367,13 +402,16 @@ export default {
calibrationConfigList: [], calibrationConfigList: [],
newConfig: { newConfig: {
deviceId: null, deviceId: null,
subDeviceId: null,
identifier: null identifier: null
}, },
configGatewayChildren: [],
configIdentifierList: [], configIdentifierList: [],
// 查询参数 // 查询参数
queryParams: { queryParams: {
deviceId: null, deviceId: null,
subDeviceId: null,
identifier: null, identifier: null,
dateRange: [ dateRange: [
moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'),
@ -384,7 +422,10 @@ export default {
}, },
// 数据 // 数据
// 设备栏只放顶层设备(网关 + 直连设备);网关的子设备走按需出现的「子设备」栏
deviceList: [], deviceList: [],
deviceMap: {},
gatewayChildren: [],
identifierList: [], identifierList: [],
tableData: [], tableData: [],
total: 0, total: 0,
@ -450,17 +491,22 @@ export default {
}; };
}, },
computed: { computed: {
// 主设备(网关或直连设备)
mainDevice() {
return this.deviceMap[this.queryParams.deviceId];
},
// 有效设备:选了子设备就是子设备,否则是主设备(网关本体)
currentDevice() {
return this.deviceMap[this.queryParams.subDeviceId] || this.mainDevice;
},
currentDeviceName() { currentDeviceName() {
const device = this.deviceList.find(d => d.deviceId === this.queryParams.deviceId); const device = this.currentDevice;
return device ? `${device.deviceName} (${device.serialNumber})` : ''; return device ? `${device.deviceName} (${device.serialNumber})` : '';
}, },
currentModelName() { currentModelName() {
const model = this.identifierList.find(m => m.identifier === this.queryParams.identifier); const model = this.identifierList.find(m => m.identifier === this.queryParams.identifier);
return model ? model.modelName : ''; return model ? model.modelName : '';
}, },
currentDevice() {
return this.deviceList.find(d => d.deviceId === this.queryParams.deviceId);
},
currentModel() { currentModel() {
return this.identifierList.find(m => m.identifier === this.queryParams.identifier); return this.identifierList.find(m => m.identifier === this.queryParams.identifier);
} }
@ -506,19 +552,27 @@ export default {
} }
}, },
// 配置设备变化 // 配置:主设备变化 → 重置子设备与物模型,并按需挂出该网关的子设备
async handleConfigDeviceChange(deviceId) { async handleConfigDeviceChange(deviceId) {
this.newConfig.subDeviceId = null;
this.newConfig.identifier = null; this.newConfig.identifier = null;
this.configGatewayChildren = [];
this.configIdentifierList = []; this.configIdentifierList = [];
if (!deviceId) return; if (!deviceId) return;
try { const device = this.deviceMap[deviceId];
const res = await listThingsModel({ deviceId, pageNum: 1, pageSize: 9999 }); if (device && device.children.length) {
if (res.code === 200) { this.configGatewayChildren = device.children;
this.configIdentifierList = res.rows || [];
}
} catch (e) {
console.error('加载物模型失败', e);
} }
this.configIdentifierList = await this.fetchIdentifierList(device);
},
// 配置:子设备变化(清空则回到网关本体)
async handleConfigSubDeviceChange(subDeviceId) {
this.newConfig.identifier = null;
this.configIdentifierList = [];
const deviceId = subDeviceId || this.newConfig.deviceId;
if (!deviceId) return;
this.configIdentifierList = await this.fetchIdentifierList(this.deviceMap[deviceId]);
}, },
// 添加自动校准配置 // 添加自动校准配置
@ -526,7 +580,7 @@ export default {
if (!this.newConfig.deviceId || !this.newConfig.identifier) { if (!this.newConfig.deviceId || !this.newConfig.identifier) {
return this.$message.warning('请选择设备和物模型'); return this.$message.warning('请选择设备和物模型');
} }
const device = this.deviceList.find(d => d.deviceId === this.newConfig.deviceId); const device = this.deviceMap[this.newConfig.subDeviceId || this.newConfig.deviceId];
const model = this.configIdentifierList.find(m => m.identifier === this.newConfig.identifier); const model = this.configIdentifierList.find(m => m.identifier === this.newConfig.identifier);
if (!device || !model) { if (!device || !model) {
return this.$message.error('设备或物模型不存在'); return this.$message.error('设备或物模型不存在');
@ -581,35 +635,81 @@ export default {
} }
}, },
// 加载设备列表 // 加载设备树:主栏只取顶层设备(网关 + 直连设备),子设备递归挂在所属网关的 children 上
async loadDeviceList() { async loadDeviceList() {
try { try {
const res = await listDeviceShort({ showChild: true, pageNum: 1, pageSize: 9999 }); const res = await listDeviceTree({ showChild: true });
if (res.code === 200) { if (res.code !== 200) return;
this.deviceList = res.rows || []; const deviceMap = {};
} // 节点 → 下拉项;label 带编号(filterable 可按名称或编号检索),并同步写进 deviceId 索引
const convert = (node) => {
const option = {
deviceId: node.deviceId,
deviceName: node.deviceName,
serialNumber: node.serialNumber,
productId: node.productId,
productName: node.productName,
deviceType: node.deviceType,
children: [],
label: `${node.deviceType === 2 ? '🌐 ' : ''}${node.deviceName || node.serialNumber} (${node.serialNumber})`
};
deviceMap[node.deviceId] = option;
option.children = (node.children || []).map(convert);
return option;
};
const roots = (res.data || []).map(convert);
// 整体替换而非逐键写入,保证 deviceMap 的响应式失效
this.deviceMap = deviceMap;
this.deviceList = roots;
} catch (e) { } catch (e) {
console.error('加载设备列表失败', e); console.error('加载设备树失败', e);
} }
}, },
// 设备变化 // 设备变化:清空子设备栏与物模型;是网关则挂出子设备,并默认按网关本体加载物模型
async handleDeviceChange(deviceId) { async handleDeviceChange(deviceId) {
this.queryParams.subDeviceId = null;
this.queryParams.identifier = null; this.queryParams.identifier = null;
this.gatewayChildren = [];
this.identifierList = []; this.identifierList = [];
this.tableData = []; this.tableData = [];
this.total = 0; this.total = 0;
if (!deviceId) return; if (!deviceId) return;
try { const device = this.deviceMap[deviceId];
const res = await listThingsModel({ deviceId, pageNum: 1, pageSize: 9999 }); if (device && device.children.length) {
if (res.code === 200) { this.gatewayChildren = device.children;
// 显示所有物模型(属性type=1、功能type=2、事件type=3)
this.identifierList = res.rows || [];
} }
// 网关本体没有物模型时,物模型栏就空着,由用户去「子设备」里选
this.identifierList = await this.fetchIdentifierList(device);
},
// 子设备变化:选中则修复该子设备,清空则回到网关本体
async handleSubDeviceChange(subDeviceId) {
this.queryParams.identifier = null;
this.identifierList = [];
this.tableData = [];
this.total = 0;
const deviceId = subDeviceId || this.queryParams.deviceId;
if (!deviceId) return;
this.identifierList = await this.fetchIdentifierList(this.deviceMap[deviceId]);
},
// 查设备「自己」的物模型(属性type=1、功能type=2、事件type=3)
// ⚠️ /iot/device/listThingsModel 会把网关下所有子设备的物模型也合并返回(带 parentSerialNumber),
// 修复页只认选中设备自身的物模型,否则子设备的标识符会被当成网关的物模型去查询/新增
async fetchIdentifierList(device) {
if (!device) return [];
try {
const res = await listThingsModel({ deviceId: device.deviceId, pageNum: 1, pageSize: 9999 });
const rows = res.code === 200 ? (res.rows || []) : [];
return rows.filter(model => model.serialNumber === device.serialNumber);
} catch (e) { } catch (e) {
console.error('加载物模型失败', e); console.error('加载物模型失败', e);
return [];
} }
}, },
@ -632,6 +732,7 @@ export default {
handleReset() { handleReset() {
this.queryParams = { this.queryParams = {
deviceId: null, deviceId: null,
subDeviceId: null,
identifier: null, identifier: null,
dateRange: [ dateRange: [
moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'),
@ -640,6 +741,7 @@ export default {
pageNum: 1, pageNum: 1,
pageSize: 50 pageSize: 50
}; };
this.gatewayChildren = [];
this.identifierList = []; this.identifierList = [];
this.tableData = []; this.tableData = [];
this.total = 0; this.total = 0;