perf:【IoT 物联网】场景联动触发器优化
parent
c740da02b9
commit
a554bc5309
|
@ -2,15 +2,7 @@
|
|||
* IoT 场景联动接口定义
|
||||
*/
|
||||
|
||||
// TODO @puhui999:枚举挪到 views/iot/utils/constants.ts 里
|
||||
// 枚举定义
|
||||
const IotRuleSceneTriggerTypeEnum = {
|
||||
DEVICE_STATE_UPDATE: 1, // 设备上下线变更
|
||||
DEVICE_PROPERTY_POST: 2, // 物模型属性上报
|
||||
DEVICE_EVENT_POST: 3, // 设备事件上报
|
||||
DEVICE_SERVICE_INVOKE: 4, // 设备服务调用
|
||||
TIMER: 100 // 定时触发
|
||||
} as const
|
||||
// 枚举定义已迁移到 constants.ts,这里不再重复导出
|
||||
|
||||
const IotRuleSceneActionTypeEnum = {
|
||||
DEVICE_PROPERTY_SET: 1, // 设备属性设置,
|
||||
|
@ -25,11 +17,7 @@ const IotDeviceMessageTypeEnum = {
|
|||
EVENT: 'event' // 事件
|
||||
} as const
|
||||
|
||||
// TODO @puhui999:这个貌似可以不要?
|
||||
const IotDeviceMessageIdentifierEnum = {
|
||||
PROPERTY_SET: 'set', // 属性设置
|
||||
SERVICE_INVOKE: '${identifier}' // 服务调用
|
||||
} as const
|
||||
// 已删除不需要的 IotDeviceMessageIdentifierEnum
|
||||
|
||||
const IotRuleSceneTriggerConditionParameterOperatorEnum = {
|
||||
EQUALS: { name: '等于', value: '=' }, // 等于
|
||||
|
@ -64,29 +52,10 @@ const IotRuleSceneTriggerTimeOperatorEnum = {
|
|||
TODAY: { name: '在今日之间', value: 'today' } // 在今日之间
|
||||
} as const
|
||||
|
||||
// TODO @puhui999:下面 IotAlertConfigReceiveTypeEnum、DeviceStateEnum 没用到,貌似可以删除下?
|
||||
const IotAlertConfigReceiveTypeEnum = {
|
||||
SMS: 1, // 短信
|
||||
MAIL: 2, // 邮箱
|
||||
NOTIFY: 3 // 通知
|
||||
} as const
|
||||
// 已删除未使用的枚举:IotAlertConfigReceiveTypeEnum、DeviceStateEnum
|
||||
// CommonStatusEnum 已在全局定义,这里不再重复定义
|
||||
|
||||
// 设备状态枚举
|
||||
const DeviceStateEnum = {
|
||||
INACTIVE: 0, // 未激活
|
||||
ONLINE: 1, // 在线
|
||||
OFFLINE: 2 // 离线
|
||||
} as const
|
||||
|
||||
// TODO @puhui999:这个全局已经有啦
|
||||
// 通用状态枚举
|
||||
const CommonStatusEnum = {
|
||||
ENABLE: 0, // 开启
|
||||
DISABLE: 1 // 关闭
|
||||
} as const
|
||||
|
||||
// 基础接口
|
||||
// TODO @puhui999:这个貌似可以不要?
|
||||
// 基础接口(如果项目中有全局的 BaseDO,可以使用全局的)
|
||||
interface TenantBaseDO {
|
||||
createTime?: Date // 创建时间
|
||||
updateTime?: Date // 更新时间
|
||||
|
@ -144,7 +113,7 @@ interface RuleSceneFormData {
|
|||
name: string
|
||||
description?: string
|
||||
status: number
|
||||
trigger: TriggerFormData
|
||||
triggers: TriggerFormData[] // 支持多个触发器
|
||||
actions: ActionFormData[]
|
||||
}
|
||||
|
||||
|
@ -209,8 +178,7 @@ interface IotRuleScene extends TenantBaseDO {
|
|||
}
|
||||
|
||||
// 工具类型 - 从枚举中提取类型
|
||||
export type TriggerType =
|
||||
(typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum]
|
||||
// TriggerType 现在从 constants.ts 中的枚举提取
|
||||
export type ActionType =
|
||||
(typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum]
|
||||
export type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum]
|
||||
|
@ -246,16 +214,11 @@ export {
|
|||
ConditionGroupContainerFormData,
|
||||
SubConditionGroupFormData,
|
||||
ConditionFormData,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum,
|
||||
IotRuleSceneTriggerConditionTypeEnum,
|
||||
IotRuleSceneTriggerTimeOperatorEnum,
|
||||
IotAlertConfigReceiveTypeEnum,
|
||||
DeviceStateEnum,
|
||||
CommonStatusEnum,
|
||||
ValidationRule,
|
||||
FormValidationRules
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
:close-on-press-escape="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px">
|
||||
<!-- 基础信息配置 -->
|
||||
<BasicInfoSection v-model="formData" :rules="formRules" />
|
||||
|
||||
<!-- 触发器配置 -->
|
||||
<TriggerSection v-model:trigger="formData.trigger" @validate="handleTriggerValidate" />
|
||||
<TriggerSection v-model:triggers="formData.triggers" @validate="handleTriggerValidate" />
|
||||
|
||||
<!-- 执行器配置 -->
|
||||
<ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
|
||||
|
@ -40,15 +40,21 @@ import BasicInfoSection from './sections/BasicInfoSection.vue'
|
|||
import TriggerSection from './sections/TriggerSection.vue'
|
||||
import ActionSection from './sections/ActionSection.vue'
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
IotRuleScene,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
RuleSceneFormData
|
||||
RuleSceneFormData,
|
||||
TriggerFormData
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { generateUUID } from '@/utils'
|
||||
|
||||
// 导入全局的 CommonStatusEnum
|
||||
const CommonStatusEnum = {
|
||||
ENABLE: 0, // 开启
|
||||
DISABLE: 1 // 关闭
|
||||
} as const
|
||||
|
||||
/** IoT 场景联动规则表单 - 主表单组件 */
|
||||
defineOptions({ name: 'RuleSceneForm' })
|
||||
|
||||
|
@ -76,17 +82,19 @@ const createDefaultFormData = (): RuleSceneFormData => {
|
|||
name: '',
|
||||
description: '',
|
||||
status: CommonStatusEnum.ENABLE, // 默认启用状态
|
||||
trigger: {
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
productId: undefined,
|
||||
deviceId: undefined,
|
||||
identifier: undefined,
|
||||
operator: undefined,
|
||||
value: undefined,
|
||||
cronExpression: undefined,
|
||||
mainCondition: undefined,
|
||||
conditionGroup: undefined
|
||||
},
|
||||
triggers: [
|
||||
{
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
productId: undefined,
|
||||
deviceId: undefined,
|
||||
identifier: undefined,
|
||||
operator: undefined,
|
||||
value: undefined,
|
||||
cronExpression: undefined,
|
||||
mainCondition: undefined,
|
||||
conditionGroup: undefined
|
||||
}
|
||||
],
|
||||
actions: []
|
||||
}
|
||||
}
|
||||
|
@ -95,13 +103,13 @@ const createDefaultFormData = (): RuleSceneFormData => {
|
|||
* 将表单数据转换为 API 请求格式
|
||||
*/
|
||||
const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
|
||||
// 构建触发器条件
|
||||
const buildTriggerConditions = () => {
|
||||
// 构建单个触发器的条件
|
||||
const buildTriggerConditions = (trigger: TriggerFormData) => {
|
||||
const conditions: any[] = []
|
||||
|
||||
// 处理主条件
|
||||
if (formData.trigger.mainCondition) {
|
||||
const mainCondition = formData.trigger.mainCondition
|
||||
if (trigger.mainCondition) {
|
||||
const mainCondition = trigger.mainCondition
|
||||
conditions.push({
|
||||
type: mainCondition.type === 2 ? 'property' : 'event',
|
||||
identifier: mainCondition.identifier || '',
|
||||
|
@ -115,8 +123,8 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
|
|||
}
|
||||
|
||||
// 处理条件组
|
||||
if (formData.trigger.conditionGroup?.subGroups) {
|
||||
formData.trigger.conditionGroup.subGroups.forEach((subGroup) => {
|
||||
if (trigger.conditionGroup?.subGroups) {
|
||||
trigger.conditionGroup.subGroups.forEach((subGroup) => {
|
||||
subGroup.conditions.forEach((condition) => {
|
||||
conditions.push({
|
||||
type: condition.type === 2 ? 'property' : 'event',
|
||||
|
@ -140,19 +148,13 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
|
|||
name: formData.name,
|
||||
description: formData.description,
|
||||
status: Number(formData.status),
|
||||
triggers: [
|
||||
{
|
||||
type: formData.trigger.type,
|
||||
productKey: formData.trigger.productId
|
||||
? `product_${formData.trigger.productId}`
|
||||
: undefined,
|
||||
deviceNames: formData.trigger.deviceId
|
||||
? [`device_${formData.trigger.deviceId}`]
|
||||
: undefined,
|
||||
cronExpression: formData.trigger.cronExpression,
|
||||
conditions: buildTriggerConditions()
|
||||
}
|
||||
],
|
||||
triggers: formData.triggers.map((trigger) => ({
|
||||
type: trigger.type,
|
||||
productKey: trigger.productId ? `product_${trigger.productId}` : undefined,
|
||||
deviceNames: trigger.deviceId ? [`device_${trigger.deviceId}`] : undefined,
|
||||
cronExpression: trigger.cronExpression,
|
||||
conditions: buildTriggerConditions(trigger)
|
||||
})),
|
||||
actions:
|
||||
formData.actions?.map((action) => ({
|
||||
type: action.type,
|
||||
|
@ -180,9 +182,7 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
|
|||
* 将 API 响应数据转换为表单格式
|
||||
*/
|
||||
const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => {
|
||||
const firstTrigger = apiData.triggers?.[0]
|
||||
|
||||
// 解析触发器条件
|
||||
// 解析单个触发器的条件
|
||||
const parseConditions = (trigger: any) => {
|
||||
if (!trigger?.conditions?.length) {
|
||||
return {
|
||||
|
@ -208,28 +208,23 @@ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => {
|
|||
}
|
||||
}
|
||||
|
||||
const conditionData = firstTrigger
|
||||
? parseConditions(firstTrigger)
|
||||
: {
|
||||
mainCondition: undefined,
|
||||
conditionGroup: undefined
|
||||
}
|
||||
|
||||
return {
|
||||
...apiData,
|
||||
status: Number(apiData.status),
|
||||
trigger: firstTrigger
|
||||
? {
|
||||
type: Number(firstTrigger.type),
|
||||
// 转换所有触发器
|
||||
const triggers = apiData.triggers?.length
|
||||
? apiData.triggers.map((trigger) => {
|
||||
const conditionData = parseConditions(trigger)
|
||||
return {
|
||||
type: Number(trigger.type),
|
||||
productId: undefined, // 需要从 productKey 解析
|
||||
deviceId: undefined, // 需要从 deviceNames 解析
|
||||
identifier: undefined,
|
||||
operator: undefined,
|
||||
value: undefined,
|
||||
cronExpression: firstTrigger.cronExpression,
|
||||
cronExpression: trigger.cronExpression,
|
||||
...conditionData
|
||||
}
|
||||
: {
|
||||
})
|
||||
: [
|
||||
{
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
productId: undefined,
|
||||
deviceId: undefined,
|
||||
|
@ -239,7 +234,13 @@ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => {
|
|||
cronExpression: undefined,
|
||||
mainCondition: undefined,
|
||||
conditionGroup: undefined
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
return {
|
||||
...apiData,
|
||||
status: Number(apiData.status),
|
||||
triggers,
|
||||
actions:
|
||||
apiData.actions?.map((action) => ({
|
||||
...action,
|
||||
|
|
|
@ -114,11 +114,8 @@
|
|||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import ConditionConfig from './ConditionConfig.vue'
|
||||
import {
|
||||
ConditionGroupFormData,
|
||||
ConditionFormData,
|
||||
IotRuleSceneTriggerTypeEnum
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { ConditionFormData, ConditionGroupFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
|
||||
|
||||
/** 条件组配置组件 */
|
||||
defineOptions({ name: 'ConditionGroupConfig' })
|
||||
|
@ -133,6 +130,7 @@ interface Props {
|
|||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: ConditionGroupFormData): void
|
||||
|
||||
(e: 'validate', result: { valid: boolean; message: string }): void
|
||||
}
|
||||
|
||||
|
|
|
@ -62,26 +62,21 @@ import { useVModel } from '@vueuse/core'
|
|||
|
||||
import MainConditionConfig from './MainConditionConfig.vue'
|
||||
import ConditionGroupContainerConfig from './ConditionGroupContainerConfig.vue'
|
||||
import {
|
||||
TriggerFormData,
|
||||
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneTriggerTypeEnum as TriggerTypeEnum } from '@/views/iot/utils/constants'
|
||||
|
||||
/** 设备触发配置组件 */
|
||||
defineOptions({ name: 'DeviceTriggerConfig' })
|
||||
|
||||
// TODO @puhui999:下面的 Props、Emits 可以合并到变量那;
|
||||
interface Props {
|
||||
// Props 和 Emits 定义
|
||||
const props = defineProps<{
|
||||
modelValue: TriggerFormData
|
||||
}
|
||||
}>()
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: TriggerFormData): void
|
||||
(e: 'validate', result: { valid: boolean; message: string }): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: TriggerFormData]
|
||||
validate: [result: { valid: boolean; message: string }]
|
||||
}>()
|
||||
|
||||
const trigger = useVModel(props, 'modelValue', emit)
|
||||
|
||||
|
|
|
@ -20,19 +20,13 @@
|
|||
</div>
|
||||
|
||||
<!-- 主条件配置 -->
|
||||
<!-- TODO @puhui999:这里可以简化下,主条件是不能删除的。。。 -->
|
||||
<div v-else class="space-y-16px">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">主条件</span>
|
||||
<el-tag size="small" type="primary">必须满足</el-tag>
|
||||
</div>
|
||||
<el-button type="danger" size="small" text @click="removeMainCondition">
|
||||
<Icon icon="ep:delete" />
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<MainConditionInnerConfig
|
||||
:model-value="modelValue"
|
||||
@update:model-value="updateCondition"
|
||||
|
@ -53,18 +47,14 @@ import {
|
|||
/** 主条件配置组件 */
|
||||
defineOptions({ name: 'MainConditionConfig' })
|
||||
|
||||
interface Props {
|
||||
defineProps<{
|
||||
modelValue?: ConditionFormData
|
||||
triggerType: number
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value?: ConditionFormData): void
|
||||
(e: 'validate', result: { valid: boolean; message: string }): void
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
}>()
|
||||
|
||||
// 事件处理
|
||||
const addMainCondition = () => {
|
||||
|
@ -79,10 +69,6 @@ const addMainCondition = () => {
|
|||
emit('update:modelValue', newCondition)
|
||||
}
|
||||
|
||||
const removeMainCondition = () => {
|
||||
emit('update:modelValue', undefined)
|
||||
}
|
||||
|
||||
const updateCondition = (condition: ConditionFormData) => {
|
||||
emit('update:modelValue', condition)
|
||||
}
|
||||
|
@ -90,4 +76,8 @@ const updateCondition = (condition: ConditionFormData) => {
|
|||
const handleValidate = (result: { valid: boolean; message: string }) => {
|
||||
emit('validate', result)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
addMainCondition()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
<template>
|
||||
<div class="space-y-16px">
|
||||
<!-- 触发事件类型显示 -->
|
||||
<div class="flex items-center gap-8px mb-16px">
|
||||
<span class="text-14px text-[var(--el-text-color-regular)]">触发事件类型:</span>
|
||||
<el-tag size="small" type="primary">{{ getTriggerTypeText(triggerType) }}</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 设备属性条件配置 -->
|
||||
<div v-if="isDevicePropertyTrigger" class="space-y-16px">
|
||||
<!-- 产品设备选择 -->
|
||||
|
@ -112,7 +106,7 @@ import OperatorSelector from '../selectors/OperatorSelector.vue'
|
|||
import ValueInput from '../inputs/ValueInput.vue'
|
||||
import DeviceStatusConditionConfig from './DeviceStatusConditionConfig.vue'
|
||||
import { ConditionFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
/** 主条件内部配置组件 */
|
||||
|
@ -125,6 +119,7 @@ interface Props {
|
|||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: ConditionFormData): void
|
||||
|
||||
(e: 'validate', result: { valid: boolean; message: string }): void
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 基础信息配置组件 -->
|
||||
<template>
|
||||
<el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
|
||||
<el-card class="border border-[var(--el-border-color-light)] rounded-8px mb-10px" shadow="never">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-8px">
|
||||
|
@ -8,10 +8,7 @@
|
|||
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">基础信息</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<!-- TODO @puhui999:dict-tag 可以哇? -->
|
||||
<el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
|
||||
{{ formData.status === 0 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="formData.status" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -60,25 +57,19 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 基础信息配置组件 */
|
||||
defineOptions({ name: 'BasicInfoSection' })
|
||||
|
||||
// TODO @puhui999:下面的 Props、Emits 可以合并到变量那;
|
||||
|
||||
interface Props {
|
||||
const props = defineProps<{
|
||||
modelValue: RuleSceneFormData
|
||||
rules?: any
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: RuleSceneFormData): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
}>()
|
||||
|
||||
const formData = useVModel(props, 'modelValue', emit)
|
||||
</script>
|
||||
|
|
|
@ -1,47 +1,97 @@
|
|||
<template>
|
||||
<el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
|
||||
<!-- TODO @puhui999:触发器还是多个。。。每个触发器里面有事件类型 + 附加条件组(最好文案上,和阿里 iot 保持相对一致) -->
|
||||
<el-card class="border border-[var(--el-border-color-light)] rounded-8px mb-10px" shadow="never">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-8px">
|
||||
<Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
|
||||
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">触发器配置</span>
|
||||
<el-tag size="small" type="info">场景触发器</el-tag>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-8px">
|
||||
<Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
|
||||
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">触发器配置</span>
|
||||
<el-tag size="small" type="info">{{ triggers.length }} 个触发器</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" size="small" @click="addTrigger">
|
||||
<Icon icon="ep:plus" />
|
||||
添加触发器
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="p-16px space-y-16px">
|
||||
<!-- 触发事件类型选择 -->
|
||||
<el-form-item label="触发事件类型" required>
|
||||
<el-select
|
||||
:model-value="trigger.type"
|
||||
@update:model-value="(value) => updateTriggerType(value)"
|
||||
@change="onTriggerTypeChange"
|
||||
placeholder="请选择触发事件类型"
|
||||
class="w-full"
|
||||
<div class="p-16px space-y-24px">
|
||||
<!-- 触发器列表 -->
|
||||
<div v-if="triggers.length > 0" class="space-y-24px">
|
||||
<div
|
||||
v-for="(triggerItem, index) in triggers"
|
||||
:key="`trigger-${index}`"
|
||||
class="border border-[var(--el-border-color-light)] rounded-8px p-16px relative"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in triggerTypeOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
<!-- 触发器头部 -->
|
||||
<div class="flex items-center justify-between mb-16px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
|
||||
触发器 {{ index + 1 }}
|
||||
</span>
|
||||
<el-tag size="small" :type="getTriggerTagType(triggerItem.type)">
|
||||
{{ getTriggerTypeLabel(triggerItem.type) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<el-button
|
||||
v-if="triggers.length > 1"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
@click="removeTrigger(index)"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 触发事件类型选择 -->
|
||||
<el-form-item label="触发事件类型" required>
|
||||
<el-select
|
||||
:model-value="triggerItem.type"
|
||||
@update:model-value="(value) => updateTriggerType(index, value)"
|
||||
placeholder="请选择触发事件类型"
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in triggerTypeOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 设备触发配置 -->
|
||||
<DeviceTriggerConfig
|
||||
v-if="isDeviceTrigger(triggerItem.type)"
|
||||
:model-value="triggerItem"
|
||||
@update:model-value="(value) => updateTriggerDeviceConfig(index, value)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 设备触发配置 -->
|
||||
<DeviceTriggerConfig
|
||||
v-if="isDeviceTrigger(trigger.type)"
|
||||
:model-value="trigger"
|
||||
@update:model-value="updateTrigger"
|
||||
/>
|
||||
<!-- 定时触发配置 -->
|
||||
<TimerTriggerConfig
|
||||
v-else-if="triggerItem.type === TriggerTypeEnum.TIMER"
|
||||
:model-value="triggerItem.cronExpression"
|
||||
@update:model-value="(value) => updateTriggerCronConfig(index, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 定时触发配置 -->
|
||||
<!-- TODO @puhui999:这里要不 v-else 好了? -->
|
||||
<TimerTriggerConfig
|
||||
v-if="trigger.type === TriggerTypeEnum.TIMER"
|
||||
:model-value="trigger.cronExpression"
|
||||
@update:model-value="updateTriggerCronExpression"
|
||||
/>
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="py-40px text-center">
|
||||
<el-empty description="暂无触发器">
|
||||
<template #description>
|
||||
<div class="space-y-8px">
|
||||
<p class="text-[var(--el-text-color-secondary)]">暂无触发器配置</p>
|
||||
<p class="text-12px text-[var(--el-text-color-placeholder)]">
|
||||
请使用上方的"添加触发器"按钮来设置触发规则
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
@ -50,108 +100,115 @@
|
|||
import { useVModel } from '@vueuse/core'
|
||||
import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue'
|
||||
import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue'
|
||||
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import {
|
||||
TriggerFormData,
|
||||
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
getTriggerTypeOptions,
|
||||
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
isDeviceTrigger
|
||||
} from '@/views/iot/utils/constants'
|
||||
|
||||
/** 触发器配置组件 */
|
||||
defineOptions({ name: 'TriggerSection' })
|
||||
|
||||
// TODO @puhui999:下面的 Props、Emits 可以合并到变量那;
|
||||
interface Props {
|
||||
trigger: TriggerFormData
|
||||
}
|
||||
// Props 和 Emits 定义
|
||||
const props = defineProps<{
|
||||
triggers: TriggerFormData[]
|
||||
}>()
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:trigger', value: TriggerFormData): void
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
'update:triggers': [value: TriggerFormData[]]
|
||||
}>()
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
const triggers = useVModel(props, 'triggers', emit)
|
||||
|
||||
const trigger = useVModel(props, 'trigger', emit)
|
||||
|
||||
// 触发器类型选项
|
||||
// TODO @puhui999:/Users/yunai/Java/yudao-ui-admin-vue3/src/views/iot/utils/constants.ts
|
||||
const triggerTypeOptions = [
|
||||
{
|
||||
value: TriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
label: '设备状态变更'
|
||||
},
|
||||
{
|
||||
value: TriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
label: '设备属性上报'
|
||||
},
|
||||
{
|
||||
value: TriggerTypeEnum.DEVICE_EVENT_POST,
|
||||
label: '设备事件上报'
|
||||
},
|
||||
{
|
||||
value: TriggerTypeEnum.DEVICE_SERVICE_INVOKE,
|
||||
label: '设备服务调用'
|
||||
},
|
||||
{
|
||||
value: TriggerTypeEnum.TIMER,
|
||||
label: '定时触发'
|
||||
}
|
||||
]
|
||||
// 触发器类型选项(从 constants 中获取)
|
||||
const triggerTypeOptions = getTriggerTypeOptions()
|
||||
|
||||
// 工具函数
|
||||
// TODO @puhui999:/Users/yunai/Java/yudao-ui-admin-vue3/src/views/iot/utils/constants.ts
|
||||
const isDeviceTrigger = (type: number) => {
|
||||
const deviceTriggerTypes = [
|
||||
TriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
TriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
TriggerTypeEnum.DEVICE_EVENT_POST,
|
||||
TriggerTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
] as number[]
|
||||
return deviceTriggerTypes.includes(type)
|
||||
const getTriggerTypeLabel = (type: number): string => {
|
||||
const option = triggerTypeOptions.find((opt) => opt.value === type)
|
||||
return option?.label || '未知类型'
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
const updateTriggerType = (type: number) => {
|
||||
trigger.value.type = type
|
||||
onTriggerTypeChange(type)
|
||||
const getTriggerTagType = (type: number): string => {
|
||||
if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
|
||||
return 'warning'
|
||||
}
|
||||
return isDeviceTrigger(type) ? 'success' : 'info'
|
||||
}
|
||||
|
||||
// TODO @puhui999:updateTriggerDeviceConfig
|
||||
const updateTrigger = (newTrigger: TriggerFormData) => {
|
||||
trigger.value = newTrigger
|
||||
// 事件处理函数
|
||||
const addTrigger = () => {
|
||||
const newTrigger: TriggerFormData = {
|
||||
type: TriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
productId: undefined,
|
||||
deviceId: undefined,
|
||||
identifier: undefined,
|
||||
operator: undefined,
|
||||
value: undefined,
|
||||
cronExpression: undefined,
|
||||
mainCondition: undefined,
|
||||
conditionGroup: undefined
|
||||
}
|
||||
triggers.value.push(newTrigger)
|
||||
}
|
||||
|
||||
// TODO @puhui999:updateTriggerCronConfig
|
||||
const updateTriggerCronExpression = (cronExpression?: string) => {
|
||||
trigger.value.cronExpression = cronExpression
|
||||
const removeTrigger = (index: number) => {
|
||||
if (triggers.value.length > 1) {
|
||||
triggers.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const onTriggerTypeChange = (type: number) => {
|
||||
const updateTriggerType = (index: number, type: number) => {
|
||||
triggers.value[index].type = type
|
||||
onTriggerTypeChange(index, type)
|
||||
}
|
||||
|
||||
const updateTriggerDeviceConfig = (index: number, newTrigger: TriggerFormData) => {
|
||||
triggers.value[index] = newTrigger
|
||||
}
|
||||
|
||||
const updateTriggerCronConfig = (index: number, cronExpression?: string) => {
|
||||
triggers.value[index].cronExpression = cronExpression
|
||||
}
|
||||
|
||||
const onTriggerTypeChange = (index: number, type: number) => {
|
||||
const triggerItem = triggers.value[index]
|
||||
|
||||
// 清理不相关的配置
|
||||
if (type === TriggerTypeEnum.TIMER) {
|
||||
trigger.value.productId = undefined
|
||||
trigger.value.deviceId = undefined
|
||||
trigger.value.identifier = undefined
|
||||
trigger.value.operator = undefined
|
||||
trigger.value.value = undefined
|
||||
trigger.value.mainCondition = undefined
|
||||
trigger.value.conditionGroup = undefined
|
||||
if (!trigger.value.cronExpression) {
|
||||
trigger.value.cronExpression = '0 0 12 * * ?'
|
||||
triggerItem.productId = undefined
|
||||
triggerItem.deviceId = undefined
|
||||
triggerItem.identifier = undefined
|
||||
triggerItem.operator = undefined
|
||||
triggerItem.value = undefined
|
||||
triggerItem.mainCondition = undefined
|
||||
triggerItem.conditionGroup = undefined
|
||||
if (!triggerItem.cronExpression) {
|
||||
triggerItem.cronExpression = '0 0 12 * * ?'
|
||||
}
|
||||
} else {
|
||||
trigger.value.cronExpression = undefined
|
||||
triggerItem.cronExpression = undefined
|
||||
if (type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
|
||||
trigger.value.mainCondition = undefined
|
||||
trigger.value.conditionGroup = undefined
|
||||
triggerItem.mainCondition = undefined
|
||||
triggerItem.conditionGroup = undefined
|
||||
} else {
|
||||
// 设备属性、事件、服务触发需要条件配置
|
||||
if (!trigger.value.mainCondition) {
|
||||
trigger.value.mainCondition = undefined // 等待用户配置
|
||||
if (!triggerItem.mainCondition) {
|
||||
triggerItem.mainCondition = undefined // 等待用户配置
|
||||
}
|
||||
if (!trigger.value.conditionGroup) {
|
||||
trigger.value.conditionGroup = undefined // 可选的条件组
|
||||
if (!triggerItem.conditionGroup) {
|
||||
triggerItem.conditionGroup = undefined // 可选的条件组
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化:确保至少有一个触发器
|
||||
onMounted(() => {
|
||||
if (triggers.value.length === 0) {
|
||||
addTrigger()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -15,26 +15,21 @@
|
|||
>
|
||||
<div class="flex items-center justify-between w-full py-4px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<div class="text-14px font-500 text-[var(--el-text-color-primary)]">{{ operator.label }}</div>
|
||||
<div class="text-12px text-[var(--el-color-primary)] bg-[var(--el-color-primary-light-9)] px-6px py-2px rounded-4px font-mono">{{ operator.symbol }}</div>
|
||||
<div class="text-14px font-500 text-[var(--el-text-color-primary)]">
|
||||
{{ operator.label }}
|
||||
</div>
|
||||
<div
|
||||
class="text-12px text-[var(--el-color-primary)] bg-[var(--el-color-primary-light-9)] px-6px py-2px rounded-4px font-mono"
|
||||
>
|
||||
{{ operator.symbol }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">
|
||||
{{ operator.description }}
|
||||
</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">{{ operator.description }}</div>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 操作符说明 -->
|
||||
<!-- TODO @puhui999:这个去掉 -->
|
||||
<div v-if="selectedOperator" class="mt-8px p-8px bg-[var(--el-fill-color-light)] rounded-4px border border-[var(--el-border-color-lighter)]">
|
||||
<div class="flex items-center gap-6px">
|
||||
<Icon icon="ep:info-filled" class="text-12px text-[var(--el-color-info)]" />
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)]">{{ selectedOperator.description }}</span>
|
||||
</div>
|
||||
<div v-if="selectedOperator.example" class="flex items-center gap-6px mt-4px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)]">示例:</span>
|
||||
<code class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] px-4px py-2px rounded-2px font-mono">{{ selectedOperator.example }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -51,6 +46,7 @@ interface Props {
|
|||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
|
||||
(e: 'change', value: string): void
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<!-- 属性选择器组件 -->
|
||||
<!-- TODO @yunai:可能要在 review 下 -->
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="flex items-center gap-8px">
|
||||
<el-select
|
||||
v-model="localValue"
|
||||
placeholder="请选择监控项"
|
||||
filterable
|
||||
clearable
|
||||
@change="handleChange"
|
||||
class="w-full"
|
||||
class="!w-150px"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-option-group v-for="group in propertyGroups" :key="group.label" :label="group.label">
|
||||
|
@ -20,8 +20,12 @@
|
|||
>
|
||||
<div class="flex items-center justify-between w-full py-4px">
|
||||
<div class="flex-1">
|
||||
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{ property.name }}</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">{{ property.identifier }}</div>
|
||||
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
|
||||
{{ property.name }}
|
||||
</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">
|
||||
{{ property.identifier }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<el-tag :type="getPropertyTypeTag(property.dataType)" size="small">
|
||||
|
@ -33,42 +37,98 @@
|
|||
</el-option-group>
|
||||
</el-select>
|
||||
|
||||
<!-- 属性详情 -->
|
||||
<div v-if="selectedProperty" class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
|
||||
<div class="flex items-center gap-8px mb-12px">
|
||||
<Icon icon="ep:info-filled" class="text-[var(--el-color-info)] text-16px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">{{ selectedProperty.name }}</span>
|
||||
<el-tag :type="getPropertyTypeTag(selectedProperty.dataType)" size="small">
|
||||
{{ getPropertyTypeName(selectedProperty.dataType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="space-y-8px ml-24px">
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">标识符:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedProperty.identifier }}</span>
|
||||
<!-- 属性详情触发按钮 -->
|
||||
<div class="relative">
|
||||
<el-button
|
||||
v-if="selectedProperty"
|
||||
ref="detailTriggerRef"
|
||||
type="info"
|
||||
:icon="InfoFilled"
|
||||
circle
|
||||
size="small"
|
||||
@click="togglePropertyDetail"
|
||||
class="flex-shrink-0"
|
||||
title="查看属性详情"
|
||||
/>
|
||||
|
||||
<!-- 属性详情弹出层 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="showPropertyDetail && selectedProperty"
|
||||
ref="propertyDetailRef"
|
||||
class="property-detail-popover"
|
||||
:style="popoverStyle"
|
||||
>
|
||||
<div
|
||||
class="p-16px bg-white rounded-8px shadow-lg border border-[var(--el-border-color)] min-w-300px max-w-400px"
|
||||
>
|
||||
<div class="flex items-center gap-8px mb-12px">
|
||||
<Icon icon="ep:info-filled" class="text-[var(--el-color-info)] text-4px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
|
||||
{{ selectedProperty.name }}
|
||||
</span>
|
||||
<el-tag :type="getPropertyTypeTag(selectedProperty.dataType)" size="small">
|
||||
{{ getPropertyTypeName(selectedProperty.dataType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="space-y-8px ml-24px">
|
||||
<div class="flex items-start gap-8px">
|
||||
<span
|
||||
class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0"
|
||||
>
|
||||
标识符:
|
||||
</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">
|
||||
{{ selectedProperty.identifier }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="selectedProperty.description" class="flex items-start gap-8px">
|
||||
<span
|
||||
class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0"
|
||||
>
|
||||
描述:
|
||||
</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">
|
||||
{{ selectedProperty.description }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="selectedProperty.unit" class="flex items-start gap-8px">
|
||||
<span
|
||||
class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0"
|
||||
>
|
||||
单位:
|
||||
</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">
|
||||
{{ selectedProperty.unit }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="selectedProperty.range" class="flex items-start gap-8px">
|
||||
<span
|
||||
class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0"
|
||||
>
|
||||
取值范围:
|
||||
</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">
|
||||
{{ selectedProperty.range }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关闭按钮 -->
|
||||
<div class="flex justify-end mt-12px">
|
||||
<el-button size="small" @click="hidePropertyDetail">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedProperty.description" class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">描述:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedProperty.description }}</span>
|
||||
</div>
|
||||
<div v-if="selectedProperty.unit" class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">单位:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedProperty.unit }}</span>
|
||||
</div>
|
||||
<div v-if="selectedProperty.range" class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">取值范围:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedProperty.range }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
import { IotRuleSceneTriggerTypeEnum, IoTThingModelTypeEnum } from '@/views/iot/utils/constants'
|
||||
import { ThingModelApi } from '@/api/iot/thingmodel'
|
||||
import { IoTThingModelTypeEnum } from '@/views/iot/utils/constants'
|
||||
import type { IotThingModelTSLRespVO, PropertySelectorItem } from './types'
|
||||
|
||||
/** 属性选择器组件 */
|
||||
|
@ -83,6 +143,7 @@ interface Props {
|
|||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
|
||||
(e: 'change', value: { type: string; config: any }): void
|
||||
}
|
||||
|
||||
|
@ -96,6 +157,25 @@ const loading = ref(false)
|
|||
const propertyList = ref<PropertySelectorItem[]>([])
|
||||
const thingModelTSL = ref<IotThingModelTSLRespVO | null>(null)
|
||||
|
||||
// 属性详情弹出层相关状态
|
||||
const showPropertyDetail = ref(false)
|
||||
const detailTriggerRef = ref()
|
||||
const propertyDetailRef = ref()
|
||||
const popoverStyle = ref({})
|
||||
|
||||
// 点击外部关闭弹出层
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
showPropertyDetail.value &&
|
||||
propertyDetailRef.value &&
|
||||
detailTriggerRef.value &&
|
||||
!propertyDetailRef.value.contains(event.target as Node) &&
|
||||
!detailTriggerRef.value.$el.contains(event.target as Node)
|
||||
) {
|
||||
hidePropertyDetail()
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const propertyGroups = computed(() => {
|
||||
const groups: { label: string; options: any[] }[] = []
|
||||
|
@ -159,6 +239,67 @@ const getPropertyTypeTag = (dataType: string) => {
|
|||
return tagMap[dataType] || 'info'
|
||||
}
|
||||
|
||||
// 弹出层控制方法
|
||||
const togglePropertyDetail = () => {
|
||||
if (showPropertyDetail.value) {
|
||||
hidePropertyDetail()
|
||||
} else {
|
||||
showPropertyDetailPopover()
|
||||
}
|
||||
}
|
||||
|
||||
const showPropertyDetailPopover = () => {
|
||||
if (!selectedProperty.value || !detailTriggerRef.value) return
|
||||
|
||||
showPropertyDetail.value = true
|
||||
|
||||
nextTick(() => {
|
||||
updatePopoverPosition()
|
||||
})
|
||||
}
|
||||
|
||||
const hidePropertyDetail = () => {
|
||||
showPropertyDetail.value = false
|
||||
}
|
||||
|
||||
const updatePopoverPosition = () => {
|
||||
if (!detailTriggerRef.value || !propertyDetailRef.value) return
|
||||
|
||||
const triggerEl = detailTriggerRef.value.$el
|
||||
const triggerRect = triggerEl.getBoundingClientRect()
|
||||
const popoverEl = propertyDetailRef.value
|
||||
|
||||
// 计算弹出层位置
|
||||
const left = triggerRect.left + triggerRect.width + 8
|
||||
const top = triggerRect.top
|
||||
|
||||
// 检查是否超出视窗右边界
|
||||
const popoverWidth = 400 // 最大宽度
|
||||
const viewportWidth = window.innerWidth
|
||||
|
||||
let finalLeft = left
|
||||
if (left + popoverWidth > viewportWidth - 16) {
|
||||
// 如果超出右边界,显示在左侧
|
||||
finalLeft = triggerRect.left - popoverWidth - 8
|
||||
}
|
||||
|
||||
// 检查是否超出视窗下边界
|
||||
let finalTop = top
|
||||
const popoverHeight = popoverEl.offsetHeight || 200
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
if (top + popoverHeight > viewportHeight - 16) {
|
||||
finalTop = Math.max(16, viewportHeight - popoverHeight - 16)
|
||||
}
|
||||
|
||||
popoverStyle.value = {
|
||||
position: 'fixed',
|
||||
left: `${finalLeft}px`,
|
||||
top: `${finalTop}px`,
|
||||
zIndex: 9999
|
||||
}
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
const handleChange = (value: string) => {
|
||||
const property = propertyList.value.find((p) => p.identifier === value)
|
||||
|
@ -168,6 +309,8 @@ const handleChange = (value: string) => {
|
|||
config: property
|
||||
})
|
||||
}
|
||||
// 选择变化时隐藏详情弹出层
|
||||
hidePropertyDetail()
|
||||
}
|
||||
|
||||
// 获取物模型TSL数据
|
||||
|
@ -331,13 +474,74 @@ watch(
|
|||
() => props.triggerType,
|
||||
() => {
|
||||
localValue.value = ''
|
||||
hidePropertyDetail()
|
||||
}
|
||||
)
|
||||
|
||||
// 监听窗口大小变化,重新计算弹出层位置
|
||||
const handleResize = () => {
|
||||
if (showPropertyDetail.value) {
|
||||
updatePopoverPosition()
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-4px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item) {
|
||||
height: auto;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
.property-detail-popover {
|
||||
animation: fadeInScale 0.2s ease-out;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
/* 弹出层箭头效果(可选) */
|
||||
.property-detail-popover::before {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: -8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 8px solid transparent;
|
||||
border-right: 8px solid var(--el-border-color);
|
||||
border-bottom: 8px solid transparent;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.property-detail-popover::after {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: -7px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 8px solid transparent;
|
||||
border-right: 8px solid white;
|
||||
border-bottom: 8px solid transparent;
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -203,3 +203,49 @@ export const IoTOtaTaskRecordStatusEnum = {
|
|||
value: 50
|
||||
}
|
||||
} as const
|
||||
|
||||
// ========== 场景联动规则相关常量 ==========
|
||||
|
||||
/** IoT 场景联动触发器类型枚举 */
|
||||
export const IotRuleSceneTriggerTypeEnum = {
|
||||
DEVICE_STATE_UPDATE: 1, // 设备上下线变更
|
||||
DEVICE_PROPERTY_POST: 2, // 物模型属性上报
|
||||
DEVICE_EVENT_POST: 3, // 设备事件上报
|
||||
DEVICE_SERVICE_INVOKE: 4, // 设备服务调用
|
||||
TIMER: 100 // 定时触发
|
||||
} as const
|
||||
|
||||
/** 触发器类型选项配置 */
|
||||
export const getTriggerTypeOptions = () => [
|
||||
{
|
||||
value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
label: '设备状态变更'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
label: '设备属性上报'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
|
||||
label: '设备事件上报'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
|
||||
label: '设备服务调用'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneTriggerTypeEnum.TIMER,
|
||||
label: '定时触发'
|
||||
}
|
||||
]
|
||||
|
||||
/** 判断是否为设备触发器类型 */
|
||||
export const isDeviceTrigger = (type: number): boolean => {
|
||||
const deviceTriggerTypes = [
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
] as number[]
|
||||
return deviceTriggerTypes.includes(type)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue