feat(iot):重构运行状态和设备功能模块适配感知平台

- 重新设计功能标签页结构,调整设备信息与功能模块的显示顺序
- 调整运行状态页面适配感知平台
- 适配新websocket格式
- 优化功能参数输入界面,支持多种表单控件类型(数字、滑块、开关、时间选择器等)
- 实现功能执行前的安全预检查机制(用户密码、固定密码、短信验证)
- 改进执行结果展示方式,使用pre标签格式化JSON数据显示
- 重构表单数据初始化逻辑,支持更精确的默认值设置
- 增强参数类型转换处理,确保数据格式正确性
- 优化代码结构和组件间通信方式,提升整体性能和可维护性
This commit is contained in:
fhysy 2025-10-17 15:37:15 +08:00
parent 8d6a326d5f
commit ef18be7b31
3 changed files with 832 additions and 536 deletions

View File

@ -54,6 +54,11 @@
></device-run-starts-wrap>
</div>
</el-tab-pane>
<el-tab-pane label="设备信息" name="info">
<div class="tabs-body">
<info-wrap :infoData="infoData" @updateInfo="updateInfo($event)" />
</div>
</el-tab-pane>
<el-tab-pane label="事件" name="eventLog">
<div class="tabs-body">
<event-log
@ -64,9 +69,9 @@
</div>
</el-tab-pane>
<el-tab-pane label="设备信息" name="info">
<el-tab-pane label="功能" name="function">
<div class="tabs-body">
<info-wrap :infoData="infoData" @updateInfo="updateInfo($event)" />
<function-wrap v-if="activeName === 'function'" :deviceInfo="infoData" :sourceId="deviceId"></function-wrap>
</div>
</el-tab-pane>
@ -105,11 +110,7 @@
/>
</div>
</el-tab-pane>
<el-tab-pane label="功能" name="function">
<div class="tabs-body">
<function-wrap v-if="activeName === 'function'" :deviceInfo="infoData" :sourceId="deviceId"></function-wrap>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -2,91 +2,38 @@
<div class="device-run-starts-wrap">
<div class="cmd-list">
<div class="cmd-title-wrap">
<!-- <svg-icon-->
<!-- icon-class="A_product1"-->
<!-- style="margin-right: 2px; height: 20px; width: 20px"-->
<!-- />-->
分组名称
<el-select v-model="activeCmd" placeholder="请选择" @change="changeCmd">
<el-option
v-for="item in cmdList"
:key="item.cmdKey"
:label="item.cmdName"
:value="item.cmdKey"
>
<el-select v-model="selectedGroup" placeholder="请选择">
<el-option v-for="item in groupOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div v-if="filterProdObj.properties.length <= 0" style="width:100%; padding: 100px;text-align: center">
<div v-if="filteredProperties.length <= 0" style="width:100%; padding: 100px;text-align: center">
<i class="el-icon-s-order" style="font-size: 50px;color: #999"></i>
<div style="color: #909399;margin-top: 10px">暂无数据</div>
</div>
<div
v-for="(item, indexs) in filterProdObj.properties"
:key="indexs"
class="param-item2"
>
<div v-for="(item, index) in filteredProperties" :key="index" class="param-item2">
<div class="title-top">
<span class="name-wr">{{ item.funName }}</span>
<span class="name-wr">{{ item.name }}</span>
<span class="type-wr" @click="handleShowData(item)">查看</span>
</div>
<div class="value-info">
<div class="value-wrap">
<span
v-if="item.dataFormatType == 'ENUM'"
class="val-span"
v-text="item.dataFormatObj[item.lastValue] || item.lastValue"
>
</span>
<span
v-else-if="item.dataFormatType == 'TIME'"
class="val-span"
v-text="formatTime(item.lastValue)"
>
</span>
<span
v-else
class="val-span"
v-text="
item.lastValue === null || item.lastValue === undefined
? '--'
: item.lastValue
"
>
<span class="val-span">
{{ formatValue(item) }}
</span>
</div>
</div>
<div class="time-w">
<span class="time-warp">{{ item.unitName }}</span>
<span
class="time"
v-text="item.lastTime ? parseTime(item.lastTime) : '--'"
></span>
<span class="time-warp">{{ item.valueParams.unit || '' }}</span>
<span class="time">{{ item.timestamp }}</span>
</div>
</div>
</div>
<el-dialog
:close-on-click-modal="false"
:visible.sync="dialogShow"
append-to-body
class="device-run-state-dailog"
title="查看数据"
width="700px"
@close="dialogCloseCell"
@opened="dialogOpen"
>
<run-state-table
ref="showChart"
:deviceId="sourceId"
:deviceKey="deviceInfo.deviceKey"
:dialogData="dialogData"
:dialogShow="dialogShow"
:pro_type="dialogData.funDataType"
:prodId="prodId"
/>
<el-dialog :close-on-click-modal="false" :visible.sync="dialogShow" append-to-body class="device-run-state-dailog"
title="查看数据" width="700px" @close="dialogCloseCell" @opened="dialogOpen">
<run-state-table ref="showChart" :deviceId="sourceId" :deviceKey="deviceInfo.deviceKey" :dialogData="dialogData"
:dialogShow="dialogShow" :pro_type="dialogData.funDataType" :prodId="prodId" />
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogShow = false"> </el-button>
</div>
@ -127,16 +74,59 @@ export default {
properties: [],
events: []
},
activeCmd: ""
runtimeProperties: [],
selectedGroup: "all",
metadata:{
properties: [],
propertyGroups: []
}
};
},
computed: {
groupOptions() {
const groups = this.metadata?.propertyGroups || [];
return [
{ label: '全部', value: 'all' },
...groups.map((group) => ({ label: group.name, value: group.id })),
];
},
filteredProperties() {
let properties = [...this.runtimeProperties];
if (this.selectedGroup !== 'all') {
const group = (this.metadata?.propertyGroups || []).find(
(g) => g.id === this.selectedGroup,
);
if (group?.properties) {
const ids = new Set(group.properties.map((p) => p.id));
properties = properties.filter((p) => ids.has(p.id));
}
}
// properties = properties.filter((p) => {
// const type = p.expands?.type || 'R';
// return selectedTypes.value.includes(type);
// });
return properties;
}
},
created() {
this.getMetaDate();
this.getProdObj();
this.getCmdList();
// this.getCmdList();
// this.connection();
},
methods: {
getMetaDate(){
try {
this.metadata = JSON.parse(this.deviceInfo.metadata);
} catch (error) {
console.log(error);
}
},
changeCmd(e) {
if (e != "") {
const filterObj =
@ -150,42 +140,57 @@ export default {
});
}
},
getProdObj() {
let prodJson = {
properties: [],
events: []
};
try {
prodJson = JSON.parse(this.deviceInfo.prodJson);
} catch (error) {
console.log(error);
formatValue(property) {
const { value, valueParams } = property;
if (value === undefined || value === null) return '--';
let displayValue = value;
if (valueParams?.formType === 'input' && valueParams?.length) {
displayValue = value.slice(0, valueParams.length);
}
if (prodJson.properties) {
let list = prodJson.properties.filter(item => item.show === true) || [];
let properties = list.map(item => {
if (item.dataFormat) {
let dataFormat = item.dataFormat;
item.dataFormatType = dataFormat.type;
if (dataFormat.list && dataFormat.type === "ENUM") {
try {
item.dataFormatObj = JSON.parse(
dataFormat.list.replace(/\\/g, "")
);
} catch (e) {
item.dataFormatObj = {};
}
}
}
return item;
});
this.prodObj.properties = properties;
if (valueParams?.formType === 'switch') {
return value.toString() === valueParams.trueValue
? valueParams.trueText
: valueParams.falseText;
}
if (valueParams?.formType === 'select') {
return valueParams.enumConf.find((item) => item.value === value)?.text;
}
if (valueParams?.formType === 'time' && valueParams?.dataType !== 'string') {
return moment(value).format(valueParams.format);
}
if (
valueParams?.formType === 'number' ||
valueParams?.formType === 'progress'
) {
if (valueParams?.scale) return value.toFixed(valueParams.scale);
return value;
}
return displayValue;
},
getProdObj() {
if (this.metadata.properties) {
let list = this.metadata.properties.filter((item) => item.show === true) || [];
let properties = list.map(item => ({
...item,
value: null,
timestamp: moment().format('YYYY-MM-DD HH:mm:ss')
}));
this.runtimeProperties = properties;
this.$nextTick(() => {
this.$set(this.filterProdObj, "properties", properties);
this.$set(this, "runtimeProperties", properties);
});
} else {
this.prodObj.properties = [];
this.runtimeProperties = [];
}
this.prodObj.events = prodJson.events || [];
this.connection();
},
formatTime(date) {
//
@ -242,32 +247,58 @@ export default {
this.$forceUpdate();
},
recursionSet(list, result) {
if (result.type === "properties") {
if (result.tags.device_key === this.deviceInfo.deviceKey) {
this.prodObj.properties = this.prodObj.properties.map(item => ({
...item,
...(item.funKey in result.properties && {
lastValue: result.properties[item.funKey],
lastTime: result.properties.timestamp
})
}));
this.filterProdObj.properties = this.filterProdObj.properties.map(
item => ({
...item,
...(item.funKey in result.properties && {
lastValue: result.properties[item.funKey],
lastTime: result.properties.timestamp
})
})
);
}
}
if(result.type === "state"){
this.$emit("updateDeviceState",{
deviceKey: this.deviceInfo.deviceKey,
state: result.state
if (result.type === "result" && result.topic.endsWith('/report') && result.payload) {
//
this.runtimeProperties = this.runtimeProperties.map(property => {
const propValue = result.payload.value[property.id];
if (propValue !== undefined) {
return {
...property,
value: propValue,
timestamp: moment(result.payload.timeString).format('YYYY-MM-DD HH:mm:ss')
};
}
return property;
});
//
// Object.entries(result.payload).forEach(([propId, propValue]) => {
// const property = this.runtimeProperties.find(
// (p) => p.id === propId,
// );
// if (property) {
// property.value = propValue;
// property.timestamp = moment(data.payload.timeString).format(
// 'YYYY-MM-DD HH:mm:ss',
// );
// }
// });
}
// if (result.type === "properties") {
// if (result.tags.device_key === this.deviceInfo.deviceKey) {
// this.prodObj.properties = this.prodObj.properties.map(item => ({
// ...item,
// ...(item.funKey in result.properties && {
// lastValue: result.properties[item.funKey],
// lastTime: result.properties.timestamp
// })
// }));
// this.filterProdObj.properties = this.filterProdObj.properties.map(
// item => ({
// ...item,
// ...(item.funKey in result.properties && {
// lastValue: result.properties[item.funKey],
// lastTime: result.properties.timestamp
// })
// })
// );
// }
// }
// if (result.type === "state") {
// this.$emit("updateDeviceState", {
// deviceKey: this.deviceInfo.deviceKey,
// state: result.state
// });
// }
// for (var i = 0; i < list.length; i++) {
@ -309,7 +340,7 @@ export default {
if (this.socket_flag) {
this.socket_flag = false;
let self = this;
setTimeout(function() {
setTimeout(function () {
self.socket_flag = true;
self.connection();
}, 10000);
@ -375,7 +406,7 @@ export default {
];
}
this.connection();
}).catch(error=>{
}).catch(error => {
this.connection();
});
},
@ -415,7 +446,7 @@ export default {
// }
// },
realTimeData: {
handler: function() {
handler: function () {
this.setListData(this.realTimeData);
},
deep: true
@ -429,11 +460,13 @@ export default {
display: flex;
flex-wrap: wrap;
overflow: auto;
//padding: 10px;
.el-button--medium {
position: absolute;
right: 30px;
}
.cmd-list {
width: 100%;
/* height: auto; */
@ -441,9 +474,11 @@ export default {
flex-wrap: wrap;
cursor: default;
padding: 10px;
&:first-child {
padding-top: 0;
}
.cmd-title-wrap {
width: 100%;
display: flex;
@ -451,6 +486,7 @@ export default {
font-size: 16px;
align-items: center;
margin-bottom: 20px;
.cmd-title {
font-size: 14px;
color: #a9a6a6;
@ -459,6 +495,7 @@ export default {
letter-spacing: 1px;
}
}
.param-item {
height: 130px;
// border: 1px solid #777474;
@ -475,6 +512,7 @@ export default {
margin-left: 15px;
margin-top: 15px;
box-shadow: 0px 0px 3px 0px #b7b4b4;
.title-top {
height: 30px;
display: flex;
@ -483,23 +521,27 @@ export default {
border-bottom: 1px dotted #c5c3c3;
padding-bottom: 3px;
justify-content: space-between;
.name-wr {
font-size: 18px;
color: #1890ff;
}
.type-wr {
font-size: 14px;
color: #1890ff;
}
}
.value-info {
height: 55px;
display: flex;
margin-top: 5px;
align-items: center;
width: 100%;
.value-wrap {
}
.value-wrap {}
.val-span {
color: #03a9f4;
font-size: 20px;
@ -520,6 +562,7 @@ export default {
color: #908c8c;
}
}
.param-item2 {
height: 150px;
width: 300px;
@ -534,6 +577,7 @@ export default {
margin-left: -1px;
margin-top: -1px;
border-color: #e0e0e0;
.title-top {
height: 30px;
display: flex;
@ -542,28 +586,33 @@ export default {
border-bottom: 1px dotted #c5c3c3;
padding-bottom: 3px;
justify-content: space-between;
.name-wr {
font-size: 18px;
// color: #1890ff;
}
.type-wr {
font-size: 14px;
// color: #606266;
color: #1890ff;
}
.type-wr:hover {
color: #1890ff;
cursor: pointer;
}
}
.value-info {
height: 55px;
display: flex;
margin-top: 5px;
align-items: center;
width: 100%;
.value-wrap {
}
.value-wrap {}
.val-span {
color: #03a9f4;
font-size: 20px;
@ -586,10 +635,12 @@ export default {
}
}
}
.device-run-state-dailog {
.el-dialog__header {
border-bottom: 1px solid #b6b6b6;
}
.el-dialog__footer {
border-top: 1px solid #b6b6b6;
padding-bottom: 10px;