From 694de3f0d11ce7451eb25c2b3fc9c3dd87fd1a7d Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 5 Aug 2025 11:24:27 +0800 Subject: [PATCH 1/6] =?UTF-8?q?perf=EF=BC=9A=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E6=89=A7=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 --- .../form/configs/DeviceControlConfig.vue | 262 ++++++++++++------ .../scene/form/selectors/ServiceSelector.vue | 24 +- 2 files changed, 178 insertions(+), 108 deletions(-) diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue index 2eb1c5a3d..7bf056bdf 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -23,11 +23,29 @@
- + > + +
+ {{ service.name }} + + {{ service.callType === 'sync' ? '同步' : '异步' }} + +
+
+
@@ -302,7 +320,6 @@ 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 ServiceSelector from '../selectors/ServiceSelector.vue' import { ActionFormData, ThingModelService } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' @@ -319,38 +336,36 @@ const emit = defineEmits<{ const action = useVModel(props, 'modelValue', emit) -// 状态 -const paramsJson = ref('') -const jsonError = ref('') -const thingModelProperties = ref([]) -const loadingThingModel = ref(false) -const propertyValues = ref>({}) +const paramsJson = ref('') // 参数JSON字符串 +const jsonError = ref('') // JSON格式错误信息 +const thingModelProperties = ref([]) // 物模型属性列表 +const loadingThingModel = ref(false) // 物模型加载状态 +const propertyValues = ref>({}) // 属性值映射 -// 服务调用相关状态 -const selectedService = ref(null) +const selectedService = ref(null) // 选中的服务对象 +const serviceList = ref([]) // 服务列表 +const loadingServices = ref(false) // 服务加载状态 -// 示例弹出层相关状态 -const showExampleDetail = ref(false) -const exampleTriggerRef = ref() -const exampleDetailRef = ref() -const examplePopoverStyle = ref({}) +const showExampleDetail = ref(false) // 示例详情弹出层显示状态 +const exampleTriggerRef = ref() // 示例触发按钮引用 +const exampleDetailRef = ref() // 示例详情弹出层引用 +const examplePopoverStyle = ref({}) // 示例弹出层样式 -// 计算属性 const isPropertySetAction = computed(() => { + // 是否为属性设置类型 return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET }) const isServiceInvokeAction = computed(() => { + // 是否为服务调用类型 return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE }) -// 事件处理 +/** + * 处理产品变化事件 + * @param productId 产品ID + */ const handleProductChange = (productId?: number) => { - console.log('🔄 handleProductChange called:', { - productId, - currentProductId: action.value.productId - }) - // 当产品变化时,清空设备选择和参数配置 if (action.value.productId !== productId) { action.value.deviceId = undefined @@ -360,16 +375,23 @@ const handleProductChange = (productId?: number) => { jsonError.value = '' propertyValues.value = {} selectedService.value = null // 清空选中的服务 - - console.log('🧹 Cleared action data due to product change') + serviceList.value = [] // 清空服务列表 } - // 加载新产品的物模型属性 - if (productId && isPropertySetAction.value) { - loadThingModelProperties(productId) + // 加载新产品的物模型属性或服务列表 + if (productId) { + if (isPropertySetAction.value) { + loadThingModelProperties(productId) + } else if (isServiceInvokeAction.value) { + loadServiceList(productId) + } } } +/** + * 处理设备变化事件 + * @param deviceId 设备ID + */ const handleDeviceChange = (deviceId?: number) => { // 当设备变化时,清空参数配置 if (action.value.deviceId !== deviceId) { @@ -379,11 +401,14 @@ const handleDeviceChange = (deviceId?: number) => { } } -const handleServiceChange = (serviceIdentifier?: string, service?: ThingModelService) => { - console.log('🔄 handleServiceChange called:', { serviceIdentifier, service: service?.name }) - - // 更新服务对象 - selectedService.value = service || null +/** + * 处理服务变化事件 + * @param serviceIdentifier 服务标识符 + */ +const handleServiceChange = (serviceIdentifier?: string) => { + // 根据服务标识符找到对应的服务对象 + const service = serviceList.value.find((s) => s.identifier === serviceIdentifier) || null + selectedService.value = service // 当服务变化时,清空参数配置并根据服务输入参数生成默认参数结构 action.value.params = {} @@ -398,19 +423,21 @@ const handleServiceChange = (serviceIdentifier?: string, service?: ThingModelSer }) action.value.params = defaultParams paramsJson.value = JSON.stringify(defaultParams, null, 2) - - console.log('✅ Generated default params:', defaultParams) } } -// 快速填充示例数据 +/** + * 快速填充示例数据 + */ const fillExampleJson = () => { const exampleData = generateExampleJson() paramsJson.value = exampleData handleParamsChange() } -// 快速填充服务示例数据 +/** + * 快速填充服务示例数据 + */ const fillServiceExampleJson = () => { if (selectedService.value && selectedService.value.inputParams) { const exampleData = generateServiceExampleJson() @@ -419,7 +446,9 @@ const fillServiceExampleJson = () => { } } -// 清空参数 +/** + * 清空参数 + */ const clearParams = () => { paramsJson.value = '' action.value.params = {} @@ -437,7 +466,10 @@ const clearParams = () => { // jsonError.value = '' // } -// 加载物模型属性 +/** + * 加载物模型属性 + * @param productId 产品ID + */ const loadThingModelProperties = async (productId: number) => { if (!productId) { thingModelProperties.value = [] @@ -490,40 +522,48 @@ const loadThingModelProperties = async (productId: number) => { } } -// 从TSL加载服务信息 -const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => { - console.log('🔍 loadServiceFromTSL called:', { productId, serviceIdentifier }) +/** + * 加载服务列表 + * @param productId 产品ID + */ +const loadServiceList = async (productId: number) => { + if (!productId) { + serviceList.value = [] + return + } + + loadingServices.value = true 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 + serviceList.value = tslData?.services || [] + } catch (error) { + console.error('加载服务列表失败:', error) + serviceList.value = [] + } finally { + loadingServices.value = false + } +} - console.log('✅ Service set:', { - serviceIdentifier, - selectedService: selectedService.value?.name - }) +/** + * 从TSL加载服务信息(用于编辑模式回显) + * @param productId 产品ID + * @param serviceIdentifier 服务标识符 + */ +const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => { + // 先加载服务列表 + await loadServiceList(productId) - // 确保在下一个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 service = serviceList.value.find((s: any) => s.identifier === serviceIdentifier) + if (service) { + selectedService.value = service } } +/** + * 处理参数变化事件 + */ const handleParamsChange = () => { try { jsonError.value = '' // 清除之前的错误 @@ -550,7 +590,11 @@ const handleParamsChange = () => { } } -// 工具函数 - 参考 PropertySelector 的设计 +/** + * 获取属性类型名称 + * @param dataType 数据类型 + * @returns 类型名称 + */ const getPropertyTypeName = (dataType: string) => { const typeMap = { int: '整数', @@ -566,7 +610,11 @@ const getPropertyTypeName = (dataType: string) => { return typeMap[dataType] || dataType } -// 根据参数类型获取默认值 +/** + * 根据参数类型获取默认值 + * @param param 参数对象 + * @returns 默认值 + */ const getDefaultValueForParam = (param: any) => { switch (param.dataType) { case 'int': @@ -589,6 +637,11 @@ const getDefaultValueForParam = (param: any) => { } } +/** + * 获取属性类型标签样式 + * @param dataType 数据类型 + * @returns 标签类型 + */ const getPropertyTypeTag = (dataType: string) => { const tagMap = { int: 'primary', @@ -604,6 +657,11 @@ const getPropertyTypeTag = (dataType: string) => { return tagMap[dataType] || 'info' } +/** + * 获取属性示例值 + * @param property 属性对象 + * @returns 示例值 + */ const getExampleValue = (property: any) => { switch (property.dataType) { case 'int': @@ -622,7 +680,11 @@ const getExampleValue = (property: any) => { } } -// 获取参数示例值 +/** + * 获取参数示例值 + * @param param 参数对象 + * @returns 示例值 + */ const getExampleValueForParam = (param: any) => { switch (param.dataType) { case 'int': @@ -644,6 +706,10 @@ const getExampleValueForParam = (param: any) => { } } +/** + * 生成示例JSON + * @returns JSON字符串 + */ const generateExampleJson = () => { if (thingModelProperties.value.length === 0) { return JSON.stringify( @@ -680,7 +746,10 @@ const generateExampleJson = () => { return JSON.stringify(example, null, 2) } -// 生成服务示例JSON +/** + * 生成服务示例JSON + * @returns JSON字符串 + */ const generateServiceExampleJson = () => { if (!selectedService.value || !selectedService.value.inputParams) { return JSON.stringify({}, null, 2) @@ -694,7 +763,9 @@ const generateServiceExampleJson = () => { return JSON.stringify(example, null, 2) } -// 示例弹出层控制方法 - 参考 PropertySelector 的设计 +/** + * 切换示例详情弹出层显示状态 + */ const toggleExampleDetail = () => { if (showExampleDetail.value) { hideExampleDetail() @@ -703,6 +774,9 @@ const toggleExampleDetail = () => { } } +/** + * 显示示例详情弹出层 + */ const showExampleDetailPopover = () => { if (!exampleTriggerRef.value) return @@ -713,10 +787,16 @@ const showExampleDetailPopover = () => { }) } +/** + * 隐藏示例详情弹出层 + */ const hideExampleDetail = () => { showExampleDetail.value = false } +/** + * 更新示例弹出层位置 + */ const updateExamplePopoverPosition = () => { if (!exampleTriggerRef.value || !exampleDetailRef.value) return @@ -754,7 +834,10 @@ const updateExamplePopoverPosition = () => { } } -// 点击外部关闭弹出层 +/** + * 点击外部关闭弹出层 + * @param event 鼠标事件 + */ const handleClickOutside = (event: MouseEvent) => { if ( showExampleDetail.value && @@ -767,14 +850,18 @@ const handleClickOutside = (event: MouseEvent) => { } } -// 监听窗口大小变化,重新计算弹出层位置 +/** + * 监听窗口大小变化,重新计算弹出层位置 + */ const handleResize = () => { if (showExampleDetail.value) { updateExamplePopoverPosition() } } -// 初始化 +/** + * 组件初始化 + */ onMounted(() => { if (action.value.params && Object.keys(action.value.params).length > 0) { try { @@ -803,7 +890,9 @@ onMounted(() => { window.addEventListener('resize', handleResize) }) -// 组件卸载时清理事件监听器 +/** + * 组件卸载时清理事件监听器 + */ onUnmounted(() => { document.removeEventListener('click', handleClickOutside) window.removeEventListener('resize', handleResize) @@ -840,21 +929,20 @@ watch( 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) + if (isServiceInvokeAction.value && newAction.productId) { + if (newAction.identifier) { + // 编辑模式:加载服务信息并设置选中的服务 + await loadServiceFromTSL(newAction.productId, newAction.identifier) + } else { + // 新建模式:只加载服务列表 + await loadServiceList(newAction.productId) + } } else if (isServiceInvokeAction.value) { // 清空服务选择 selectedService.value = null + serviceList.value = [] } // 处理参数回显 @@ -865,10 +953,9 @@ watch( paramsJson.value = newJsonString propertyValues.value = { ...newAction.params } jsonError.value = '' - console.log('✅ Params restored:', newAction.params) } } catch (error) { - console.error('❌ 参数格式化失败:', error) + console.error('参数格式化失败:', error) jsonError.value = '参数格式化失败' } } else { @@ -876,7 +963,6 @@ watch( paramsJson.value = '' propertyValues.value = {} jsonError.value = '' - console.log('🧹 Params cleared') } } } diff --git a/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue b/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue index f03911bc2..d9ef7ed19 100644 --- a/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue @@ -209,6 +209,10 @@ const servicePopoverStyle = ref({}) // 事件处理 const handleChange = (value?: string) => { + // 更新 modelValue(这是 v-model 绑定的关键) + emit('update:modelValue', value) + + // 触发 change 事件,传递服务对象 const service = serviceList.value.find((s) => s.identifier === value) emit('change', value, service) } @@ -336,22 +340,11 @@ watch( watch( () => props.modelValue, (newValue) => { - console.log('🔄 ServiceSelector modelValue changed:', { - newValue, - serviceListLength: serviceList.value.length, - serviceList: serviceList.value.map((s) => s.identifier) - }) - if (newValue && serviceList.value.length > 0) { // 确保服务列表已加载,然后设置选中的服务 const service = serviceList.value.find((s) => s.identifier === newValue) - console.log('🎯 ServiceSelector found service:', service) - if (service) { selectedService.value = service - console.log('✅ ServiceSelector service set:', service.name) - } else { - console.warn('⚠️ ServiceSelector service not found for identifier:', newValue) } } }, @@ -362,20 +355,11 @@ watch( watch( () => serviceList.value, (newServiceList) => { - console.log('📋 ServiceSelector serviceList changed:', { - length: newServiceList.length, - services: newServiceList.map((s) => s.identifier), - modelValue: props.modelValue - }) - if (newServiceList.length > 0 && props.modelValue) { // 服务列表加载完成后,如果有modelValue,设置选中的服务 const service = newServiceList.find((s) => s.identifier === props.modelValue) - console.log('🎯 ServiceSelector found service in list:', service) - if (service) { selectedService.value = service - console.log('✅ ServiceSelector service set from list:', service.name) } } }, -- Gitee From 6c954c4ff123623117e3e81aa4287a520574151e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 5 Aug 2025 11:59:46 +0800 Subject: [PATCH 2/6] =?UTF-8?q?perf=EF=BC=9A=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=9F=E4=B8=80=E7=B1=BB=E5=9E=8B=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=EF=BC=8C=E7=AE=80=E5=8C=96=E5=91=8A=E8=AD=A6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/index.ts | 17 +- src/api/iot/rule/scene/scene.types.ts | 139 +-------- .../iot/rule/scene/form/RuleSceneForm.vue | 8 +- .../rule/scene/form/configs/AlertConfig.vue | 275 +++--------------- .../scene/form/configs/ConditionConfig.vue | 10 +- .../form/configs/DeviceControlConfig.vue | 6 +- .../configs/DeviceStatusConditionConfig.vue | 6 +- .../form/configs/SubConditionGroupConfig.vue | 10 +- .../scene/form/sections/ActionSection.vue | 60 ++-- .../scene/form/sections/BasicInfoSection.vue | 6 +- src/views/iot/rule/scene/index.vue | 20 +- 11 files changed, 134 insertions(+), 423 deletions(-) diff --git a/src/api/iot/rule/scene/index.ts b/src/api/iot/rule/scene/index.ts index 75b121544..9bbf8db6a 100644 --- a/src/api/iot/rule/scene/index.ts +++ b/src/api/iot/rule/scene/index.ts @@ -1,5 +1,5 @@ import request from '@/config/axios' -import { IotRuleScene } from './scene.types' +import { IotSceneRule } from './scene.types' // IoT 场景联动 API export const RuleSceneApi = { @@ -14,21 +14,24 @@ export const RuleSceneApi = { }, // 新增场景联动 - createRuleScene: async (data: IotRuleScene) => { + createRuleScene: async (data: IotSceneRule) => { return await request.post({ url: `/iot/rule-scene/create`, data }) }, // 修改场景联动 - updateRuleScene: async (data: IotRuleScene) => { + updateRuleScene: async (data: IotSceneRule) => { return await request.put({ url: `/iot/rule-scene/update`, data }) }, // 修改场景联动 updateRuleSceneStatus: async (id: number, status: number) => { - return await request.put({ url: `/iot/rule-scene/update-status`, data: { - id, - status - }}) + return await request.put({ + url: `/iot/rule-scene/update-status`, + data: { + id, + status + } + }) }, // 删除场景联动 diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index 577786502..dc9342ca4 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -137,122 +137,18 @@ export interface PropertySelectorItem { // ========== 场景联动规则相关接口定义 ========== -// 基础接口(如果项目中有全局的 BaseDO,可以使用全局的) -interface TenantBaseDO { - createTime?: Date // 创建时间 - updateTime?: Date // 更新时间 - creator?: string // 创建者 - updater?: string // 更新者 - deleted?: boolean // 是否删除 - tenantId?: number // 租户编号 -} - -// 触发条件参数 -interface TriggerConditionParameter { - identifier0?: string // 标识符(事件、服务) - identifier?: string // 标识符(属性) - operator: string // 操作符(必填) - value: string // 比较值(必填,多值用逗号分隔) -} - -// 触发条件 -interface TriggerCondition { - type: string // 消息类型 - identifier: string // 消息标识符 - parameters: TriggerConditionParameter[] // 参数数组 -} - -// 触发器配置 -interface TriggerConfig { - key?: string // 组件唯一标识符,用于解决索引重用问题 - type: number // 触发类型(必填) - productKey?: string // 产品标识(设备触发时必填) - deviceNames?: string[] // 设备名称数组(设备触发时必填) - conditions?: TriggerCondition[] // 触发条件数组(设备触发时必填) - cronExpression?: string // CRON表达式(定时触发时必填) -} - -// 执行设备控制 -interface ActionDeviceControl { - productKey: string // 产品标识(必填) - deviceNames: string[] // 设备名称数组(必填) - type: string // 消息类型(必填) - identifier: string // 消息标识符(必填) - params: Record // 参数对象(必填)- 统一使用 params 字段 -} - -// 执行器配置 -interface ActionConfig { - key?: string // 组件唯一标识符,用于解决索引重用问题 - type: number // 执行类型(必填) - deviceControl?: ActionDeviceControl // 设备控制(设备控制时必填) - alertConfigId?: number // 告警配置ID(告警恢复时必填) -} - -// 表单数据接口 - 直接对应后端 DO 结构 -interface RuleSceneFormData { - id?: number - name: string - description?: string - status: number - triggers: TriggerFormData[] // 支持多个触发器 - actions: ActionFormData[] -} - -// 触发器表单数据 - 直接对应 TriggerDO -interface TriggerFormData { - type: number // 触发类型 - productId?: number // 产品编号 - deviceId?: number // 设备编号 - identifier?: string // 物模型标识符 - operator?: string // 操作符 - value?: string // 参数值 - cronExpression?: string // CRON 表达式 - conditionGroups?: TriggerConditionFormData[][] // 条件组(二维数组) -} - -// 触发条件表单数据 - 直接对应 TriggerConditionDO -interface TriggerConditionFormData { - type: number // 条件类型:1-设备状态,2-设备属性,3-当前时间 - productId?: number // 产品编号 - deviceId?: number // 设备编号 - identifier?: string // 标识符 - operator: string // 操作符 - param: string // 参数值 -} - -// 执行器表单数据 - 直接对应 ActionDO -interface ActionFormData { - type: number // 执行类型 - productId?: number // 产品编号 - deviceId?: number // 设备编号 - identifier?: string // 物模型标识符(服务调用时使用) - params?: Record // 请求参数 - alertConfigId?: number // 告警配置编号 -} - -// 主接口 - 原有的 API 接口格式(保持兼容性) -interface IotRuleScene extends TenantBaseDO { - id?: number // 场景编号(新增时为空) - name: string // 场景名称(必填) - description?: string // 场景描述(可选) - status: number // 场景状态:0-开启,1-关闭 - triggers: TriggerConfig[] // 触发器数组(必填,至少一个) - actions: ActionConfig[] // 执行器数组(必填,至少一个) -} - // 后端 DO 接口 - 匹配后端数据结构 -interface IotRuleSceneDO { +interface IotSceneRule { id?: number // 场景编号 name: string // 场景名称 description?: string // 场景描述 status: number // 场景状态:0-开启,1-关闭 - triggers: TriggerDO[] // 触发器数组 - actions: ActionDO[] // 执行器数组 + triggers: Trigger[] // 触发器数组 + actions: Action[] // 执行器数组 } // 触发器 DO 结构 -interface TriggerDO { +interface Trigger { type: number // 触发类型 productId?: number // 产品编号 deviceId?: number // 设备编号 @@ -260,12 +156,12 @@ interface TriggerDO { operator?: string // 操作符 value?: string // 参数值 cronExpression?: string // CRON 表达式 - conditionGroups?: TriggerConditionDO[][] // 条件组(二维数组) + conditionGroups?: TriggerCondition[][] // 条件组(二维数组) } // 触发条件 DO 结构 -interface TriggerConditionDO { - type: number // 条件类型 +interface TriggerCondition { + type: number // 条件类型:1-设备状态,2-设备属性,3-当前时间 productId?: number // 产品编号 deviceId?: number // 设备编号 identifier?: string // 标识符 @@ -274,7 +170,7 @@ interface TriggerConditionDO { } // 执行器 DO 结构 -interface ActionDO { +interface Action { type: number // 执行类型 productId?: number // 产品编号 deviceId?: number // 设备编号 @@ -300,21 +196,4 @@ interface FormValidationRules { // TODO @puhui999:这个文件,目标最终没有哈,和别的模块一致; -export { - IotRuleScene, - IotRuleSceneDO, - TriggerDO, - TriggerConditionDO, - ActionDO, - TriggerConfig, - TriggerCondition, - TriggerConditionParameter, - ActionConfig, - ActionDeviceControl, - RuleSceneFormData, - TriggerFormData, - TriggerConditionFormData, - ActionFormData, - ValidationRule, - FormValidationRules -} +export { IotSceneRule, Trigger, TriggerCondition, Action, ValidationRule, FormValidationRules } diff --git a/src/views/iot/rule/scene/form/RuleSceneForm.vue b/src/views/iot/rule/scene/form/RuleSceneForm.vue index 94c94579b..0362bc4bf 100644 --- a/src/views/iot/rule/scene/form/RuleSceneForm.vue +++ b/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -36,7 +36,7 @@ import { useVModel } from '@vueuse/core' import BasicInfoSection from './sections/BasicInfoSection.vue' import TriggerSection from './sections/TriggerSection.vue' import ActionSection from './sections/ActionSection.vue' -import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' +import { IotSceneRule } from '@/api/iot/rule/scene/scene.types' import { RuleSceneApi } from '@/api/iot/rule/scene' import { IotRuleSceneTriggerTypeEnum, @@ -54,7 +54,7 @@ const props = defineProps<{ /** 抽屉显示状态 */ modelValue: boolean /** 编辑的场景联动规则数据 */ - ruleScene?: IotRuleSceneDO + ruleScene?: IotSceneRule }>() /** 组件事件定义 */ @@ -66,7 +66,7 @@ const emit = defineEmits<{ const drawerVisible = useVModel(props, 'modelValue', emit) // 是否可见 /** 创建默认的表单数据 */ -const createDefaultFormData = (): RuleSceneFormData => { +const createDefaultFormData = (): IotSceneRule => { return { name: '', description: '', @@ -89,7 +89,7 @@ const createDefaultFormData = (): RuleSceneFormData => { // 表单数据和状态 const formRef = ref() -const formData = ref(createDefaultFormData()) +const formData = ref(createDefaultFormData()) // 自定义校验器 const validateTriggers = (_rule: any, value: any, callback: any) => { if (!value || !Array.isArray(value) || value.length === 0) { diff --git a/src/views/iot/rule/scene/form/configs/AlertConfig.vue b/src/views/iot/rule/scene/form/configs/AlertConfig.vue index bffe9d578..8073c351a 100644 --- a/src/views/iot/rule/scene/form/configs/AlertConfig.vue +++ b/src/views/iot/rule/scene/form/configs/AlertConfig.vue @@ -1,262 +1,81 @@ - - diff --git a/src/views/iot/rule/scene/form/configs/ConditionConfig.vue b/src/views/iot/rule/scene/form/configs/ConditionConfig.vue index 3eeed5143..9574c58ce 100644 --- a/src/views/iot/rule/scene/form/configs/ConditionConfig.vue +++ b/src/views/iot/rule/scene/form/configs/ConditionConfig.vue @@ -123,7 +123,7 @@ import DeviceSelector from '../selectors/DeviceSelector.vue' import PropertySelector from '../selectors/PropertySelector.vue' import OperatorSelector from '../selectors/OperatorSelector.vue' import ValueInput from '../inputs/ValueInput.vue' -import { TriggerConditionFormData } from '@/api/iot/rule/scene/scene.types' +import { TriggerCondition } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneTriggerConditionTypeEnum, IotRuleSceneTriggerConditionParameterOperatorEnum @@ -133,12 +133,12 @@ import { defineOptions({ name: 'ConditionConfig' }) const props = defineProps<{ - modelValue: TriggerConditionFormData + modelValue: TriggerCondition triggerType: number }>() const emit = defineEmits<{ - (e: 'update:modelValue', value: TriggerConditionFormData): void + (e: 'update:modelValue', value: TriggerCondition): void (e: 'validate', result: { valid: boolean; message: string }): void }>() @@ -155,12 +155,12 @@ const isValid = ref(true) const valueValidation = ref({ valid: true, message: '' }) // 事件处理 -const updateConditionField = (field: keyof TriggerConditionFormData, value: any) => { +const updateConditionField = (field: keyof TriggerCondition, value: any) => { ;(condition.value as any)[field] = value emit('update:modelValue', condition.value) } -const updateCondition = (newCondition: TriggerConditionFormData) => { +const updateCondition = (newCondition: TriggerCondition) => { condition.value = newCondition emit('update:modelValue', condition.value) } diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue index 7bf056bdf..e8ea5f1ba 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -320,18 +320,18 @@ 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, ThingModelService } from '@/api/iot/rule/scene/scene.types' +import { Action, ThingModelService } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' /** 设备控制配置组件 */ defineOptions({ name: 'DeviceControlConfig' }) const props = defineProps<{ - modelValue: ActionFormData + modelValue: Action }>() const emit = defineEmits<{ - (e: 'update:modelValue', value: ActionFormData): void + (e: 'update:modelValue', value: Action): void }>() const action = useVModel(props, 'modelValue', emit) diff --git a/src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue index 0f77124ec..a380192ab 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue @@ -84,17 +84,17 @@ import { useVModel } from '@vueuse/core' import ProductSelector from '../selectors/ProductSelector.vue' import DeviceSelector from '../selectors/DeviceSelector.vue' -import { TriggerConditionFormData } from '@/api/iot/rule/scene/scene.types' +import { TriggerCondition } from '@/api/iot/rule/scene/scene.types' /** 设备状态条件配置组件 */ defineOptions({ name: 'DeviceStatusConditionConfig' }) const props = defineProps<{ - modelValue: TriggerConditionFormData + modelValue: TriggerCondition }>() const emit = defineEmits<{ - (e: 'update:modelValue', value: TriggerConditionFormData): void + (e: 'update:modelValue', value: TriggerCondition): void (e: 'validate', result: { valid: boolean; message: string }): void }>() diff --git a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue index d89e4c4de..1e86d301d 100644 --- a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue +++ b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue @@ -83,7 +83,7 @@ import { nextTick } from 'vue' import { useVModel } from '@vueuse/core' import ConditionConfig from './ConditionConfig.vue' -import { TriggerConditionFormData } from '@/api/iot/rule/scene/scene.types' +import { TriggerCondition } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneTriggerConditionTypeEnum, IotRuleSceneTriggerConditionParameterOperatorEnum @@ -93,13 +93,13 @@ import { defineOptions({ name: 'SubConditionGroupConfig' }) const props = defineProps<{ - modelValue: TriggerConditionFormData[] + modelValue: TriggerCondition[] triggerType: number maxConditions?: number }>() const emit = defineEmits<{ - (e: 'update:modelValue', value: TriggerConditionFormData[]): void + (e: 'update:modelValue', value: TriggerCondition[]): void (e: 'validate', result: { valid: boolean; message: string }): void }>() @@ -123,7 +123,7 @@ const addCondition = () => { return } - const newCondition: TriggerConditionFormData = { + const newCondition: TriggerCondition = { type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 默认为设备属性 productId: undefined, deviceId: undefined, @@ -161,7 +161,7 @@ const removeCondition = (index: number) => { } } -const updateCondition = (index: number, condition: TriggerConditionFormData) => { +const updateCondition = (index: number, condition: TriggerCondition) => { if (subGroup.value) { subGroup.value[index] = condition } diff --git a/src/views/iot/rule/scene/form/sections/ActionSection.vue b/src/views/iot/rule/scene/form/sections/ActionSection.vue index 344030745..c181e5e68 100644 --- a/src/views/iot/rule/scene/form/sections/ActionSection.vue +++ b/src/views/iot/rule/scene/form/sections/ActionSection.vue @@ -78,12 +78,27 @@ @update:model-value="(value) => updateAction(index, value)" /> - + + + +
+
+ + 触发告警 + 自动执行 +
+
+ 当触发条件满足时,系统将自动发送告警通知,无需额外配置。 +
+
@@ -107,7 +122,7 @@ import { useVModel } from '@vueuse/core' import ActionTypeSelector from '../selectors/ActionTypeSelector.vue' import DeviceControlConfig from '../configs/DeviceControlConfig.vue' import AlertConfig from '../configs/AlertConfig.vue' -import { ActionFormData } from '@/api/iot/rule/scene/scene.types' +import { Action } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneActionTypeEnum as ActionTypeEnum, isDeviceAction, @@ -118,23 +133,20 @@ import { /** 执行器配置组件 */ defineOptions({ name: 'ActionSection' }) -interface Props { - actions: ActionFormData[] -} +const props = defineProps<{ + actions: Action[] +}>() -interface Emits { - (e: 'update:actions', value: ActionFormData[]): void -} - -const props = defineProps() -const emit = defineEmits() +const emit = defineEmits<{ + (e: 'update:actions', value: Action[]): void +}>() const actions = useVModel(props, 'actions', emit) /** * 创建默认的执行器数据 */ -const createDefaultActionData = (): ActionFormData => { +const createDefaultActionData = (): Action => { return { type: ActionTypeEnum.DEVICE_PROPERTY_SET, // 默认为设备属性设置 productId: undefined, @@ -145,9 +157,7 @@ const createDefaultActionData = (): ActionFormData => { } } -// 配置常量 -// TODO @puhui999:去掉最大;注释风格改下; -const maxActions = 5 +const maxActions = 5 // 最大执行器数量 // 工具函数 const getActionTypeName = (type: number) => { @@ -164,7 +174,7 @@ const getActionTypeTag = (type: number) => { return actionTypeTags[type] || 'info' } -// 事件处理 +/** 添加执行器 */ const addAction = () => { if (actions.value.length >= maxActions) { return @@ -174,24 +184,29 @@ const addAction = () => { actions.value.push(newAction) } +/** 删除执行器 */ const removeAction = (index: number) => { actions.value.splice(index, 1) } +/** 更新执行器类型 */ const updateActionType = (index: number, type: number) => { actions.value[index].type = type onActionTypeChange(actions.value[index], type) } -const updateAction = (index: number, action: ActionFormData) => { +/** 更新执行器 */ +const updateAction = (index: number, action: Action) => { actions.value[index] = action } +/** 更新告警配置 */ const updateActionAlertConfig = (index: number, alertConfigId?: number) => { actions.value[index].alertConfigId = alertConfigId } -const onActionTypeChange = (action: ActionFormData, type: number) => { +/** 监听执行器类型变化 */ +const onActionTypeChange = (action: Action, type: number) => { // 清理不相关的配置,确保数据结构干净 if (isDeviceAction(type)) { // 设备控制类型:清理告警配置,确保设备参数存在 @@ -204,16 +219,11 @@ const onActionTypeChange = (action: ActionFormData, type: number) => { action.identifier = undefined } } else if (isAlertAction(type)) { - // 告警类型:清理设备配置 action.productId = undefined action.deviceId = undefined action.identifier = undefined // 清理服务标识符 action.params = undefined + action.alertConfigId = undefined } - - // 触发重新校验 - nextTick(() => { - // 这里可以添加校验逻辑 - }) } diff --git a/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue b/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue index 786731765..fd27cd35c 100644 --- a/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue +++ b/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue @@ -58,17 +58,17 @@ diff --git a/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue b/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue deleted file mode 100644 index d9ef7ed19..000000000 --- a/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - -- Gitee From 9917683f0ae88401477b14f45bb0bf1c5de90ea2 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 5 Aug 2025 17:33:20 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=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=96=B0?= =?UTF-8?q?=E5=A2=9E=20JsonParamsInput=20=E6=94=AF=E6=8C=81=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E6=8A=A5=EF=BC=88=E5=A1=AB=E5=86=99=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E8=BE=93=E5=87=BA=E5=8F=82=E6=95=B0=EF=BC=89?= =?UTF-8?q?=20=E6=9C=8D=E5=8A=A1=E8=B0=83=E7=94=A8=EF=BC=88=E5=A1=AB?= =?UTF-8?q?=E5=86=99=E6=9C=8D=E5=8A=A1=E7=9A=84=E8=BE=93=E5=85=A5=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=89=E5=B1=9E=E6=80=A7=E8=AE=BE=E7=BD=AE=EF=BC=88?= =?UTF-8?q?=E5=A1=AB=E5=86=99=E4=BA=A7=E5=93=81=E7=89=A9=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=8F=AF=E5=86=99=E5=B1=9E=E6=80=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/scene.types.ts | 3 + .../form/configs/DeviceControlConfig.vue | 752 +----------------- .../form/configs/MainConditionInnerConfig.vue | 76 +- ...iceParamsInput.vue => JsonParamsInput.vue} | 498 ++++++------ .../scene/form/selectors/PropertySelector.vue | 1 - 5 files changed, 355 insertions(+), 975 deletions(-) rename src/views/iot/rule/scene/form/inputs/{ServiceParamsInput.vue => JsonParamsInput.vue} (43%) diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index dc9342ca4..9c0ee6f1f 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -194,6 +194,9 @@ interface FormValidationRules { [key: string]: ValidationRule[] } +// 表单数据类型别名 +export type TriggerFormData = Trigger + // TODO @puhui999:这个文件,目标最终没有哈,和别的模块一致; export { IotSceneRule, Trigger, TriggerCondition, Action, ValidationRule, FormValidationRules } diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue index e8ea5f1ba..0598c4540 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -1,5 +1,4 @@ - - - diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index 9906160b4..3ef84848a 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -82,11 +82,23 @@ required > - + + @@ -106,11 +118,44 @@
- + + + + + + + + + + + + + + + + + + + + + + + +
@@ -131,8 +176,8 @@ import DeviceSelector from '../selectors/DeviceSelector.vue' import PropertySelector from '../selectors/PropertySelector.vue' import OperatorSelector from '../selectors/OperatorSelector.vue' import ValueInput from '../inputs/ValueInput.vue' -import ServiceParamsInput from '../inputs/ServiceParamsInput.vue' -import DeviceStatusConditionConfig from './DeviceStatusConditionConfig.vue' +import JsonParamsInput from '../inputs/JsonParamsInput.vue' + import { TriggerFormData } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneTriggerTypeEnum, getTriggerTypeOptions } from '@/views/iot/utils/constants' import { useVModel } from '@vueuse/core' @@ -198,11 +243,6 @@ const updateConditionField = (field: keyof TriggerFormData, value: any) => { updateValidationResult() } -const updateCondition = (value: TriggerFormData) => { - emit('update:modelValue', value) - updateValidationResult() -} - const handleTriggerTypeChange = (type: number) => { emit('trigger-type-change', type) } @@ -232,14 +272,12 @@ const handleOperatorChange = () => { updateValidationResult() } -const handleValueValidate = (_result: { valid: boolean; message: string }) => { - updateValidationResult() -} - -const handleValidate = (result: { valid: boolean; message: string }) => { +// 处理参数验证结果 +const handleValueValidate = (result: { valid: boolean; message: string }) => { isValid.value = result.valid validationMessage.value = result.message emit('validate', result) + updateValidationResult() } // 验证逻辑 diff --git a/src/views/iot/rule/scene/form/inputs/ServiceParamsInput.vue b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue similarity index 43% rename from src/views/iot/rule/scene/form/inputs/ServiceParamsInput.vue rename to src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue index 2751168ec..8abff7914 100644 --- a/src/views/iot/rule/scene/form/inputs/ServiceParamsInput.vue +++ b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue @@ -1,29 +1,101 @@ - + @@ -149,41 +137,170 @@ import { useVModel } from '@vueuse/core' import { InfoFilled } from '@element-plus/icons-vue' -/** 服务参数输入组件 */ -defineOptions({ name: 'ServiceParamsInput' }) +/** JSON参数输入组件 - 通用版本 */ +defineOptions({ name: 'JsonParamsInput' }) + +export interface JsonParamsConfig { + // 服务配置 + service?: { + name: string + inputParams?: any[] + } + // 事件配置 + event?: { + name: string + outputParams?: any[] + } + // 属性配置 + properties?: any[] + // 自定义配置 + custom?: { + name: string + params: any[] + } +} interface Props { modelValue?: string - serviceConfig?: any + config?: JsonParamsConfig + type?: 'service' | 'event' | 'property' | 'custom' + placeholder?: string } interface Emits { (e: 'update:modelValue', value: string): void + (e: 'validate', result: { valid: boolean; message: string }): void } -const props = defineProps() +const props = withDefaults(defineProps(), { + type: 'service', + placeholder: '请输入JSON格式的参数' +}) + const emit = defineEmits() const localValue = useVModel(props, 'modelValue', emit, { defaultValue: '' }) -// TODO @puhui999:一些注释风格; - // 状态 const paramsJson = ref('') const jsonError = ref('') -// 示例弹出层相关状态 -const showExampleDetail = ref(false) -const exampleTriggerRef = ref() -const exampleDetailRef = ref() -const examplePopoverStyle = ref({}) - // 计算属性 -const inputParams = computed(() => { - return props.serviceConfig?.service?.inputParams || [] +const hasConfig = computed(() => { + return !!( + props.config?.service || + props.config?.event || + props.config?.properties || + props.config?.custom + ) +}) + +const paramsList = computed(() => { + switch (props.type) { + case 'service': + return props.config?.service?.inputParams || [] + case 'event': + return props.config?.event?.outputParams || [] + case 'property': + return props.config?.properties || [] + case 'custom': + return props.config?.custom?.params || [] + default: + return [] + } +}) + +const title = computed(() => { + switch (props.type) { + case 'service': + return `${props.config?.service?.name || '服务'} - 输入参数示例` + case 'event': + return `${props.config?.event?.name || '事件'} - 输出参数示例` + case 'property': + return '属性设置 - 参数示例' + case 'custom': + return `${props.config?.custom?.name || '自定义'} - 参数示例` + default: + return '参数示例' + } +}) + +const titleIcon = computed(() => { + switch (props.type) { + case 'service': + return 'ep:service' + case 'event': + return 'ep:bell' + case 'property': + return 'ep:edit' + case 'custom': + return 'ep:document' + default: + return 'ep:document' + } +}) + +const paramsIcon = computed(() => { + switch (props.type) { + case 'service': + return 'ep:edit' + case 'event': + return 'ep:upload' + case 'property': + return 'ep:setting' + case 'custom': + return 'ep:list' + default: + return 'ep:edit' + } +}) + +const paramsLabel = computed(() => { + switch (props.type) { + case 'service': + return '输入参数' + case 'event': + return '输出参数' + case 'property': + return '属性参数' + case 'custom': + return '参数列表' + default: + return '参数' + } +}) + +const emptyMessage = computed(() => { + switch (props.type) { + case 'service': + return '此服务无需输入参数' + case 'event': + return '此事件无输出参数' + case 'property': + return '无可设置的属性' + case 'custom': + return '无参数配置' + default: + return '无参数' + } +}) + +const noConfigMessage = computed(() => { + switch (props.type) { + case 'service': + return '请先选择服务' + case 'event': + return '请先选择事件' + case 'property': + return '请先选择产品' + case 'custom': + return '请先进行配置' + default: + return '请先进行配置' + } }) // 事件处理 @@ -203,7 +320,7 @@ const handleParamsChange = () => { } // 验证必填参数 - for (const param of inputParams.value) { + for (const param of paramsList.value) { if (param.required && (!parsed[param.identifier] || parsed[param.identifier] === '')) { jsonError.value = `参数 ${param.name} 为必填项` emit('validate', { valid: false, message: jsonError.value }) @@ -237,7 +354,6 @@ const clearParams = () => { } // 工具函数 -// TODO @puhui999:这里的复用 const getParamTypeName = (dataType: string) => { const typeMap = { int: '整数', @@ -291,12 +407,12 @@ const getExampleValue = (param: any) => { } const generateExampleJson = () => { - if (inputParams.value.length === 0) { + if (paramsList.value.length === 0) { return '{}' } const example = {} - inputParams.value.forEach((param) => { + paramsList.value.forEach((param) => { switch (param.dataType) { case 'int': example[param.identifier] = 25 @@ -325,86 +441,6 @@ const generateExampleJson = () => { return JSON.stringify(example, null, 2) } -// 示例弹出层控制方法 -const toggleExampleDetail = () => { - if (showExampleDetail.value) { - hideExampleDetail() - } else { - showExampleDetailPopover() - } -} - -const showExampleDetailPopover = () => { - if (!exampleTriggerRef.value) return - - showExampleDetail.value = true - - nextTick(() => { - updateExamplePopoverPosition() - }) -} - -const hideExampleDetail = () => { - showExampleDetail.value = false -} - -const updateExamplePopoverPosition = () => { - if (!exampleTriggerRef.value || !exampleDetailRef.value) return - - const triggerEl = exampleTriggerRef.value.$el - const triggerRect = triggerEl.getBoundingClientRect() - - // 计算弹出层位置 - const left = triggerRect.left + triggerRect.width + 8 - const top = triggerRect.top - - // 检查是否超出视窗右边界 - const popoverWidth = 500 // 最大宽度 - const viewportWidth = window.innerWidth - - let finalLeft = left - if (left + popoverWidth > viewportWidth - 16) { - // 如果超出右边界,显示在左侧 - finalLeft = triggerRect.left - popoverWidth - 8 - } - - // 检查是否超出视窗下边界 - let finalTop = top - const popoverHeight = exampleDetailRef.value.offsetHeight || 300 - const viewportHeight = window.innerHeight - - if (top + popoverHeight > viewportHeight - 16) { - finalTop = Math.max(16, viewportHeight - popoverHeight - 16) - } - - examplePopoverStyle.value = { - position: 'fixed', - left: `${finalLeft}px`, - top: `${finalTop}px`, - zIndex: 9999 - } -} - -// 点击外部关闭弹出层 -const handleClickOutside = (event: MouseEvent) => { - if ( - showExampleDetail.value && - exampleDetailRef.value && - exampleTriggerRef.value && - !exampleDetailRef.value.contains(event.target as Node) && - !exampleTriggerRef.value.$el.contains(event.target as Node) - ) { - hideExampleDetail() - } -} - -// 监听窗口大小变化,重新计算弹出层位置 -const handleResize = () => { - if (showExampleDetail.value) { - updateExamplePopoverPosition() - } -} - // 初始化 onMounted(() => { if (localValue.value) { @@ -416,16 +452,6 @@ onMounted(() => { jsonError.value = '初始参数格式错误' } } - - // 添加事件监听器 - document.addEventListener('click', handleClickOutside) - window.addEventListener('resize', handleResize) -}) - -// 组件卸载时清理事件监听器 -onUnmounted(() => { - document.removeEventListener('click', handleClickOutside) - window.removeEventListener('resize', handleResize) }) // 监听输入值变化 @@ -438,11 +464,11 @@ watch( } ) -// 监听服务配置变化 +// 监听配置变化 watch( - () => props.serviceConfig, + () => props.config, () => { - // 服务变化时清空参数 + // 配置变化时清空参数 paramsJson.value = '' localValue.value = '' jsonError.value = '' @@ -451,45 +477,23 @@ watch( diff --git a/src/views/iot/rule/scene/form/selectors/PropertySelector.vue b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue index e353fc273..5012c77e6 100644 --- a/src/views/iot/rule/scene/form/selectors/PropertySelector.vue +++ b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue @@ -286,7 +286,6 @@ const handleChange = (value: string) => { config: property }) } - // 选择变化时,el-popover 会自动关闭 } // 获取物模型TSL数据 -- Gitee From d81c544ad93a033635a7f30140b9dbed043ce124 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 5 Aug 2025 21:26:01 +0800 Subject: [PATCH 5/6] =?UTF-8?q?perf:=20=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=E5=92=8C=E8=A7=A6=E5=8F=91=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=80=BC=E7=B1=BB=E5=9E=8B=E9=83=BD=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E4=B8=BA=E4=BA=86=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/scene.types.ts | 2 +- .../form/configs/DeviceControlConfig.vue | 184 ++++++++++-------- .../form/configs/MainConditionInnerConfig.vue | 91 +++++++-- .../scene/form/inputs/JsonParamsInput.vue | 80 ++++++-- .../scene/form/selectors/PropertySelector.vue | 19 +- src/views/iot/utils/constants.ts | 10 + 6 files changed, 277 insertions(+), 109 deletions(-) diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index 9c0ee6f1f..d8ba01faa 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -175,7 +175,7 @@ interface Action { productId?: number // 产品编号 deviceId?: number // 设备编号 identifier?: string // 物模型标识符(服务调用时使用) - params?: Record // 请求参数 + params?: string // 请求参数 alertConfigId?: number // 告警配置编号 } diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue index 0598c4540..9ab82e721 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -82,8 +82,12 @@ import { useVModel } from '@vueuse/core' import ProductSelector from '../selectors/ProductSelector.vue' import DeviceSelector from '../selectors/DeviceSelector.vue' import JsonParamsInput from '../inputs/JsonParamsInput.vue' -import { Action, ThingModelService } from '@/api/iot/rule/scene/scene.types' -import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' +import { Action, ThingModelProperty, ThingModelService } from '@/api/iot/rule/scene/scene.types' +import { + IotRuleSceneActionTypeEnum, + IoTThingModelAccessModeEnum +} from '@/views/iot/utils/constants' +import { ThingModelApi } from '@/api/iot/thingmodel' /** 设备控制配置组件 */ defineOptions({ name: 'DeviceControlConfig' }) @@ -99,7 +103,7 @@ const emit = defineEmits<{ const action = useVModel(props, 'modelValue', emit) // 简化后的状态变量 -const thingModelProperties = ref([]) // 物模型属性列表 +const thingModelProperties = ref([]) // 物模型属性列表 const loadingThingModel = ref(false) // 物模型加载状态 const selectedService = ref(null) // 选中的服务对象 const serviceList = ref([]) // 服务列表 @@ -108,21 +112,16 @@ const loadingServices = ref(false) // 服务加载状态 // 参数值的计算属性,用于双向绑定 const paramsValue = computed({ get: () => { + // 如果 params 是对象,转换为 JSON 字符串(兼容旧数据) if (action.value.params && typeof action.value.params === 'object') { return JSON.stringify(action.value.params, null, 2) } - return '' + // 如果 params 已经是字符串,直接返回 + return action.value.params || '' }, set: (value: string) => { - try { - if (value.trim()) { - action.value.params = JSON.parse(value) - } else { - action.value.params = {} - } - } catch (error) { - console.error('JSON解析错误:', error) - } + // 直接保存为 JSON 字符串,不进行解析转换 + action.value.params = value.trim() || '' } }) @@ -151,7 +150,7 @@ const handleProductChange = (productId?: number) => { if (action.value.productId !== productId) { action.value.deviceId = undefined action.value.identifier = undefined // 清空服务标识符 - action.value.params = {} + action.value.params = '' // 清空参数,保存为空字符串 selectedService.value = null // 清空选中的服务 serviceList.value = [] // 清空服务列表 } @@ -173,7 +172,7 @@ const handleProductChange = (productId?: number) => { const handleDeviceChange = (deviceId?: number) => { // 当设备变化时,清空参数配置 if (action.value.deviceId !== deviceId) { - action.value.params = {} + action.value.params = '' // 清空参数,保存为空字符串 } } @@ -187,7 +186,7 @@ const handleServiceChange = (serviceIdentifier?: string) => { selectedService.value = service // 当服务变化时,清空参数配置 - action.value.params = {} + action.value.params = '' // 如果选择了服务且有输入参数,生成默认参数结构 if (service && service.inputParams && service.inputParams.length > 0) { @@ -195,12 +194,29 @@ const handleServiceChange = (serviceIdentifier?: string) => { service.inputParams.forEach((param) => { defaultParams[param.identifier] = getDefaultValueForParam(param) }) - action.value.params = defaultParams + // 将默认参数转换为 JSON 字符串保存 + action.value.params = JSON.stringify(defaultParams, null, 2) + } +} + +/** + * 获取物模型TSL数据 + * @param productId 产品ID + * @returns 物模型TSL数据 + */ +const getThingModelTSL = async (productId: number) => { + if (!productId) return null + + try { + return await ThingModelApi.getThingModelTSLByProductId(productId) + } catch (error) { + console.error('获取物模型TSL数据失败:', error) + return null } } /** - * 加载物模型属性 + * 加载物模型属性(可写属性) * @param productId 产品ID */ const loadThingModelProperties = async (productId: number) => { @@ -211,39 +227,22 @@ const loadThingModelProperties = async (productId: number) => { try { loadingThingModel.value = true - // TODO: 这里需要调用实际的物模型API - // const response = await ProductApi.getThingModel(productId) - // 暂时使用模拟数据 - thingModelProperties.value = [ - { - identifier: 'BatteryLevel', - name: '电池电量', - dataType: 'int', - description: '设备电池电量百分比' - }, - { - identifier: 'WaterLeachState', - name: '漏水状态', - dataType: 'bool', - description: '设备漏水检测状态' - }, - { - identifier: 'Temperature', - name: '温度', - dataType: 'float', - description: '环境温度值' - }, - { - identifier: 'Humidity', - name: '湿度', - dataType: 'float', - description: '环境湿度值' - } - ] + const tslData = await getThingModelTSL(productId) + + if (!tslData?.properties) { + thingModelProperties.value = [] + return + } - // 属性加载完成,无需额外初始化 + // 过滤出可写的属性(accessMode 包含 'w') + thingModelProperties.value = tslData.properties.filter( + (property: ThingModelProperty) => + property.accessMode && + (property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value || + property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value) + ) } catch (error) { - console.error('加载物模型失败:', error) + console.error('加载物模型属性失败:', error) thingModelProperties.value = [] } finally { loadingThingModel.value = false @@ -260,11 +259,16 @@ const loadServiceList = async (productId: number) => { return } - loadingServices.value = true try { - const { ThingModelApi } = await import('@/api/iot/thingmodel') - const tslData = await ThingModelApi.getThingModelTSLByProductId(productId) - serviceList.value = tslData?.services || [] + loadingServices.value = true + const tslData = await getThingModelTSL(productId) + + if (!tslData?.services) { + serviceList.value = [] + return + } + + serviceList.value = tslData.services } catch (error) { console.error('加载服务列表失败:', error) serviceList.value = [] @@ -316,43 +320,67 @@ const getDefaultValueForParam = (param: any) => { } } +// 防止重复初始化的标志 +const isInitialized = ref(false) + /** - * 组件初始化 + * 初始化组件数据 */ -onMounted(() => { +const initializeComponent = async () => { + if (isInitialized.value) return + + const currentAction = action.value + if (!currentAction) return + // 如果已经选择了产品且是属性设置类型,加载物模型 - if (action.value.productId && isPropertySetAction.value) { - loadThingModelProperties(action.value.productId) + if (currentAction.productId && isPropertySetAction.value) { + await loadThingModelProperties(currentAction.productId) } // 如果是服务调用类型且已有标识符,初始化服务选择 - if (action.value.productId && isServiceInvokeAction.value && action.value.identifier) { + if (currentAction.productId && isServiceInvokeAction.value && currentAction.identifier) { // 加载物模型TSL以获取服务信息 - loadServiceFromTSL(action.value.productId, action.value.identifier) + await loadServiceFromTSL(currentAction.productId, currentAction.identifier) } + + isInitialized.value = true +} + +/** + * 组件初始化 + */ +onMounted(() => { + initializeComponent() }) -// 监听action.value变化,处理编辑模式的数据回显 +// 只监听关键字段的变化,避免深度监听导致的性能问题 watch( - () => action.value, - async (newAction) => { - if (newAction) { - // 处理服务调用的数据回显 - if (isServiceInvokeAction.value && newAction.productId) { - if (newAction.identifier) { - // 编辑模式:加载服务信息并设置选中的服务 - await loadServiceFromTSL(newAction.productId, newAction.identifier) - } else { - // 新建模式:只加载服务列表 - await loadServiceList(newAction.productId) - } - } else if (isServiceInvokeAction.value) { - // 清空服务选择 - selectedService.value = null - serviceList.value = [] + () => [action.value.productId, action.value.type, action.value.identifier], + async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => { + // 避免初始化时的重复调用 + if (!isInitialized.value) return + + // 产品变化时重新加载数据 + if (newProductId !== oldProductId) { + if (newProductId && isPropertySetAction.value) { + await loadThingModelProperties(newProductId as number) + } else if (newProductId && isServiceInvokeAction.value) { + await loadServiceList(newProductId as number) } } - }, - { deep: true, immediate: true } + + // 服务标识符变化时更新选中的服务 + if ( + newIdentifier !== oldIdentifier && + newProductId && + isServiceInvokeAction.value && + newIdentifier + ) { + const service = serviceList.value.find((s: any) => s.identifier === newIdentifier) + if (service) { + selectedService.value = service + } + } + } ) diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index 3ef84848a..9bdf89134 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -59,8 +59,14 @@ - - + + - + @@ -217,6 +228,52 @@ const isDeviceStatusTrigger = computed(() => { return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE }) +// 服务配置 - 用于 JsonParamsInput +const serviceConfig = computed(() => { + if ( + propertyConfig.value && + props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE + ) { + return { + service: { + name: propertyConfig.value.name || '服务', + inputParams: propertyConfig.value.inputParams || [] + } + } + } + return undefined +}) + +// 事件配置 - 用于 JsonParamsInput +const eventConfig = computed(() => { + if (propertyConfig.value && props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) { + return { + event: { + name: propertyConfig.value.name || '事件', + outputParams: propertyConfig.value.outputParams || [] + } + } + } + return undefined +}) + +// 确保传递给 JsonParamsInput 的值始终是字符串类型 +const conditionValueAsString = computed({ + get: () => { + const value = condition.value.value + if (value === null || value === undefined) { + return '' + } + if (typeof value === 'object') { + return JSON.stringify(value, null, 2) + } + return String(value) + }, + set: (newValue: string) => { + condition.value.value = newValue + } +}) + // 获取触发类型文本 // TODO @puhui999:是不是有枚举可以服用哈; const getTriggerTypeText = (type: number) => { @@ -264,6 +321,14 @@ const handlePropertyChange = (propertyInfo: any) => { if (propertyInfo) { propertyType.value = propertyInfo.type propertyConfig.value = propertyInfo.config + + // 对于事件上报和服务调用,自动设置操作符为 '=' + if ( + props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST || + props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE + ) { + condition.value.operator = '=' + } } updateValidationResult() } @@ -306,9 +371,10 @@ const updateValidationResult = () => { return } - // 服务调用不需要操作符 + // 服务调用和事件上报不需要操作符 if ( props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE && + props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST && !condition.value.operator ) { isValid.value = false @@ -336,8 +402,9 @@ watch( condition.value.productId, condition.value.deviceId, condition.value.identifier, - // 服务调用不需要监听操作符 - props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE + // 服务调用和事件上报不需要监听操作符 + props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE && + props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ? condition.value.operator : null, condition.value.value diff --git a/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue index 8abff7914..ac94cb096 100644 --- a/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue +++ b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue @@ -169,7 +169,6 @@ interface Props { interface Emits { (e: 'update:modelValue', value: string): void - (e: 'validate', result: { valid: boolean; message: string }): void } @@ -441,37 +440,90 @@ const generateExampleJson = () => { return JSON.stringify(example, null, 2) } -// 初始化 -onMounted(() => { +// 初始化标志,防止重复初始化 +const isInitialized = ref(false) + +// 初始化数据 +const initializeData = () => { + if (isInitialized.value) return + if (localValue.value) { try { - paramsJson.value = localValue.value + // modelValue 已经是字符串类型,直接使用 + if (localValue.value.trim()) { + try { + // 尝试解析JSON,如果成功则格式化 + const parsed = JSON.parse(localValue.value) + paramsJson.value = JSON.stringify(parsed, null, 2) + } catch { + // 如果不是有效的JSON,直接使用原字符串 + paramsJson.value = localValue.value + } + } else { + paramsJson.value = '' + } + jsonError.value = '' } catch (error) { console.error('初始化参数失败:', error) jsonError.value = '初始参数格式错误' } } + + isInitialized.value = true +} + +// 组件挂载时初始化 +onMounted(() => { + initializeData() }) -// 监听输入值变化 +// 监听外部值变化(编辑模式数据回显) watch( () => localValue.value, - (newValue) => { - if (newValue !== paramsJson.value) { - paramsJson.value = newValue || '' + (newValue, oldValue) => { + // 避免循环更新 + if (newValue === oldValue) return + + try { + let newJsonString = '' + + if (newValue && newValue.trim()) { + try { + // 尝试解析JSON,如果成功则格式化 + const parsed = JSON.parse(newValue) + newJsonString = JSON.stringify(parsed, null, 2) + } catch { + // 如果不是有效的JSON,直接使用原字符串 + newJsonString = newValue + } + } + + // 只有当JSON字符串真正改变时才更新 + if (newJsonString !== paramsJson.value) { + paramsJson.value = newJsonString + jsonError.value = '' + } + } catch (error) { + console.error('数据回显失败:', error) + jsonError.value = '数据格式错误' } - } + }, + { immediate: true } ) // 监听配置变化 watch( () => props.config, - () => { - // 配置变化时清空参数 - paramsJson.value = '' - localValue.value = '' - jsonError.value = '' + (newConfig, oldConfig) => { + // 只有在配置真正变化时才清空数据 + if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) { + // 如果没有外部传入的值,才清空数据 + if (!localValue.value) { + paramsJson.value = '' + jsonError.value = '' + } + } } ) diff --git a/src/views/iot/rule/scene/form/selectors/PropertySelector.vue b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue index 5012c77e6..c8d237a4d 100644 --- a/src/views/iot/rule/scene/form/selectors/PropertySelector.vue +++ b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue @@ -288,7 +288,9 @@ const handleChange = (value: string) => { } } -// 获取物模型TSL数据 +/** + * 获取物模型TSL数据 + */ const getThingModelTSL = async () => { if (!props.productId) { thingModelTSL.value = null @@ -298,8 +300,15 @@ const getThingModelTSL = async () => { loading.value = true try { - thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(props.productId) - parseThingModelData() + const tslData = await ThingModelApi.getThingModelTSLByProductId(props.productId) + + if (tslData) { + thingModelTSL.value = tslData + parseThingModelData() + } else { + // 如果TSL获取失败,尝试获取物模型列表 + await getThingModelList() + } } catch (error) { console.error('获取物模型TSL失败:', error) // 如果TSL获取失败,尝试获取物模型列表 @@ -309,7 +318,9 @@ const getThingModelTSL = async () => { } } -// 获取物模型列表(备用方案) +/** + * 获取物模型列表(备用方案) + */ const getThingModelList = async () => { if (!props.productId) { propertyList.value = [] diff --git a/src/views/iot/utils/constants.ts b/src/views/iot/utils/constants.ts index f33f398fc..c5412c0ef 100644 --- a/src/views/iot/utils/constants.ts +++ b/src/views/iot/utils/constants.ts @@ -109,9 +109,19 @@ export const IoTThingModelAccessModeEnum = { READ_ONLY: { label: '只读', value: 'r' + }, + WRITE_ONLY: { + label: '只写', + value: 'w' } } as const +/** 获取访问模式标签 */ +export const getAccessModeLabel = (value: string): string => { + const mode = Object.values(IoTThingModelAccessModeEnum).find((mode) => mode.value === value) + return mode?.label || value +} + /** 属性值的数据类型 */ export const IoTDataSpecsDataTypeEnum = { INT: 'int', -- Gitee From 9f3eb14a0f72696d3e3db2dde9c593ed84ee4bac Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 5 Aug 2025 22:12:19 +0800 Subject: [PATCH 6/6] =?UTF-8?q?perf:=20=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=E5=92=8C=E8=A7=A6=E5=8F=91=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=80=BC=E7=B1=BB=E5=9E=8B=E9=83=BD=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E4=B8=BA=E4=BA=86=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/configs/MainConditionInnerConfig.vue | 21 +--- .../scene/form/inputs/JsonParamsInput.vue | 101 +++++++----------- 2 files changed, 40 insertions(+), 82 deletions(-) diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index 9bdf89134..e08264155 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -97,7 +97,7 @@ { return undefined }) -// 确保传递给 JsonParamsInput 的值始终是字符串类型 -const conditionValueAsString = computed({ - get: () => { - const value = condition.value.value - if (value === null || value === undefined) { - return '' - } - if (typeof value === 'object') { - return JSON.stringify(value, null, 2) - } - return String(value) - }, - set: (newValue: string) => { - condition.value.value = newValue - } -}) - // 获取触发类型文本 // TODO @puhui999:是不是有枚举可以服用哈; const getTriggerTypeText = (type: number) => { diff --git a/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue index ac94cb096..b7001f6a9 100644 --- a/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue +++ b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue @@ -189,12 +189,15 @@ const jsonError = ref('') // 计算属性 const hasConfig = computed(() => { - return !!( - props.config?.service || - props.config?.event || - props.config?.properties || - props.config?.custom - ) + // TODO @puhui999: 后续统一处理 + console.log(props.config) + // return !!( + // props.config?.service || + // props.config?.event || + // props.config?.properties || + // props.config?.custom + // ) + return true }) const paramsList = computed(() => { @@ -440,44 +443,26 @@ const generateExampleJson = () => { return JSON.stringify(example, null, 2) } -// 初始化标志,防止重复初始化 -const isInitialized = ref(false) - -// 初始化数据 -const initializeData = () => { - if (isInitialized.value) return - - if (localValue.value) { - try { - // modelValue 已经是字符串类型,直接使用 - if (localValue.value.trim()) { - try { - // 尝试解析JSON,如果成功则格式化 - const parsed = JSON.parse(localValue.value) - paramsJson.value = JSON.stringify(parsed, null, 2) - } catch { - // 如果不是有效的JSON,直接使用原字符串 - paramsJson.value = localValue.value - } - } else { - paramsJson.value = '' - } - - jsonError.value = '' - } catch (error) { - console.error('初始化参数失败:', error) - jsonError.value = '初始参数格式错误' - } +// 处理数据回显的函数 +const handleDataDisplay = (value: string) => { + if (!value || !value.trim()) { + paramsJson.value = '' + jsonError.value = '' + return } - isInitialized.value = true + try { + // 尝试解析JSON,如果成功则格式化 + const parsed = JSON.parse(value) + paramsJson.value = JSON.stringify(parsed, null, 2) + jsonError.value = '' + } catch { + // 如果不是有效的JSON,直接使用原字符串 + paramsJson.value = value + jsonError.value = '' + } } -// 组件挂载时初始化 -onMounted(() => { - initializeData() -}) - // 监听外部值变化(编辑模式数据回显) watch( () => localValue.value, @@ -485,33 +470,23 @@ watch( // 避免循环更新 if (newValue === oldValue) return - try { - let newJsonString = '' - - if (newValue && newValue.trim()) { - try { - // 尝试解析JSON,如果成功则格式化 - const parsed = JSON.parse(newValue) - newJsonString = JSON.stringify(parsed, null, 2) - } catch { - // 如果不是有效的JSON,直接使用原字符串 - newJsonString = newValue - } - } - - // 只有当JSON字符串真正改变时才更新 - if (newJsonString !== paramsJson.value) { - paramsJson.value = newJsonString - jsonError.value = '' - } - } catch (error) { - console.error('数据回显失败:', error) - jsonError.value = '数据格式错误' - } + // 使用 nextTick 确保在下一个 tick 中处理数据 + nextTick(() => { + handleDataDisplay(newValue || '') + }) }, { immediate: true } ) +// 组件挂载后也尝试处理一次数据回显 +onMounted(() => { + nextTick(() => { + if (localValue.value) { + handleDataDisplay(localValue.value) + } + }) +}) + // 监听配置变化 watch( () => props.config, -- Gitee