perf:【IoT 物联网】场景联动优化统一类型定义,简化告警配置

pull/806/head
puhui999 2025-08-05 11:59:46 +08:00
parent 694de3f0d1
commit 6c954c4ff1
11 changed files with 134 additions and 423 deletions

View File

@ -1,5 +1,5 @@
import request from '@/config/axios' import request from '@/config/axios'
import { IotRuleScene } from './scene.types' import { IotSceneRule } from './scene.types'
// IoT 场景联动 API // IoT 场景联动 API
export const RuleSceneApi = { 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 }) 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 }) return await request.put({ url: `/iot/rule-scene/update`, data })
}, },
// 修改场景联动 // 修改场景联动
updateRuleSceneStatus: async (id: number, status: number) => { updateRuleSceneStatus: async (id: number, status: number) => {
return await request.put({ url: `/iot/rule-scene/update-status`, data: { return await request.put({
id, url: `/iot/rule-scene/update-status`,
status data: {
}}) id,
status
}
})
}, },
// 删除场景联动 // 删除场景联动

View File

@ -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<string, any> // 参数对象(必填)- 统一使用 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<string, any> // 请求参数
alertConfigId?: number // 告警配置编号
}
// 主接口 - 原有的 API 接口格式(保持兼容性)
interface IotRuleScene extends TenantBaseDO {
id?: number // 场景编号(新增时为空)
name: string // 场景名称(必填)
description?: string // 场景描述(可选)
status: number // 场景状态0-开启1-关闭
triggers: TriggerConfig[] // 触发器数组(必填,至少一个)
actions: ActionConfig[] // 执行器数组(必填,至少一个)
}
// 后端 DO 接口 - 匹配后端数据结构 // 后端 DO 接口 - 匹配后端数据结构
interface IotRuleSceneDO { interface IotSceneRule {
id?: number // 场景编号 id?: number // 场景编号
name: string // 场景名称 name: string // 场景名称
description?: string // 场景描述 description?: string // 场景描述
status: number // 场景状态0-开启1-关闭 status: number // 场景状态0-开启1-关闭
triggers: TriggerDO[] // 触发器数组 triggers: Trigger[] // 触发器数组
actions: ActionDO[] // 执行器数组 actions: Action[] // 执行器数组
} }
// 触发器 DO 结构 // 触发器 DO 结构
interface TriggerDO { interface Trigger {
type: number // 触发类型 type: number // 触发类型
productId?: number // 产品编号 productId?: number // 产品编号
deviceId?: number // 设备编号 deviceId?: number // 设备编号
@ -260,12 +156,12 @@ interface TriggerDO {
operator?: string // 操作符 operator?: string // 操作符
value?: string // 参数值 value?: string // 参数值
cronExpression?: string // CRON 表达式 cronExpression?: string // CRON 表达式
conditionGroups?: TriggerConditionDO[][] // 条件组(二维数组) conditionGroups?: TriggerCondition[][] // 条件组(二维数组)
} }
// 触发条件 DO 结构 // 触发条件 DO 结构
interface TriggerConditionDO { interface TriggerCondition {
type: number // 条件类型 type: number // 条件类型1-设备状态2-设备属性3-当前时间
productId?: number // 产品编号 productId?: number // 产品编号
deviceId?: number // 设备编号 deviceId?: number // 设备编号
identifier?: string // 标识符 identifier?: string // 标识符
@ -274,7 +170,7 @@ interface TriggerConditionDO {
} }
// 执行器 DO 结构 // 执行器 DO 结构
interface ActionDO { interface Action {
type: number // 执行类型 type: number // 执行类型
productId?: number // 产品编号 productId?: number // 产品编号
deviceId?: number // 设备编号 deviceId?: number // 设备编号
@ -300,21 +196,4 @@ interface FormValidationRules {
// TODO @puhui999这个文件目标最终没有哈和别的模块一致 // TODO @puhui999这个文件目标最终没有哈和别的模块一致
export { export { IotSceneRule, Trigger, TriggerCondition, Action, ValidationRule, FormValidationRules }
IotRuleScene,
IotRuleSceneDO,
TriggerDO,
TriggerConditionDO,
ActionDO,
TriggerConfig,
TriggerCondition,
TriggerConditionParameter,
ActionConfig,
ActionDeviceControl,
RuleSceneFormData,
TriggerFormData,
TriggerConditionFormData,
ActionFormData,
ValidationRule,
FormValidationRules
}

View File

@ -36,7 +36,7 @@ import { useVModel } from '@vueuse/core'
import BasicInfoSection from './sections/BasicInfoSection.vue' import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue' import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.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 { RuleSceneApi } from '@/api/iot/rule/scene'
import { import {
IotRuleSceneTriggerTypeEnum, IotRuleSceneTriggerTypeEnum,
@ -54,7 +54,7 @@ const props = defineProps<{
/** 抽屉显示状态 */ /** 抽屉显示状态 */
modelValue: boolean modelValue: boolean
/** 编辑的场景联动规则数据 */ /** 编辑的场景联动规则数据 */
ruleScene?: IotRuleSceneDO ruleScene?: IotSceneRule
}>() }>()
/** 组件事件定义 */ /** 组件事件定义 */
@ -66,7 +66,7 @@ const emit = defineEmits<{
const drawerVisible = useVModel(props, 'modelValue', emit) // const drawerVisible = useVModel(props, 'modelValue', emit) //
/** 创建默认的表单数据 */ /** 创建默认的表单数据 */
const createDefaultFormData = (): RuleSceneFormData => { const createDefaultFormData = (): IotSceneRule => {
return { return {
name: '', name: '',
description: '', description: '',
@ -89,7 +89,7 @@ const createDefaultFormData = (): RuleSceneFormData => {
// //
const formRef = ref() const formRef = ref()
const formData = ref<RuleSceneFormData>(createDefaultFormData()) const formData = ref<IotSceneRule>(createDefaultFormData())
// //
const validateTriggers = (_rule: any, value: any, callback: any) => { const validateTriggers = (_rule: any, value: any, callback: any) => {
if (!value || !Array.isArray(value) || value.length === 0) { if (!value || !Array.isArray(value) || value.length === 0) {

View File

@ -1,262 +1,81 @@
<!-- 告警配置组件 --> <!-- 告警配置组件 -->
<template> <template>
<div class="w-full"> <div class="w-full">
<!-- 告警配置选择区域 --> <el-form-item label="告警配置" required>
<div <el-select
class="border border-[var(--el-border-color-light)] rounded-6px p-16px bg-[var(--el-fill-color-blank)]" v-model="localValue"
> placeholder="请选择告警配置"
<div class="flex items-center gap-8px mb-12px"> filterable
<Icon icon="ep:bell" class="text-[var(--el-color-warning)] text-16px" /> clearable
<span class="text-14px font-600 text-[var(--el-text-color-primary)]">告警配置选择</span> @change="handleChange"
<el-tag size="small" type="warning">必选</el-tag> class="w-full"
</div> :loading="loading"
>
<el-form-item label="告警配置" required> <el-option
<el-select v-for="config in alertConfigs"
v-model="localValue" :key="config.id"
placeholder="请选择告警配置" :label="config.name"
filterable :value="config.id"
clearable
@change="handleChange"
class="w-full"
:loading="loading"
> >
<template #empty> <div class="flex items-center justify-between">
<div class="text-center py-20px"> <span>{{ config.name }}</span>
<Icon <el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
icon="ep:warning" {{ config.enabled ? '启用' : '禁用' }}
class="text-24px text-[var(--el-text-color-placeholder)] mb-8px" </el-tag>
/>
<p class="text-12px text-[var(--el-text-color-secondary)]">暂无可用的告警配置</p>
</div>
</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 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-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="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>
<div class="pl-22px space-y-8px"> </el-option>
<div class="flex items-start gap-8px"> </el-select>
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">名称</span> </el-form-item>
<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="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>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { AlertConfigApi } from '@/api/iot/alert/config'
/** 告警配置组件 */ /** 告警配置组件 */
defineOptions({ name: 'AlertConfig' }) defineOptions({ name: 'AlertConfig' })
interface Props { const props = defineProps<{
modelValue?: number modelValue?: number
} }>()
interface Emits { const emit = defineEmits<{
(e: 'update:modelValue', value?: number): void (e: 'update:modelValue', value?: number): void
} }>()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const localValue = useVModel(props, 'modelValue', emit) const localValue = useVModel(props, 'modelValue', emit)
// const loading = ref(false) //
const loading = ref(false) const alertConfigs = ref<any[]>([]) //
const alertConfigs = ref<any[]>([])
// /**
const selectedConfig = computed(() => { * 处理选择变化事件
return alertConfigs.value.find((config) => config.id === localValue.value) * @param value 选中的值
}) */
//
const getNotifyTypeName = (type: number) => {
const typeMap = {
1: '邮件通知',
2: '短信通知',
3: '微信通知',
4: '钉钉通知'
}
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) => { const handleChange = (value?: number) => {
// emit('update:modelValue', value)
console.log('告警配置选择变化:', value)
} }
// API /**
const getAlertConfigs = async () => { * 加载告警配置列表
*/
const loadAlertConfigs = async () => {
loading.value = true loading.value = true
try { try {
// API const data = await AlertConfigApi.getAlertConfigPage({
// 使 pageNo: 1,
// TODO @puhui999 pageSize: 100,
alertConfigs.value = [ enabled: true //
{ })
id: 1, alertConfigs.value = data.list || []
name: '设备异常告警',
description: '设备状态异常时发送告警',
enabled: true,
notifyType: 1,
receivers: ['admin@example.com', 'operator@example.com']
},
{
id: 2,
name: '温度超限告警',
description: '温度超过阈值时发送告警',
enabled: true,
notifyType: 2,
receivers: ['13800138000', '13900139000']
},
{
id: 3,
name: '系统故障告警',
description: '系统发生故障时发送告警',
enabled: false,
notifyType: 3,
receivers: ['技术支持群']
}
]
} catch (error) {
console.error('获取告警配置失败:', error)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// //
onMounted(() => { onMounted(() => {
getAlertConfigs() loadAlertConfigs()
}) })
</script> </script>
<style scoped>
:deep(.el-select-dropdown__item) {
height: auto;
padding: 8px 20px;
}
</style>

View File

@ -123,7 +123,7 @@ import DeviceSelector from '../selectors/DeviceSelector.vue'
import PropertySelector from '../selectors/PropertySelector.vue' import PropertySelector from '../selectors/PropertySelector.vue'
import OperatorSelector from '../selectors/OperatorSelector.vue' import OperatorSelector from '../selectors/OperatorSelector.vue'
import ValueInput from '../inputs/ValueInput.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 { import {
IotRuleSceneTriggerConditionTypeEnum, IotRuleSceneTriggerConditionTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum IotRuleSceneTriggerConditionParameterOperatorEnum
@ -133,12 +133,12 @@ import {
defineOptions({ name: 'ConditionConfig' }) defineOptions({ name: 'ConditionConfig' })
const props = defineProps<{ const props = defineProps<{
modelValue: TriggerConditionFormData modelValue: TriggerCondition
triggerType: number triggerType: number
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerConditionFormData): void (e: 'update:modelValue', value: TriggerCondition): void
(e: 'validate', result: { valid: boolean; message: string }): void (e: 'validate', result: { valid: boolean; message: string }): void
}>() }>()
@ -155,12 +155,12 @@ const isValid = ref(true)
const valueValidation = ref({ valid: true, message: '' }) 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 ;(condition.value as any)[field] = value
emit('update:modelValue', condition.value) emit('update:modelValue', condition.value)
} }
const updateCondition = (newCondition: TriggerConditionFormData) => { const updateCondition = (newCondition: TriggerCondition) => {
condition.value = newCondition condition.value = newCondition
emit('update:modelValue', condition.value) emit('update:modelValue', condition.value)
} }

View File

@ -320,18 +320,18 @@ import { useVModel } from '@vueuse/core'
import { InfoFilled } from '@element-plus/icons-vue' import { InfoFilled } from '@element-plus/icons-vue'
import ProductSelector from '../selectors/ProductSelector.vue' import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.vue' import DeviceSelector from '../selectors/DeviceSelector.vue'
import { ActionFormData, ThingModelService } from '@/api/iot/rule/scene/scene.types' import { Action, ThingModelService } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants'
/** 设备控制配置组件 */ /** 设备控制配置组件 */
defineOptions({ name: 'DeviceControlConfig' }) defineOptions({ name: 'DeviceControlConfig' })
const props = defineProps<{ const props = defineProps<{
modelValue: ActionFormData modelValue: Action
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: ActionFormData): void (e: 'update:modelValue', value: Action): void
}>() }>()
const action = useVModel(props, 'modelValue', emit) const action = useVModel(props, 'modelValue', emit)

View File

@ -84,17 +84,17 @@
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import ProductSelector from '../selectors/ProductSelector.vue' import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.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' }) defineOptions({ name: 'DeviceStatusConditionConfig' })
const props = defineProps<{ const props = defineProps<{
modelValue: TriggerConditionFormData modelValue: TriggerCondition
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerConditionFormData): void (e: 'update:modelValue', value: TriggerCondition): void
(e: 'validate', result: { valid: boolean; message: string }): void (e: 'validate', result: { valid: boolean; message: string }): void
}>() }>()

View File

@ -83,7 +83,7 @@
import { nextTick } from 'vue' import { nextTick } from 'vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import ConditionConfig from './ConditionConfig.vue' import ConditionConfig from './ConditionConfig.vue'
import { TriggerConditionFormData } from '@/api/iot/rule/scene/scene.types' import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
import { import {
IotRuleSceneTriggerConditionTypeEnum, IotRuleSceneTriggerConditionTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum IotRuleSceneTriggerConditionParameterOperatorEnum
@ -93,13 +93,13 @@ import {
defineOptions({ name: 'SubConditionGroupConfig' }) defineOptions({ name: 'SubConditionGroupConfig' })
const props = defineProps<{ const props = defineProps<{
modelValue: TriggerConditionFormData[] modelValue: TriggerCondition[]
triggerType: number triggerType: number
maxConditions?: number maxConditions?: number
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerConditionFormData[]): void (e: 'update:modelValue', value: TriggerCondition[]): void
(e: 'validate', result: { valid: boolean; message: string }): void (e: 'validate', result: { valid: boolean; message: string }): void
}>() }>()
@ -123,7 +123,7 @@ const addCondition = () => {
return return
} }
const newCondition: TriggerConditionFormData = { const newCondition: TriggerCondition = {
type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, //
productId: undefined, productId: undefined,
deviceId: 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) { if (subGroup.value) {
subGroup.value[index] = condition subGroup.value[index] = condition
} }

View File

@ -78,12 +78,27 @@
@update:model-value="(value) => updateAction(index, value)" @update:model-value="(value) => updateAction(index, value)"
/> />
<!-- 告警配置 --> <!-- 告警配置 - 只有恢复告警时才显示 -->
<AlertConfig <AlertConfig
v-if="isAlertAction(action.type)" v-if="action.type === ActionTypeEnum.ALERT_RECOVER"
:model-value="action.alertConfigId" :model-value="action.alertConfigId"
@update:model-value="(value) => updateActionAlertConfig(index, value)" @update:model-value="(value) => updateActionAlertConfig(index, value)"
/> />
<!-- 触发告警提示 - 触发告警时显示 -->
<div
v-if="action.type === ActionTypeEnum.ALERT_TRIGGER"
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-8px">
<Icon icon="ep:warning" 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>
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
当触发条件满足时系统将自动发送告警通知无需额外配置
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -107,7 +122,7 @@ import { useVModel } from '@vueuse/core'
import ActionTypeSelector from '../selectors/ActionTypeSelector.vue' import ActionTypeSelector from '../selectors/ActionTypeSelector.vue'
import DeviceControlConfig from '../configs/DeviceControlConfig.vue' import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
import AlertConfig from '../configs/AlertConfig.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 { import {
IotRuleSceneActionTypeEnum as ActionTypeEnum, IotRuleSceneActionTypeEnum as ActionTypeEnum,
isDeviceAction, isDeviceAction,
@ -118,23 +133,20 @@ import {
/** 执行器配置组件 */ /** 执行器配置组件 */
defineOptions({ name: 'ActionSection' }) defineOptions({ name: 'ActionSection' })
interface Props { const props = defineProps<{
actions: ActionFormData[] actions: Action[]
} }>()
interface Emits { const emit = defineEmits<{
(e: 'update:actions', value: ActionFormData[]): void (e: 'update:actions', value: Action[]): void
} }>()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const actions = useVModel(props, 'actions', emit) const actions = useVModel(props, 'actions', emit)
/** /**
* 创建默认的执行器数据 * 创建默认的执行器数据
*/ */
const createDefaultActionData = (): ActionFormData => { const createDefaultActionData = (): Action => {
return { return {
type: ActionTypeEnum.DEVICE_PROPERTY_SET, // type: ActionTypeEnum.DEVICE_PROPERTY_SET, //
productId: undefined, productId: undefined,
@ -145,9 +157,7 @@ const createDefaultActionData = (): ActionFormData => {
} }
} }
// const maxActions = 5 //
// TODO @puhui999
const maxActions = 5
// //
const getActionTypeName = (type: number) => { const getActionTypeName = (type: number) => {
@ -164,7 +174,7 @@ const getActionTypeTag = (type: number) => {
return actionTypeTags[type] || 'info' return actionTypeTags[type] || 'info'
} }
// /** 添加执行器 */
const addAction = () => { const addAction = () => {
if (actions.value.length >= maxActions) { if (actions.value.length >= maxActions) {
return return
@ -174,24 +184,29 @@ const addAction = () => {
actions.value.push(newAction) actions.value.push(newAction)
} }
/** 删除执行器 */
const removeAction = (index: number) => { const removeAction = (index: number) => {
actions.value.splice(index, 1) actions.value.splice(index, 1)
} }
/** 更新执行器类型 */
const updateActionType = (index: number, type: number) => { const updateActionType = (index: number, type: number) => {
actions.value[index].type = type actions.value[index].type = type
onActionTypeChange(actions.value[index], type) onActionTypeChange(actions.value[index], type)
} }
const updateAction = (index: number, action: ActionFormData) => { /** 更新执行器 */
const updateAction = (index: number, action: Action) => {
actions.value[index] = action actions.value[index] = action
} }
/** 更新告警配置 */
const updateActionAlertConfig = (index: number, alertConfigId?: number) => { const updateActionAlertConfig = (index: number, alertConfigId?: number) => {
actions.value[index].alertConfigId = alertConfigId actions.value[index].alertConfigId = alertConfigId
} }
const onActionTypeChange = (action: ActionFormData, type: number) => { /** 监听执行器类型变化 */
const onActionTypeChange = (action: Action, type: number) => {
// //
if (isDeviceAction(type)) { if (isDeviceAction(type)) {
// //
@ -204,16 +219,11 @@ const onActionTypeChange = (action: ActionFormData, type: number) => {
action.identifier = undefined action.identifier = undefined
} }
} else if (isAlertAction(type)) { } else if (isAlertAction(type)) {
//
action.productId = undefined action.productId = undefined
action.deviceId = undefined action.deviceId = undefined
action.identifier = undefined // action.identifier = undefined //
action.params = undefined action.params = undefined
action.alertConfigId = undefined
} }
//
nextTick(() => {
//
})
} }
</script> </script>

View File

@ -58,17 +58,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
/** 基础信息配置组件 */ /** 基础信息配置组件 */
defineOptions({ name: 'BasicInfoSection' }) defineOptions({ name: 'BasicInfoSection' })
const props = defineProps<{ const props = defineProps<{
modelValue: RuleSceneFormData modelValue: IotSceneRule
rules?: any rules?: any
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: RuleSceneFormData): void (e: 'update:modelValue', value: IotSceneRule): void
}>() }>()
const formData = useVModel(props, 'modelValue', emit) const formData = useVModel(props, 'modelValue', emit)

View File

@ -239,7 +239,7 @@
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import RuleSceneForm from './form/RuleSceneForm.vue' 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 { RuleSceneApi } from '@/api/iot/rule/scene'
import { import {
IotRuleSceneTriggerTypeEnum, IotRuleSceneTriggerTypeEnum,
@ -264,14 +264,14 @@ const queryParams = reactive({
}) })
const loading = ref(true) // const loading = ref(true) //
const list = ref<IotRuleSceneDO[]>([]) // const list = ref<IotSceneRule[]>([]) //
const total = ref(0) // const total = ref(0) //
const selectedRows = ref<IotRuleSceneDO[]>([]) // const selectedRows = ref<IotSceneRule[]>([]) //
const queryFormRef = ref() // const queryFormRef = ref() //
/** 表单状态 */ /** 表单状态 */
const formVisible = ref(false) // const formVisible = ref(false) //
const currentRule = ref<IotRuleSceneDO>() // const currentRule = ref<IotSceneRule>() //
/** 统计数据 */ /** 统计数据 */
const statistics = ref({ const statistics = ref({
@ -327,7 +327,7 @@ const formatCronExpression = (cron: string): string => {
} }
/** 获取规则摘要信息 */ /** 获取规则摘要信息 */
const getRuleSceneSummary = (rule: IotRuleSceneDO) => { const getRuleSceneSummary = (rule: IotSceneRule) => {
const triggerSummary = const triggerSummary =
rule.triggers?.map((trigger: any) => { rule.triggers?.map((trigger: any) => {
// //
@ -455,12 +455,12 @@ const updateStatistics = () => {
} }
/** 获取触发器摘要 */ /** 获取触发器摘要 */
const getTriggerSummary = (rule: IotRuleSceneDO) => { const getTriggerSummary = (rule: IotSceneRule) => {
return getRuleSceneSummary(rule).triggerSummary return getRuleSceneSummary(rule).triggerSummary
} }
/** 获取执行器摘要 */ /** 获取执行器摘要 */
const getActionSummary = (rule: IotRuleSceneDO) => { const getActionSummary = (rule: IotSceneRule) => {
return getRuleSceneSummary(rule).actionSummary return getRuleSceneSummary(rule).actionSummary
} }
@ -484,7 +484,7 @@ const handleAdd = () => {
} }
/** 修改操作 */ /** 修改操作 */
const handleEdit = (row: IotRuleSceneDO) => { const handleEdit = (row: IotSceneRule) => {
currentRule.value = row currentRule.value = row
formVisible.value = true formVisible.value = true
} }
@ -506,7 +506,7 @@ const handleDelete = async (id: number) => {
} }
/** 修改状态 */ /** 修改状态 */
const handleToggleStatus = async (row: IotRuleSceneDO) => { const handleToggleStatus = async (row: IotSceneRule) => {
try { try {
// //
// TODO @puhui999status // TODO @puhui999status
@ -525,7 +525,7 @@ const handleToggleStatus = async (row: IotRuleSceneDO) => {
} }
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: IotRuleSceneDO[]) => { const handleSelectionChange = (selection: IotSceneRule[]) => {
selectedRows.value = selection selectedRows.value = selection
} }