feat(iot): 新增设备功能调用模块

- 添加功能列表和事件列表的 vuex state管理
- 实现功能调用的简化模式和高级模式
- 增加功能调用结果的展示和记录
- 优化设备信息展示界面,支持功能和事件的显示
- 新增 Monaco 编辑器组件用于 JSON 数据编辑
This commit is contained in:
fhysy 2025-05-21 11:52:10 +08:00
parent bae4c40a65
commit c1ab37bfc1
11 changed files with 1118 additions and 136 deletions

View File

@ -64,6 +64,8 @@
"jsbarcode": "^3.11.6",
"jsencrypt": "3.0.0-rc.1",
"moment": "^2.29.4",
"monaco-editor": "0.24.0",
"monaco-editor-webpack-plugin": "3.1.0",
"nprogress": "0.2.0",
"qrcodejs2": "^0.0.2",
"quill": "1.3.7",

View File

@ -18,6 +18,7 @@ const getters = {
attributeList: state => state.attribute.attributeList,
groupList: state => state.attribute.groupList,
functionList: state => state.attribute.functionList,
eventList: state => state.attribute.eventList,
attributeInfo: state => state.permission.attribute
}
export default getters

View File

@ -6,6 +6,7 @@ const attribute = {
attributeList: [],
groupList: [],
functionList: [],
eventList: [],
},
mutations: {
@ -66,6 +67,25 @@ const attribute = {
GET_FUNCTION_LIST: (state) => {
return state.functionList
},
//功能
SET_EVENT_LIST: (state, list) => {
state.eventList = list
},
PUSH_EVENT: (state, item) => {
state.eventList.push(item)
},
UPDATE_EVENT: (state, {item, idx}) => {
state.eventList[idx] = item
},
DELETE_EVENT: (state, idx) => {
state.eventList.splice(idx,1);
},
GET_EVENT_ITEM: (state, idx) => {
return state.eventList[idx]
},
GET_EVENT_LIST: (state) => {
return state.eventList
},
},
actions: {
@ -85,6 +105,11 @@ const attribute = {
resolve(state.functionList)
})
},
GetEventList({state}) {
return new Promise((resolve, reject) => {
resolve(state.eventList)
})
},
setAttribute({ commit, state }, data){
commit('SET_ATTRIBUTE_LIST', data)
},
@ -93,6 +118,7 @@ const attribute = {
commit('SET_ATTRIBUTE_LIST', data.attrList)
commit('SET_GROUP_LIST', data.groupList)
commit('SET_FUNCTION_LIST', data.functionList)
commit('SET_EVENT_LIST', data.eventList)
console.log('res:', state, data)
},
// 新增 属性
@ -107,6 +133,10 @@ const attribute = {
AddFunction({ commit, state }, data) {
commit('PUSH_FUNCTION', data)
},
// 新增 功能
AddEvent({ commit, state }, data) {
commit('PUSH_EVENT', data)
},
// 获取 属性 单个信息
GetAttributeItem({ commit, state }, idx) {
return new Promise((resolve, reject) => {
@ -119,6 +149,18 @@ const attribute = {
resolve(state.groupList[idx])
})
},
// 获取 功能 单个信息
GetFunctionItem({ commit, state }, idx) {
return new Promise((resolve, reject) => {
resolve(state.functionList[idx])
})
},
// 获取 功能 单个信息
GetEventItem({ commit, state }, idx) {
return new Promise((resolve, reject) => {
resolve(state.eventList[idx])
})
},
// 修改 属性
EditAttribute({ commit, state }, param) {
commit('UPDATE_ATTRIBUTE', param)
@ -131,6 +173,10 @@ const attribute = {
EditFunction({ commit, state }, param) {
commit('UPDATE_FUNCTION', param)
},
// 修改 事件
EditEvent({ commit, state }, param) {
commit('UPDATE_EVENT', param)
},
// 删除分组 删除分组 判断分组是否有属性,有属性就不能删除
DeleteGroup({ commit, state }, idx) {
let groupItem = state.groupList[idx];
@ -153,7 +199,10 @@ const attribute = {
DeleteFunction({ commit, state }, idx) {
commit('DELETE_FUNCTION', idx)
},
// 删除事件
DeleteEvent({ commit, state }, idx) {
commit('DELETE_EVENT', idx)
},
}
}

View File

@ -0,0 +1,106 @@
<!--
my-monaco-editor:
基于monaco-editor封装的vue组件支持尺寸配置的响应式
-->
<template>
<div :style="{height, width}" class="my-monaco-editor"></div>
</template>
<script>
import * as monaco from 'monaco-editor'
export default {
props: {
options: {
type: Object,
default: () => {
}
},
height: {
type: String,
default: '300px'
},
width: {
type: String,
default: '100%'
},
code: {
type: [String,Object],
default: ''
}
},
data() {
return {
// defaultOptions: {
// value: this.code, //
// language: 'json', //
// theme: 'vs-dark', // vs, hc-black, or vs-dark
// automaticLayout: true,
// tabSize: 2,
// scrollBeyondLastLine: false,
// formatOnPaste: true,
// }
}
},
computed: {
standaloneEditorConstructionOptions() {
return Object.assign(this.defaultOptions, this.options)
}
},
methods: {
createMonacoEditor() {
const finalOptions = Object.assign(
{
value: this.code,
language: 'json',
theme: 'vs-dark',
automaticLayout: true,
tabSize: 2,
scrollBeyondLastLine: false,
formatOnPaste: true
},
this.options
);
this.monacoEditor = monaco.editor.create(this.$el, finalOptions);
this.monacoEditor.onDidChangeModelContent(event => {
this.$emit('update:code', this.monacoEditor.getValue())
});
},
reSize() {
this.$nextTick(() => {
this.monacoEditor.layout()
})
}
},
mounted() {
this.createMonacoEditor()
},
watch: {
height() {
this.reSize()
},
width() {
this.reSize()
},
options: {
handler() {
this.$nextTick(() => {
this.monacoEditor.updateOptions(this.standaloneEditorConstructionOptions)
})
},
deep: true
},
code(newVal) {
if (this.monacoEditor && newVal !== this.monacoEditor.getValue()) {
this.monacoEditor.setValue(newVal);
}
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -14,9 +14,259 @@
</div>
</div>
</div>
<div v-if="functionList.length > 0" class="model-function-list">
<div class="function-container">
<!-- 左侧功能列表 -->
<div :span="4" class="function-tabs">
<el-tabs
v-model="activeFunction"
style="min-height: 200px;width: 150px;height: 100%"
tab-position="left"
@tab-click="handleFunctionSelect"
>
<el-tab-pane
v-for="(item, index) in functionList"
:key="index"
:label="item.name"
:name="item.id"
></el-tab-pane>
</el-tabs>
</div>
<!-- 中间参数区域 -->
<div :span="14" class="function-params">
<div class="params-header">
<el-radio-group v-model="functionMode" size="small">
<el-radio-button label="simple">精简模式</el-radio-button>
<el-radio-button label="advanced">高级模式</el-radio-button>
</el-radio-group>
</div>
<div v-if="currentFunction" class="params-content">
<div v-if="functionMode === 'simple'" >
<el-form
:ref="'form_' + currentFunction.id"
:model="formData[currentFunction.id]"
>
<el-table :data="getSimpleTableData()" style="width: 100%">
<el-table-column
label="参数名称"
prop="name"
width="180"
></el-table-column>
<el-table-column
label="输入类型"
prop="type"
width="180"
></el-table-column>
<el-table-column label="值">
<template slot-scope="scope">
<el-form-item
:prop="scope.row.id"
:required="scope.row.expands.required"
>
<!-- 整数类型 -->
<el-input-number
v-if="
['int', 'long'].includes(scope.row.valueType.type)
"
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请输入' + scope.row.name"
size="small"
></el-input-number>
<!-- 浮点数类型 -->
<el-input-number
v-else-if="
['float', 'double'].includes(scope.row.valueType.type)
"
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请输入' + scope.row.name"
:precision="scope.row.valueType.type==='float'?1:2"
size="small"
></el-input-number>
<!-- 字符串类型 -->
<el-input
v-else-if="scope.row.valueType.type === 'string'"
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请输入' + scope.row.name"
size="small"
style="width: 100%"
></el-input>
<!-- 布尔类型 -->
<el-switch
v-else-if="scope.row.valueType.type === 'boolean'"
v-model="formData[currentFunction.id][scope.row.id]"
></el-switch>
<!-- &lt;!&ndash; 日期类型 &ndash;&gt;-->
<!-- <el-date-picker-->
<!-- v-else-if="scope.row.valueType.type === 'date'"-->
<!-- v-model="formData[currentFunction.id][scope.row.id]"-->
<!-- :placeholder="'请选择' + scope.row.name"-->
<!-- size="small"-->
<!-- style="width: 100%"-->
<!-- type="datetime"-->
<!-- ></el-date-picker>-->
<!-- 枚举类型 -->
<el-select
v-else-if="scope.row.valueType.type === 'enum'"
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请选择' + scope.row.name"
size="small"
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in scope.row.valueType
.elements"
:key="optionIndex"
:label="option.text"
:value="option.value"
></el-option>
</el-select>
<!-- 数组类型 -->
<!-- <div-->
<!-- v-else-if="scope.row.valueType.type === 'array'"-->
<!-- class="array-input"-->
<!-- >-->
<!-- <el-tag-->
<!-- v-for="(tag, tagIndex) in formData[-->
<!-- currentFunction.id-->
<!-- ][scope.row.id]"-->
<!-- :key="tagIndex"-->
<!-- closable-->
<!-- size="small"-->
<!-- @close="-->
<!-- handleArrayItemRemove(-->
<!-- currentFunction.id,-->
<!-- scope.row.id,-->
<!-- tagIndex-->
<!-- )-->
<!-- "-->
<!-- >-->
<!-- {{ tag }}-->
<!-- </el-tag>-->
<!-- <el-input-->
<!-- v-if="inputVisible[currentFunction.id + scope.row.id]"-->
<!-- v-model="-->
<!-- inputValue[currentFunction.id + scope.row.id]-->
<!-- "-->
<!-- class="array-input-new"-->
<!-- size="small"-->
<!-- @blur="-->
<!-- handleArrayItemConfirm(-->
<!-- currentFunction.id,-->
<!-- scope.row.id-->
<!-- )-->
<!-- "-->
<!-- @keyup.enter.native="-->
<!-- handleArrayItemConfirm(-->
<!-- currentFunction.id,-->
<!-- scope.row.id-->
<!-- )-->
<!-- "-->
<!-- ></el-input>-->
<!-- <el-button-->
<!-- v-else-->
<!-- size="small"-->
<!-- @click="-->
<!-- showArrayInput(currentFunction.id, scope.row.id)-->
<!-- "-->
<!-- >+ 添加</el-button-->
<!-- >-->
<!-- </div>-->
<!-- &lt;!&ndash; 对象类型 &ndash;&gt;-->
<!-- <el-button-->
<!-- v-else-if="scope.row.valueType.type === 'object'"-->
<!-- size="small"-->
<!-- @click="-->
<!-- showJsonEditor(currentFunction.id, scope.row.id)-->
<!-- "-->
<!-- >编辑对象</el-button-->
<!-- >-->
<el-input
v-else-if="scope.row.valueType.type === 'object'"
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请输入' + scope.row.name"
size="small"
style="width: 100%"
>
<template slot="append">
<i class="el-icon-edit-outline" style="cursor: pointer" @click="showJsonEditor(currentFunction.id,scope.row.id)"></i>
</template>
</el-input>
<!-- 默认输入框 -->
<el-input
v-else
v-model="formData[currentFunction.id][scope.row.id]"
:placeholder="'请输入' + scope.row.name"
size="small"
style="width: 100%"
></el-input>
</el-form-item>
</template>
</el-table-column>
</el-table>
<div class="btn-box">
<el-button
type="primary"
@click="submitFunction(currentFunction,'simple')"
>执行</el-button
>
<el-button @click="resetForm()"
>重置</el-button
>
</div>
</el-form>
</div>
<div v-else class="advanced-mode">
<div class="editor-container">
<my-monaco-editor :code.sync="monacoValue" :options="options" height="360px" ></my-monaco-editor>
</div>
<div class="btn-box">
<el-button type="primary" @click="submitFunction(currentFunction,'advanced')">执行</el-button>
<el-button @click="initFormData('advanced')">重置</el-button>
</div>
</div>
</div>
</div>
<!-- 右侧执行结果区域 -->
<div :span="6" class="function-result">
<div class="result-header">执行结果:</div>
<div ref="resultContent" class="result-content">
<div
v-for="(item, index) in executionResults"
:key="index"
class="result-item"
>
<!-- <div class="result-function">{{ item.functionName }}</div>-->
<!-- <div-->
<!-- :class="item.success ? 'success' : 'error'"-->
<!-- class="result-status"-->
<!-- >-->
<!-- {{ item.success ? "成功" : "失败" }}-->
<!-- </div>-->
<div class="result-data">{{ item.data }}</div>
<div class="result-time">{{ item.time }}</div>
</div>
<!-- <div v-if="executionResults.length === 0" class="no-result">-->
<!-- <div>暂无执行结果</div>-->
<!-- </div>-->
</div>
</div>
</div>
</div>
<div v-else style="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 style="color: #909399;margin-top: 10px">暂无功能</div>
</div>
<el-dialog :visible.sync="open" title="卡管理" width="700px">
@ -42,18 +292,25 @@
>添加卡号</el-button
>
</div>
<el-table v-loading="loading" :data="tableData" height="500" style="width: 100%">
<el-table-column
type="index"
width="70">
</el-table-column>
<el-table
v-loading="loading"
:data="tableData"
height="500"
style="width: 100%"
>
<el-table-column type="index" width="70"> </el-table-column>
<el-table-column align="center" label="卡号" prop="cardNumber">
</el-table-column>
<!-- <el-table-column align="center" label="卡号(16进制)" prop="name">-->
<!-- </el-table-column>-->
<!-- <el-table-column align="center" label="卡号(16进制)" prop="name">-->
<!-- </el-table-column>-->
<el-table-column align="center" label="操作" width="100">
<template slot-scope="scope">
<el-button size="small" type="danger" @click="delCardNumber(scope.row.cardNumber)">删除</el-button>
<el-button
size="small"
type="danger"
@click="delCardNumber(scope.row.cardNumber)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@ -64,6 +321,15 @@
>
</span>
</el-dialog>
<!-- JSON编辑器弹窗 -->
<el-dialog :visible.sync="jsonEditorVisible" title="编辑对象" width="600px">
<my-monaco-editor :code.sync="jsonEditorValue" :options="options" height="360px" ></my-monaco-editor>
<span slot="footer" class="dialog-footer">
<el-button @click="jsonEditorVisible = false"> </el-button>
<el-button type="primary" @click="confirmJsonEdit"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
@ -72,21 +338,29 @@ import DialogTemplate from "@/components/DialogTemplate";
import DeviceAlarmConfig from "@/views/profile/DeviceAlarmConfig/DeviceAlarmConfig";
import DeviceTimingConfig from "@/views/profile/DeviceTimingConfig/DeviceTimingConfig";
import { addCard, delCard, getCard, setCardQuery } from "@/api/iot/cardNumber";
import MyMonacoEditor from '@/views/components/my-monaco-editor'
// import JsonEditor from "@/components/JsonEditor";
// import { callFunction } from "@/api/iot/function";
export default {
name: "functionWrap",
props: ["deviceInfo"],
components: { DialogTemplate, DeviceAlarmConfig, DeviceTimingConfig },
components: { DialogTemplate, DeviceAlarmConfig, DeviceTimingConfig, MyMonacoEditor },
data() {
const validatorNull = (rule, value, callback) => {
callback();
};
return {
monacoValue: 'ok',
options: {},
height: '100px',
width: '500px',
updateState: false,
prodtreeOptions: [],
modelList: [],
showProdSecret: false,
showDevicePassword: false,
loading:false,
loading: false,
temp: {
deviceName: "",
deviceId: undefined
@ -106,60 +380,424 @@ export default {
title: "",
open: false,
newCardNumber: "",
tableData: []
tableData: [],
functionList: [],
functionMode: "simple", // simple-advanced-
formData: {}, //
inputVisible: {}, //
inputValue: {}, //
jsonEditorVisible: false, // JSON
jsonEditorValue: {}, // JSON
currentJsonField: {
// JSON
functionId: "",
inputId: ""
},
activeFunction: "", // ID
currentFunction: null, //
executionResults: [] //
};
},
created() {
if(this.deviceInfo.deviceId){
this.init();
if (this.deviceInfo.deviceId) {
this.getProdFunctionList();
}
},
watch: {
deviceInfo: function(val) {
if (val.deviceId) {
//
this.formData = {};
this.executionResults = [];
this.activeFunction = "";
this.currentFunction = null;
this.monacoValue = "{}";
this.getProdFunctionList();
}
},
functionMode(newVal) {
if (newVal === 'advanced') {
//
this.updateAdvancedModeCode();
}
}
},
methods: {
init(){
console.log("this.d eviceInfo",this.deviceInfo)
let params = {
data: {params:{}},
deviceId: this.deviceInfo.deviceId
};
setCardQuery(params).then(response => {
// if(response?.code == 200){
// let data = response?.data ||[];
// this.tableData = data.map(item=>{
// return {
// cardNumber: item,
// // name: item.cardNumber.toString(16).toUpperCase()
// }
// })
// }else{
// this.tableData = []
//
getSimpleTableData() {
if (!this.currentFunction || !this.currentFunction.inputs) {
return [];
}
return this.currentFunction.inputs.map(input => {
//
let typeName = "";
// switch (input.valueType.type) {
// case "int":
// typeName = "";
// break;
// case "long":
// typeName = "";
// break;
// case "float":
// typeName = "";
// break;
// case "double":
// typeName = "";
// break;
// case "string":
// typeName = "";
// break;
// case "boolean":
// typeName = "";
// break;
// case "date":
// typeName = "";
// break;
// case "enum":
// typeName = "";
// break;
// case "array":
// typeName = "";
// break;
// case "object":
// typeName = "";
// break;
// default:
// typeName = input.valueType.type;
// }
// this.loading = false;
})
return {
...input,
type: input.valueType.type
};
});
},
getProdFunctionList() {
//
this.functionList = [];
this.formData = {};
this.executionResults = [];
let functionList = [];
try {
let json = JSON.parse(this.deviceInfo.prodJson);
functionList = json.functions || [];
} catch (error) {
console.log(error);
}
console.log("this.deviceInfo", this.deviceInfo);
this.functionList = functionList || [];
//
this.initFormData();
//
if (this.functionList.length > 0) {
this.activeFunction = this.functionList[0].id;
this.currentFunction = this.functionList[0];
//
if (this.functionMode === 'advanced') {
this.updateAdvancedModeCode();
}
} else {
this.activeFunction = "";
this.currentFunction = null;
this.monacoValue = "{}";
}
},
//
handleFunctionSelect(e) {
console.log(e._props.name);
this.functionMode = "simple";
this.activeFunction = e._props.name;
this.currentFunction = this.functionList.find(
item => item.id === e._props.name
);
//
if (this.functionMode === 'advanced') {
this.updateAdvancedModeCode();
}
},
//
initFormData(type) {
let formData = {};
this.functionList.forEach(item => {
this.formData[item.id] = {};
formData[item.id] = {};
item.inputs.forEach(input => {
//
if (['int', 'long'].includes(input.valueType.type)) {
formData[item.id][input.id] = 0;
} else if (['float'].includes(input.valueType.type)) {
formData[item.id][input.id] = 0.0;
} else if (['double'].includes(input.valueType.type)) {
formData[item.id][input.id] = 0.00;
} else if (input.valueType.type === 'boolean') {
formData[item.id][input.id] = false;
} else if (input.valueType.type === 'date') {
formData[item.id][input.id] = new Date();
} else if (input.valueType.type === 'enum' && input.valueType.elements && input.valueType.elements.length > 0) {
formData[item.id][input.id] = input.valueType.elements[0].value;
} else if (input.valueType.type === 'array') {
formData[item.id][input.id] = [];
} else if (input.valueType.type === 'object') {
formData[item.id][input.id] = '{}';
} else {
formData[item.id][input.id] = '';
}
});
});
if(type==='advanced'){
let monacoValue= JSON.stringify(formData[this.currentFunction.id], null, 2);
this.$nextTick(() => {
this.$set(this, 'monacoValue', monacoValue);
})
}
this.$nextTick(() => {
this.$set(this, 'formData', formData);
})
},
//
submitFunction(item,type) {
if(type==='simple'){
this.$refs["form_" + item.id].validate(valid => {
if (valid) {
this.loading = true;
//
const processedData = {};
//
for (const key in this.formData[item.id]) {
const value = this.formData[item.id][key];
//
const inputDef = item.inputs.find(input => input.id === key);
//
if (inputDef && inputDef.valueType.type === 'object' && typeof value === 'string') {
try {
processedData[key] = JSON.parse(value);
} catch (error) {
processedData[key] = {};
console.error('解析对象失败:', error);
}
} else {
processedData[key] = value;
}
}
// API
setTimeout(() => {
this.loading = false;
//
const result = {
time: new Date().toLocaleString(),
functionName: item.name,
success: Math.random() > 0.2, // /
data: JSON.stringify(processedData, null, 2)
};
this.executionResults.unshift(result);
//
if (this.executionResults.length > 20) {
this.executionResults.pop();
}
//
if (result.success) {
this.$message.success("功能执行成功");
} else {
this.$message.error("功能执行失败");
}
//
this.$nextTick(() => {
if (this.$refs.resultContent) {
this.$refs.resultContent.scrollTop = 0;
}
});
}, 1000);
}
});
}else{
this.loading = true;
// API
setTimeout(() => {
this.loading = false;
//
const result = {
time: new Date().toLocaleString(),
functionName: item.name,
success: Math.random() > 0.2, // /
data: this.monacoValue
};
this.executionResults.unshift(result);
//
if (this.executionResults.length > 20) {
this.executionResults.pop();
}
//
if (result.success) {
this.$message.success("功能执行成功");
} else {
this.$message.error("功能执行失败");
}
//
this.$nextTick(() => {
if (this.$refs.resultContent) {
this.$refs.resultContent.scrollTop = 0;
}
});
}, 1000);
}
},
//
resetForm() {
this.initFormData();
},
//
updateAdvancedModeCode() {
if (this.currentFunction && this.formData[this.currentFunction.id]) {
//
const processedData = {};
//
for (const key in this.formData[this.currentFunction.id]) {
const value = this.formData[this.currentFunction.id][key];
//
const inputDef = this.currentFunction.inputs.find(input => input.id === key);
//
if (inputDef && inputDef.valueType.type === 'object' && typeof value === 'string') {
try {
processedData[key] = JSON.parse(value);
} catch (error) {
processedData[key] = {};
console.error('解析对象失败:', error);
}
} else {
processedData[key] = value;
}
}
this.monacoValue = JSON.stringify(processedData, null, 2);
}
},
//
showArrayInput(functionId, inputId) {
this.inputVisible[functionId + inputId] = true;
this.inputValue[functionId + inputId] = "";
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
//
handleArrayItemRemove(functionId, inputId, index) {
this.formData[functionId][inputId].splice(index, 1);
},
//
handleArrayItemConfirm(functionId, inputId) {
const inputKey = functionId + inputId;
const inputValue = this.inputValue[inputKey];
if (inputValue) {
this.formData[functionId][inputId].push(inputValue);
}
this.inputVisible[inputKey] = false;
this.inputValue[inputKey] = "";
},
// JSON
showJsonEditor(functionId, inputId) {
this.currentJsonField = {
functionId,
inputId
};
//
let currentValue = this.formData[functionId][inputId];
// JSON
try {
// JSON
if (typeof currentValue === 'string') {
const parsedValue = JSON.parse(currentValue);
// JSON
this.jsonEditorValue = JSON.stringify(parsedValue, null, 2);
} else {
//
this.jsonEditorValue = JSON.stringify(currentValue, null, 2);
}
} catch (error) {
// 使
this.jsonEditorValue = '{}';
this.$message.warning('当前值不是有效的JSON已重置为空对象');
}
this.jsonEditorVisible = true;
},
// JSON
confirmJsonEdit() {
try {
// JSON
const parsedValue = JSON.parse(this.jsonEditorValue);
//
this.$set(
this.formData[this.currentJsonField.functionId],
this.currentJsonField.inputId,
this.jsonEditorValue
);
//
this.jsonEditorVisible = false;
this.$message.success('对象编辑成功');
} catch (error) {
this.$message.error('JSON格式错误请检查输入');
}
},
getCardNumberList() {
this.loading = true;
let params = {
data: {params:{}},
data: { params: {} },
deviceId: this.deviceInfo.deviceId
};
getCard(params).then(response => {
console.log("获取卡号列表", response);
if(response?.code == 200){
let data = response?.data ||[];
this.tableData = data.map(item=>{
return {
cardNumber: item,
// name: item.cardNumber.toString(16).toUpperCase()
}
})
}else{
this.tableData = []
}
this.loading = false;
}).catch(e => {
this.tableData = []
this.loading = false;
});
getCard(params)
.then(response => {
console.log("获取卡号列表", response);
if (response?.code == 200) {
let data = response?.data || [];
this.tableData = data.map(item => {
return {
cardNumber: item
// name: item.cardNumber.toString(16).toUpperCase()
};
});
} else {
this.tableData = [];
}
this.loading = false;
})
.catch(e => {
this.tableData = [];
this.loading = false;
});
},
openLock() {
console.log("开锁");
@ -168,20 +806,20 @@ export default {
cancelButtonText: "取消",
inputPattern: /^[a-z A-z 0-9 $.]+/,
inputType: "password",
inputErrorMessage: "登录密码不能为空",
inputErrorMessage: "登录密码不能为空"
}).then(({ value }) => {
let params = {
data: {
// cmd: "set_switch",
params: {
switch: 0,
},
switch: 0
}
},
deviceId: this.deviceInfo.deviceId,
verifyKey: value,
verifyKey: value
};
setSwitchControl(params).then((res) => {
if(res.code==200){
setSwitchControl(params).then(res => {
if (res.code == 200) {
this.msgSuccess("开锁成功");
}
});
@ -196,12 +834,12 @@ export default {
addCardNumber() {
console.log("新增卡", this.newCardNumber);
let params = {
data: {params:{card:this.newCardNumber}},
data: { params: { card: this.newCardNumber } },
deviceId: this.deviceInfo.deviceId
};
addCard(params).then(response => {
console.log("添加卡号", response);
if(response?.code == 200){
if (response?.code == 200) {
this.msgSuccess("添加成功");
this.getCardNumberList();
}
@ -210,21 +848,21 @@ export default {
delCardNumber(cardNumber) {
console.log("删除卡", cardNumber);
let params = {
data: {params:{card:cardNumber}},
data: { params: { card: cardNumber } },
deviceId: this.deviceInfo.deviceId
};
delCard(params).then(response => {
if(response?.code == 200){
if (response?.code == 200) {
this.msgSuccess("删除成功");
this.getCardNumberList();
}
});
},
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.function-list-box {
.function-list {
.function-item {
@ -239,9 +877,126 @@ export default {
}
}
.model-function-list {
.function-container {
display: flex;
.function-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;
}
}
.function-params {
flex: 5;
padding: 0 20px;
.params-header {
text-align: left;
margin-bottom: 10px;
}
::v-deep .el-form-item{
margin-bottom: 0;
}
.btn-box{
margin-top: 10px;
text-align: right;
}
.advanced-mode {
height: 100%;
.editor-container {
width: 100%;
min-height: 300px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.btn-box {
margin-top: 15px;
text-align: right;
}
}
}
.function-result {
flex: 3;
padding: 0 20px;
.result-header{
font-size: 13px;
padding-bottom: 5px;
}
.result-content{
height: calc(100% - 30px);
max-height: 50vh;
overflow-y: auto;
border: 1px solid #999;
border-radius: 5px;
padding: 10px;
.result-item{
margin-bottom: 10px;
.result-time{
text-align: right;
}
}
}
}
}
.function-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ebeef5;
.function-title {
font-size: 16px;
font-weight: bold;
}
}
.function-content {
.function-item {
display: flex;
padding: 15px;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
.function-item-label {
width: 150px;
font-weight: bold;
padding-top: 10px;
}
.function-item-main {
flex: 1;
.array-input {
.el-tag {
margin-right: 10px;
margin-bottom: 10px;
}
.array-input-new {
width: 100px;
margin-right: 10px;
vertical-align: bottom;
}
}
}
}
}
}
.eldaialog-wrap {
.el-dialog__header {
}
.el-radio {
width: 90px;

View File

@ -232,7 +232,7 @@
<el-input
v-if="form.protocolType === 'OFFICIAL'"
v-model="form.prodKey"
:disabled="form.modelId || form.modelId === 0"
:disabled="form.modelId !== undefined && form.modelId !== null && form.modelId !== ''"
placeholder="请填写产品PK"
></el-input>
<el-input
@ -1069,6 +1069,7 @@ export default {
});
groupList.sort((a, b) => a.sort - b.sort);
}
let functionList = [];
if (
this.$store.getters.functionList &&
@ -1077,10 +1078,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;
}

View File

@ -2,14 +2,27 @@
<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"
/>
<span class="cmd-title">全部</span>
<!-- <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-option>
</el-select>
</div>
<div v-if="filterProdObj.properties.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 prodObj.properties"
v-for="(item, indexs) in filterProdObj.properties"
:key="indexs"
class="param-item2"
>
@ -20,13 +33,13 @@
<div class="value-info">
<div class="value-wrap">
<span
v-if="item.dataFormatType =='ENUM'"
v-if="item.dataFormatType == 'ENUM'"
class="val-span"
v-text="item.dataFormatObj[item.lastValue]||item.lastValue"
v-text="item.dataFormatObj[item.lastValue] || item.lastValue"
>
</span>
<span
v-else-if="item.dataFormatType =='TIME'"
v-else-if="item.dataFormatType == 'TIME'"
class="val-span"
v-text="formatTime(item.lastValue)"
>
@ -51,8 +64,10 @@
></span>
</div>
</div>
</div>
<el-dialog
:close-on-click-modal="false"
:visible.sync="dialogShow"
@ -82,7 +97,7 @@
import { getDeviceFunList, getDeviceCmdList } from "@/api/iot/device";
import { iotWebSocketBaseUrl } from "@/config/env";
import RunStateTable from "./table";
import moment from 'moment';
import moment from "moment";
export default {
name: "RunStartsWrap",
props: ["prodId", "sourceId", "deviceInfo", "wsUrl", "realTimeData"],
@ -91,7 +106,12 @@ export default {
},
data() {
return {
cmdList: [],
cmdList: [
{
cmdKey: "",
cmdName: "全部"
}
],
cmdObject: {},
stompClient: null,
deviceKey: "",
@ -99,10 +119,15 @@ export default {
dialogData: {},
dialogShow: false,
firstWsMassage: true,
prodObj:{
properties:[],
events:[],
}
prodObj: {
properties: [],
events: []
},
filterProdObj: {
properties: [],
events: []
},
activeCmd: ""
};
},
created() {
@ -112,25 +137,40 @@ export default {
// this.connection();
},
methods: {
getProdObj(){
changeCmd(e) {
if (e != "") {
const filterObj =
this.prodObj.properties.filter(item => item.cmdKey === e) || [];
this.$nextTick(() => {
this.$set(this.filterProdObj, "properties", filterObj);
});
} else {
this.$nextTick(() => {
this.$set(this.filterProdObj, "properties", this.prodObj.properties);
});
}
},
getProdObj() {
let prodJson = {
properties: [],
events: []
}
};
try {
prodJson = JSON.parse(this.deviceInfo.prodJson);
} catch (error) {
console.log(error);
}
if(prodJson.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') {
if (dataFormat.list && dataFormat.type === "ENUM") {
try {
item.dataFormatObj = JSON.parse(dataFormat.list.replace(/\\/g, ''));
item.dataFormatObj = JSON.parse(
dataFormat.list.replace(/\\/g, "")
);
} catch (e) {
item.dataFormatObj = {};
}
@ -138,23 +178,23 @@ export default {
}
return item;
});
console.log("properties",properties)
this.$nextTick(()=>{
this.$set(this.prodObj,'properties',properties)
})
}else{
this.prodObj.properties=[];
this.prodObj.properties = properties;
this.$nextTick(() => {
this.$set(this.filterProdObj, "properties", properties);
});
} else {
this.prodObj.properties = [];
}
this.prodObj.events=prodJson.events || [];
this.prodObj.events = prodJson.events || [];
},
formatTime(date){
formatTime(date) {
//
if (date && date.toString().length === 10) {
// 10
return moment(date * 1000).format('YYYY-MM-DD HH:mm:ss');
return moment(date * 1000).format("YYYY-MM-DD HH:mm:ss");
} else if (date && date.toString().length === 13) {
// 13
return moment(date).format('YYYY-MM-DD HH:mm:ss');
return moment(date).format("YYYY-MM-DD HH:mm:ss");
} else {
return date;
}
@ -192,18 +232,18 @@ export default {
this.stompClient.onclose = this.socket_onclose;
},
socket_onmsg(evt) {
console.log("evt.data",evt.data)
console.log("evt.data", evt.data);
this.setListData(evt.data);
},
setListData(data) {
// this.recursionSet(this.cmdList, JSON.parse(data));
this.recursionSet('', JSON.parse(data));
this.recursionSet("", JSON.parse(data));
this.firstWsMassage = false;
this.$forceUpdate();
},
recursionSet(list, result) {
if(result.type === "properties"){
if(result.tags.device_key === this.deviceInfo.deviceKey){
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 && {
@ -211,6 +251,15 @@ export default {
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
})
})
);
}
}
@ -274,9 +323,11 @@ export default {
if (item.dataFormat) {
let dataFormat = JSON.parse(item.dataFormat);
item.dataFormatType = dataFormat.type;
if (dataFormat.list && dataFormat.type === 'ENUM') {
if (dataFormat.list && dataFormat.type === "ENUM") {
try {
item.dataFormatObj = JSON.parse(dataFormat.list.replace(/\\/g, ''));
item.dataFormatObj = JSON.parse(
dataFormat.list.replace(/\\/g, "")
);
} catch (e) {
item.dataFormatObj = {};
}
@ -307,7 +358,15 @@ export default {
cmdType: "1"
};
getDeviceCmdList(params).then(response => {
this.cmdList = response.data;
if (response.data && response.data.length > 0) {
this.cmdList = [
{
cmdKey: "",
cmdName: "全部"
},
...response.data
];
}
});
},
closeWebscoket() {
@ -320,14 +379,16 @@ export default {
},
async processAllRequests(data) {
try {
await Promise.all(data.map(async (row, index) => {
await this.forGetParams(row, index);
}));
await Promise.all(
data.map(async (row, index) => {
await this.forGetParams(row, index);
})
);
this.connection();
} catch (error) {
console.error('Error processing all requests:', error);
console.error("Error processing all requests:", error);
}
},
}
},
destroyed() {
this.closeWebscoket();
@ -370,16 +431,16 @@ export default {
flex-wrap: wrap;
cursor: default;
padding: 10px;
&:first-child{
&:first-child {
padding-top: 0;
}
.cmd-title-wrap {
width: 100%;
display: flex;
// border-bottom: 1px solid #bdb7b7;
height: 35px;
font-size: 16px;
align-items: center;
margin-bottom: 20px;
.cmd-title {
font-size: 14px;
color: #a9a6a6;

View File

@ -357,7 +357,6 @@ export default {
}, 50)
},
setList(list) {
debugger
this.loading = true
this.functionList = []
setTimeout(() => {

View File

@ -1,6 +1,7 @@
<template>
<div class="attribute-form-view">
<el-dialog
:close-on-click-modal="false"
:modal="false"
:title="title"
:visible.sync="visible"
@ -83,6 +84,7 @@
</el-dialog>
<!-- 输入参数弹窗 -->
<el-dialog
:close-on-click-modal="false"
:visible.sync="inputParamOpen"
append-to-body
class="params-eldialog"
@ -126,9 +128,8 @@
v-model="scope.row.value"
:class="{ 'is-error': scope.row.value }"
placeholder="请输入值"
@blur="validateEnumItem(scope.row, 'value', scope.$index)"
></el-input>
<div v-if="scope.row.value" class="el-form-item__error">值不能为空</div>
<div v-if="scope.row.value===''" class="el-form-item__error">值不能为空</div>
</template>
</el-table-column>
<el-table-column label="描述" prop="text">
@ -137,9 +138,8 @@
v-model="scope.row.text"
:class="{ 'is-error': scope.row.text }"
placeholder="请输入描述"
@blur="validateEnumItem(scope.row, 'text', scope.$index)"
></el-input>
<div v-if="scope.row.text" class="el-form-item__error">描述不能为空</div>
<div v-if="scope.row.text===''" class="el-form-item__error">描述不能为空</div>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="80">
@ -317,7 +317,7 @@ export default {
id: '',
name: '',
expands: {},
async: false,
async: true,
inputs: [],
output: {}
};
@ -336,7 +336,7 @@ export default {
id: '',
name: '',
expands: {},
async: false,
async: true,
inputs: [],
output: {}
};
@ -433,20 +433,11 @@ export default {
//
const newEnumItem = JSON.parse(JSON.stringify(defaultenumForm));
newEnumItem.value = false;
newEnumItem.text = false;
newEnumItem.value = '';
newEnumItem.text = '';
this.inputFormObj.form.valueType.elements.push(newEnumItem);
},
validateEnumItem(item, field, index) {
if (field === 'value') {
item.value = !item.value;
} else if (field === 'text') {
item.text = !item.text;
}
return !item.value && !item.text;
},
//
submitInputForm() {
//
@ -465,9 +456,9 @@ export default {
let hasError = false;
for (let i = 0; i < this.inputFormObj.form.valueType.elements.length; i++) {
const item = this.inputFormObj.form.valueType.elements[i];
item.value = !item.value;
item.text = !item.text;
if (item.value || item.text) {
let value = !item.value;
let text = !item.text;
if (value || text) {
hasError = true;
}
}

View File

@ -198,21 +198,27 @@ export default {
handleInitData() {
let arttributeList = [],
groupList = [],
functionList = [];
functionList = [],
eventList = [];
try {
let json = JSON.parse(this.arttributeList);
if(json.properties){
arttributeList = json.properties;
groupList = JSON.parse(this.groupList);
functionList = json.functions;
eventList = json.events;
}else{
arttributeList = json;
groupList = JSON.parse(this.groupList);
functionList = json.functions;
eventList = json.events;
}
} catch (error) {
arttributeList = [];
groupList = [];
functionList = [];
eventList = [];
console.log(error);
}
@ -221,7 +227,8 @@ export default {
this.$store.dispatch("InitAttributeAndGroup", {
attrList: arttributeList,
groupList: groupList,
functionList:functionList
functionList:functionList,
eventList:eventList
});
},
},

View File

@ -3,6 +3,7 @@ const path = require('path')
const defaultSettings = require('./src/settings.js')
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
@ -55,6 +56,7 @@ module.exports = {
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf'},
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js', to: 'cdn/js/liveplayer/'},
]),
new MonacoWebpackPlugin()
],
name: name,
resolve: {
@ -93,7 +95,7 @@ module.exports = {
}]);
config
.when(process.env.NODE_ENV !== 'development'&&process.env.NODE_ENV !== 'drgy'&&process.env.NODE_ENV !== 'dr',
.when(process.env.NODE_ENV !== 'development'&&process.env.NODE_ENV !== 'drgyprod'&&process.env.NODE_ENV !== 'dr',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')