diff --git a/src/api/iot/rule/scene/index.ts b/src/api/iot/rule/scene/index.ts index 75b121544f6831d44b8590b2504be77a8213ce12..9bbf8db6a09f35c3660f8274eeaf503bf25860f2 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 577786502532dc9f68c27c1a56f3cf9c0b501256..d8ba01faaba235f6d73e6e4b189155d266c4b2c4 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,12 +170,12 @@ interface TriggerConditionDO { } // 执行器 DO 结构 -interface ActionDO { +interface Action { type: number // 执行类型 productId?: number // 产品编号 deviceId?: number // 设备编号 identifier?: string // 物模型标识符(服务调用时使用) - params?: Record // 请求参数 + params?: string // 请求参数 alertConfigId?: number // 告警配置编号 } @@ -298,23 +194,9 @@ interface FormValidationRules { [key: string]: ValidationRule[] } +// 表单数据类型别名 +export type TriggerFormData = Trigger + // 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 94c94579b3c303b5305a39917bc471b877d1ffc3..0362bc4bf04a3abd2272e099b6ecb476caa088a7 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 bffe9d578615b8dd08432d948f77fc65715bd894..8073c351abaf123f1eac4263dcca0886949d896c 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 3eeed51438f9c7e3240a1dd5bfde8a70e65f5f1a..9574c58cefb510dd76b1503477a4de3c5695c395 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 2eb1c5a3da9ee88d73041134b7a5267c60f804d6..9ab82e72168518dbcc82aff56f1a58fa5dcc2f2a 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/DeviceStatusConditionConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue index 0f77124ecee551fd8f0d94a6b396c98a61c72feb..a380192ab0851b47a92b8293649f1e868ea4205b 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/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index 9906160b4c1b41030d5e310477a28584eaffa7b5..e082641553b8ebff04151ff82d2b62a2cc63bad7 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 @@ - - + + - + + + @@ -106,11 +129,44 @@
- + + + + + + + + + + + + + + + + + + + + + + + +
@@ -131,8 +187,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' @@ -172,6 +228,35 @@ 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 +}) + // 获取触发类型文本 // TODO @puhui999:是不是有枚举可以服用哈; const getTriggerTypeText = (type: number) => { @@ -198,11 +283,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) } @@ -224,6 +304,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() } @@ -232,14 +320,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() } // 验证逻辑 @@ -268,9 +354,10 @@ const updateValidationResult = () => { return } - // 服务调用不需要操作符 + // 服务调用和事件上报不需要操作符 if ( props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE && + props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST && !condition.value.operator ) { isValid.value = false @@ -298,8 +385,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/configs/SubConditionGroupConfig.vue b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue index d89e4c4dec51ceebc66dec2d94728872aee6bedc..1e86d301d633eb9c2350a28cd45b15feabd63221 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/inputs/ServiceParamsInput.vue b/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue similarity index 39% rename from src/views/iot/rule/scene/form/inputs/ServiceParamsInput.vue rename to src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue index 2751168ece7ea652fc667a485c4f10a481c4e07e..b7001f6a978a3ab675e5823919b1d58f91af4e03 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,12 +137,34 @@ 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 { @@ -162,28 +172,137 @@ interface Emits { (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(() => { + // TODO @puhui999: 后续统一处理 + console.log(props.config) + // return !!( + // props.config?.service || + // props.config?.event || + // props.config?.properties || + // props.config?.custom + // ) + return true +}) + +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 +322,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 +356,6 @@ const clearParams = () => { } // 工具函数 -// TODO @puhui999:这里的复用 const getParamTypeName = (dataType: string) => { const typeMap = { int: '整数', @@ -291,12 +409,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,171 +443,84 @@ 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 handleDataDisplay = (value: string) => { + if (!value || !value.trim()) { + paramsJson.value = '' + jsonError.value = '' + return } -} -// 点击外部关闭弹出层 -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() + try { + // 尝试解析JSON,如果成功则格式化 + const parsed = JSON.parse(value) + paramsJson.value = JSON.stringify(parsed, null, 2) + jsonError.value = '' + } catch { + // 如果不是有效的JSON,直接使用原字符串 + paramsJson.value = value + jsonError.value = '' } } -// 监听窗口大小变化,重新计算弹出层位置 -const handleResize = () => { - if (showExampleDetail.value) { - updateExamplePopoverPosition() - } -} +// 监听外部值变化(编辑模式数据回显) +watch( + () => localValue.value, + (newValue, oldValue) => { + // 避免循环更新 + if (newValue === oldValue) return + + // 使用 nextTick 确保在下一个 tick 中处理数据 + nextTick(() => { + handleDataDisplay(newValue || '') + }) + }, + { immediate: true } +) -// 初始化 +// 组件挂载后也尝试处理一次数据回显 onMounted(() => { - if (localValue.value) { - try { - paramsJson.value = localValue.value - jsonError.value = '' - } catch (error) { - console.error('初始化参数失败:', error) - jsonError.value = '初始参数格式错误' + nextTick(() => { + if (localValue.value) { + handleDataDisplay(localValue.value) } - } - - // 添加事件监听器 - document.addEventListener('click', handleClickOutside) - window.addEventListener('resize', handleResize) -}) - -// 组件卸载时清理事件监听器 -onUnmounted(() => { - document.removeEventListener('click', handleClickOutside) - window.removeEventListener('resize', handleResize) + }) }) -// 监听输入值变化 +// 监听配置变化 watch( - () => localValue.value, - (newValue) => { - if (newValue !== paramsJson.value) { - paramsJson.value = newValue || '' + () => props.config, + (newConfig, oldConfig) => { + // 只有在配置真正变化时才清空数据 + if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) { + // 如果没有外部传入的值,才清空数据 + if (!localValue.value) { + paramsJson.value = '' + jsonError.value = '' + } } } ) - -// 监听服务配置变化 -watch( - () => props.serviceConfig, - () => { - // 服务变化时清空参数 - paramsJson.value = '' - localValue.value = '' - jsonError.value = '' - } -) diff --git a/src/views/iot/rule/scene/form/sections/ActionSection.vue b/src/views/iot/rule/scene/form/sections/ActionSection.vue index 34403074547e97ca03c5d7015dd165f248f39964..c181e5e68b1a68f3314177f2d9810ddc81e582d4 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 78673176511397f0ce0128a14801d00c9b4c4f1c..fd27cd35c1af52cedda0ae946064f6702f57cabe 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 f03911bc2a71c293b7820941e8aa5268f7619ead..0000000000000000000000000000000000000000 --- a/src/views/iot/rule/scene/form/selectors/ServiceSelector.vue +++ /dev/null @@ -1,462 +0,0 @@ - - - - - - diff --git a/src/views/iot/rule/scene/index.vue b/src/views/iot/rule/scene/index.vue index 8f972913217ec290cf294ce2766902b12814c9cf..4a587072c1d98a4f95c5d0fcb5bacc465f5b7a7d 100644 --- a/src/views/iot/rule/scene/index.vue +++ b/src/views/iot/rule/scene/index.vue @@ -239,7 +239,7 @@ import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict' import { ContentWrap } from '@/components/ContentWrap' import RuleSceneForm from './form/RuleSceneForm.vue' -import { IotRuleSceneDO } 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, @@ -264,14 +264,14 @@ const queryParams = reactive({ }) const loading = ref(true) // 列表的加载中 -const list = ref([]) // 列表的数据 +const list = ref([]) // 列表的数据 const total = ref(0) // 列表的总页数 -const selectedRows = ref([]) // 选中的行数据 +const selectedRows = ref([]) // 选中的行数据 const queryFormRef = ref() // 搜索的表单 /** 表单状态 */ const formVisible = ref(false) // 是否可见 -const currentRule = ref() // 表单数据 +const currentRule = ref() // 表单数据 /** 统计数据 */ const statistics = ref({ @@ -327,7 +327,7 @@ const formatCronExpression = (cron: string): string => { } /** 获取规则摘要信息 */ -const getRuleSceneSummary = (rule: IotRuleSceneDO) => { +const getRuleSceneSummary = (rule: IotSceneRule) => { const triggerSummary = rule.triggers?.map((trigger: any) => { // 构建基础描述 @@ -455,12 +455,12 @@ const updateStatistics = () => { } /** 获取触发器摘要 */ -const getTriggerSummary = (rule: IotRuleSceneDO) => { +const getTriggerSummary = (rule: IotSceneRule) => { return getRuleSceneSummary(rule).triggerSummary } /** 获取执行器摘要 */ -const getActionSummary = (rule: IotRuleSceneDO) => { +const getActionSummary = (rule: IotSceneRule) => { return getRuleSceneSummary(rule).actionSummary } @@ -484,7 +484,7 @@ const handleAdd = () => { } /** 修改操作 */ -const handleEdit = (row: IotRuleSceneDO) => { +const handleEdit = (row: IotSceneRule) => { currentRule.value = row formVisible.value = true } @@ -506,7 +506,7 @@ const handleDelete = async (id: number) => { } /** 修改状态 */ -const handleToggleStatus = async (row: IotRuleSceneDO) => { +const handleToggleStatus = async (row: IotSceneRule) => { try { // 修改状态的二次确认 // TODO @puhui999:status 枚举; @@ -525,7 +525,7 @@ const handleToggleStatus = async (row: IotRuleSceneDO) => { } /** 多选框选中数据 */ -const handleSelectionChange = (selection: IotRuleSceneDO[]) => { +const handleSelectionChange = (selection: IotSceneRule[]) => { selectedRows.value = selection } diff --git a/src/views/iot/utils/constants.ts b/src/views/iot/utils/constants.ts index f33f398fce0a30fb071937a479d327945cc3da4f..c5412c0ef1df36dc80227a74ca951edd49acb9de 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',