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> ></device-run-starts-wrap>
</div> </div>
</el-tab-pane> </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"> <el-tab-pane label="事件" name="eventLog">
<div class="tabs-body"> <div class="tabs-body">
<event-log <event-log
@ -64,9 +69,9 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="设备信息" name="info"> <el-tab-pane label="功能" name="function">
<div class="tabs-body"> <div class="tabs-body">
<info-wrap :infoData="infoData" @updateInfo="updateInfo($event)" /> <function-wrap v-if="activeName === 'function'" :deviceInfo="infoData" :sourceId="deviceId"></function-wrap>
</div> </div>
</el-tab-pane> </el-tab-pane>
@ -105,11 +110,7 @@
/> />
</div> </div>
</el-tab-pane> </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> </el-tabs>
</div> </div>
</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="device-run-starts-wrap">
<div class="cmd-list"> <div class="cmd-list">
<div class="cmd-title-wrap"> <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-select v-model="selectedGroup" placeholder="请选择">
<el-option <el-option v-for="item in groupOptions" :key="item.value" :label="item.label" :value="item.value">
v-for="item in cmdList"
:key="item.cmdKey"
:label="item.cmdName"
:value="item.cmdKey"
>
</el-option> </el-option>
</el-select> </el-select>
</div> </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> <i class="el-icon-s-order" style="font-size: 50px;color: #999"></i>
<div style="color: #909399;margin-top: 10px">暂无数据</div> <div style="color: #909399;margin-top: 10px">暂无数据</div>
</div> </div>
<div <div v-for="(item, index) in filteredProperties" :key="index" class="param-item2">
v-for="(item, indexs) in filterProdObj.properties"
:key="indexs"
class="param-item2"
>
<div class="title-top"> <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> <span class="type-wr" @click="handleShowData(item)">查看</span>
</div> </div>
<div class="value-info"> <div class="value-info">
<div class="value-wrap"> <div class="value-wrap">
<span <span class="val-span">
v-if="item.dataFormatType == 'ENUM'" {{ formatValue(item) }}
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> </span>
</div> </div>
</div> </div>
<div class="time-w"> <div class="time-w">
<span class="time-warp">{{ item.unitName }}</span> <span class="time-warp">{{ item.valueParams.unit || '' }}</span>
<span <span class="time">{{ item.timestamp }}</span>
class="time"
v-text="item.lastTime ? parseTime(item.lastTime) : '--'"
></span>
</div> </div>
</div> </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">
<el-dialog <run-state-table ref="showChart" :deviceId="sourceId" :deviceKey="deviceInfo.deviceKey" :dialogData="dialogData"
:close-on-click-modal="false" :dialogShow="dialogShow" :pro_type="dialogData.funDataType" :prodId="prodId" />
: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"> <div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogShow = false"> </el-button> <el-button size="small" @click="dialogShow = false"> </el-button>
</div> </div>
@ -127,16 +74,59 @@ export default {
properties: [], properties: [],
events: [] 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() { created() {
this.getMetaDate();
this.getProdObj(); this.getProdObj();
this.getCmdList(); // this.getCmdList();
// this.connection(); // this.connection();
}, },
methods: { methods: {
getMetaDate(){
try {
this.metadata = JSON.parse(this.deviceInfo.metadata);
} catch (error) {
console.log(error);
}
},
changeCmd(e) { changeCmd(e) {
if (e != "") { if (e != "") {
const filterObj = const filterObj =
@ -150,42 +140,57 @@ export default {
}); });
} }
}, },
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 (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() { getProdObj() {
let prodJson = { if (this.metadata.properties) {
properties: [], let list = this.metadata.properties.filter((item) => item.show === true) || [];
events: [] let properties = list.map(item => ({
}; ...item,
try { value: null,
prodJson = JSON.parse(this.deviceInfo.prodJson); timestamp: moment().format('YYYY-MM-DD HH:mm:ss')
} catch (error) { }));
console.log(error); this.runtimeProperties = properties;
}
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;
this.$nextTick(() => { this.$nextTick(() => {
this.$set(this.filterProdObj, "properties", properties); this.$set(this, "runtimeProperties", properties);
}); });
} else { } else {
this.prodObj.properties = []; this.runtimeProperties = [];
} }
this.prodObj.events = prodJson.events || []; this.connection();
}, },
formatTime(date) { formatTime(date) {
// //
@ -242,32 +247,58 @@ export default {
this.$forceUpdate(); this.$forceUpdate();
}, },
recursionSet(list, result) { recursionSet(list, result) {
if (result.type === "properties") { if (result.type === "result" && result.topic.endsWith('/report') && result.payload) {
if (result.tags.device_key === this.deviceInfo.deviceKey) { //
this.prodObj.properties = this.prodObj.properties.map(item => ({ this.runtimeProperties = this.runtimeProperties.map(property => {
...item, const propValue = result.payload.value[property.id];
...(item.funKey in result.properties && { if (propValue !== undefined) {
lastValue: result.properties[item.funKey], return {
lastTime: result.properties.timestamp ...property,
}) value: propValue,
})); timestamp: moment(result.payload.timeString).format('YYYY-MM-DD HH:mm:ss')
this.filterProdObj.properties = this.filterProdObj.properties.map( };
item => ({
...item,
...(item.funKey in result.properties && {
lastValue: result.properties[item.funKey],
lastTime: result.properties.timestamp
})
})
);
} }
} return property;
if(result.type === "state"){
this.$emit("updateDeviceState",{
deviceKey: this.deviceInfo.deviceKey,
state: result.state
}); });
//
// 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++) { // for (var i = 0; i < list.length; i++) {
@ -429,11 +460,13 @@ export default {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow: auto; overflow: auto;
//padding: 10px; //padding: 10px;
.el-button--medium { .el-button--medium {
position: absolute; position: absolute;
right: 30px; right: 30px;
} }
.cmd-list { .cmd-list {
width: 100%; width: 100%;
/* height: auto; */ /* height: auto; */
@ -441,9 +474,11 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
cursor: default; cursor: default;
padding: 10px; padding: 10px;
&:first-child { &:first-child {
padding-top: 0; padding-top: 0;
} }
.cmd-title-wrap { .cmd-title-wrap {
width: 100%; width: 100%;
display: flex; display: flex;
@ -451,6 +486,7 @@ export default {
font-size: 16px; font-size: 16px;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
.cmd-title { .cmd-title {
font-size: 14px; font-size: 14px;
color: #a9a6a6; color: #a9a6a6;
@ -459,6 +495,7 @@ export default {
letter-spacing: 1px; letter-spacing: 1px;
} }
} }
.param-item { .param-item {
height: 130px; height: 130px;
// border: 1px solid #777474; // border: 1px solid #777474;
@ -475,6 +512,7 @@ export default {
margin-left: 15px; margin-left: 15px;
margin-top: 15px; margin-top: 15px;
box-shadow: 0px 0px 3px 0px #b7b4b4; box-shadow: 0px 0px 3px 0px #b7b4b4;
.title-top { .title-top {
height: 30px; height: 30px;
display: flex; display: flex;
@ -483,23 +521,27 @@ export default {
border-bottom: 1px dotted #c5c3c3; border-bottom: 1px dotted #c5c3c3;
padding-bottom: 3px; padding-bottom: 3px;
justify-content: space-between; justify-content: space-between;
.name-wr { .name-wr {
font-size: 18px; font-size: 18px;
color: #1890ff; color: #1890ff;
} }
.type-wr { .type-wr {
font-size: 14px; font-size: 14px;
color: #1890ff; color: #1890ff;
} }
} }
.value-info { .value-info {
height: 55px; height: 55px;
display: flex; display: flex;
margin-top: 5px; margin-top: 5px;
align-items: center; align-items: center;
width: 100%; width: 100%;
.value-wrap {
} .value-wrap {}
.val-span { .val-span {
color: #03a9f4; color: #03a9f4;
font-size: 20px; font-size: 20px;
@ -520,6 +562,7 @@ export default {
color: #908c8c; color: #908c8c;
} }
} }
.param-item2 { .param-item2 {
height: 150px; height: 150px;
width: 300px; width: 300px;
@ -534,6 +577,7 @@ export default {
margin-left: -1px; margin-left: -1px;
margin-top: -1px; margin-top: -1px;
border-color: #e0e0e0; border-color: #e0e0e0;
.title-top { .title-top {
height: 30px; height: 30px;
display: flex; display: flex;
@ -542,28 +586,33 @@ export default {
border-bottom: 1px dotted #c5c3c3; border-bottom: 1px dotted #c5c3c3;
padding-bottom: 3px; padding-bottom: 3px;
justify-content: space-between; justify-content: space-between;
.name-wr { .name-wr {
font-size: 18px; font-size: 18px;
// color: #1890ff; // color: #1890ff;
} }
.type-wr { .type-wr {
font-size: 14px; font-size: 14px;
// color: #606266; // color: #606266;
color: #1890ff; color: #1890ff;
} }
.type-wr:hover { .type-wr:hover {
color: #1890ff; color: #1890ff;
cursor: pointer; cursor: pointer;
} }
} }
.value-info { .value-info {
height: 55px; height: 55px;
display: flex; display: flex;
margin-top: 5px; margin-top: 5px;
align-items: center; align-items: center;
width: 100%; width: 100%;
.value-wrap {
} .value-wrap {}
.val-span { .val-span {
color: #03a9f4; color: #03a9f4;
font-size: 20px; font-size: 20px;
@ -586,10 +635,12 @@ export default {
} }
} }
} }
.device-run-state-dailog { .device-run-state-dailog {
.el-dialog__header { .el-dialog__header {
border-bottom: 1px solid #b6b6b6; border-bottom: 1px solid #b6b6b6;
} }
.el-dialog__footer { .el-dialog__footer {
border-top: 1px solid #b6b6b6; border-top: 1px solid #b6b6b6;
padding-bottom: 10px; padding-bottom: 10px;