commit
30c9f0b872
|
|
@ -51,6 +51,7 @@
|
|||
"fast-xml-parser": "^4.3.2",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsoneditor": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markmap-common": "^0.16.0",
|
||||
|
|
@ -67,7 +68,6 @@
|
|||
"sortablejs": "^1.15.3",
|
||||
"steady-xml": "^0.1.0",
|
||||
"url": "^0.11.3",
|
||||
"v3-jsoneditor": "^0.0.6",
|
||||
"video.js": "^7.21.5",
|
||||
"vue": "3.5.12",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
"@iconify/json": "^2.2.187",
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/jsoneditor": "^9.9.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.21",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ importers:
|
|||
jsencrypt:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
jsoneditor:
|
||||
specifier: ^10.1.3
|
||||
version: 10.1.3
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
|
@ -134,9 +137,6 @@ importers:
|
|||
url:
|
||||
specifier: ^0.11.3
|
||||
version: 0.11.4
|
||||
v3-jsoneditor:
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
video.js:
|
||||
specifier: ^7.21.5
|
||||
version: 7.21.6
|
||||
|
|
@ -183,6 +183,9 @@ importers:
|
|||
'@purge-icons/generated':
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.0
|
||||
'@types/jsoneditor':
|
||||
specifier: ^9.9.5
|
||||
version: 9.9.5
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
|
|
@ -1693,6 +1696,9 @@ packages:
|
|||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
'@types/ace@0.0.52':
|
||||
resolution: {integrity: sha512-YPF9S7fzpuyrxru+sG/rrTpZkC6gpHBPF14W3x70kqVOD+ks6jkYLapk4yceh36xej7K4HYxcyz9ZDQ2lTvwgQ==}
|
||||
|
||||
'@types/conventional-commits-parser@5.0.1':
|
||||
resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
|
||||
|
||||
|
|
@ -1804,6 +1810,9 @@ packages:
|
|||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/jsoneditor@9.9.5':
|
||||
resolution: {integrity: sha512-+Wex7QCirPcG90WA8/CmvDO21KUjz63/G7Yk52Yx/NhWHw5DyeET/L+wjZHAeNeNCCnMOTEtVX5gc3F4UXwXMQ==}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||
|
||||
|
|
@ -2336,8 +2345,8 @@ packages:
|
|||
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
|
||||
hasBin: true
|
||||
|
||||
ace-builds@1.39.0:
|
||||
resolution: {integrity: sha512-MqoZojv4gpc5QyTMor/dS6kmruDV9db9LVZbCiT4qYz6WsDiv4qyG5f7ZPc+wjUl6oLMqgCAsBjo1whdSVyMlQ==}
|
||||
ace-builds@1.39.1:
|
||||
resolution: {integrity: sha512-HcJbBzx8qY66t9gZo/sQu7pi0wO/CFLdYn1LxQO1WQTfIkMfyc7LRnBpsp/oNCSSU/LL83jXHN1fqyOTuIhUjg==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
|
|
@ -4069,8 +4078,8 @@ packages:
|
|||
resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
jsoneditor@9.10.5:
|
||||
resolution: {integrity: sha512-fVZ0NMt+zm4rqTKBv2x7zPdLeaRyKo1EjJkaR1QjK4gEM1rMwICILYSW1OPxSc1qqyAoDaA/eeNrluKoxOocCA==}
|
||||
jsoneditor@10.1.3:
|
||||
resolution: {integrity: sha512-zvbkiduFR19vLMJN1sSvBs9baGDdQRJGmKy6+/vQzDFhx//oEd6WAkrmmTeU4NNk9MAo+ZirENuwbtJXvS9M5g==}
|
||||
|
||||
jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
|
|
@ -4079,8 +4088,8 @@ packages:
|
|||
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
|
||||
engines: {'0': node >= 0.2.0}
|
||||
|
||||
jsonrepair@3.1.0:
|
||||
resolution: {integrity: sha512-idqReg23J0PVRAADmZMc5xQM3xeOX5bTB6OTyMnzq33IXJXmn9iJuWIEvGmrN80rQf4d7uLTMEDwpzujNcI0Rg==}
|
||||
jsonrepair@3.12.0:
|
||||
resolution: {integrity: sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==}
|
||||
hasBin: true
|
||||
|
||||
katex@0.16.11:
|
||||
|
|
@ -4402,9 +4411,6 @@ packages:
|
|||
mlly@1.7.3:
|
||||
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
|
||||
|
||||
mobius1-selectr@2.4.13:
|
||||
resolution: {integrity: sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==}
|
||||
|
||||
moddle-xml@10.1.0:
|
||||
resolution: {integrity: sha512-erWckwLt+dYskewKXJso9u+aAZ5172lOiYxSOqKCPTy7L/xmqH1PoeoA7eVC7oJTt3PqF5TkZzUmbjGH6soQBg==}
|
||||
|
||||
|
|
@ -5561,9 +5567,6 @@ packages:
|
|||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
v3-jsoneditor@0.0.6:
|
||||
resolution: {integrity: sha512-9G0sXWXUn67SBkn46ycWfwPwjuJu/lcsQaNzMtXAR2/95hMV21WfcRNsqJ+vVVrSHQehohB/9fVLwYEXz0u/KA==}
|
||||
|
||||
vanilla-picker@2.12.3:
|
||||
resolution: {integrity: sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==}
|
||||
|
||||
|
|
@ -7334,6 +7337,8 @@ snapshots:
|
|||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
|
||||
'@types/ace@0.0.52': {}
|
||||
|
||||
'@types/conventional-commits-parser@5.0.1':
|
||||
dependencies:
|
||||
'@types/node': 20.17.9
|
||||
|
|
@ -7468,6 +7473,11 @@ snapshots:
|
|||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/jsoneditor@9.9.5':
|
||||
dependencies:
|
||||
'@types/ace': 0.0.52
|
||||
ajv: 6.12.6
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.13
|
||||
|
|
@ -7830,7 +7840,7 @@ snapshots:
|
|||
'@unocss/rule-utils@0.58.9':
|
||||
dependencies:
|
||||
'@unocss/core': 0.58.9
|
||||
magic-string: 0.30.14
|
||||
magic-string: 0.30.17
|
||||
|
||||
'@unocss/rule-utils@66.1.0-beta.5':
|
||||
dependencies:
|
||||
|
|
@ -8272,7 +8282,7 @@ snapshots:
|
|||
jsonparse: 1.3.1
|
||||
through: 2.3.8
|
||||
|
||||
ace-builds@1.39.0: {}
|
||||
ace-builds@1.39.1: {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.14.0):
|
||||
dependencies:
|
||||
|
|
@ -10179,15 +10189,14 @@ snapshots:
|
|||
espree: 9.6.1
|
||||
semver: 7.6.3
|
||||
|
||||
jsoneditor@9.10.5:
|
||||
jsoneditor@10.1.3:
|
||||
dependencies:
|
||||
ace-builds: 1.39.0
|
||||
ace-builds: 1.39.1
|
||||
ajv: 6.12.6
|
||||
javascript-natural-sort: 0.7.1
|
||||
jmespath: 0.16.0
|
||||
json-source-map: 0.6.1
|
||||
jsonrepair: 3.1.0
|
||||
mobius1-selectr: 2.4.13
|
||||
jsonrepair: 3.12.0
|
||||
picomodal: 3.0.0
|
||||
vanilla-picker: 2.12.3
|
||||
|
||||
|
|
@ -10199,7 +10208,7 @@ snapshots:
|
|||
|
||||
jsonparse@1.3.1: {}
|
||||
|
||||
jsonrepair@3.1.0: {}
|
||||
jsonrepair@3.12.0: {}
|
||||
|
||||
katex@0.16.11:
|
||||
dependencies:
|
||||
|
|
@ -10550,8 +10559,6 @@ snapshots:
|
|||
pkg-types: 1.2.1
|
||||
ufo: 1.5.4
|
||||
|
||||
mobius1-selectr@2.4.13: {}
|
||||
|
||||
moddle-xml@10.1.0:
|
||||
dependencies:
|
||||
min-dash: 4.2.2
|
||||
|
|
@ -11811,10 +11818,6 @@ snapshots:
|
|||
|
||||
uuid@10.0.0: {}
|
||||
|
||||
v3-jsoneditor@0.0.6:
|
||||
dependencies:
|
||||
jsoneditor: 9.10.5
|
||||
|
||||
vanilla-picker@2.12.3:
|
||||
dependencies:
|
||||
'@sphinxxxx/color-conversion': 2.2.2
|
||||
|
|
|
|||
|
|
@ -165,5 +165,16 @@ export const DeviceApi = {
|
|||
// 获取设备MQTT连接参数
|
||||
getMqttConnectionParams: async (deviceId: number) => {
|
||||
return await request.get({ url: `/iot/device/mqtt-connection-params`, params: { deviceId } })
|
||||
},
|
||||
|
||||
// 根据ProductKey和DeviceNames获取设备列表
|
||||
getDevicesByProductKeyAndNames: async (productKey: string, deviceNames: string[]) => {
|
||||
return await request.get({
|
||||
url: `/iot/device/list-by-product-key-and-names`,
|
||||
params: {
|
||||
productKey,
|
||||
deviceNames: deviceNames.join(',')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,10 @@ export const ProductApi = {
|
|||
// 查询产品(精简)列表
|
||||
getSimpleProductList() {
|
||||
return request.get({ url: '/iot/product/simple-list' })
|
||||
},
|
||||
|
||||
// 根据ProductKey获取产品信息
|
||||
getProductByKey: async (productKey: string) => {
|
||||
return await request.get({ url: `/iot/product/get-by-key`, params: { productKey } })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,8 +124,8 @@ export const DataBridgeApi = {
|
|||
return await request.delete({ url: `/iot/data-bridge/delete?id=` + id })
|
||||
},
|
||||
|
||||
// 导出数据桥梁 Excel
|
||||
exportDataBridge: async (params) => {
|
||||
return await request.download({ url: `/iot/data-bridge/export-excel`, params })
|
||||
// 查询数据桥梁(精简)列表
|
||||
getSimpleDataBridgeList() {
|
||||
return request.get({ url: '/iot/data-bridge/simple-list' })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ interface TenantBaseDO {
|
|||
|
||||
// 触发条件参数
|
||||
interface TriggerConditionParameter {
|
||||
identifier: string // 标识符(属性、事件、服务)
|
||||
identifier0: string // 标识符(事件、服务)
|
||||
identifier: string // 标识符(属性)
|
||||
operator: string // 操作符
|
||||
value: string // 比较值
|
||||
}
|
||||
|
|
@ -72,6 +73,7 @@ interface TriggerCondition {
|
|||
|
||||
// 触发器配置
|
||||
interface TriggerConfig {
|
||||
key: any // 解决组件索引重用
|
||||
type: number // 触发类型
|
||||
productKey: string // 产品标识
|
||||
deviceNames: string[] // 设备名称数组
|
||||
|
|
@ -98,6 +100,7 @@ interface ActionAlert {
|
|||
|
||||
// 执行器配置
|
||||
interface ActionConfig {
|
||||
key: any // 解决组件索引重用
|
||||
type: number // 执行类型
|
||||
deviceControl?: ActionDeviceControl // 设备控制
|
||||
alert?: ActionAlert // 告警执行
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import JsonEditor from './src/JsonEditor.vue'
|
||||
|
||||
export { JsonEditor }
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div ref="jsonEditorContainer" class="json-editor" :style="{ height }"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import JSONEditor, { JSONEditorMode, JSONEditorOptions } from 'jsoneditor'
|
||||
import 'jsoneditor/dist/jsoneditor.min.css'
|
||||
import { JsonEditorEmits, JsonEditorExpose, JsonEditorProps } from '../types'
|
||||
|
||||
/** 基于 https://github.com/josdejong/jsoneditor 二次封装组件,提供 JSON 编辑器功能。 */
|
||||
defineOptions({ name: 'JsonEditor' })
|
||||
|
||||
const props = withDefaults(defineProps<JsonEditorProps>(), {
|
||||
mode: 'view' as JSONEditorMode,
|
||||
height: '400px',
|
||||
showModeSelection: false,
|
||||
showNavigationBar: false,
|
||||
showStatusBar: false,
|
||||
showMainMenuBar: true
|
||||
})
|
||||
|
||||
const emits = defineEmits<JsonEditorEmits>()
|
||||
const jsonObj = useVModel(props, 'modelValue', emits) as Ref<any>
|
||||
const jsonEditorContainer = ref<HTMLElement | null>(null)
|
||||
let jsonEditor: JSONEditor | null = null
|
||||
|
||||
// 设置默认值
|
||||
const height = props.height
|
||||
|
||||
// 初始化JSONEditor
|
||||
const initJsonEditor = () => {
|
||||
if (!jsonEditorContainer.value) return
|
||||
|
||||
// 合并默认配置和用户自定义配置
|
||||
const options: JSONEditorOptions = {
|
||||
mode: props.mode,
|
||||
modes: props.showModeSelection
|
||||
? (['tree', 'code', 'form', 'text', 'view', 'preview'] as JSONEditorMode[])
|
||||
: undefined,
|
||||
navigationBar: props.showNavigationBar,
|
||||
statusBar: props.showStatusBar,
|
||||
mainMenuBar: props.showMainMenuBar,
|
||||
onChange: () => {
|
||||
jsonObj.value = jsonEditor?.get()
|
||||
emits('change', jsonEditor?.get())
|
||||
},
|
||||
onValidationError: (errors: any) => {
|
||||
emits('error', errors)
|
||||
},
|
||||
...props.options
|
||||
} as JSONEditorOptions
|
||||
|
||||
// 创建JSONEditor实例
|
||||
jsonEditor = new JSONEditor(jsonEditorContainer.value, options)
|
||||
|
||||
// 设置初始值
|
||||
if (jsonObj.value) {
|
||||
jsonEditor.set(jsonObj.value)
|
||||
}
|
||||
|
||||
if (props.mode === 'view') {
|
||||
jsonEditor?.expandAll() // 默认展开全部
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => jsonObj.value,
|
||||
(newValue) => {
|
||||
if (!jsonEditor) return
|
||||
|
||||
try {
|
||||
// 防止无限循环更新
|
||||
const currentJson = jsonEditor.get()
|
||||
if (JSON.stringify(currentJson) !== JSON.stringify(newValue)) {
|
||||
jsonEditor.update(newValue)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('JSON更新失败:', error)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听模式变化
|
||||
watch(
|
||||
() => props.mode,
|
||||
(newMode) => {
|
||||
if (!jsonEditor) return
|
||||
try {
|
||||
jsonEditor.setMode(newMode)
|
||||
} catch (error) {
|
||||
console.error('切换模式失败:', error)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
initJsonEditor()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (jsonEditor) {
|
||||
jsonEditor.destroy()
|
||||
jsonEditor = null
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose<JsonEditorExpose>({
|
||||
// 获取编辑器实例,以便可以调用更多JSONEditor的原生方法
|
||||
getEditor: () => jsonEditor
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 隐藏 Ace 编辑器的 powered by ace 标记 */
|
||||
:deep(.jsoneditor-menu) {
|
||||
/* 隐藏 powered by ace 标记 */
|
||||
.jsoneditor-poweredBy {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { JSONEditorOptions, JSONEditorMode } from 'jsoneditor'
|
||||
|
||||
export interface JsonEditorProps {
|
||||
/**
|
||||
* JSON数据,支持双向绑定
|
||||
*/
|
||||
modelValue: any
|
||||
|
||||
/**
|
||||
* 编辑器模式
|
||||
* @default 'tree'
|
||||
*/
|
||||
mode?: JSONEditorMode
|
||||
|
||||
/**
|
||||
* 编辑器高度
|
||||
* @default '400px'
|
||||
*/
|
||||
height?: string
|
||||
|
||||
/**
|
||||
* 是否显示模式选择下拉菜单
|
||||
* @default false
|
||||
*/
|
||||
showModeSelection?: boolean
|
||||
|
||||
/**
|
||||
* 是否显示导航栏
|
||||
* @default false
|
||||
*/
|
||||
showNavigationBar?: boolean
|
||||
|
||||
/**
|
||||
* 是否显示状态栏
|
||||
* @default true
|
||||
*/
|
||||
showStatusBar?: boolean
|
||||
|
||||
/**
|
||||
* 是否显示主菜单栏
|
||||
* @default true
|
||||
*/
|
||||
showMainMenuBar?: boolean
|
||||
|
||||
/**
|
||||
* JSONEditor配置选项
|
||||
* @see https://github.com/josdejong/jsoneditor/blob/develop/docs/api.md
|
||||
*/
|
||||
options?: Partial<JSONEditorOptions>
|
||||
}
|
||||
|
||||
/**
|
||||
* JsonEditor组件触发的事件
|
||||
*/
|
||||
export interface JsonEditorEmits {
|
||||
/**
|
||||
* 数据更新时触发
|
||||
*/
|
||||
(e: 'update:modelValue', value: any): void
|
||||
|
||||
/**
|
||||
* 数据变化时触发
|
||||
*/
|
||||
(e: 'change', value: any): void
|
||||
|
||||
/**
|
||||
* 验证错误时触发
|
||||
*/
|
||||
(e: 'error', errors: any): void
|
||||
}
|
||||
|
||||
/**
|
||||
* JsonEditor组件暴露的方法
|
||||
*/
|
||||
export interface JsonEditorExpose {
|
||||
/**
|
||||
* 获取原始的JSONEditor实例
|
||||
*/
|
||||
getEditor: () => any
|
||||
}
|
||||
|
|
@ -247,5 +247,6 @@ export enum DICT_TYPE {
|
|||
IOT_DATA_BRIDGE_DIRECTION_ENUM = 'iot_data_bridge_direction_enum', // 桥梁方向
|
||||
IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum', // 桥梁类型
|
||||
IOT_DEVICE_MESSAGE_TYPE_ENUM = 'iot_device_message_type_enum', // IoT 设备消息类型枚举
|
||||
IOT_RULE_SCENE_TRIGGER_TYPE_ENUM = 'iot_rule_scene_trigger_type_enum' // IoT 场景流转的触发类型枚举
|
||||
IOT_RULE_SCENE_TRIGGER_TYPE_ENUM = 'iot_rule_scene_trigger_type_enum', // IoT 场景流转的触发类型枚举
|
||||
IOT_RULE_SCENE_ACTION_TYPE_ENUM = 'iot_rule_scene_action_type_enum' // IoT 规则场景的触发类型枚举
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@
|
|||
<template #default="scope">
|
||||
<el-radio
|
||||
v-model="selectedId"
|
||||
:label="scope.row.id"
|
||||
:value="scope.row.id"
|
||||
@change="() => handleRadioChange(scope.row)"
|
||||
>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,24 +8,10 @@
|
|||
class="my-4"
|
||||
description="如需编辑文件,请点击下方编辑按钮"
|
||||
/>
|
||||
|
||||
<!-- JSON 编辑器:读模式 -->
|
||||
<Vue3Jsoneditor
|
||||
v-if="isEditing"
|
||||
<JsonEditor
|
||||
v-model="config"
|
||||
:options="editorOptions"
|
||||
height="500px"
|
||||
currentMode="code"
|
||||
@error="onError"
|
||||
/>
|
||||
<!-- JSON 编辑器:写模式 -->
|
||||
<Vue3Jsoneditor
|
||||
v-else
|
||||
v-model="config"
|
||||
:options="editorOptions"
|
||||
height="500px"
|
||||
currentMode="view"
|
||||
v-loading.fullscreen.lock="loading"
|
||||
:mode="isEditing ? 'code' : 'view'"
|
||||
height="600px"
|
||||
@error="onError"
|
||||
/>
|
||||
<div class="mt-5 text-center">
|
||||
|
|
@ -40,9 +26,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Vue3Jsoneditor from 'v3-jsoneditor/src/Vue3Jsoneditor.vue'
|
||||
import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||
import { jsonParse } from '@/utils'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'DeviceDetailConfig' })
|
||||
|
||||
const props = defineProps<{
|
||||
device: DeviceVO
|
||||
|
|
@ -63,12 +51,6 @@ watchEffect(() => {
|
|||
})
|
||||
|
||||
const isEditing = ref(false) // 编辑状态
|
||||
const editorOptions = computed(() => ({
|
||||
mainMenuBar: false,
|
||||
navigationBar: false,
|
||||
statusBar: false
|
||||
})) // JSON 编辑器的选项
|
||||
|
||||
/** 启用编辑模式的函数 */
|
||||
const enableEdit = () => {
|
||||
isEditing.value = true
|
||||
|
|
@ -112,8 +94,11 @@ const updateDeviceConfig = async () => {
|
|||
}
|
||||
|
||||
/** 处理 JSON 编辑器错误的函数 */
|
||||
const onError = (e: any) => {
|
||||
console.log('onError', e)
|
||||
const onError = (errors: any) => {
|
||||
if (isEmpty(errors)) {
|
||||
hasJsonError.value = false
|
||||
return
|
||||
}
|
||||
hasJsonError.value = true
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
<template #default="scope">
|
||||
<el-radio
|
||||
v-model="selectedId"
|
||||
:label="scope.row.id"
|
||||
:value="scope.row.id"
|
||||
@change="() => handleRadioChange(scope.row)"
|
||||
>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<el-divider content-position="left">触发器配置</el-divider>
|
||||
<device-listener
|
||||
v-for="(trigger, index) in formData.triggers"
|
||||
:key="index"
|
||||
:key="trigger.key"
|
||||
:model-value="trigger"
|
||||
@update:model-value="(val) => (formData.triggers[index] = val)"
|
||||
class="mb-10px"
|
||||
|
|
@ -49,10 +49,21 @@
|
|||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-divider content-position="left">执行动作配置</el-divider>
|
||||
<el-form-item label="执行器数组" prop="actionConfigs">
|
||||
<!-- <el-input v-model="formData.actions" placeholder="请输入执行器数组" />-->
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">执行器配置</el-divider>
|
||||
<action-executor
|
||||
v-for="(action, index) in formData.actions"
|
||||
:key="action.key"
|
||||
:model-value="action"
|
||||
@update:model-value="(val) => (formData.actions[index] = val)"
|
||||
class="mb-10px"
|
||||
>
|
||||
<el-button type="danger" round size="small" @click="removeAction(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</action-executor>
|
||||
<el-button class="ml-10px!" type="primary" size="small" @click="addAction">
|
||||
添加执行器
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
|
@ -65,15 +76,19 @@
|
|||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RuleSceneApi } from '@/api/iot/rule/scene'
|
||||
import DeviceListener from './components/DeviceListener.vue'
|
||||
import DeviceListener from './components/listener/DeviceListener.vue'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import {
|
||||
ActionConfig,
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleScene,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import ActionExecutor from './components/action/ActionExecutor.vue'
|
||||
import { generateUUID } from '@/utils'
|
||||
|
||||
/** IoT 场景联动表单 */
|
||||
defineOptions({ name: 'IotRuleSceneForm' })
|
||||
|
|
@ -87,7 +102,8 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加
|
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<IotRuleScene>({
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
triggers: [] as TriggerConfig[]
|
||||
triggers: [] as TriggerConfig[],
|
||||
actions: [] as ActionConfig[]
|
||||
} as IotRuleScene)
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '场景名称不能为空', trigger: 'blur' }],
|
||||
|
|
@ -100,6 +116,7 @@ const formRef = ref() // 表单 Ref
|
|||
/** 添加触发器 */
|
||||
const addTrigger = () => {
|
||||
formData.value.triggers.push({
|
||||
key: generateUUID(), // 解决组件索引重用
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE,
|
||||
productKey: '',
|
||||
deviceNames: [],
|
||||
|
|
@ -114,9 +131,19 @@ const addTrigger = () => {
|
|||
}
|
||||
/** 移除触发器 */
|
||||
const removeTrigger = (index: number) => {
|
||||
const newTriggers = [...formData.value.triggers]
|
||||
newTriggers.splice(index, 1)
|
||||
formData.value.triggers = newTriggers
|
||||
formData.value.triggers.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加执行器 */
|
||||
const addAction = () => {
|
||||
formData.value.actions.push({
|
||||
key: generateUUID(), // 解决组件索引重用
|
||||
type: IotRuleSceneActionTypeEnum.DEVICE_CONTROL
|
||||
} as ActionConfig)
|
||||
}
|
||||
/** 移除执行器 */
|
||||
const removeAction = (index: number) => {
|
||||
formData.value.actions.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
|
|
@ -129,7 +156,11 @@ const open = async (type: string, id?: number) => {
|
|||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await RuleSceneApi.getRuleScene(id)
|
||||
const data = (await RuleSceneApi.getRuleScene(id)) as IotRuleScene
|
||||
// 解决组件索引重用
|
||||
data.triggers?.forEach((item) => (item.key = generateUUID()))
|
||||
data.actions?.forEach((item) => (item.key = generateUUID()))
|
||||
formData.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
|
|
@ -165,7 +196,8 @@ const submitForm = async () => {
|
|||
const resetForm = () => {
|
||||
formData.value = {
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
triggers: [] as TriggerConfig[]
|
||||
triggers: [] as TriggerConfig[],
|
||||
actions: [] as ActionConfig[]
|
||||
} as IotRuleScene
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,242 +0,0 @@
|
|||
<template>
|
||||
<div class="m-10px">
|
||||
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">触发条件</span>
|
||||
<el-select
|
||||
v-model="triggerConfig.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择触发条件"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_TRIGGER_TYPE_ENUM)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">产品</span>
|
||||
<el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">设备</span>
|
||||
<el-button type="primary" @click="openDeviceSelect" size="small" plain>
|
||||
{{ isEmpty(deviceList) ? '选择设备' : triggerConfig.deviceNames.join(',') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 删除触发器 -->
|
||||
<div class="absolute top-auto right-16px bottom-auto">
|
||||
<el-tooltip content="删除触发器" placement="top">
|
||||
<slot></slot>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 设备触发器条件 -->
|
||||
<template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
|
||||
<div
|
||||
class="bg-[#dbe5f6] flex p-10px"
|
||||
v-for="(condition, index) in triggerConfig.conditions"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center mr-10px h-a">
|
||||
<el-select
|
||||
v-model="condition.type"
|
||||
@change="condition.parameters = []"
|
||||
class="!w-160px"
|
||||
clearable
|
||||
placeholder=""
|
||||
>
|
||||
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
|
||||
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="">
|
||||
<DeviceListenerCondition
|
||||
v-for="(parameter, index2) in condition.parameters"
|
||||
:key="index2"
|
||||
:model-value="parameter"
|
||||
:thingModels="thingModels(condition)"
|
||||
@update:model-value="(val) => (condition.parameters[index2] = val)"
|
||||
class="mb-10px last:mb-0"
|
||||
>
|
||||
<el-tooltip content="删除参数" placement="top">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
size="small"
|
||||
@click="removeConditionParameter(condition.parameters, index2)"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</DeviceListenerCondition>
|
||||
</div>
|
||||
<!-- 添加参数 -->
|
||||
<div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
|
||||
<el-tooltip content="添加参数" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
size="small"
|
||||
@click="addConditionParameter(condition.parameters)"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 删除条件 -->
|
||||
<div
|
||||
class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
|
||||
>
|
||||
<el-tooltip content="删除条件" placement="top">
|
||||
<el-button type="danger" size="small" @click="removeCondition(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 定时触发 -->
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.TIMER"
|
||||
class="bg-[#dbe5f6] flex items-center justify-between p-10px"
|
||||
>
|
||||
<span class="w-120px">CRON 表达式</span>
|
||||
<crontab v-model="triggerConfig.cronExpression" />
|
||||
</div>
|
||||
<!-- 设备触发才可以设置多个触发条件 -->
|
||||
<el-text
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="ml-10px!"
|
||||
type="primary"
|
||||
@click="addCondition"
|
||||
>
|
||||
添加触发条件
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 产品、设备的选择 -->
|
||||
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<DeviceTableSelect
|
||||
ref="deviceTableSelectRef"
|
||||
multiple
|
||||
:product-id="product?.id"
|
||||
@success="handleDeviceSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import DeviceListenerCondition from './DeviceListenerCondition.vue'
|
||||
import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
|
||||
import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
|
||||
import { ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceVO } from '@/api/iot/device/device'
|
||||
import { ThingModelApi } from '@/api/iot/thingmodel'
|
||||
import {
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
TriggerCondition,
|
||||
TriggerConditionParameter,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 场景联动之监听器组件 */
|
||||
defineOptions({ name: 'DeviceListener' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 添加触发条件 */
|
||||
const addCondition = () => {
|
||||
triggerConfig.value.conditions?.push({
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
parameters: []
|
||||
})
|
||||
}
|
||||
/** 移除触发条件 */
|
||||
const removeCondition = (index: number) => {
|
||||
triggerConfig.value.conditions?.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加参数 */
|
||||
const addConditionParameter = (conditionParameters: TriggerConditionParameter[]) => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
conditionParameters.push({} as TriggerConditionParameter)
|
||||
}
|
||||
/** 移除参数 */
|
||||
const removeConditionParameter = (
|
||||
conditionParameters: TriggerConditionParameter[],
|
||||
index: number
|
||||
) => {
|
||||
conditionParameters.splice(index, 1)
|
||||
}
|
||||
|
||||
const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
|
||||
const product = ref<ProductVO>()
|
||||
const deviceList = ref<DeviceVO[]>([])
|
||||
/** 处理产品选择 */
|
||||
const handleProductSelect = (val: ProductVO) => {
|
||||
product.value = val
|
||||
triggerConfig.value.productKey = val.productKey
|
||||
deviceList.value = []
|
||||
getThingModelTSL()
|
||||
}
|
||||
/** 处理设备选择 */
|
||||
const handleDeviceSelect = (val: DeviceVO[]) => {
|
||||
deviceList.value = val
|
||||
triggerConfig.value.deviceNames = val.map((item) => item.deviceName)
|
||||
}
|
||||
/** 打开设备选择器 */
|
||||
const openDeviceSelect = () => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
deviceTableSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 获取产品物模型 */
|
||||
const thingModelTSL = ref<any>()
|
||||
const thingModels = computed(() => (condition: TriggerCondition) => {
|
||||
switch (condition.type) {
|
||||
case IotDeviceMessageTypeEnum.PROPERTY:
|
||||
return thingModelTSL.value.properties
|
||||
// TODO puhui999: 服务和事件后续考虑
|
||||
case IotDeviceMessageTypeEnum.SERVICE:
|
||||
return thingModelTSL.value.services
|
||||
case IotDeviceMessageTypeEnum.EVENT:
|
||||
return thingModelTSL.value.events
|
||||
}
|
||||
return []
|
||||
})
|
||||
const getThingModelTSL = async () => {
|
||||
if (!product.value) {
|
||||
return
|
||||
}
|
||||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product.value.id)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
<template>
|
||||
<div class="device-listener-condition">
|
||||
<el-select
|
||||
v-model="conditionParameter.identifier"
|
||||
class="!w-240px mr-10px"
|
||||
clearable
|
||||
placeholder="请选择物模型"
|
||||
>
|
||||
<el-option
|
||||
v-for="thingModel in thingModels"
|
||||
:key="thingModel.identifier"
|
||||
:label="thingModel.name"
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<ConditionSelector
|
||||
v-model="conditionParameter.operator"
|
||||
:data-type="getDataType"
|
||||
class="!w-180px mr-10px"
|
||||
/>
|
||||
<!-- TODO puhui999: 输入值范围校验? -->
|
||||
<el-input
|
||||
v-if="
|
||||
conditionParameter.operator !==
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value
|
||||
"
|
||||
v-model="conditionParameter.value"
|
||||
class="!w-240px mr-10px"
|
||||
placeholder="请输入值"
|
||||
>
|
||||
<template v-if="getUnitName" #append> {{ getUnitName }} </template>
|
||||
</el-input>
|
||||
<!-- 按钮插槽 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ConditionSelector from './ConditionSelector.vue'
|
||||
import {
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum,
|
||||
TriggerConditionParameter
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
defineOptions({ name: 'DeviceListenerCondition' })
|
||||
const props = defineProps<{ modelValue: any; thingModels: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter>
|
||||
|
||||
/** 获得物模型属性类型 */
|
||||
const getDataType = computed(() => {
|
||||
const model = props.thingModels?.find(
|
||||
(item: any) => item.identifier === conditionParameter.value.identifier
|
||||
)
|
||||
// 属性
|
||||
if (model?.dataSpecs) {
|
||||
return model.dataSpecs.dataType
|
||||
}
|
||||
return ''
|
||||
})
|
||||
/** 获得属性单位 */
|
||||
const getUnitName = computed(() => {
|
||||
const model = props.thingModels?.find(
|
||||
(item: any) => item.identifier === conditionParameter.value.identifier
|
||||
)
|
||||
// 属性
|
||||
if (model?.dataSpecs) {
|
||||
return model.dataSpecs.unitName
|
||||
}
|
||||
// TODO puhui999: 先不考虑服务和事件的情况
|
||||
// 服务和事件
|
||||
// if (model?.outputParams) {
|
||||
// return model.dataSpecs.unitName
|
||||
// }
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" :appendToBody="true" v-loading="loading">
|
||||
<div class="flex h-600px">
|
||||
<!-- 左侧物模型属性(view模式) -->
|
||||
<div class="w-1/2 border-r border-gray-200 pr-2 overflow-auto">
|
||||
<JsonEditor :model-value="thingModel" mode="view" height="600px" />
|
||||
</div>
|
||||
|
||||
<!-- 右侧JSON编辑器(code模式) -->
|
||||
<div class="w-1/2 pl-2 overflow-auto">
|
||||
<JsonEditor v-model="editableModelTSL" mode="code" height="600px" @error="handleError" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave" :disabled="hasJsonError">保存</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'ThingModelDualView' })
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any // 物模型的值
|
||||
thingModel: any[] // 物模型
|
||||
}>()
|
||||
const emits = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const message = useMessage()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('物模型编辑器') // 弹窗的标题
|
||||
const editableModelTSL = ref([
|
||||
{
|
||||
identifier: '对应左侧 identifier 属性值',
|
||||
value: '如果 identifier 是 int 类型则输入数字,具体查看产品物模型定义'
|
||||
}
|
||||
]) // 物模型数据
|
||||
const hasJsonError = ref(false) // 是否有 JSON 格式错误
|
||||
const loading = ref(false) // 加载状态
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
try {
|
||||
// 数据回显
|
||||
if (props.modelValue) {
|
||||
editableModelTSL.value = JSON.parse(props.modelValue)
|
||||
}
|
||||
} catch (e) {
|
||||
message.error('物模型编辑器参数')
|
||||
console.error(e)
|
||||
} finally {
|
||||
dialogVisible.value = true
|
||||
// 重置状态
|
||||
hasJsonError.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 暴露方法供父组件调用
|
||||
|
||||
/** 保存修改 */
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await message.confirm('确定要保存物模型参数吗?')
|
||||
emits('update:modelValue', JSON.stringify(editableModelTSL.value))
|
||||
message.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 处理 JSON 编辑器错误的函数 */
|
||||
const handleError = (errors: any) => {
|
||||
if (isEmpty(errors)) {
|
||||
hasJsonError.value = false
|
||||
return
|
||||
}
|
||||
hasJsonError.value = true
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="flex items-center">
|
||||
<!-- 数值类型输入框 -->
|
||||
<template v-if="isNumeric">
|
||||
<el-input
|
||||
v-model="value"
|
||||
class="w-1/1!"
|
||||
:placeholder="`请输入${dataSpecs.unitName ? dataSpecs.unitName : '数值'}`"
|
||||
>
|
||||
<template #append> {{ dataSpecs.unit }} </template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<!-- 布尔类型使用开关 -->
|
||||
<template v-else-if="isBool">
|
||||
<el-switch
|
||||
v-model="value"
|
||||
size="large"
|
||||
:active-text="dataSpecsList[1].name"
|
||||
:active-value="dataSpecsList[1].value"
|
||||
:inactive-text="dataSpecsList[0].name"
|
||||
:inactive-value="dataSpecsList[0].value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 枚举类型使用下拉选择 -->
|
||||
<template v-else-if="isEnum">
|
||||
<el-select class="w-1/1!" v-model="value">
|
||||
<el-option
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<!-- 时间类型使用时间选择器 -->
|
||||
<template v-else-if="isDate">
|
||||
<el-date-picker
|
||||
class="w-1/1!"
|
||||
v-model="value"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择日期时间"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 文本类型使用文本输入框 -->
|
||||
<template v-else-if="isText">
|
||||
<el-input
|
||||
class="w-1/1!"
|
||||
v-model="value"
|
||||
:maxlength="dataSpecs?.length"
|
||||
:show-word-limit="true"
|
||||
placeholder="请输入文本"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- array、struct 直接输入 -->
|
||||
<template v-else>
|
||||
<el-input class="w-1/1!" :model-value="value" disabled placeholder="请输入值">
|
||||
<template #append>
|
||||
<el-button type="primary" @click="openJsonEditor">编辑</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- array、struct 类型数据编辑 -->
|
||||
<ThingModelDualView
|
||||
ref="thingModelDualViewRef"
|
||||
v-model="value"
|
||||
:thing-model="dataSpecsList"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { DataSpecsDataType } from '@/views/iot/thingmodel/config'
|
||||
import ThingModelDualView from './ThingModelDualView.vue'
|
||||
|
||||
/** 物模型属性参数输入组件 */
|
||||
defineOptions({ name: 'ThingModelParamInput' })
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any // 物模型的值
|
||||
thingModel: any // 物模型
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'change'])
|
||||
const value = useVModel(props, 'modelValue', emits)
|
||||
|
||||
const thingModelDualViewRef = ref<InstanceType<typeof ThingModelDualView>>()
|
||||
const openJsonEditor = () => {
|
||||
thingModelDualViewRef.value?.open()
|
||||
}
|
||||
|
||||
/** 计算属性:判断数据类型 */
|
||||
const isNumeric = computed(() =>
|
||||
[DataSpecsDataType.INT, DataSpecsDataType.FLOAT, DataSpecsDataType.DOUBLE].includes(
|
||||
props.thingModel?.dataType as any
|
||||
)
|
||||
)
|
||||
const isBool = computed(() => props.thingModel?.dataType === DataSpecsDataType.BOOL)
|
||||
const isEnum = computed(() => props.thingModel?.dataType === DataSpecsDataType.ENUM)
|
||||
const isDate = computed(() => props.thingModel?.dataType === DataSpecsDataType.DATE)
|
||||
const isText = computed(() => props.thingModel?.dataType === DataSpecsDataType.TEXT)
|
||||
/** 获取数据规格 */
|
||||
const dataSpecs = computed(() => {
|
||||
if (isNumeric.value || isDate.value || isText.value) {
|
||||
return props.thingModel?.dataSpecs || {}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
const dataSpecsList = computed(() => {
|
||||
if (
|
||||
isBool.value ||
|
||||
isEnum.value ||
|
||||
[DataSpecsDataType.ARRAY, DataSpecsDataType.STRUCT].includes(props.thingModel?.dataType)
|
||||
) {
|
||||
return props.thingModel?.dataSpecsList || []
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
/** 物模型切换重置值 */
|
||||
watch(
|
||||
() => props.thingModel?.dataType,
|
||||
(_, oldValue) => {
|
||||
if (!oldValue) {
|
||||
return
|
||||
}
|
||||
value.value = undefined
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="m-10px">
|
||||
<!-- 产品设备回显区域 -->
|
||||
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">执行动作</span>
|
||||
<el-select
|
||||
v-model="actionConfig.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择执行类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_ACTION_TYPE_ENUM)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">产品</span>
|
||||
<el-button type="primary" @click="handleSelectProduct" size="small" plain>
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">设备</span>
|
||||
<el-button type="primary" @click="handleSelectDevice" size="small" plain>
|
||||
{{ isEmpty(deviceList) ? '选择设备' : deviceList.map((d) => d.deviceName).join(',') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 删除执行器 -->
|
||||
<div class="absolute top-auto right-16px bottom-auto">
|
||||
<el-tooltip content="删除执行器" placement="top">
|
||||
<slot></slot>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备控制执行器 -->
|
||||
<DeviceControlAction
|
||||
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
|
||||
:model-value="actionConfig.deviceControl"
|
||||
:product-id="product?.id"
|
||||
:product-key="product?.productKey"
|
||||
@update:model-value="(val) => (actionConfig.deviceControl = val)"
|
||||
/>
|
||||
|
||||
<!-- 告警执行器 -->
|
||||
<AlertAction
|
||||
v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.ALERT"
|
||||
:model-value="actionConfig.alert"
|
||||
@update:model-value="(val) => (actionConfig.alert = val)"
|
||||
/>
|
||||
|
||||
<!-- 数据桥接执行器 -->
|
||||
<DataBridgeAction
|
||||
v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.DATA_BRIDGE"
|
||||
:model-value="actionConfig.dataBridgeId"
|
||||
@update:model-value="(val) => (actionConfig.dataBridgeId = val)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 产品、设备的选择 -->
|
||||
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<DeviceTableSelect
|
||||
ref="deviceTableSelectRef"
|
||||
multiple
|
||||
:product-id="product?.id"
|
||||
@success="handleDeviceSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
|
||||
import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
|
||||
import DeviceControlAction from './DeviceControlAction.vue'
|
||||
import AlertAction from './AlertAction.vue'
|
||||
import DataBridgeAction from './DataBridgeAction.vue'
|
||||
import { ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||
import {
|
||||
ActionAlert,
|
||||
ActionConfig,
|
||||
ActionDeviceControl,
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleSceneActionTypeEnum
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 场景联动之执行器组件 */
|
||||
defineOptions({ name: 'ActionExecutor' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const actionConfig = useVModel(props, 'modelValue', emits) as Ref<ActionConfig>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 初始化执行器结构 */
|
||||
const initActionConfig = () => {
|
||||
if (!actionConfig.value) {
|
||||
actionConfig.value = { type: IotRuleSceneActionTypeEnum.DEVICE_CONTROL } as ActionConfig
|
||||
}
|
||||
|
||||
// 设备控制执行器初始化
|
||||
if (
|
||||
actionConfig.value.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL &&
|
||||
!actionConfig.value.deviceControl
|
||||
) {
|
||||
actionConfig.value.deviceControl = {
|
||||
productKey: '',
|
||||
deviceNames: [],
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
data: {}
|
||||
} as ActionDeviceControl
|
||||
}
|
||||
|
||||
// 告警执行器初始化
|
||||
if (actionConfig.value.type === IotRuleSceneActionTypeEnum.ALERT && !actionConfig.value.alert) {
|
||||
actionConfig.value.alert = {} as ActionAlert
|
||||
}
|
||||
|
||||
// 数据桥接执行器初始化
|
||||
if (
|
||||
actionConfig.value.type === IotRuleSceneActionTypeEnum.DATA_BRIDGE &&
|
||||
!actionConfig.value.dataBridgeId
|
||||
) {
|
||||
actionConfig.value.dataBridgeId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 产品和设备选择 */
|
||||
const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
|
||||
const product = ref<ProductVO>()
|
||||
const deviceList = ref<DeviceVO[]>([])
|
||||
|
||||
/** 处理选择产品 */
|
||||
const handleSelectProduct = () => {
|
||||
productTableSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 处理选择设备 */
|
||||
const handleSelectDevice = () => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
deviceTableSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 处理产品选择成功 */
|
||||
const handleProductSelect = (val: ProductVO) => {
|
||||
product.value = val
|
||||
if (actionConfig.value.deviceControl) {
|
||||
actionConfig.value.deviceControl.productKey = val.productKey
|
||||
}
|
||||
// 重置设备选择
|
||||
deviceList.value = []
|
||||
if (actionConfig.value.deviceControl) {
|
||||
actionConfig.value.deviceControl.deviceNames = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理设备选择成功 */
|
||||
const handleDeviceSelect = (val: DeviceVO[]) => {
|
||||
deviceList.value = val
|
||||
if (actionConfig.value.deviceControl) {
|
||||
actionConfig.value.deviceControl.deviceNames = val.map((item) => item.deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听执行类型变化,初始化对应配置 */
|
||||
watch(
|
||||
() => actionConfig.value.type,
|
||||
() => {
|
||||
initActionConfig()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/**
|
||||
* 初始化产品回显信息
|
||||
*/
|
||||
const initProductInfo = async () => {
|
||||
if (!actionConfig.value.deviceControl?.productKey) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用新的API直接通过productKey获取产品信息
|
||||
const productData = await ProductApi.getProductByKey(
|
||||
actionConfig.value.deviceControl.productKey
|
||||
)
|
||||
if (productData) {
|
||||
product.value = productData
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取产品信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设备回显信息
|
||||
*/
|
||||
const initDeviceInfo = async () => {
|
||||
if (
|
||||
!actionConfig.value.deviceControl?.productKey ||
|
||||
!actionConfig.value.deviceControl?.deviceNames?.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用新的API直接通过productKey和deviceNames获取设备列表
|
||||
const deviceData = await DeviceApi.getDevicesByProductKeyAndNames(
|
||||
actionConfig.value.deviceControl.productKey,
|
||||
actionConfig.value.deviceControl.deviceNames
|
||||
)
|
||||
|
||||
if (deviceData && deviceData.length > 0) {
|
||||
deviceList.value = deviceData
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
initActionConfig()
|
||||
|
||||
// 初始化产品和设备回显
|
||||
if (actionConfig.value.deviceControl) {
|
||||
await initProductInfo()
|
||||
await initDeviceInfo()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class="bg-[#dbe5f6] p-10px">
|
||||
<div class="flex items-center mb-10px">
|
||||
<span class="mr-10px w-80px">接收方式</span>
|
||||
<el-select
|
||||
v-model="alertConfig.receiveType"
|
||||
class="!w-160px"
|
||||
clearable
|
||||
placeholder="选择接收方式"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) in IotAlertConfigReceiveTypeEnum"
|
||||
:key="value"
|
||||
:label="key === 'SMS' ? '短信' : key === 'MAIL' ? '邮箱' : '通知'"
|
||||
:value="value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div v-if="alertConfig.receiveType === IotAlertConfigReceiveTypeEnum.SMS" class="flex items-center mb-10px">
|
||||
<span class="mr-10px w-80px">手机号码</span>
|
||||
<el-select
|
||||
v-model="alertConfig.phoneNumbers"
|
||||
class="!w-360px"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请输入手机号码"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="alertConfig.receiveType === IotAlertConfigReceiveTypeEnum.MAIL" class="flex items-center mb-10px">
|
||||
<span class="mr-10px w-80px">邮箱地址</span>
|
||||
<el-select
|
||||
v-model="alertConfig.emails"
|
||||
class="!w-360px"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请输入邮箱地址"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-10px w-80px align-self-start">通知内容</span>
|
||||
<el-input
|
||||
v-model="alertConfig.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
class="!w-360px"
|
||||
placeholder="请输入通知内容"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { ActionAlert, IotAlertConfigReceiveTypeEnum } from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 告警执行器组件 */
|
||||
defineOptions({ name: 'AlertAction' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const alertConfig = useVModel(props, 'modelValue', emits) as Ref<ActionAlert>
|
||||
|
||||
/** 初始化告警执行器结构 */
|
||||
const initAlertConfig = () => {
|
||||
if (!alertConfig.value) {
|
||||
alertConfig.value = {
|
||||
receiveType: IotAlertConfigReceiveTypeEnum.NOTIFY,
|
||||
phoneNumbers: [],
|
||||
emails: [],
|
||||
content: ''
|
||||
} as ActionAlert
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
initAlertConfig()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div class="bg-[#dbe5f6] p-10px">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-10px w-80px">数据桥梁</span>
|
||||
<el-select v-model="dataBridgeId" class="!w-240px" clearable placeholder="选择数据桥接">
|
||||
<el-option
|
||||
v-for="bridge in dataBridgeList"
|
||||
:key="bridge.id"
|
||||
:label="bridge.name"
|
||||
:value="bridge.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { DataBridgeApi } from '@/api/iot/rule/databridge'
|
||||
|
||||
/** 数据桥接执行器组件 */
|
||||
defineOptions({ name: 'DataBridgeAction' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const dataBridgeId = useVModel(props, 'modelValue', emits)
|
||||
|
||||
const dataBridgeList = ref<any[]>([]) /** 数据桥接列表 */
|
||||
|
||||
/** 获取数据桥接列表 */
|
||||
const getDataBridgeList = async () => {
|
||||
dataBridgeList.value = await DataBridgeApi.getSimpleDataBridgeList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataBridgeList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
<template>
|
||||
<div class="bg-[#dbe5f6] flex p-10px">
|
||||
<div class="flex flex-col items-center justify-center mr-10px h-a">
|
||||
<el-select v-model="deviceControlConfig.type" class="!w-160px" clearable placeholder="">
|
||||
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="">
|
||||
<div
|
||||
class="flex items-center justify-around mb-10px last:mb-0"
|
||||
v-for="(parameter, index) in parameters"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 选择服务 -->
|
||||
<el-select
|
||||
v-if="IotDeviceMessageTypeEnum.SERVICE === deviceControlConfig.type"
|
||||
v-model="parameter.identifier0"
|
||||
class="!w-240px mr-10px"
|
||||
clearable
|
||||
placeholder="请选择服务"
|
||||
>
|
||||
<el-option
|
||||
v-for="thingModel in getThingModelTSLServices"
|
||||
:key="thingModel.identifier"
|
||||
:label="thingModel.name"
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="parameter.identifier"
|
||||
class="!w-240px mr-10px"
|
||||
clearable
|
||||
placeholder="请选择物模型"
|
||||
>
|
||||
<el-option
|
||||
v-for="thingModel in thingModels(parameter?.identifier0)"
|
||||
:key="thingModel.identifier"
|
||||
:label="thingModel.name"
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<ThingModelParamInput
|
||||
class="!w-240px mr-10px"
|
||||
v-model="parameter.value"
|
||||
:thing-model="
|
||||
thingModels(parameter?.identifier0)?.find(
|
||||
(item) => item.identifier === parameter.identifier
|
||||
)
|
||||
"
|
||||
/>
|
||||
<el-tooltip content="删除参数" placement="top">
|
||||
<el-button type="danger" circle size="small" @click="removeParameter(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 添加参数 -->
|
||||
<div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
|
||||
<el-tooltip content="添加参数" placement="top">
|
||||
<el-button type="primary" circle size="small" @click="addParameter">
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { ThingModelApi } from '@/api/iot/thingmodel'
|
||||
import {
|
||||
ActionDeviceControl,
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import ThingModelParamInput from '../ThingModelParamInput.vue'
|
||||
|
||||
/** 设备控制执行器组件 */
|
||||
defineOptions({ name: 'DeviceControlAction' })
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
productId?: number
|
||||
productKey?: string
|
||||
}>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const deviceControlConfig = useVModel(props, 'modelValue', emits) as Ref<ActionDeviceControl>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 执行器参数 */
|
||||
const parameters = ref<{ identifier: string; value: any; identifier0?: string }[]>([])
|
||||
const addParameter = () => {
|
||||
if (!props.productId) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
if (parameters.value.length >= thingModels.value().length) {
|
||||
message.warning(`该产品只有${thingModels.value().length}个物模型!!!`)
|
||||
return
|
||||
}
|
||||
parameters.value.push({ identifier: '', value: undefined })
|
||||
}
|
||||
const removeParameter = (index: number) => {
|
||||
parameters.value.splice(index, 1)
|
||||
}
|
||||
watch(
|
||||
() => parameters.value,
|
||||
(newVal) => {
|
||||
if (isEmpty(newVal)) {
|
||||
return
|
||||
}
|
||||
for (const parameter of newVal) {
|
||||
if (isEmpty(parameter.identifier)) {
|
||||
break
|
||||
}
|
||||
// 单独处理服务的情况
|
||||
if (IotDeviceMessageTypeEnum.SERVICE === deviceControlConfig.value.type) {
|
||||
if (!parameter.identifier0) {
|
||||
continue
|
||||
}
|
||||
deviceControlConfig.value.data[parameter.identifier0] = {
|
||||
identifier: parameter.identifier,
|
||||
value: parameter.value
|
||||
}
|
||||
continue
|
||||
}
|
||||
deviceControlConfig.value.data[parameter.identifier] = parameter.value
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/** 初始化设备控制执行器结构 */
|
||||
const initDeviceControlConfig = () => {
|
||||
if (!deviceControlConfig.value) {
|
||||
deviceControlConfig.value = {
|
||||
productKey: '',
|
||||
deviceNames: [],
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
data: {}
|
||||
} as ActionDeviceControl
|
||||
} else {
|
||||
// 单独处理服务的情况
|
||||
if (IotDeviceMessageTypeEnum.SERVICE === deviceControlConfig.value.type) {
|
||||
// 参数回显
|
||||
parameters.value = Object.entries(deviceControlConfig.value.data).map(([key, value]) => ({
|
||||
identifier0: key,
|
||||
identifier: value.identifier,
|
||||
value: value.value
|
||||
}))
|
||||
return
|
||||
}
|
||||
// 参数回显
|
||||
parameters.value = Object.entries(deviceControlConfig.value.data).map(([key, value]) => ({
|
||||
identifier: key,
|
||||
value: value
|
||||
}))
|
||||
}
|
||||
|
||||
// 确保data对象存在
|
||||
if (!deviceControlConfig.value.data) {
|
||||
deviceControlConfig.value.data = {}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取产品物模型 */
|
||||
const thingModelTSL = ref<any>()
|
||||
const getThingModelTSL = async () => {
|
||||
if (!props.productId) {
|
||||
return
|
||||
}
|
||||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(props.productId)
|
||||
}
|
||||
const thingModels = computed(() => (identifier?: string): any[] => {
|
||||
if (isEmpty(thingModelTSL.value)) {
|
||||
return []
|
||||
}
|
||||
switch (deviceControlConfig.value.type) {
|
||||
case IotDeviceMessageTypeEnum.PROPERTY:
|
||||
return thingModelTSL.value?.properties || []
|
||||
case IotDeviceMessageTypeEnum.SERVICE:
|
||||
const service = thingModelTSL.value.services?.find(
|
||||
(item: any) => item.identifier === identifier
|
||||
)
|
||||
return service?.inputParams || []
|
||||
}
|
||||
return []
|
||||
})
|
||||
/** 获取物模型服务 */
|
||||
const getThingModelTSLServices = computed(() => thingModelTSL.value?.services || [])
|
||||
|
||||
/** 监听 productId 变化 */
|
||||
watch(
|
||||
() => props.productId,
|
||||
() => {
|
||||
getThingModelTSL()
|
||||
if (deviceControlConfig.value && deviceControlConfig.value.productKey === props.productKey) {
|
||||
return
|
||||
}
|
||||
// 当产品ID变化时,清空原有数据
|
||||
deviceControlConfig.value.data = {}
|
||||
parameters.value = []
|
||||
}
|
||||
)
|
||||
|
||||
/** 监听消息类型变化 */
|
||||
watch(
|
||||
() => deviceControlConfig.value.type,
|
||||
() => {
|
||||
// 切换消息类型时清空参数
|
||||
deviceControlConfig.value.data = {}
|
||||
parameters.value = []
|
||||
if (deviceControlConfig.value.type === IotDeviceMessageTypeEnum.PROPERTY) {
|
||||
deviceControlConfig.value.identifier = IotDeviceMessageIdentifierEnum.PROPERTY_SET
|
||||
} else if (deviceControlConfig.value.type === IotDeviceMessageTypeEnum.SERVICE) {
|
||||
deviceControlConfig.value.identifier = IotDeviceMessageIdentifierEnum.SERVICE_INVOKE
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
initDeviceControlConfig()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="m-10px">
|
||||
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">触发条件</span>
|
||||
<el-select
|
||||
v-model="triggerConfig.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择触发条件"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_TRIGGER_TYPE_ENUM)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">产品</span>
|
||||
<el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<span class="mr-10px">设备</span>
|
||||
<el-button type="primary" @click="openDeviceSelect" size="small" plain>
|
||||
{{ isEmpty(deviceList) ? '选择设备' : triggerConfig.deviceNames.join(',') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 删除触发器 -->
|
||||
<div class="absolute top-auto right-16px bottom-auto">
|
||||
<el-tooltip content="删除触发器" placement="top">
|
||||
<slot></slot>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 设备触发器条件 -->
|
||||
<template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
|
||||
<div
|
||||
class="bg-[#dbe5f6] flex p-10px"
|
||||
v-for="(condition, index) in triggerConfig.conditions"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center mr-10px h-a">
|
||||
<el-select
|
||||
v-model="condition.type"
|
||||
@change="condition.parameters = []"
|
||||
class="!w-160px"
|
||||
clearable
|
||||
placeholder=""
|
||||
>
|
||||
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
|
||||
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="w-70%">
|
||||
<DeviceListenerCondition
|
||||
v-for="(parameter, index2) in condition.parameters"
|
||||
:key="index2"
|
||||
:model-value="parameter"
|
||||
:condition-type="condition.type"
|
||||
:thingModels="thingModels(condition)"
|
||||
@update:model-value="(val) => (condition.parameters[index2] = val)"
|
||||
class="mb-10px last:mb-0"
|
||||
>
|
||||
<el-tooltip content="删除参数" placement="top">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
size="small"
|
||||
@click="removeConditionParameter(condition.parameters, index2)"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</DeviceListenerCondition>
|
||||
</div>
|
||||
<!-- 添加参数 -->
|
||||
<div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
|
||||
<el-tooltip content="添加参数" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
size="small"
|
||||
@click="addConditionParameter(condition.parameters)"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 删除条件 -->
|
||||
<div
|
||||
class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
|
||||
>
|
||||
<el-tooltip content="删除条件" placement="top">
|
||||
<el-button type="danger" size="small" @click="removeCondition(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 定时触发 -->
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.TIMER"
|
||||
class="bg-[#dbe5f6] flex items-center justify-between p-10px"
|
||||
>
|
||||
<span class="w-120px">CRON 表达式</span>
|
||||
<crontab v-model="triggerConfig.cronExpression" />
|
||||
</div>
|
||||
<!-- 设备触发才可以设置多个触发条件 -->
|
||||
<el-text
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="ml-10px!"
|
||||
type="primary"
|
||||
@click="addCondition"
|
||||
>
|
||||
添加触发条件
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 产品、设备的选择 -->
|
||||
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<DeviceTableSelect
|
||||
ref="deviceTableSelectRef"
|
||||
multiple
|
||||
:product-id="product?.id"
|
||||
@success="handleDeviceSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import DeviceListenerCondition from './DeviceListenerCondition.vue'
|
||||
import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
|
||||
import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
|
||||
import { ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||
import { ThingModelApi } from '@/api/iot/thingmodel'
|
||||
import {
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
TriggerCondition,
|
||||
TriggerConditionParameter,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 场景联动之监听器组件 */
|
||||
defineOptions({ name: 'DeviceListener' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 添加触发条件 */
|
||||
const addCondition = () => {
|
||||
triggerConfig.value.conditions?.push({
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
parameters: []
|
||||
})
|
||||
}
|
||||
/** 移除触发条件 */
|
||||
const removeCondition = (index: number) => {
|
||||
triggerConfig.value.conditions?.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加参数 */
|
||||
const addConditionParameter = (conditionParameters: TriggerConditionParameter[]) => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
conditionParameters.push({} as TriggerConditionParameter)
|
||||
}
|
||||
/** 移除参数 */
|
||||
const removeConditionParameter = (
|
||||
conditionParameters: TriggerConditionParameter[],
|
||||
index: number
|
||||
) => {
|
||||
conditionParameters.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 产品和设备选择引用 */
|
||||
const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
|
||||
const product = ref<ProductVO>()
|
||||
const deviceList = ref<DeviceVO[]>([])
|
||||
/** 处理产品选择 */
|
||||
const handleProductSelect = (val: ProductVO) => {
|
||||
product.value = val
|
||||
triggerConfig.value.productKey = val.productKey
|
||||
deviceList.value = []
|
||||
getThingModelTSL()
|
||||
}
|
||||
/** 处理设备选择 */
|
||||
const handleDeviceSelect = (val: DeviceVO[]) => {
|
||||
deviceList.value = val
|
||||
triggerConfig.value.deviceNames = val.map((item) => item.deviceName)
|
||||
}
|
||||
/** 打开设备选择器 */
|
||||
const openDeviceSelect = () => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
deviceTableSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化产品回显信息
|
||||
*/
|
||||
const initProductInfo = async () => {
|
||||
if (!triggerConfig.value.productKey) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用新的API直接通过productKey获取产品信息
|
||||
const productData = await ProductApi.getProductByKey(triggerConfig.value.productKey)
|
||||
if (productData) {
|
||||
product.value = productData
|
||||
// 加载物模型数据
|
||||
await getThingModelTSL()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取产品信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设备回显信息
|
||||
*/
|
||||
const initDeviceInfo = async () => {
|
||||
if (!triggerConfig.value.productKey || !triggerConfig.value.deviceNames?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用新的API直接通过productKey和deviceNames获取设备列表
|
||||
const deviceData = await DeviceApi.getDevicesByProductKeyAndNames(
|
||||
triggerConfig.value.productKey,
|
||||
triggerConfig.value.deviceNames
|
||||
)
|
||||
|
||||
if (deviceData && deviceData.length > 0) {
|
||||
deviceList.value = deviceData
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取产品物模型 */
|
||||
const thingModelTSL = ref<any>()
|
||||
const thingModels = computed(() => (condition: TriggerCondition) => {
|
||||
if (isEmpty(thingModelTSL.value)) {
|
||||
return []
|
||||
}
|
||||
switch (condition.type) {
|
||||
case IotDeviceMessageTypeEnum.PROPERTY:
|
||||
return thingModelTSL.value?.properties || []
|
||||
case IotDeviceMessageTypeEnum.SERVICE:
|
||||
return thingModelTSL.value?.services || []
|
||||
case IotDeviceMessageTypeEnum.EVENT:
|
||||
return thingModelTSL.value?.events || []
|
||||
}
|
||||
return []
|
||||
})
|
||||
const getThingModelTSL = async () => {
|
||||
if (!product.value) {
|
||||
return
|
||||
}
|
||||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product.value.id)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 初始化产品和设备回显
|
||||
if (triggerConfig.value) {
|
||||
// 初始化conditions数组,如果不存在
|
||||
if (!triggerConfig.value.conditions) {
|
||||
triggerConfig.value.conditions = []
|
||||
}
|
||||
|
||||
await initProductInfo()
|
||||
await initDeviceInfo()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="flex items-center w-1/1">
|
||||
<!-- 选择服务 -->
|
||||
<el-select
|
||||
v-if="
|
||||
[IotDeviceMessageTypeEnum.SERVICE, IotDeviceMessageTypeEnum.EVENT].includes(conditionType)
|
||||
"
|
||||
v-model="conditionParameter.identifier0"
|
||||
class="!w-150px mr-10px"
|
||||
clearable
|
||||
placeholder="请选择服务"
|
||||
>
|
||||
<el-option
|
||||
v-for="thingModel in thingModels"
|
||||
:key="thingModel.identifier"
|
||||
:label="thingModel.name"
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="conditionParameter.identifier"
|
||||
class="!w-150px mr-10px"
|
||||
clearable
|
||||
placeholder="请选择物模型"
|
||||
>
|
||||
<el-option
|
||||
v-for="thingModel in getThingModels"
|
||||
:key="thingModel.identifier"
|
||||
:label="thingModel.name"
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<ConditionSelector
|
||||
v-model="conditionParameter.operator"
|
||||
:data-type="model?.dataType"
|
||||
class="!w-150px mr-10px"
|
||||
/>
|
||||
<ThingModelParamInput
|
||||
v-if="
|
||||
conditionParameter.operator !==
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value
|
||||
"
|
||||
class="!w-200px mr-10px"
|
||||
v-model="conditionParameter.value"
|
||||
:thing-model="model"
|
||||
/>
|
||||
<!-- 按钮插槽 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ConditionSelector from './ConditionSelector.vue'
|
||||
import {
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum,
|
||||
TriggerConditionParameter
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import ThingModelParamInput from '@/views/iot/rule/scene/components/ThingModelParamInput.vue'
|
||||
|
||||
/** 设备触发条件 */
|
||||
defineOptions({ name: 'DeviceListenerCondition' })
|
||||
const props = defineProps<{ modelValue: any; conditionType: any; thingModels: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter>
|
||||
|
||||
/** 属性就是 thingModels,服务和事件取对应的 outputParams */
|
||||
const getThingModels = computed(() => {
|
||||
switch (props.conditionType) {
|
||||
case IotDeviceMessageTypeEnum.PROPERTY:
|
||||
return props.thingModels || []
|
||||
case IotDeviceMessageTypeEnum.SERVICE:
|
||||
case IotDeviceMessageTypeEnum.EVENT:
|
||||
return (
|
||||
props.thingModels.find(
|
||||
(item: any) => item.identifier === conditionParameter.value.identifier0
|
||||
)?.outputParams || []
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/** 获得物模型属性、类型 */
|
||||
const model = computed(() =>
|
||||
getThingModels.value.find((item: any) => item.identifier === conditionParameter.value.identifier)
|
||||
)
|
||||
</script>
|
||||
|
|
@ -69,9 +69,12 @@
|
|||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- TODO @puhui999:触发器数组 => 触发器,然后展示 x 个? ps:执行器数组也类似哈。-->
|
||||
<el-table-column label="触发器数组" align="center" prop="triggers" />
|
||||
<el-table-column label="执行器数组" align="center" prop="actions" />
|
||||
<el-table-column label="触发器" align="center" prop="triggers">
|
||||
<template #default="{ row }"> {{ row.triggers?.length }}个 </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行器" align="center" prop="actions">
|
||||
<template #default="{ row }"> {{ row.actions?.length }}个 </template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-scrollbar height="600px">
|
||||
<pre><code v-dompurify-html="highlightedCode()" class="hljs"></code></pre>
|
||||
</el-scrollbar>
|
||||
<JsonEditor
|
||||
v-model="thingModelTSL"
|
||||
:mode="viewMode === 'editor' ? 'code' : 'view'"
|
||||
height="600px"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-radio-group v-model="viewMode" size="small">
|
||||
<el-radio-button label="code">代码视图</el-radio-button>
|
||||
<el-radio-button label="editor">编辑器视图</el-radio-button>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -19,6 +27,7 @@ defineOptions({ name: 'ThingModelTSL' })
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('物模型 TSL') // 弹窗的标题
|
||||
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
|
||||
const viewMode = ref('code') // 查看模式:code-代码视图,editor-编辑器视图
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
|
|
@ -27,17 +36,9 @@ const open = () => {
|
|||
defineExpose({ open })
|
||||
|
||||
/** 获取 TSL */
|
||||
const thingModelTSL = ref('')
|
||||
const thingModelTSL = ref({})
|
||||
const getTsl = async () => {
|
||||
const res = await ThingModelApi.getThingModelTSLByProductId(product?.value?.id || 0)
|
||||
thingModelTSL.value = JSON.stringify(res, null, 2)
|
||||
}
|
||||
|
||||
/** 代码高亮 */
|
||||
const highlightedCode = () => {
|
||||
// TODO @puhui999:可以考虑 highlight 的告警解决下;另外,可以考虑配置设置里面,有个 json editor 预览
|
||||
const result = hljs.highlight('json', thingModelTSL.value, true)
|
||||
return result.value || ' '
|
||||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product?.value?.id || 0)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@
|
|||
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import ThingModelForm from './ThingModelForm.vue'
|
||||
import ThingModelTSL from './ThingModelTSL.vue'
|
||||
import { ProductVO } from '@/api/iot/product/product'
|
||||
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
||||
import { getDataTypeOptionsLabel } from './config'
|
||||
|
|
|
|||
Loading…
Reference in New Issue