feat(iot): 物联网平台增加设备事件列表功能

- 新增设备事件列表接口和相关组件
- 在设备详情页面添加事件标签页
- 优化设备运行状态和日志的展示样式
- 重构功能定义相关的组件,提高复用性
- 优化 JSON 数据的展示方式
This commit is contained in:
fhysy 2025-05-23 18:02:51 +08:00
parent e2ee83dae8
commit a23c97c513
19 changed files with 679 additions and 239 deletions

View File

@ -80,6 +80,15 @@ export function listDeviceLogList(query) {
});
}
// 获取设备事件列表
export function listDeviceEventLogList(query) {
return request({
url: "/iot/device/event-list",
method: "get",
params: query
});
}
// 获取设备类型列表
export function getDeviceFunList(query) {
return request({

View File

@ -0,0 +1,20 @@
export const dataTypeOption = {
int: 'int(整数型)',
long: 'long(长整数型)',
float: 'float(单精度浮点型)',
double: 'double(双精度浮点数)',
string: 'text(字符串)',
boolean: 'boolean(布尔型)',
date: 'date(时间型)',
enum: 'enum(枚举)',
array: 'array(数组)',
object: 'object(结构体)',
file: 'file(文件)',
password: 'password(密码)',
geoPoint: 'geoPoint(地理位置)',
};
export const eventLevel = {
ordinary:'普通',
warn:'警告',
urgent:'紧急',
}

View File

@ -1,6 +1,6 @@
<template>
<div class="dialog-bit">
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="visible" :width="width" append-to-body class="eldialog-wrap-t" @close="close">
<el-dialog :close-on-click-modal="false" :title="title" :visible="visible" :width="width" append-to-body class="eldialog-wrap-t" @close="close">
<slot name="dialog-center"></slot>
<div slot="footer" class="dialog-footer">
<slot name="dialog-footer"></slot>

View File

@ -37,6 +37,15 @@
></device-run-starts-wrap>
</div>
</el-tab-pane>
<el-tab-pane label="事件" name="eventLog">
<div class="tabs-body">
<event-log
v-if="activeName === 'eventLog'"
:pDevcieInfo="infoData"
:sourceId="infoData.deviceId"
></event-log>
</div>
</el-tab-pane>
<el-tab-pane label="设备信息" name="info">
<div class="tabs-body">
@ -93,6 +102,7 @@ import { getDevice } from "@/api/iot/device";
import InfoWrap from "./info";
import ChildDevice from "./childDevice";
import DeviceLog from "@/views/profile/DeviceDetailsView/deviceLog";
import EventLog from "@/views/profile/DeviceDetailsView/eventLog";
import DeviceRunStartsWrap from "@/views/profile/DeviceRunStarts/index";
import { iotWebSocketBaseUrl } from "@/config/env";
import TriggerWrap from "@/views/profile/DeviceTrigger/index";
@ -108,7 +118,8 @@ export default {
DeviceRunStartsWrap,
TriggerWrap,
EDeviceScene,
functionWrap
functionWrap,
EventLog
},
data() {
return {

View File

@ -347,9 +347,6 @@ export default {
props: ["deviceInfo"],
components: { DialogTemplate, DeviceAlarmConfig, DeviceTimingConfig, MyMonacoEditor },
data() {
const validatorNull = (rule, value, callback) => {
callback();
};
return {
monacoValue: 'ok',
options: {},

View File

@ -428,7 +428,7 @@
<el-dialog
:visible.sync="modelOpen"
class="params-eldialog"
class="model-eldialog"
title="物模型"
top="5vh"
width="800px"
@ -1230,4 +1230,9 @@ export default {
border-radius: 10px;
background: #ffffff;
}
.model-eldialog{
.el-dialog__body{
padding: 10px 0;
}
}
</style>

View File

@ -296,7 +296,7 @@
</dialog-template>
<el-dialog
:visible.sync="modelOpen"
class="params-eldialog"
class="model-eldialog"
title="物模型"
top="5vh"
width="800px"
@ -417,6 +417,7 @@ export default {
JSON.stringify({
properties:this.$store.getters.attributeList,
functions:this.$store.getters.functionList,
events: this.$store.getters.eventList
})
);
},
@ -574,10 +575,18 @@ export default {
functionList = this.$store.getters.functionList;
}
let eventList = [];
if (
this.$store.getters.eventList &&
this.$store.getters.eventList.length > 0
) {
eventList = this.$store.getters.eventList;
}
this.form.prodJson = JSON.stringify({
properties:attrList,
functions:functionList,
events:[]
events:eventList
}) || null;
this.form.remark = JSON.stringify(groupList) || null;
// }
@ -692,4 +701,9 @@ export default {
border-radius: 10px;
background: #ffffff;
}
.model-eldialog{
.el-dialog__body{
padding: 10px 0;
}
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<div class="iot-project-details-warp">
<div class="info-tabs">
<div class="breadcrumb-wrap" v-show="breadcrumbList.length > 1">
<div v-show="breadcrumbList.length > 1" class="breadcrumb-wrap">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span @click="deviceClick(item, index)" :class="item.deviceId === deviceId ? 'breadcrumb-span show-wrap' : 'breadcrumb-span'">{{item.deviceName}}</span>
<span :class="item.deviceId === deviceId ? 'breadcrumb-span show-wrap' : 'breadcrumb-span'" @click="deviceClick(item, index)">{{item.deviceName}}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
@ -17,20 +17,30 @@
<div class="tabs-body protocol-wrap">
<device-run-starts-wrap
v-if="devudeRunState"
:sourceId="deviceId"
:deviceInfo="infoData"
:prodId="infoData.prodKey"
:sourceId="deviceId"
:wsUrl="iotWebSocketBaseUrl"
></device-run-starts-wrap>
</div>
</el-tab-pane>
<el-tab-pane label="事件" name="eventLog">
<div class="tabs-body">
<event-log
v-if="activeName === 'eventLog'"
:pDevcieInfo="infoData"
:sourceId="infoData.deviceId"
></event-log>
</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="child" v-if="infoData.deviceType === 'GATEWAY_CONTROLLER'">
<el-tab-pane v-if="infoData.deviceType === 'GATEWAY_CONTROLLER'" label="子设备" name="child">
<div class="tabs-body">
<child-device
v-if="activeName === 'child'"
@ -57,6 +67,7 @@
import { getDevice } from "@/api/personal/device";
import InfoWrap from "./info";
import DeviceLog from "@/views/profile/DeviceDetailsView/deviceLog";
import EventLog from "@/views/profile/DeviceDetailsView/eventLog";
import ChildDevice from "./childDevice";
import DeviceRunStartsWrap from "@/views/profile/DeviceRunStarts/index";
import { iotWebSocketBaseUrl } from "@/config/env";
@ -67,7 +78,8 @@ export default {
InfoWrap,
DeviceLog,
ChildDevice,
DeviceRunStartsWrap
DeviceRunStartsWrap,
EventLog
},
data() {
return {

View File

@ -50,13 +50,18 @@
<el-table-column :index="indexFormatter" align="center" label="序号" type="index" width="80px"/>
<el-table-column align="left" label="设备名称" prop="deviceName" width="200px"/>
<el-table-column align="left" label="用户账号" prop="userName" width="200px"/>
<el-table-column align="left" label="报文" prop="logData">
<el-table-column align="left" label="内容" prop="logData">
<template v-slot="scope">
<span class="column-part-hide">{{ scope.row.logData }}</span>
<span class="copy-link" @click="copyOnClick(scope.row.logData)">复制</span>
</template>
</el-table-column>
<el-table-column :formatter="dateFormat" align="center" label="创建时间" prop="timestamp" sortable="custom" width="160px"/>
<el-table-column align="center" label="操作" width="100">
<template slot-scope="scope">
<el-button circle icon="el-icon-search" type="text" @click="openDetail(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination
@ -67,13 +72,28 @@
@pagination="getDeviceLogList"
/>
<el-dialog
:visible.sync="logContentShow"
class="params-eldialog"
title="内容"
top="5vh"
width="400px"
>
<json-viewer :expand-depth="10" :value="logContent" copyable style="max-height: calc(100vh - 400px); overflow: auto">
</json-viewer>
<template #footer>
<el-button type="primary" @click="logContentShow = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import {listDeviceLogList} from "@/api/iot/device";
import SelectTableWrap from "@/components/SelectTable/index";
import ParamWrap from "@/components/ParamWrap/deviceParam";
import {formatDate} from "@/utils";
import JsonViewer from "vue-json-viewer";
import "vue-json-viewer/style.css";
const direction = [
{"label": "上报", "value": "0"},
@ -83,7 +103,7 @@ export default {
props: ['sourceId', 'pDevcieInfo'],
components: {
SelectTableWrap,
ParamWrap
JsonViewer
},
data() {
return {
@ -93,23 +113,35 @@ export default {
queryParams: {
pageNum: 1,
pageSize: 10,
orderType: 1, // 1 2
orderType: 2, // 1 2
deviceId: null,
deviceName: null,
direction: direction[0].value,
beginTime: null,
endTime: null
endTime: null,
},
showSearch: true,
//
deviceLogList: [],
total: 0
total: 0,
logContentShow:false,
logContent:''
}
},
created() {
this.getDeviceLogList();
},
methods: {
openDetail(item){
let content = '';
try {
content = JSON.parse(item.logData);
} catch (error) {
console.log(error);
}
this.logContent = content;
this.logContentShow = true;
},
indexFormatter(val) {
return val + 1 + ((this.queryParams.pageNum - 1) * this.queryParams.pageSize);
},

View File

@ -0,0 +1,282 @@
<template>
<div class="iot-child-device">
<div class="event-box">
<!-- 左侧事件类型列表 -->
<div :span="4" class="event-tabs">
<el-tabs v-model="activeEventType" style="min-height: 200px;width: 150px;height: 100%" tab-position="left" @tab-click="handleTabClick">
<el-tab-pane
v-for="item in eventTypeList"
:key="item.id"
:label="item.name"
:name="item.id">
</el-tab-pane>
</el-tabs>
</div>
<!-- 右侧内容区域 -->
<div class="event-main">
<el-form
v-show="showSearch"
ref="queryForm"
:inline="true"
:model="queryParams"
label-width="68px"
>
<el-form-item label="时间">
<el-date-picker
v-model="pickerValue"
:default-time="['00:00:00', '23:59:59']"
end-placeholder="结束日期"
range-separator="至"
size="small"
start-placeholder="开始日期"
type="datetimerange"
@change="pickerChange"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" size="mini" type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="eventList"
:default-sort="{ prop: 'timestamp', order: 'descending' }"
@sort-change="sortChange">
<el-table-column :index="indexFormatter" align="center" label="序号" type="index" width="80px"/>
<el-table-column
v-for="(item,index) in activeEventObj.valueType.properties"
:key="index"
:label="item.name"
:prop="item.id"
align="center"
/>
<el-table-column
:formatter="dateFormat"
align="center"
label="时间"
prop="timestamp"
sortable="custom"
width="160px"
/>
<el-table-column align="center" label="操作" width="100">
<template slot-scope="scope">
<el-button circle icon="el-icon-search" type="text" @click="openDetail(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:limit.sync="queryParams.pageSize"
:page.sync="queryParams.pageNum"
:total="total"
@pagination="getDeviceLogList"
/>
</div>
</div>
<el-dialog
:visible.sync="eventContentShow"
class="params-eldialog"
title="详情"
top="5vh"
width="800px"
>
<json-viewer
:expand-depth="10"
:value="eventContent"
copyable
style="max-height: calc(100vh - 400px); overflow: auto"
>
</json-viewer>
<template #footer>
<el-button type="primary" @click="eventContentShow = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { listDeviceEventLogList, listDeviceLogList } from "@/api/iot/device";
import SelectTableWrap from "@/components/SelectTable/index";
import {formatDate} from "@/utils";
import JsonViewer from "vue-json-viewer";
import "vue-json-viewer/style.css";
export default {
name: 'EventLog',
props: ['sourceId', 'pDevcieInfo'],
components: {
SelectTableWrap,
JsonViewer
},
data() {
return {
direction,
//
pickerValue: null,
queryParams: {
pageNum: 1,
pageSize: 10,
orderType: 2,
deviceId: null,
deviceName: null,
direction: direction[0].value,
eventType: '',
beginTime: null,
endTime: null
},
showSearch: true,
//
eventList: [],
total: 0,
eventTypeList: [],
activeEventType: '',
activeEventObj: {
id: '',
name: '',
valueType:{
type: 'object',
properties: []
}
},
eventContentShow:false,
eventContent:'',
}
},
created() {
this.getProdEventList();
},
methods: {
handleTabClick(tab) {
console.log("tab",tab)
this.eventTypeList.forEach(item => {
if (item.id === tab.name) {
this.activeEventObj = item;
}
});
this.handleQuery();
},
getProdEventList(){
this.eventTypeList = [];
let eventTypeList = [];
try {
let json = JSON.parse(this.pDevcieInfo.prodJson);
eventTypeList = json.events || [];
} catch (error) {
console.log(error);
}
console.log("当前事件列表",eventTypeList);
this.eventTypeList = eventTypeList || [];
if(eventTypeList.length>0){
this.handleTabClick({name:eventTypeList[0].id});
this.activeEventType = eventTypeList[0].id;
}
},
indexFormatter(val) {
return val + 1 + ((this.queryParams.pageNum - 1) * this.queryParams.pageSize);
},
getDeviceLogList() {
this.loading = true;
this.queryParams.deviceId = this.sourceId;
this.queryParams.eventType = this.activeEventType;
listDeviceEventLogList(this.queryParams).then(res => {
this.eventList = res.data.records;
this.total = res.data.total;
this.loading = false;
});
},
handleQuery() {
let pickerValue = this.pickerValue
if (pickerValue != null) {
this.queryParams.beginTime = Date.parse(pickerValue[0]);
this.queryParams.endTime = Date.parse(pickerValue[1]);
}
this.queryParams.pageNum = 1;
this.getDeviceLogList();
},
resetQuery() {
this.resetForm("queryForm");
this.resetPicker();
// if (this.eventTypeList && this.eventTypeList.length > 0) {
// this.queryParams.eventType = this.eventTypeList[0].id;
// this.activeEventObj = this.eventTypeList[0];
// }
this.handleQuery();
},
resetPicker() {
this.pickerValue = null
this.queryParams.beginTime = null
this.queryParams.endTime = null
},
pickerChange(val) {
console.log("pickerChange", val)
if (val == null) {
this.resetPicker();
}
},
dateFormat(val) {
return formatDate(val.timestamp)
},
sortChange(column) {
const sort = {
orderType: column.order === "descending" ? 1 : 2,
};
this.queryParams = Object.assign(this.queryParams, sort);
this.handleQuery();
},
copyOnClick(val) {
let self = this;
this.$copyText(val).then(
function () {
self.$message({
message: "复制成功",
type: "success",
});
},
function () {
self.$message.error("复制失败");
}
);
},
openDetail(item){
let eventContent = '';
try {
eventContent = JSON.parse(item.raw_data);
} catch (error) {
console.log(error);
}
this.eventContent = eventContent;
this.eventContentShow = true;
},
},
}
</script>
<style lang="scss" scoped>
.iot-child-device {
.event-box{
display: flex;
.event-tabs {
width: 130px;
/* 调整标签栏样式 */
::v-deep .is-left.el-tabs__header {
width: 130px;
}
::v-deep .el-tabs--left .el-tabs__item {
width: 100%;
text-align: center;
padding: 0 20px;
}
}
.event-main{
flex: 5;
padding: 0 20px;
}
}
}
</style>

View File

@ -49,6 +49,16 @@
</div>
</el-tab-pane>
<el-tab-pane label="事件" name="eventLog">
<div class="tabs-body">
<event-log
v-if="activeName === 'eventLog'"
:pDevcieInfo="infoData"
:sourceId="infoData.deviceId"
></event-log>
</div>
</el-tab-pane>
<el-tab-pane label="设备信息" name="info">
<div class="tabs-body">
<info-wrap
@ -153,6 +163,7 @@
import { getDevice } from "@/api/iot/device";
import InfoWrap from "./info";
import DeviceLog from "./deviceLog";
import EventLog from "@/views/profile/DeviceDetailsView/eventLog";
import StateManagement from "./stateManagement";
import TriggerWrap from "@/views/profile/DeviceTrigger/index";
import DeviceSelect from "./deviceSelectNav";
@ -179,7 +190,8 @@ export default {
DialogTemplate,
EBatchFirmwareUpgrade,
BatchUpgradeRate,
functionWrap
functionWrap,
EventLog
},
data() {
return {

View File

@ -1,5 +1,5 @@
<template>
<div class="app-container product-customparams-wrap">
<div class="product-customparams-wrap">
<el-row v-show="isOption" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button icon="el-icon-plus" size="mini" type="primary" @click="handleAdd"

View File

@ -4,10 +4,11 @@
:close-on-click-modal="false"
:modal="false"
:title="title"
:visible.sync="visible"
:visible="visible"
class="params-eldialog"
top="5vh"
width="800px">
width="800px"
@close="$emit('update:visible', false)">
<el-form
ref="form"
:model="form"
@ -94,8 +95,9 @@
:visible.sync="inputParamOpen"
append-to-body
class="params-eldialog"
title="输参数"
title="输参数"
width="600px"
@close="handleInputDialogClose"
>
<el-form
ref="inputForm"
@ -176,26 +178,27 @@
<script>
import { mapGetters, mapState } from "vuex";
import JsonEditor from "./jsonEditor";
const dataTypeOption = {
int: 'int(整数型)',
long: 'long(长整数型)',
float: 'float(单精度浮点型)',
double: 'double(双精度浮点数)',
string: 'text(字符串)',
boolean: 'boolean(布尔型)',
date: 'date(时间型)',
enum: 'enum(枚举)',
array: 'array(数组)',
object: 'object(结构体)',
file: 'file(文件)',
password: 'password(密码)',
geoPoint: 'geoPoint(地理位置)',
};
const eventLevel = {
ordinary:'普通',
warn:'警告',
urgent:'紧急',
}
import {dataTypeOption,eventLevel} from "@/basedata/physicalModel"
// const dataTypeOption = {
// int: 'int()',
// long: 'long()',
// float: 'float()',
// double: 'double()',
// string: 'text()',
// boolean: 'boolean()',
// date: 'date()',
// enum: 'enum()',
// array: 'array()',
// object: 'object()',
// file: 'file()',
// password: 'password()',
// geoPoint: 'geoPoint()',
// };
// const eventLevel = {
// ordinary:'',
// warn:'',
// urgent:'',
// }
const defaultenumForm = {
value: '',
@ -253,6 +256,10 @@ export default {
paramIdx: {
type: Number,
default: -1
},
eventList: {
type: Array,
default: []
}
},
computed: {
@ -284,7 +291,8 @@ export default {
inputFormRules: {
id: [
{ required: true, message: "标识不能为空", trigger: "blur" },
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"}
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"},
{ validator: this.validateOutputId, trigger: "blur" }
],
name: [
{ required: true, message: "名称不能为空", trigger: "blur" }
@ -297,7 +305,8 @@ export default {
],
id: [
{ required: true, message: "标识不能为空", trigger: "blur" },
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"}
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"},
{ validator: this.validateEventId, trigger: "blur" }
],
type: [
{ required: true, message: "数据类型不能为空", trigger: "blur" },
@ -333,13 +342,60 @@ watch: {
index: -1,
form: JSON.parse(JSON.stringify(defaultInputForm))
};
} else {
//
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
if (this.$refs.inputForm) {
this.$refs.inputForm.clearValidate();
}
});
}
}
},
methods: {
//
validateEventId(rule, value, callback){
if (this.tempType === "update" && value === this.row.id) {
// ID
callback();
return;
}
//
const isDuplicate = this.eventList && this.eventList.some(item => item.id === value);
if (isDuplicate) {
callback(new Error('标识符已存在,请更换'));
} else {
callback();
}
},
//
validateOutputId(rule, value, callback){
if (this.inputFormObj.isEdit && value === this.form.valueType.properties[this.inputFormObj.index].id) {
// ID
callback();
return;
}
//
const isDuplicate = this.form.valueType.properties.some(item => item.id === value);
if (isDuplicate) {
callback(new Error('参数标识符已存在,请更换'));
} else {
callback();
}
},
submitForm() {
this.$refs["form"].validate((valid) => {
if (valid) {
//
if (!this.form.valueType.properties || this.form.valueType.properties.length === 0) {
this.$message.warning('请至少添加一个输出参数');
return;
}
//
if (this.tempType === "add") {
this.$store.dispatch("AddEvent", this.form).then(() => {
@ -439,13 +495,20 @@ watch: {
//
if (this.inputFormObj.form.valueType.type === 'enum') {
let hasError = false;
const enumValues = new Set();
for (let i = 0; i < this.inputFormObj.form.valueType.elements.length; i++) {
const item = this.inputFormObj.form.valueType.elements[i];
let value = !item.value;
let text = !item.text;
if (value || text) {
hasError = true;
break;
}
if (enumValues.has(item.value)) {
this.$message.warning(`枚举值 "${item.value}" 重复,请修改`);
return;
}
enumValues.add(item.value);
}
if (hasError) {
this.$message.warning('请完善枚举项的值和描述');
@ -465,6 +528,11 @@ watch: {
//
this.inputParamOpen = false;
//
this.$nextTick(() => {
this.$refs.inputForm.clearValidate();
});
//
this.$message.success(this.inputFormObj.isEdit ? '修改成功' : '添加成功');
}
@ -498,7 +566,7 @@ watch: {
* @param {Number} index 行索引
*/
handleInputDelete(row, index) {
this.$confirm('是否确认删除该输参数?', '警告', {
this.$confirm('是否确认删除该输参数?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@ -597,6 +665,14 @@ handleInputDelete(row, index) {
}
this.$forceUpdate()
},
//
handleInputDialogClose() {
this.$nextTick(() => {
if (this.$refs.inputForm) {
this.$refs.inputForm.clearValidate();
}
});
}
},
};
</script>
@ -692,7 +768,6 @@ handleInputDelete(row, index) {
}
}
}
</style>
.enum-box {
border: 1px solid #d8d7d7;
padding: 10px;

View File

@ -1,5 +1,5 @@
<template>
<div class="app-container product-customparams-wrap">
<div class="product-customparams-wrap">
<el-row v-show="isOption" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
@ -65,6 +65,7 @@
<event-form-model
ref="eventForm"
:eventList="eventList"
:paramIdx="eventFormObj.paramIdx"
:row="eventFormObj.eventForm"
:tempType="eventFormObj.modelType"

View File

@ -4,27 +4,34 @@
:close-on-click-modal="false"
:modal="false"
:title="title"
:visible.sync="visible"
:visible="visible"
class="params-eldialog"
top="5vh"
width="800px">
width="800px"
@close="$emit('update:visible', false)">
<el-form
ref="form"
:model="form"
:rules="rules"
label-position="top"
style="padding: 10px"
style="padding: 0 15px"
>
<el-form-item label="标识:" prop="id">
<el-input
v-model="form.id"
placeholder="请输入标识"
@input="inputChange"
/>
</el-form-item>
<el-form-item label="参数名称:" prop="name">
<el-input v-model="form.name" placeholder="请输入参数名称" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="标识:" prop="id">
<el-input
v-model="form.id"
placeholder="请输入标识"
@input="inputChange"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="功能名称:" prop="name">
<el-input v-model="form.name" placeholder="请输入功能名称" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="输入参数:" prop="inputs">
<el-table :data="form.inputs" height="300px">
@ -54,7 +61,6 @@
@click="handleInputUpdate(scope.row, scope.$index)"
>修改</el-button
>
<el-button
icon="el-icon-delete"
size="mini"
@ -90,6 +96,7 @@
class="params-eldialog"
title="输入参数"
width="600px"
@close="handleInputDialogClose"
>
<el-form
ref="inputForm"
@ -170,39 +177,23 @@
<script>
import { mapGetters, mapState } from "vuex";
import JsonEditor from "./jsonEditor";
const dataTypeOption = {
int: 'int(整数型)',
long: 'long(长整数型)',
float: 'float(单精度浮点型)',
double: 'double(双精度浮点数)',
string: 'text(字符串)',
boolean: 'boolean(布尔型)',
date: 'date(时间型)',
enum: 'enum(枚举)',
array: 'array(数组)',
object: 'object(结构体)',
file: 'file(文件)',
password: 'password(密码)',
geoPoint: 'geoPoint(地理位置)',
};
const funRwTypeOption = {
READWRITE: "可上报可下发",
READ: "只上报",
WRITE: "只下发",
};
import {dataTypeOption} from "@/basedata/physicalModel"
// const dataTypeOption = {
// int: 'int()',
// long: 'long()',
// float: 'float()',
// double: 'double()',
// string: 'text()',
// boolean: 'boolean()',
// date: 'date()',
// enum: 'enum()',
// array: 'array()',
// object: 'object()',
// file: 'file()',
// password: 'password()',
// geoPoint: 'geoPoint()',
// };
const funValidTypeOption = {
ENUM: "枚举",
RANGE: "范围",
LENGTH: "长度",
NOT: "不校验",
};
const dataFormatTypeOption = {
null: "无",
ENUM: "枚举",
TIME: "时间戳格式化为yyyy-mm-dd hh:MM:ss",
};
const defaultenumForm = {
value: '',
text: ''
@ -221,6 +212,8 @@ const defaultInputForm = {
}
};
export default {
name: "FunctionFormModel",
components: { JsonEditor },
@ -244,6 +237,10 @@ export default {
paramIdx: {
type: Number,
default: -1
},
functionList: {
type: Array,
default: []
}
},
computed: {
@ -252,9 +249,6 @@ export default {
data() {
return {
dataTypeOption,
funRwTypeOption,
funValidTypeOption,
dataFormatTypeOption,
inputParamOpen: false,
form: {
id: '',
@ -272,7 +266,8 @@ export default {
inputFormRules: {
id: [
{ required: true, message: "标识不能为空", trigger: "blur" },
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"}
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"},
{ validator: this.validateInputId, trigger: "blur" }
],
name: [
{ required: true, message: "参数名称不能为空", trigger: "blur" }
@ -286,7 +281,8 @@ export default {
cmdKey: [{ required: true, message: "分组不能为空", trigger: "blur" }],
id: [
{ required: true, message: "标识不能为空", trigger: "blur" },
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"}
{ pattern: /^[a-zA-Z0-9_\-]+$/,message: '标识只能由数字、字母、下划线、中划线组成',trigger: "blur"},
{ validator: this.validateFunctionId, trigger: "blur" }
],
type: [
{ required: true, message: "数据类型不能为空", trigger: "blur" },
@ -311,58 +307,75 @@ export default {
}
},
watch: {
visible(val) {
if (val) {
//
if (this.tempType === "update" && this.paramIdx >= 0) {
//
this.form = JSON.parse(JSON.stringify(this.row));
} else {
//
this.form = {
id: '',
name: '',
expands: {},
async: true,
inputs: [],
output: {}
visible(val) {
if (val) {
//
if (this.tempType === "update" && this.paramIdx >= 0) {
//
this.form = JSON.parse(JSON.stringify(this.row));
} else {
//
this.form = {
id: '',
name: '',
expands: {},
async: true,
inputs: [],
output: {}
};
}
//
this.inputFormObj = {
isEdit: false,
index: -1,
form: JSON.parse(JSON.stringify(defaultInputForm))
};
} else {
//
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
if (this.$refs.inputForm) {
this.$refs.inputForm.clearValidate();
}
});
}
}
},
methods: {
//
validateFunctionId(rule, value, callback){
if (this.tempType === "update" && value === this.row.id) {
// ID
callback();
return;
}
//
this.inputFormObj = {
isEdit: false,
index: -1,
form: JSON.parse(JSON.stringify(defaultInputForm))
};
}
}
},
methods: {
submitForm() {
this.$refs["form"].validate((valid) => {
if (valid) {
//
if (this.tempType === "add") {
this.$store.dispatch("AddFunction", this.form).then(() => {
this.$message.success("添加成功");
this.$emit("ok");
}).catch(err => {
this.$message.error("添加失败:" + err);
});
} else if (this.tempType === "update") {
this.$store.dispatch("EditFunction", {
item: this.form,
idx: this.paramIdx,
}).then(() => {
this.$message.success("修改成功");
this.$emit("ok");
}).catch(err => {
this.$message.error("修改失败:" + err);
});
}
}
});
//
const isDuplicate = this.functionList && this.functionList.some(item => item.id === value);
if (isDuplicate) {
callback(new Error('标识符已存在,请更换'));
} else {
callback();
}
},
//
validateInputId(rule, value, callback){
if (this.inputFormObj.isEdit && value === this.form.inputs[this.inputFormObj.index].id) {
// ID
callback();
return;
}
//
const isDuplicate = this.form.inputs.some(item => item.id === value);
if (isDuplicate) {
callback(new Error('输入参数标识符已存在,请更换'));
} else {
callback();
}
},
inputDataTypeChange(val) {
// valueTypetype
@ -441,13 +454,20 @@ export default {
//
if (this.inputFormObj.form.valueType.type === 'enum') {
let hasError = false;
const enumValues = new Set();
for (let i = 0; i < this.inputFormObj.form.valueType.elements.length; i++) {
const item = this.inputFormObj.form.valueType.elements[i];
let value = !item.value;
let text = !item.text;
if (value || text) {
hasError = true;
break;
}
if (enumValues.has(item.value)) {
this.$message.warning(`枚举值 "${item.value}" 重复,请修改`);
return;
}
enumValues.add(item.value);
}
if (hasError) {
this.$message.warning('请完善枚举项的值和描述');
@ -467,6 +487,11 @@ export default {
//
this.inputParamOpen = false;
//
this.$nextTick(() => {
this.$refs.inputForm.clearValidate();
});
//
this.$message.success(this.inputFormObj.isEdit ? '修改成功' : '添加成功');
}
@ -540,6 +565,11 @@ handleInputDelete(row, index) {
submitForm: function () {
this.$refs["form"].validate((valid) => {
if (valid) {
//
if (!this.form.inputs || this.form.inputs.length === 0) {
this.$message.warning('请至少添加一个输入参数');
return;
}
//
if (this.tempType === "add") {
this.$store.dispatch("AddFunction", this.form).then(() => {
@ -599,6 +629,14 @@ handleInputDelete(row, index) {
}
this.$forceUpdate()
},
//
handleInputDialogClose() {
this.$nextTick(() => {
if (this.$refs.inputForm) {
this.$refs.inputForm.clearValidate();
}
});
}
},
};
</script>
@ -694,7 +732,6 @@ handleInputDelete(row, index) {
}
}
}
</style>
.enum-box {
border: 1px solid #d8d7d7;
padding: 10px;

View File

@ -1,5 +1,5 @@
<template>
<div class="app-container product-customparams-wrap">
<div class="product-customparams-wrap">
<el-row v-show="isOption" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
@ -14,7 +14,7 @@
<el-table v-loading="loading" :data="functionList" height="300px">
<el-table-column align="center" label="序号" type="index" width="75" />
<el-table-column align="left" label="参数名称" prop="name" />
<el-table-column align="left" label="功能名称" prop="name" />
<el-table-column align="left" label="标识符" prop="id" />
<el-table-column align="left" label="是否异步" prop="async">
<template slot-scope="scope">
@ -53,16 +53,9 @@
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:limit.sync="queryParams.pageSize"
:page.sync="queryParams.pageNum"
:total="total"
@pagination="getList"
/>
<function-form-model
ref="functionForm"
:functionList="functionList"
:paramIdx="functionFormObj.paramIdx"
:row="functionFormObj.functionForm"
:tempType="functionFormObj.modelType"
@ -74,33 +67,11 @@
</template>
<script>
import JsonEditor from "./jsonEditor.vue";
import ParamsJsonWrap from "./paramsJson";
import functionFormModel from "@/views/profile/attribute/functionFormModel";
const funDataTypeOption = {
INT32: "整数型",
FLOAT: "浮点型",
TEXT: "字符串",
BOOL: "布尔型"
};
const funRwTypeOption = {
READWRITE: "可上报可下发",
READ: "只上报",
WRITE: "只下发"
};
const funValidTypeOption = {
ENUM: "枚举",
RANGE: "范围",
LENGTH: "长度",
NOT: "不校验"
};
export default {
name: "FunctionView",
components: {
JsonEditor,
ParamsJsonWrap,
functionFormModel
},
props: {
@ -115,50 +86,15 @@ export default {
},
data() {
return {
funDataTypeOption,
funRwTypeOption,
funValidTypeOption,
functionOpen: false,
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
total: 0,
//
functionList: [],
//
title: "",
//
attributeopen: false,
//
queryParams: {},
//
form: {},
//
rules: {
funName: [
{ required: true, message: "参数名称不能为空", trigger: "blur" }
],
funKey: [
{ required: true, message: "标识符不能为空", trigger: "blur" }
],
funDataType: [
{ required: true, message: "数据类型不能为空", trigger: "blur" }
],
funRwType: [
{ required: true, message: "传输类型不能为空", trigger: "blur" }
],
funValidType: [
{ required: true, message: "校验方式不能为空", trigger: "blur" }
]
},
baseModelId: "",
tempType: "",
functionFormObj: {
modelType: "add",
paramIdx: 0,
@ -177,17 +113,6 @@ export default {
this.getList();
},
methods: {
//
funDataTypeChange(val) {
this.form.funValAcc = null;
this.form.funValidType = "";
},
addJsonObj() {
this.$refs["jsonEditors"].addHandel();
},
jsonEvent(data) {
this.form.funObj = data;
},
//
funValidTypeChange(val) {
this.form.funValMax = "";
@ -399,7 +324,7 @@ export default {
height: 100%;
max-height: calc(100vh - 200px);
overflow: auto;
padding: 20px 5px;
padding: 20px;
}
}
}

View File

@ -1,8 +1,8 @@
<template>
<div class="app-container prodcut-cmd-wrap">
<div class="prodcut-cmd-wrap">
<el-row v-if="isOption" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button icon="el-icon-plus" size="mini" type="primary" @click="handleAdd">创建分组</el-button>
<el-button icon="el-icon-plus" size="mini" type="primary" @click="handleAdd">新增</el-button>
</el-col>
</el-row>

View File

@ -49,13 +49,9 @@
<!-- <span v-else>暂无数据</span>-->
<!-- </div>-->
<div class="right-json">
<div
style="border: 1px solid #ccc; margin-top: -15px;width: 100%; height: 600px; overflow: scroll"
>
<json-viewer :expand-depth="10" :value="paramsList" copyable>
<template v-slot:copy>复制</template>
</json-viewer>
</div>
<json-viewer :expand-depth="10" :value="paramsList" copyable style="width:100%;max-height: calc(100vh - 400px); overflow: auto">
<template v-slot:copy>复制</template>
</json-viewer>
<!-- <div class="option-wrap">-->
<!-- <el-button type="text" @click="copeText">复制</el-button>-->
<!-- </div>-->

View File

@ -37,6 +37,16 @@
</div>
</el-tab-pane>
<el-tab-pane label="事件" name="eventLog">
<div class="tabs-body">
<event-log
v-if="activeName === 'eventLog'"
:pDevcieInfo="infoData"
:sourceId="infoData.deviceId"
></event-log>
</div>
</el-tab-pane>
<el-tab-pane label="设备信息" name="info">
<div class="tabs-body">
<info-wrap :infoData="infoData" @updateInfo="updateInfo($event)" />
@ -80,6 +90,7 @@
import { getDevice } from "@/api/tenant/device";
import InfoWrap from "./info";
import DeviceLog from "@/views/profile/DeviceDetailsView/deviceLog";
import EventLog from "@/views/profile/DeviceDetailsView/eventLog";
import ChildDevice from "./childDevice";
import DeviceRunStartsWrap from "@/views/profile/DeviceRunStarts/index";
import { iotWebSocketBaseUrl } from "@/config/env";
@ -94,7 +105,8 @@ export default {
ChildDevice,
DeviceRunStartsWrap,
TriggerWrap,
functionWrap
functionWrap,
EventLog
},
data() {
return {