From 9684593623fbda26ed1073ca42ab747c1e6ae7ae Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 4 Aug 2025 21:04:18 +0800 Subject: [PATCH] =?UTF-8?q?perf=EF=BC=9A=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=99=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/scene.types.ts | 2 + .../iot/rule/scene/form/RuleSceneForm.vue | 9 + .../form/configs/DeviceControlConfig.vue | 345 ++++++++++++- .../scene/form/sections/ActionSection.vue | 6 + .../scene/form/selectors/ServiceSelector.vue | 460 ++++++++++++++++++ 5 files changed, 810 insertions(+), 12 deletions(-) create mode 100644 src/views/iot/rule/scene/form/selectors/ServiceSelector.vue diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index b74832bcc..577786502 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -226,6 +226,7 @@ interface ActionFormData { type: number // 执行类型 productId?: number // 产品编号 deviceId?: number // 设备编号 + identifier?: string // 物模型标识符(服务调用时使用) params?: Record // 请求参数 alertConfigId?: number // 告警配置编号 } @@ -277,6 +278,7 @@ interface ActionDO { type: number // 执行类型 productId?: number // 产品编号 deviceId?: number // 设备编号 + identifier?: string // 物模型标识符(服务调用时使用) params?: Record // 请求参数 alertConfigId?: number // 告警配置编号 } diff --git a/src/views/iot/rule/scene/form/RuleSceneForm.vue b/src/views/iot/rule/scene/form/RuleSceneForm.vue index b70071270..94c94579b 100644 --- a/src/views/iot/rule/scene/form/RuleSceneForm.vue +++ b/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -170,6 +170,15 @@ const validateActions = (_rule: any, value: any, callback: any) => { callback(new Error(`执行器 ${i + 1}: 设备不能为空`)) return } + + // 服务调用需要验证服务标识符 + if (action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE) { + if (!action.identifier) { + callback(new Error(`执行器 ${i + 1}: 服务不能为空`)) + return + } + } + if (!action.params || Object.keys(action.params).length === 0) { callback(new Error(`执行器 ${i + 1}: 参数配置不能为空`)) return diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue index f13172762..2eb1c5a3d 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -20,7 +20,82 @@ - + +
+ + + + + +
+ +
+ +
+ + +
+ +
+
+ + +
+
+ + + {{ jsonError || 'JSON格式正确' }} + +
+ + +
+ 快速填充: + + 示例数据 + + 清空 +
+
+
+
+
+
+ +
@@ -100,8 +175,51 @@
- -
+ +
+
+ + + 当前服务输入参数 + +
+
+
+
+
+ {{ param.name }} +
+
+ {{ param.identifier }} +
+
+
+ + {{ getPropertyTypeName(param.dataType) }} + + + {{ getExampleValueForParam(param) }} + +
+
+
+ +
+
+ 完整JSON格式: +
+
{{ generateServiceExampleJson() }}
+
+
+ + +
@@ -184,7 +302,8 @@ import { useVModel } from '@vueuse/core' import { InfoFilled } from '@element-plus/icons-vue' import ProductSelector from '../selectors/ProductSelector.vue' import DeviceSelector from '../selectors/DeviceSelector.vue' -import { ActionFormData } from '@/api/iot/rule/scene/scene.types' +import ServiceSelector from '../selectors/ServiceSelector.vue' +import { ActionFormData, ThingModelService } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' /** 设备控制配置组件 */ @@ -207,6 +326,9 @@ const thingModelProperties = ref([]) const loadingThingModel = ref(false) const propertyValues = ref>({}) +// 服务调用相关状态 +const selectedService = ref(null) + // 示例弹出层相关状态 const showExampleDetail = ref(false) const exampleTriggerRef = ref() @@ -218,15 +340,28 @@ const isPropertySetAction = computed(() => { return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET }) +const isServiceInvokeAction = computed(() => { + return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE +}) + // 事件处理 const handleProductChange = (productId?: number) => { + console.log('🔄 handleProductChange called:', { + productId, + currentProductId: action.value.productId + }) + // 当产品变化时,清空设备选择和参数配置 if (action.value.productId !== productId) { action.value.deviceId = undefined + action.value.identifier = undefined // 清空服务标识符 action.value.params = {} paramsJson.value = '' jsonError.value = '' propertyValues.value = {} + selectedService.value = null // 清空选中的服务 + + console.log('🧹 Cleared action data due to product change') } // 加载新产品的物模型属性 @@ -244,6 +379,30 @@ const handleDeviceChange = (deviceId?: number) => { } } +const handleServiceChange = (serviceIdentifier?: string, service?: ThingModelService) => { + console.log('🔄 handleServiceChange called:', { serviceIdentifier, service: service?.name }) + + // 更新服务对象 + selectedService.value = service || null + + // 当服务变化时,清空参数配置并根据服务输入参数生成默认参数结构 + action.value.params = {} + paramsJson.value = '' + jsonError.value = '' + + // 如果选择了服务且有输入参数,生成默认参数结构 + if (service && service.inputParams && service.inputParams.length > 0) { + const defaultParams = {} + service.inputParams.forEach((param) => { + defaultParams[param.identifier] = getDefaultValueForParam(param) + }) + action.value.params = defaultParams + paramsJson.value = JSON.stringify(defaultParams, null, 2) + + console.log('✅ Generated default params:', defaultParams) + } +} + // 快速填充示例数据 const fillExampleJson = () => { const exampleData = generateExampleJson() @@ -251,6 +410,15 @@ const fillExampleJson = () => { handleParamsChange() } +// 快速填充服务示例数据 +const fillServiceExampleJson = () => { + if (selectedService.value && selectedService.value.inputParams) { + const exampleData = generateServiceExampleJson() + paramsJson.value = exampleData + handleParamsChange() + } +} + // 清空参数 const clearParams = () => { paramsJson.value = '' @@ -260,14 +428,14 @@ const clearParams = () => { } // 更新属性值(保留但不在模板中使用) -const updatePropertyValue = (identifier: string, value: any) => { - propertyValues.value[identifier] = value - // 同步更新到 action.params - action.value.params = { ...propertyValues.value } - // 同步更新 JSON 显示 - paramsJson.value = JSON.stringify(action.value.params, null, 2) - jsonError.value = '' -} +// const updatePropertyValue = (identifier: string, value: any) => { +// propertyValues.value[identifier] = value +// // 同步更新到 action.params +// action.value.params = { ...propertyValues.value } +// // 同步更新 JSON 显示 +// paramsJson.value = JSON.stringify(action.value.params, null, 2) +// jsonError.value = '' +// } // 加载物模型属性 const loadThingModelProperties = async (productId: number) => { @@ -322,6 +490,40 @@ const loadThingModelProperties = async (productId: number) => { } } +// 从TSL加载服务信息 +const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => { + console.log('🔍 loadServiceFromTSL called:', { productId, serviceIdentifier }) + try { + const { ThingModelApi } = await import('@/api/iot/thingmodel') + const tslData = await ThingModelApi.getThingModelTSLByProductId(productId) + console.log('📡 TSL data loaded:', tslData) + + if (tslData?.services) { + const service = tslData.services.find((s: any) => s.identifier === serviceIdentifier) + console.log('🎯 Found service:', service) + + if (service) { + // 设置服务对象 + selectedService.value = service + + console.log('✅ Service set:', { + serviceIdentifier, + selectedService: selectedService.value?.name + }) + + // 确保在下一个tick中更新,让ServiceSelector有时间处理 + await nextTick() + } else { + console.warn('⚠️ Service not found in TSL') + } + } else { + console.warn('⚠️ No services in TSL data') + } + } catch (error) { + console.error('❌ 加载服务信息失败:', error) + } +} + const handleParamsChange = () => { try { jsonError.value = '' // 清除之前的错误 @@ -364,6 +566,29 @@ const getPropertyTypeName = (dataType: string) => { return typeMap[dataType] || dataType } +// 根据参数类型获取默认值 +const getDefaultValueForParam = (param: any) => { + switch (param.dataType) { + case 'int': + return 0 + case 'float': + case 'double': + return 0.0 + case 'bool': + return false + case 'text': + return '' + case 'enum': + // 如果有枚举值,使用第一个 + if (param.dataSpecs?.dataSpecsList && param.dataSpecs.dataSpecsList.length > 0) { + return param.dataSpecs.dataSpecsList[0].value + } + return '' + default: + return '' + } +} + const getPropertyTypeTag = (dataType: string) => { const tagMap = { int: 'primary', @@ -397,6 +622,28 @@ const getExampleValue = (property: any) => { } } +// 获取参数示例值 +const getExampleValueForParam = (param: any) => { + switch (param.dataType) { + case 'int': + return '0' + case 'float': + case 'double': + return '0.0' + case 'bool': + return 'false' + case 'text': + return '"text"' + case 'enum': + if (param.dataSpecs?.dataSpecsList && param.dataSpecs.dataSpecsList.length > 0) { + return `"${param.dataSpecs.dataSpecsList[0].name}"` + } + return '"option1"' + default: + return '""' + } +} + const generateExampleJson = () => { if (thingModelProperties.value.length === 0) { return JSON.stringify( @@ -433,6 +680,20 @@ const generateExampleJson = () => { return JSON.stringify(example, null, 2) } +// 生成服务示例JSON +const generateServiceExampleJson = () => { + if (!selectedService.value || !selectedService.value.inputParams) { + return JSON.stringify({}, null, 2) + } + + const example = {} + selectedService.value.inputParams.forEach((param) => { + example[param.identifier] = getDefaultValueForParam(param) + }) + + return JSON.stringify(example, null, 2) +} + // 示例弹出层控制方法 - 参考 PropertySelector 的设计 const toggleExampleDetail = () => { if (showExampleDetail.value) { @@ -531,6 +792,12 @@ onMounted(() => { loadThingModelProperties(action.value.productId) } + // 如果是服务调用类型且已有标识符,初始化服务选择 + if (action.value.productId && isServiceInvokeAction.value && action.value.identifier) { + // 加载物模型TSL以获取服务信息 + loadServiceFromTSL(action.value.productId, action.value.identifier) + } + // 添加事件监听器 document.addEventListener('click', handleClickOutside) window.addEventListener('resize', handleResize) @@ -558,10 +825,64 @@ watch( console.error('参数格式化失败:', error) jsonError.value = '参数格式化失败' } + } else { + // 参数为空时清空JSON显示 + if (paramsJson.value !== '') { + paramsJson.value = '' + jsonError.value = '' + } } }, { deep: true } ) + +// 监听action.value变化,处理编辑模式的数据回显 +watch( + () => action.value, + async (newAction) => { + console.log('🔄 action.value changed:', { + type: newAction?.type, + productId: newAction?.productId, + identifier: newAction?.identifier, + isServiceInvokeAction: isServiceInvokeAction.value + }) + + if (newAction) { + // 处理服务调用的数据回显 + if (isServiceInvokeAction.value && newAction.productId && newAction.identifier) { + // 异步加载服务信息以设置selectedService + await loadServiceFromTSL(newAction.productId, newAction.identifier) + } else if (isServiceInvokeAction.value) { + // 清空服务选择 + selectedService.value = null + } + + // 处理参数回显 + if (newAction.params && Object.keys(newAction.params).length > 0) { + try { + const newJsonString = JSON.stringify(newAction.params, null, 2) + if (paramsJson.value !== newJsonString) { + paramsJson.value = newJsonString + propertyValues.value = { ...newAction.params } + jsonError.value = '' + console.log('✅ Params restored:', newAction.params) + } + } catch (error) { + console.error('❌ 参数格式化失败:', error) + jsonError.value = '参数格式化失败' + } + } else { + if (paramsJson.value !== '') { + paramsJson.value = '' + propertyValues.value = {} + jsonError.value = '' + console.log('🧹 Params cleared') + } + } + } + }, + { deep: true, immediate: true } +)