perf:【IoT 物联网】场景联动执行器优化
This commit is contained in:
@@ -36,11 +36,12 @@ 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 { IotRuleScene, IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import { RuleSceneApi } from '@/api/iot/rule/scene'
|
||||
import {
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
isDeviceTrigger
|
||||
} from '@/views/iot/utils/constants'
|
||||
import { ElMessage } from 'element-plus'
|
||||
@@ -53,6 +54,57 @@ const CommonStatusEnum = {
|
||||
DISABLE: 1 // 关闭
|
||||
} as const
|
||||
|
||||
// 工具函数:根据触发器类型获取消息类型
|
||||
const getMessageTypeByTriggerType = (triggerType: number): string => {
|
||||
switch (triggerType) {
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
|
||||
return IotDeviceMessageTypeEnum.PROPERTY
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
|
||||
return IotDeviceMessageTypeEnum.EVENT
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
|
||||
return IotDeviceMessageTypeEnum.SERVICE
|
||||
default:
|
||||
return IotDeviceMessageTypeEnum.PROPERTY
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数:根据执行器类型获取消息类型
|
||||
const getMessageTypeByActionType = (actionType: number): string => {
|
||||
switch (actionType) {
|
||||
case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET:
|
||||
return IotDeviceMessageTypeEnum.PROPERTY
|
||||
case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE:
|
||||
return IotDeviceMessageTypeEnum.SERVICE
|
||||
default:
|
||||
return IotDeviceMessageTypeEnum.PROPERTY
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数:根据执行器类型和参数获取标识符
|
||||
const getIdentifierByActionType = (actionType: number, params?: Record<string, any>): string => {
|
||||
if (!params) return ''
|
||||
|
||||
switch (actionType) {
|
||||
case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET:
|
||||
// 属性设置:取第一个属性名作为标识符
|
||||
return Object.keys(params)[0] || ''
|
||||
case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE:
|
||||
// 服务调用:取 method 字段作为标识符
|
||||
return params.method || ''
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数:判断是否为设备执行器
|
||||
const isDeviceAction = (type: number): boolean => {
|
||||
const deviceActionTypes = [
|
||||
IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
|
||||
IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
] as number[]
|
||||
return deviceActionTypes.includes(type)
|
||||
}
|
||||
|
||||
/** IoT 场景联动规则表单 - 主表单组件 */
|
||||
defineOptions({ name: 'RuleSceneForm' })
|
||||
|
||||
@@ -95,31 +147,50 @@ const createDefaultFormData = (): RuleSceneFormData => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将表单数据转换为后端 DO 格式
|
||||
* 由于数据结构已对齐,转换变得非常简单
|
||||
* 将表单数据转换为后端 API 格式
|
||||
* 转换为 IotRuleScene 格式,与后端 API 接口对齐
|
||||
*/
|
||||
const convertFormToVO = (formData: RuleSceneFormData): IotRuleSceneDO => {
|
||||
const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
|
||||
return {
|
||||
id: formData.id,
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
status: Number(formData.status),
|
||||
triggers: formData.triggers.map((trigger) => ({
|
||||
key: generateUUID(), // 为每个触发器生成唯一标识
|
||||
type: trigger.type,
|
||||
productId: trigger.productId,
|
||||
deviceId: trigger.deviceId,
|
||||
identifier: trigger.identifier,
|
||||
operator: trigger.operator,
|
||||
value: trigger.value,
|
||||
cronExpression: trigger.cronExpression,
|
||||
conditionGroups: trigger.conditionGroups || []
|
||||
productKey: trigger.productId ? `product_${trigger.productId}` : undefined, // 转换为产品标识
|
||||
deviceNames: trigger.deviceId ? [`device_${trigger.deviceId}`] : undefined, // 转换为设备名称数组
|
||||
conditions: trigger.identifier
|
||||
? [
|
||||
{
|
||||
type: getMessageTypeByTriggerType(trigger.type),
|
||||
identifier: trigger.identifier,
|
||||
parameters: [
|
||||
{
|
||||
identifier: trigger.identifier,
|
||||
operator: trigger.operator || '=',
|
||||
value: trigger.value || ''
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
cronExpression: trigger.cronExpression
|
||||
})),
|
||||
actions:
|
||||
formData.actions?.map((action) => ({
|
||||
key: generateUUID(), // 为每个执行器生成唯一标识
|
||||
type: action.type,
|
||||
productId: action.productId,
|
||||
deviceId: action.deviceId,
|
||||
params: action.params,
|
||||
deviceControl: isDeviceAction(action.type)
|
||||
? {
|
||||
productKey: action.productId ? `product_${action.productId}` : '',
|
||||
deviceNames: action.deviceId ? [`device_${action.deviceId}`] : [],
|
||||
type: getMessageTypeByActionType(action.type),
|
||||
identifier: getIdentifierByActionType(action.type, action.params),
|
||||
params: action.params || {}
|
||||
}
|
||||
: undefined,
|
||||
alertConfigId: action.alertConfigId
|
||||
})) || []
|
||||
}
|
||||
|
||||
@@ -1,72 +1,152 @@
|
||||
<!-- 告警配置组件 -->
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<!-- TODO @puhui999:触发告警时,不用选择配置哈; -->
|
||||
<el-form-item label="告警配置" required>
|
||||
<el-select
|
||||
v-model="localValue"
|
||||
placeholder="请选择告警配置"
|
||||
filterable
|
||||
clearable
|
||||
@change="handleChange"
|
||||
class="w-full"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-option
|
||||
v-for="config in alertConfigs"
|
||||
:key="config.id"
|
||||
:label="config.name"
|
||||
:value="config.id"
|
||||
<!-- 告警配置选择区域 -->
|
||||
<div
|
||||
class="border border-[var(--el-border-color-light)] rounded-6px p-16px bg-[var(--el-fill-color-blank)]"
|
||||
>
|
||||
<div class="flex items-center gap-8px mb-12px">
|
||||
<Icon icon="ep:bell" class="text-[var(--el-color-warning)] text-16px" />
|
||||
<span class="text-14px font-600 text-[var(--el-text-color-primary)]">告警配置选择</span>
|
||||
<el-tag size="small" type="warning">必选</el-tag>
|
||||
</div>
|
||||
|
||||
<el-form-item label="告警配置" required>
|
||||
<el-select
|
||||
v-model="localValue"
|
||||
placeholder="请选择告警配置"
|
||||
filterable
|
||||
clearable
|
||||
@change="handleChange"
|
||||
class="w-full"
|
||||
:loading="loading"
|
||||
>
|
||||
<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">{{
|
||||
config.name
|
||||
}}</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">{{
|
||||
config.description
|
||||
}}</div>
|
||||
<template #empty>
|
||||
<div class="text-center py-20px">
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
class="text-24px text-[var(--el-text-color-placeholder)] mb-8px"
|
||||
/>
|
||||
<p class="text-12px text-[var(--el-text-color-secondary)]">暂无可用的告警配置</p>
|
||||
</div>
|
||||
<el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
|
||||
{{ config.enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="config in alertConfigs"
|
||||
:key="config.id"
|
||||
:label="config.name"
|
||||
:value="config.id"
|
||||
:disabled="!config.enabled"
|
||||
>
|
||||
<div class="flex items-center justify-between w-full py-6px">
|
||||
<div class="flex items-center gap-12px flex-1">
|
||||
<Icon
|
||||
:icon="config.enabled ? 'ep:circle-check' : 'ep:circle-close'"
|
||||
:class="
|
||||
config.enabled
|
||||
? 'text-[var(--el-color-success)]'
|
||||
: 'text-[var(--el-color-danger)]'
|
||||
"
|
||||
class="text-16px flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{
|
||||
config.name
|
||||
}}</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)] line-clamp-1">{{
|
||||
config.description
|
||||
}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<el-tag :type="getNotifyTypeTag(config.notifyType)" size="small">
|
||||
{{ getNotifyTypeName(config.notifyType) }}
|
||||
</el-tag>
|
||||
<el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
|
||||
{{ config.enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 告警配置详情 -->
|
||||
<div
|
||||
v-if="selectedConfig"
|
||||
class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
|
||||
class="mt-16px border border-[var(--el-border-color-light)] rounded-6px p-16px bg-gradient-to-r from-orange-50 to-yellow-50"
|
||||
>
|
||||
<div class="flex items-center gap-8px mb-12px">
|
||||
<Icon icon="ep:bell" class="text-[var(--el-color-warning)] text-16px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">{{
|
||||
selectedConfig.name
|
||||
}}</span>
|
||||
<el-tag :type="selectedConfig.enabled ? 'success' : 'danger'" size="small">
|
||||
{{ selectedConfig.enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
<div class="flex items-center gap-8px mb-16px">
|
||||
<Icon icon="ep:info-filled" class="text-[var(--el-color-warning)] text-18px" />
|
||||
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">配置详情</span>
|
||||
</div>
|
||||
<div class="space-y-8px">
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">描述:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
|
||||
selectedConfig.description
|
||||
}}</span>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-16px">
|
||||
<!-- 基本信息 -->
|
||||
<div class="space-y-12px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<Icon icon="ep:document" class="text-[var(--el-color-primary)] text-14px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">基本信息</span>
|
||||
</div>
|
||||
<div class="pl-22px space-y-8px">
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">名称:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1 font-500">{{
|
||||
selectedConfig.name
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">描述:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
|
||||
selectedConfig.description
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">状态:</span>
|
||||
<el-tag :type="selectedConfig.enabled ? 'success' : 'danger'" size="small">
|
||||
{{ selectedConfig.enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">通知方式:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
|
||||
getNotifyTypeName(selectedConfig.notifyType)
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="selectedConfig.receivers" class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">接收人:</span>
|
||||
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
|
||||
selectedConfig.receivers.join(', ')
|
||||
}}</span>
|
||||
|
||||
<!-- 通知配置 -->
|
||||
<div class="space-y-12px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<Icon icon="ep:message" class="text-[var(--el-color-success)] text-14px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">通知配置</span>
|
||||
</div>
|
||||
<div class="pl-22px space-y-8px">
|
||||
<div class="flex items-start gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">方式:</span>
|
||||
<el-tag :type="getNotifyTypeTag(selectedConfig.notifyType)" size="small">
|
||||
{{ getNotifyTypeName(selectedConfig.notifyType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedConfig.receivers && selectedConfig.receivers.length > 0"
|
||||
class="flex items-start gap-8px"
|
||||
>
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px"
|
||||
>接收人:</span
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-wrap gap-4px">
|
||||
<el-tag
|
||||
v-for="receiver in selectedConfig.receivers.slice(0, 3)"
|
||||
:key="receiver"
|
||||
size="small"
|
||||
type="info"
|
||||
>
|
||||
{{ receiver }}
|
||||
</el-tag>
|
||||
<el-tag v-if="selectedConfig.receivers.length > 3" size="small" type="info">
|
||||
+{{ selectedConfig.receivers.length - 3 }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,6 +192,22 @@ const getNotifyTypeName = (type: number) => {
|
||||
return typeMap[type] || '未知'
|
||||
}
|
||||
|
||||
const getNotifyTypeTag = (type: number) => {
|
||||
const tagMap = {
|
||||
1: 'primary', // 邮件
|
||||
2: 'success', // 短信
|
||||
3: 'warning', // 微信
|
||||
4: 'info' // 钉钉
|
||||
}
|
||||
return tagMap[type] || 'info'
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
const handleChange = (value?: number) => {
|
||||
// 可以在这里添加额外的处理逻辑
|
||||
console.log('告警配置选择变化:', value)
|
||||
}
|
||||
|
||||
// API 调用
|
||||
const getAlertConfigs = async () => {
|
||||
loading.value = true
|
||||
|
||||
@@ -2,50 +2,190 @@
|
||||
<!-- TODO @puhui999:貌似没生效~~~ -->
|
||||
<template>
|
||||
<div class="flex flex-col gap-16px">
|
||||
<!-- 产品和设备选择 -->
|
||||
<ProductDeviceSelector
|
||||
v-model:product-id="action.productId"
|
||||
v-model:device-id="action.deviceId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
<!-- 产品和设备选择 - 与触发器保持一致的分离式选择器 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品" required>
|
||||
<ProductSelector v-model="action.productId" @change="handleProductChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="设备" required>
|
||||
<DeviceSelector
|
||||
v-model="action.deviceId"
|
||||
:product-id="action.productId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 控制参数配置 -->
|
||||
<div v-if="action.productId && action.deviceId" class="space-y-16px">
|
||||
<el-form-item label="控制参数" required>
|
||||
<el-input
|
||||
v-model="paramsJson"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入JSON格式的控制参数"
|
||||
@input="handleParamsChange"
|
||||
/>
|
||||
<!-- 控制参数配置 - 只要选择了产品就显示,支持全部设备和单独设备 -->
|
||||
<div v-if="action.productId && isPropertySetAction" class="space-y-16px">
|
||||
<!-- 参数配置 -->
|
||||
<el-form-item label="参数" required>
|
||||
<div class="w-full space-y-8px">
|
||||
<!-- JSON 输入框 -->
|
||||
<div class="relative">
|
||||
<el-input
|
||||
v-model="paramsJson"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="请输入JSON格式的控制参数"
|
||||
@input="handleParamsChange"
|
||||
:class="{ 'is-error': jsonError }"
|
||||
/>
|
||||
<!-- 查看详细示例按钮 -->
|
||||
<div class="absolute top-8px right-8px">
|
||||
<el-button
|
||||
ref="exampleTriggerRef"
|
||||
type="info"
|
||||
:icon="InfoFilled"
|
||||
circle
|
||||
size="small"
|
||||
@click="toggleExampleDetail"
|
||||
title="查看详细示例"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 验证状态和错误提示 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-8px">
|
||||
<Icon
|
||||
:icon="jsonError ? 'ep:warning' : 'ep:circle-check'"
|
||||
:class="
|
||||
jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'
|
||||
"
|
||||
class="text-14px"
|
||||
/>
|
||||
<span
|
||||
:class="
|
||||
jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'
|
||||
"
|
||||
class="text-12px"
|
||||
>
|
||||
{{ jsonError || 'JSON格式正确' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 快速填充按钮 -->
|
||||
<div v-if="thingModelProperties.length > 0" class="flex items-center gap-8px">
|
||||
<span class="text-12px text-[var(--el-text-color-secondary)]">快速填充:</span>
|
||||
<el-button size="small" type="primary" plain @click="fillExampleJson">
|
||||
示例数据
|
||||
</el-button>
|
||||
<el-button size="small" type="default" plain @click="clearParams"> 清空 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 参数示例 -->
|
||||
<div class="mt-12px">
|
||||
<el-alert title="参数格式示例" type="info" :closable="false" show-icon>
|
||||
<template #default>
|
||||
<div class="space-y-8px">
|
||||
<p class="m-0 text-14px text-[var(--el-text-color-primary)]">属性设置示例:</p>
|
||||
<pre
|
||||
class="m-0 p-8px bg-[var(--el-fill-color-light)] rounded-4px text-12px text-[var(--el-text-color-regular)] overflow-x-auto"
|
||||
><code>{ "temperature": 25, "power": true }</code></pre>
|
||||
<p class="m-0 text-14px text-[var(--el-text-color-primary)]">服务调用示例:</p>
|
||||
<pre
|
||||
class="m-0 p-8px bg-[var(--el-fill-color-light)] rounded-4px text-12px text-[var(--el-text-color-regular)] overflow-x-auto"
|
||||
><code>{ "method": "restart", "params": { "delay": 5 } }</code></pre>
|
||||
<!-- 详细示例弹出层 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="showExampleDetail"
|
||||
ref="exampleDetailRef"
|
||||
class="example-detail-popover"
|
||||
:style="examplePopoverStyle"
|
||||
>
|
||||
<div
|
||||
class="p-16px bg-white rounded-8px shadow-lg border border-[var(--el-border-color)] min-w-400px max-w-500px"
|
||||
>
|
||||
<div class="flex items-center gap-8px mb-16px">
|
||||
<Icon icon="ep:document" class="text-[var(--el-color-info)] text-18px" />
|
||||
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">
|
||||
参数配置详细示例
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<div class="space-y-16px">
|
||||
<!-- 物模型属性示例 -->
|
||||
<div v-if="thingModelProperties.length > 0">
|
||||
<div class="flex items-center gap-8px mb-8px">
|
||||
<Icon icon="ep:edit" class="text-[var(--el-color-primary)] text-14px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
|
||||
当前物模型属性
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-22px space-y-8px">
|
||||
<div
|
||||
v-for="property in thingModelProperties.slice(0, 4)"
|
||||
:key="property.identifier"
|
||||
class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="text-12px font-500 text-[var(--el-text-color-primary)]">
|
||||
{{ property.name }}
|
||||
</div>
|
||||
<div class="text-11px text-[var(--el-text-color-secondary)]">
|
||||
{{ property.identifier }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<el-tag :type="getPropertyTypeTag(property.dataType)" size="small">
|
||||
{{ getPropertyTypeName(property.dataType) }}
|
||||
</el-tag>
|
||||
<span class="text-11px text-[var(--el-text-color-secondary)]">
|
||||
{{ getExampleValue(property) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12px ml-22px">
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
|
||||
完整JSON格式:
|
||||
</div>
|
||||
<pre
|
||||
class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-primary)]"
|
||||
><code>{{ generateExampleJson() }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通用示例 -->
|
||||
<div>
|
||||
<div class="flex items-center gap-8px mb-8px">
|
||||
<Icon icon="ep:service" class="text-[var(--el-color-success)] text-14px" />
|
||||
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
|
||||
通用格式示例
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-22px space-y-8px">
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">
|
||||
服务调用格式:
|
||||
</div>
|
||||
<pre
|
||||
class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-success)]"
|
||||
><code>{
|
||||
"method": "restart",
|
||||
"params": {
|
||||
"delay": 5,
|
||||
"force": false
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<div class="flex justify-end mt-16px">
|
||||
<el-button size="small" @click="hideExampleDetail">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue'
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
import ProductSelector from '../selectors/ProductSelector.vue'
|
||||
import DeviceSelector from '../selectors/DeviceSelector.vue'
|
||||
import { ActionFormData } from '@/api/iot/rule/scene/scene.types'
|
||||
import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants'
|
||||
|
||||
/** 设备控制配置组件 */
|
||||
defineOptions({ name: 'DeviceControlConfig' })
|
||||
@@ -62,38 +202,362 @@ const action = useVModel(props, 'modelValue', emit)
|
||||
|
||||
// 状态
|
||||
const paramsJson = ref('')
|
||||
const jsonError = ref('')
|
||||
const thingModelProperties = ref<any[]>([])
|
||||
const loadingThingModel = ref(false)
|
||||
const propertyValues = ref<Record<string, any>>({})
|
||||
|
||||
// 示例弹出层相关状态
|
||||
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 handleDeviceChange = ({ productId, deviceId }: { productId?: number; deviceId?: number }) => {
|
||||
action.value.productId = productId
|
||||
action.value.deviceId = deviceId
|
||||
const handleProductChange = (productId?: number) => {
|
||||
// 当产品变化时,清空设备选择和参数配置
|
||||
if (action.value.productId !== productId) {
|
||||
action.value.deviceId = undefined
|
||||
action.value.params = {}
|
||||
paramsJson.value = ''
|
||||
jsonError.value = ''
|
||||
propertyValues.value = {}
|
||||
}
|
||||
|
||||
// 加载新产品的物模型属性
|
||||
if (productId && isPropertySetAction.value) {
|
||||
loadThingModelProperties(productId)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeviceChange = (deviceId?: number) => {
|
||||
// 当设备变化时,清空参数配置
|
||||
if (action.value.deviceId !== deviceId) {
|
||||
action.value.params = {}
|
||||
paramsJson.value = ''
|
||||
jsonError.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 快速填充示例数据
|
||||
const fillExampleJson = () => {
|
||||
const exampleData = generateExampleJson()
|
||||
paramsJson.value = exampleData
|
||||
handleParamsChange()
|
||||
}
|
||||
|
||||
// 清空参数
|
||||
const clearParams = () => {
|
||||
paramsJson.value = ''
|
||||
action.value.params = {}
|
||||
propertyValues.value = {}
|
||||
jsonError.value = ''
|
||||
}
|
||||
|
||||
// 更新属性值(保留但不在模板中使用)
|
||||
const updatePropertyValue = (identifier: string, value: any) => {
|
||||
propertyValues.value[identifier] = value
|
||||
// 同步更新到 action.params
|
||||
action.value.params = { ...propertyValues.value }
|
||||
// 同步更新 JSON 显示
|
||||
paramsJson.value = JSON.stringify(action.value.params, null, 2)
|
||||
jsonError.value = ''
|
||||
}
|
||||
|
||||
// 加载物模型属性
|
||||
const loadThingModelProperties = async (productId: number) => {
|
||||
if (!productId) {
|
||||
thingModelProperties.value = []
|
||||
return
|
||||
}
|
||||
|
||||
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: '环境湿度值'
|
||||
}
|
||||
]
|
||||
|
||||
// 初始化属性值
|
||||
thingModelProperties.value.forEach((property) => {
|
||||
if (!(property.identifier in propertyValues.value)) {
|
||||
propertyValues.value[property.identifier] = ''
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载物模型失败:', error)
|
||||
thingModelProperties.value = []
|
||||
} finally {
|
||||
loadingThingModel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleParamsChange = () => {
|
||||
try {
|
||||
jsonError.value = '' // 清除之前的错误
|
||||
|
||||
if (paramsJson.value.trim()) {
|
||||
action.value.params = JSON.parse(paramsJson.value)
|
||||
const parsed = JSON.parse(paramsJson.value)
|
||||
action.value.params = parsed
|
||||
|
||||
// 同步更新到属性值
|
||||
propertyValues.value = { ...parsed }
|
||||
|
||||
// 额外的参数验证
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
jsonError.value = '参数必须是一个有效的JSON对象'
|
||||
return
|
||||
}
|
||||
} else {
|
||||
action.value.params = {}
|
||||
propertyValues.value = {}
|
||||
}
|
||||
} catch (error) {
|
||||
jsonError.value = `JSON格式错误: ${error instanceof Error ? error.message : '未知错误'}`
|
||||
console.error('JSON格式错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数 - 参考 PropertySelector 的设计
|
||||
const getPropertyTypeName = (dataType: string) => {
|
||||
const typeMap = {
|
||||
int: '整数',
|
||||
float: '浮点数',
|
||||
double: '双精度',
|
||||
text: '字符串',
|
||||
bool: '布尔值',
|
||||
enum: '枚举',
|
||||
date: '日期',
|
||||
struct: '结构体',
|
||||
array: '数组'
|
||||
}
|
||||
return typeMap[dataType] || dataType
|
||||
}
|
||||
|
||||
const getPropertyTypeTag = (dataType: string) => {
|
||||
const tagMap = {
|
||||
int: 'primary',
|
||||
float: 'success',
|
||||
double: 'success',
|
||||
text: 'info',
|
||||
bool: 'warning',
|
||||
enum: 'danger',
|
||||
date: 'primary',
|
||||
struct: 'info',
|
||||
array: 'warning'
|
||||
}
|
||||
return tagMap[dataType] || 'info'
|
||||
}
|
||||
|
||||
const getExampleValue = (property: any) => {
|
||||
switch (property.dataType) {
|
||||
case 'int':
|
||||
return property.identifier === 'BatteryLevel' ? '85' : '25'
|
||||
case 'float':
|
||||
case 'double':
|
||||
return property.identifier === 'Temperature' ? '25.5' : '60.0'
|
||||
case 'bool':
|
||||
return 'false'
|
||||
case 'text':
|
||||
return '"auto"'
|
||||
case 'enum':
|
||||
return '"option1"'
|
||||
default:
|
||||
return '""'
|
||||
}
|
||||
}
|
||||
|
||||
const generateExampleJson = () => {
|
||||
if (thingModelProperties.value.length === 0) {
|
||||
return JSON.stringify(
|
||||
{
|
||||
BatteryLevel: '',
|
||||
WaterLeachState: ''
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
|
||||
const example = {}
|
||||
thingModelProperties.value.forEach((property) => {
|
||||
switch (property.dataType) {
|
||||
case 'int':
|
||||
example[property.identifier] = property.identifier === 'BatteryLevel' ? 85 : 25
|
||||
break
|
||||
case 'float':
|
||||
case 'double':
|
||||
example[property.identifier] = property.identifier === 'Temperature' ? 25.5 : 60.0
|
||||
break
|
||||
case 'bool':
|
||||
example[property.identifier] = false
|
||||
break
|
||||
case 'text':
|
||||
example[property.identifier] = 'auto'
|
||||
break
|
||||
default:
|
||||
example[property.identifier] = ''
|
||||
}
|
||||
})
|
||||
|
||||
return JSON.stringify(example, null, 2)
|
||||
}
|
||||
|
||||
// 示例弹出层控制方法 - 参考 PropertySelector 的设计
|
||||
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 (action.value.params) {
|
||||
paramsJson.value = JSON.stringify(action.value.params, null, 2)
|
||||
if (action.value.params && Object.keys(action.value.params).length > 0) {
|
||||
try {
|
||||
paramsJson.value = JSON.stringify(action.value.params, null, 2)
|
||||
propertyValues.value = { ...action.value.params }
|
||||
jsonError.value = '' // 清除错误状态
|
||||
} catch (error) {
|
||||
console.error('初始化参数格式化失败:', error)
|
||||
jsonError.value = '初始参数格式错误'
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经选择了产品且是属性设置类型,加载物模型
|
||||
if (action.value.productId && isPropertySetAction.value) {
|
||||
loadThingModelProperties(action.value.productId)
|
||||
}
|
||||
|
||||
// 添加事件监听器
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 组件卸载时清理事件监听器
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 监听参数变化
|
||||
watch(
|
||||
() => action.value.params,
|
||||
(newParams) => {
|
||||
if (newParams && typeof newParams === 'object') {
|
||||
paramsJson.value = JSON.stringify(newParams, null, 2)
|
||||
if (newParams && typeof newParams === 'object' && Object.keys(newParams).length > 0) {
|
||||
try {
|
||||
const newJsonString = JSON.stringify(newParams, null, 2)
|
||||
// 只有当JSON字符串真正改变时才更新,避免循环更新
|
||||
if (newJsonString !== paramsJson.value) {
|
||||
paramsJson.value = newJsonString
|
||||
jsonError.value = ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('参数格式化失败:', error)
|
||||
jsonError.value = '参数格式化失败'
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
@@ -101,6 +565,49 @@ watch(
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 参考 PropertySelector 的弹出层样式 */
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-4px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.example-detail-popover {
|
||||
animation: fadeInScale 0.2s ease-out;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
/* 弹出层箭头效果 */
|
||||
.example-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: '';
|
||||
}
|
||||
|
||||
.example-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: '';
|
||||
}
|
||||
|
||||
:deep(.example-content code) {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--el-color-primary);
|
||||
|
||||
@@ -108,7 +108,12 @@ 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 { IotRuleSceneActionTypeEnum as ActionTypeEnum } from '@/views/iot/utils/constants'
|
||||
import {
|
||||
IotRuleSceneActionTypeEnum as ActionTypeEnum,
|
||||
isDeviceAction,
|
||||
isAlertAction,
|
||||
getActionTypeLabel
|
||||
} from '@/views/iot/utils/constants'
|
||||
|
||||
/** 执行器配置组件 */
|
||||
defineOptions({ name: 'ActionSection' })
|
||||
@@ -142,37 +147,18 @@ const createDefaultActionData = (): ActionFormData => {
|
||||
// 配置常量
|
||||
const maxActions = 5
|
||||
|
||||
// 执行器类型映射
|
||||
const actionTypeNames = {
|
||||
[ActionTypeEnum.DEVICE_PROPERTY_SET]: '属性设置',
|
||||
[ActionTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
|
||||
[ActionTypeEnum.ALERT_TRIGGER]: '触发告警',
|
||||
[ActionTypeEnum.ALERT_RECOVER]: '恢复告警'
|
||||
}
|
||||
|
||||
const actionTypeTags = {
|
||||
[ActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
|
||||
[ActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
|
||||
[ActionTypeEnum.ALERT_TRIGGER]: 'danger',
|
||||
[ActionTypeEnum.ALERT_RECOVER]: 'warning'
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const isDeviceAction = (type: number) => {
|
||||
return [ActionTypeEnum.DEVICE_PROPERTY_SET, ActionTypeEnum.DEVICE_SERVICE_INVOKE].includes(
|
||||
type as any
|
||||
)
|
||||
}
|
||||
|
||||
const isAlertAction = (type: number) => {
|
||||
return [ActionTypeEnum.ALERT_TRIGGER, ActionTypeEnum.ALERT_RECOVER].includes(type as any)
|
||||
}
|
||||
|
||||
const getActionTypeName = (type: number) => {
|
||||
return actionTypeNames[type] || '未知类型'
|
||||
return getActionTypeLabel(type)
|
||||
}
|
||||
|
||||
const getActionTypeTag = (type: number) => {
|
||||
const actionTypeTags = {
|
||||
[ActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
|
||||
[ActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
|
||||
[ActionTypeEnum.ALERT_TRIGGER]: 'danger',
|
||||
[ActionTypeEnum.ALERT_RECOVER]: 'warning'
|
||||
}
|
||||
return actionTypeTags[type] || 'info'
|
||||
}
|
||||
|
||||
@@ -204,16 +190,23 @@ const updateActionAlertConfig = (index: number, alertConfigId?: number) => {
|
||||
}
|
||||
|
||||
const onActionTypeChange = (action: ActionFormData, type: number) => {
|
||||
// 清理不相关的配置
|
||||
// 清理不相关的配置,确保数据结构干净
|
||||
if (isDeviceAction(type)) {
|
||||
// 设备控制类型:清理告警配置,确保设备参数存在
|
||||
action.alertConfigId = undefined
|
||||
if (!action.params) {
|
||||
action.params = {}
|
||||
}
|
||||
} else if (isAlertAction(type)) {
|
||||
// 告警类型:清理设备配置
|
||||
action.productId = undefined
|
||||
action.deviceId = undefined
|
||||
action.params = undefined
|
||||
}
|
||||
|
||||
// 触发重新校验
|
||||
nextTick(() => {
|
||||
// 这里可以添加校验逻辑
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -39,10 +39,20 @@
|
||||
<!-- 设备选择模式 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="设备选择模式" required>
|
||||
<el-radio-group v-model="deviceSelectionMode" @change="handleDeviceSelectionModeChange">
|
||||
<el-radio-group
|
||||
v-model="deviceSelectionMode"
|
||||
@change="handleDeviceSelectionModeChange"
|
||||
:disabled="!localProductId"
|
||||
>
|
||||
<el-radio value="all">全部设备</el-radio>
|
||||
<el-radio value="specific">选择设备</el-radio>
|
||||
</el-radio-group>
|
||||
<div
|
||||
v-if="!localProductId"
|
||||
class="text-12px text-[var(--el-text-color-placeholder)] mt-4px"
|
||||
>
|
||||
请先选择产品
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -50,12 +60,10 @@
|
||||
<!-- 具体设备选择 -->
|
||||
<el-row v-if="deviceSelectionMode === 'specific'" :gutter="16">
|
||||
<el-col :span="24">
|
||||
<!-- TODO @puhui999:貌似产品选择不上; -->
|
||||
<el-form-item label="选择设备" required>
|
||||
<!-- TODO @puhui999:请先选择产品,是不是改成请选择设备?然后上面,localProductId 为空(未选择)的时候,禁用 deviceSelectionMode -->
|
||||
<el-select
|
||||
v-model="localDeviceId"
|
||||
placeholder="请先选择产品"
|
||||
:placeholder="localProductId ? '请选择设备' : '请先选择产品'"
|
||||
filterable
|
||||
clearable
|
||||
@change="handleDeviceChange"
|
||||
@@ -152,8 +160,8 @@ const localProductId = useVModel(props, 'productId', emit)
|
||||
const localDeviceId = useVModel(props, 'deviceId', emit)
|
||||
|
||||
// 设备选择模式
|
||||
// TODO @puhui999:默认选中 all
|
||||
const deviceSelectionMode = ref<'specific' | 'all'>('all')
|
||||
// 默认选择具体设备,这样用户可以看到设备选择器
|
||||
const deviceSelectionMode = ref<'specific' | 'all'>('specific')
|
||||
|
||||
// 数据状态
|
||||
const productLoading = ref(false)
|
||||
|
||||
@@ -260,6 +260,66 @@ export const IotRuleSceneActionTypeEnum = {
|
||||
ALERT_RECOVER: 101 // 告警恢复
|
||||
} as const
|
||||
|
||||
/** 执行器类型选项配置 */
|
||||
export const getActionTypeOptions = () => [
|
||||
{
|
||||
value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
|
||||
label: '设备属性设置',
|
||||
description: '设置目标设备的属性值',
|
||||
icon: 'ep:edit',
|
||||
tag: 'primary',
|
||||
category: '设备控制'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE,
|
||||
label: '设备服务调用',
|
||||
description: '调用目标设备的服务',
|
||||
icon: 'ep:service',
|
||||
tag: 'success',
|
||||
category: '设备控制'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
|
||||
label: '触发告警',
|
||||
description: '触发系统告警通知',
|
||||
icon: 'ep:warning',
|
||||
tag: 'danger',
|
||||
category: '告警通知'
|
||||
},
|
||||
{
|
||||
value: IotRuleSceneActionTypeEnum.ALERT_RECOVER,
|
||||
label: '恢复告警',
|
||||
description: '恢复已触发的告警',
|
||||
icon: 'ep:circle-check',
|
||||
tag: 'warning',
|
||||
category: '告警通知'
|
||||
}
|
||||
]
|
||||
|
||||
/** 判断是否为设备执行器类型 */
|
||||
export const isDeviceAction = (type: number): boolean => {
|
||||
const deviceActionTypes = [
|
||||
IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
|
||||
IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
] as number[]
|
||||
return deviceActionTypes.includes(type)
|
||||
}
|
||||
|
||||
/** 判断是否为告警执行器类型 */
|
||||
export const isAlertAction = (type: number): boolean => {
|
||||
const alertActionTypes = [
|
||||
IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
|
||||
IotRuleSceneActionTypeEnum.ALERT_RECOVER
|
||||
] as number[]
|
||||
return alertActionTypes.includes(type)
|
||||
}
|
||||
|
||||
/** 获取执行器类型标签 */
|
||||
export const getActionTypeLabel = (type: number): string => {
|
||||
const option = getActionTypeOptions().find((opt) => opt.value === type)
|
||||
return option?.label || '未知类型'
|
||||
}
|
||||
|
||||
/** IoT 设备消息类型枚举 */
|
||||
export const IotDeviceMessageTypeEnum = {
|
||||
PROPERTY: 'property', // 属性
|
||||
@@ -309,15 +369,3 @@ export const getTriggerTypeLabel = (type: number): string => {
|
||||
const option = options.find((item) => item.value === type)
|
||||
return option?.label || '未知类型'
|
||||
}
|
||||
|
||||
/** 获取执行器类型标签 */
|
||||
export const getActionTypeLabel = (type: number): string => {
|
||||
const actionTypeOptions = [
|
||||
{ label: '设备属性设置', value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET },
|
||||
{ label: '设备服务调用', value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE },
|
||||
{ label: '告警触发', value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER },
|
||||
{ label: '告警恢复', value: IotRuleSceneActionTypeEnum.ALERT_RECOVER }
|
||||
]
|
||||
const option = actionTypeOptions.find((item) => item.value === type)
|
||||
return option?.label || '未知类型'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user