perf:【IoT 物联网】场景联动目录结构优化

This commit is contained in:
puhui999
2025-07-27 22:44:06 +08:00
parent 8549399ae8
commit d3d6f8f8ab
13 changed files with 275 additions and 1819 deletions

View File

@ -26,13 +26,6 @@
<!-- 执行器配置 -->
<ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
<!-- 预览区域 -->
<PreviewSection
:form-data="formData"
:validation-result="validationResult"
@validate="handleValidate"
/>
</el-form>
</div>
@ -48,11 +41,16 @@ import { useVModel } from '@vueuse/core'
import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue'
import PreviewSection from './sections/PreviewSection.vue'
import { RuleSceneFormData, IotRuleScene } from '@/api/iot/rule/scene/scene.types'
import {
RuleSceneFormData,
IotRuleScene,
IotRuleSceneTriggerTypeEnum,
IotRuleSceneActionTypeEnum,
CommonStatusEnum
} from '@/api/iot/rule/scene/scene.types'
import { getBaseValidationRules } from '../utils/validation'
import { transformFormToApi, transformApiToForm, createDefaultFormData } from '../utils/transform'
import { ElMessage } from 'element-plus'
import { generateUUID } from '@/utils'
/** IoT 场景联动规则表单 - 主表单组件 */
defineOptions({ name: 'RuleSceneForm' })
@ -72,12 +70,93 @@ const emit = defineEmits<Emits>()
const drawerVisible = useVModel(props, 'modelValue', emit)
/**
* 创建默认的表单数据
*/
const createDefaultFormData = (): RuleSceneFormData => {
return {
name: '',
description: '',
status: CommonStatusEnum.ENABLE, // 默认启用状态
triggers: [],
actions: []
}
}
/**
* 将表单数据转换为API请求格式
*/
const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => {
return {
id: formData.id,
name: formData.name,
description: formData.description,
status: Number(formData.status),
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:
trigger.conditionGroups?.map((group) => ({
type: 'property',
identifier: trigger.identifier || '',
parameters: group.conditions.map((condition) => ({
identifier: condition.identifier,
operator: condition.operator,
value: condition.param
}))
})) || []
})) || [],
actions:
formData.actions?.map((action) => ({
type: action.type,
alertConfigId: action.alertConfigId,
deviceControl:
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
? {
productKey: action.productId ? `product_${action.productId}` : '',
deviceNames: action.deviceId ? [`device_${action.deviceId}`] : [],
type: 'property',
identifier: 'set',
params: action.params || {}
}
: undefined
})) || []
} as IotRuleScene
}
/**
* 将 API 响应数据转换为表单格式
*/
const transformApiToForm = (apiData: IotRuleScene): RuleSceneFormData => {
return {
...apiData,
status: Number(apiData.status), // 确保状态为数字类型
triggers:
apiData.triggers?.map((trigger) => ({
...trigger,
type: Number(trigger.type),
// 为每个触发器添加唯一标识符,解决组件索引重用问题
key: generateUUID()
})) || [],
actions:
apiData.actions?.map((action) => ({
...action,
type: Number(action.type),
// 为每个执行器添加唯一标识符,解决组件索引重用问题
key: generateUUID()
})) || []
}
}
// 表单数据和状态
const formRef = ref()
const formData = ref<RuleSceneFormData>(createDefaultFormData())
const formRules = getBaseValidationRules()
const submitLoading = ref(false)
const validationResult = ref<{ valid: boolean; message?: string } | null>(null)
// 验证状态
const triggerValidation = ref({ valid: true, message: '' })
@ -87,16 +166,6 @@ const actionValidation = ref({ valid: true, message: '' })
const isEdit = computed(() => !!props.ruleScene?.id)
const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则'))
const canSubmit = computed(() => {
return (
formData.value.name &&
formData.value.triggers.length > 0 &&
formData.value.actions.length > 0 &&
triggerValidation.value.valid &&
actionValidation.value.valid
)
})
// 事件处理
const handleTriggerValidate = (result: { valid: boolean; message: string }) => {
triggerValidation.value = result
@ -106,29 +175,6 @@ const handleActionValidate = (result: { valid: boolean; message: string }) => {
actionValidation.value = result
}
const handleValidate = async () => {
try {
await formRef.value?.validate()
if (!triggerValidation.value.valid) {
throw new Error(triggerValidation.value.message)
}
if (!actionValidation.value.valid) {
throw new Error(actionValidation.value.message)
}
validationResult.value = { valid: true, message: '验证通过' }
ElMessage.success('规则验证通过')
return true
} catch (error: any) {
const message = error.message || '表单验证失败'
validationResult.value = { valid: false, message }
ElMessage.error(message)
return false
}
}
const handleSubmit = async () => {
// 校验表单
if (!formRef.value) return
@ -167,7 +213,6 @@ const handleSubmit = async () => {
const handleClose = () => {
drawerVisible.value = false
validationResult.value = null
}
// 初始化表单数据

View File

@ -1,76 +0,0 @@
<!-- 执行器预览组件 -->
<template>
<div class="w-full">
<div v-if="actions.length === 0" class="text-center py-20px">
<el-text type="info" size="small">暂无执行器配置</el-text>
</div>
<div v-else class="space-y-12px">
<div
v-for="(action, index) in actions"
:key="index"
class="p-12px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
>
<div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">执行器 {{ index + 1 }}</span>
<el-tag :type="getActionTypeTag(action.type)" size="small">
{{ getActionTypeName(action.type) }}
</el-tag>
</div>
<div class="pl-24px">
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
{{ getActionSummary(action) }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ActionFormData, IotRuleSceneActionTypeEnum } from '@/api/iot/rule/scene/scene.types'
/** 执行器预览组件 */
defineOptions({ name: 'ActionPreview' })
interface Props {
actions: ActionFormData[]
}
const props = defineProps<Props>()
// 执行器类型映射
const actionTypeNames = {
[IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: '属性设置',
[IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
[IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: '触发告警',
[IotRuleSceneActionTypeEnum.ALERT_RECOVER]: '恢复告警'
}
const actionTypeTags = {
[IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
[IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
[IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'danger',
[IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning'
}
// 工具函数
const getActionTypeName = (type: number) => {
return actionTypeNames[type] || '未知类型'
}
const getActionTypeTag = (type: number) => {
return actionTypeTags[type] || 'info'
}
const getActionSummary = (action: ActionFormData) => {
if (action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER || action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER) {
return `告警配置: ${action.alertConfigId ? `配置ID ${action.alertConfigId}` : '未选择'}`
} else {
const paramsCount = action.params ? Object.keys(action.params).length : 0
return `设备控制: 产品${action.productId || '未选择'} 设备${action.deviceId || '未选择'} (${paramsCount}个参数)`
}
}
</script>

View File

@ -1,37 +0,0 @@
<!-- 配置预览组件 -->
<!-- TODO @puhui999应该暂时不用预览哈 -->
<template>
<div class="w-full">
<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 flex-shrink-0">场景名称</span>
<span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ formData.name || '未设置' }}</span>
</div>
<div class="flex items-start gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">场景状态</span>
<el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
{{ formData.status === 0 ? '启用' : '禁用' }}
</el-tag>
</div>
<div v-if="formData.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">{{ formData.description }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
/** 配置预览组件 */
defineOptions({ name: 'ConfigPreview' })
interface Props {
formData: RuleSceneFormData
}
defineProps<Props>()
</script>

View File

@ -1,226 +0,0 @@
<!-- 下次执行时间预览组件 -->
<template>
<div class="next-execution-preview">
<div class="preview-header">
<Icon icon="ep:timer" class="preview-icon" />
<span class="preview-title">执行时间预览</span>
</div>
<div v-if="isValidCron" class="preview-content">
<div class="current-expression">
<span class="expression-label">CRON表达式</span>
<code class="expression-code">{{ cronExpression }}</code>
</div>
<div class="description">
<span class="description-label">执行规律</span>
<span class="description-text">{{ cronDescription }}</span>
</div>
<div class="next-times">
<span class="times-label">接下来5次执行时间</span>
<div class="times-list">
<div
v-for="(time, index) in nextExecutionTimes"
:key="index"
class="time-item"
>
<Icon icon="ep:clock" class="time-icon" />
<span class="time-text">{{ time }}</span>
</div>
</div>
</div>
</div>
<div v-else class="preview-error">
<el-alert
title="CRON表达式无效"
description="请检查CRON表达式格式是否正确"
type="error"
:closable="false"
show-icon
/>
</div>
</div>
</template>
<script setup lang="ts">
import { validateCronExpression } from '../../utils/validation'
/** 下次执行时间预览组件 */
defineOptions({ name: 'NextExecutionPreview' })
interface Props {
cronExpression?: string
}
const props = defineProps<Props>()
// 计算属性
const isValidCron = computed(() => {
return props.cronExpression ? validateCronExpression(props.cronExpression) : false
})
const cronDescription = computed(() => {
if (!isValidCron.value) return ''
// 简单的CRON描述生成
const parts = props.cronExpression?.split(' ') || []
if (parts.length < 6) return '无法解析'
const [second, minute, hour, day, month, week] = parts
// 生成描述
let description = ''
if (second === '0' && minute === '0' && hour === '12' && day === '*' && month === '*' && week === '?') {
description = '每天中午12点执行'
} else if (second === '0' && minute === '*' && hour === '*' && day === '*' && month === '*' && week === '?') {
description = '每分钟执行一次'
} else if (second === '0' && minute === '0' && hour === '*' && day === '*' && month === '*' && week === '?') {
description = '每小时执行一次'
} else {
description = '按自定义时间规律执行'
}
return description
})
const nextExecutionTimes = computed(() => {
if (!isValidCron.value) return []
// 模拟生成下次执行时间
const now = new Date()
const times = []
for (let i = 1; i <= 5; i++) {
// 这里应该使用真实的CRON解析库来计算
// 暂时生成模拟时间
const nextTime = new Date(now.getTime() + i * 60 * 60 * 1000)
times.push(nextTime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}))
}
return times
})
</script>
<style scoped>
.next-execution-preview {
margin-top: 16px;
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
background: var(--el-fill-color-blank);
}
.preview-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.preview-icon {
color: var(--el-color-primary);
font-size: 16px;
}
.preview-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.preview-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.current-expression {
display: flex;
align-items: center;
gap: 8px;
}
.expression-label {
font-size: 12px;
color: var(--el-text-color-secondary);
min-width: 80px;
}
.expression-code {
font-family: 'Courier New', monospace;
background: var(--el-fill-color-light);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
color: var(--el-color-primary);
}
.description {
display: flex;
align-items: center;
gap: 8px;
}
.description-label {
font-size: 12px;
color: var(--el-text-color-secondary);
min-width: 80px;
}
.description-text {
font-size: 12px;
color: var(--el-text-color-primary);
font-weight: 500;
}
.next-times {
display: flex;
flex-direction: column;
gap: 8px;
}
.times-label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.times-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-left: 12px;
}
.time-item {
display: flex;
align-items: center;
gap: 6px;
}
.time-icon {
color: var(--el-color-success);
font-size: 12px;
}
.time-text {
font-size: 12px;
color: var(--el-text-color-primary);
font-family: 'Courier New', monospace;
}
.preview-error {
padding: 16px;
}
</style>

View File

@ -1,80 +0,0 @@
<!-- 触发器预览组件 -->
<template>
<div class="w-full">
<div v-if="triggers.length === 0" class="text-center py-20px">
<el-text type="info" size="small">暂无触发器配置</el-text>
</div>
<div v-else class="space-y-12px">
<div
v-for="(trigger, index) in triggers"
:key="index"
class="p-12px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
>
<div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:lightning" class="text-[var(--el-color-warning)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发器 {{ index + 1 }}</span>
<el-tag :type="getTriggerTypeTag(trigger.type)" size="small">
{{ getTriggerTypeName(trigger.type) }}
</el-tag>
</div>
<div class="pl-24px">
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
{{ getTriggerSummary(trigger) }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { TriggerFormData, IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
/** 触发器预览组件 */
defineOptions({ name: 'TriggerPreview' })
interface Props {
triggers: TriggerFormData[]
}
const props = defineProps<Props>()
// 触发器类型映射
const triggerTypeNames = {
[IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE]: '设备状态变更',
[IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: '属性上报',
[IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: '事件上报',
[IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
[IotRuleSceneTriggerTypeEnum.TIMER]: '定时触发'
}
const triggerTypeTags = {
[IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE]: 'warning',
[IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: 'primary',
[IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: 'success',
[IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: 'info',
[IotRuleSceneTriggerTypeEnum.TIMER]: 'danger'
}
// 工具函数
const getTriggerTypeName = (type: number) => {
return triggerTypeNames[type] || '未知类型'
}
const getTriggerTypeTag = (type: number) => {
return triggerTypeTags[type] || 'info'
}
const getTriggerSummary = (trigger: TriggerFormData) => {
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
return `定时执行: ${trigger.cronExpression || '未配置'}`
} else if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
return `设备状态变更: 产品${trigger.productId || '未选择'} 设备${trigger.deviceId || '未选择'}`
} else {
const conditionCount = trigger.conditionGroups?.reduce((total, group) => total + (group.conditions?.length || 0), 0) || 0
return `设备监控: 产品${trigger.productId || '未选择'} 设备${trigger.deviceId || '未选择'} (${conditionCount}个条件)`
}
}
</script>

View File

@ -1,120 +0,0 @@
<!-- 验证结果组件 -->
<template>
<div class="validation-result">
<div v-if="!validationResult" class="no-validation">
<el-text type="info" size="small">
<Icon icon="ep:info-filled" />
点击"验证配置"按钮检查规则配置
</el-text>
</div>
<div v-else class="validation-content">
<el-alert
:title="validationResult.valid ? '配置验证通过' : '配置验证失败'"
:description="validationResult.message"
:type="validationResult.valid ? 'success' : 'error'"
:closable="false"
show-icon
>
<template #default>
<div v-if="validationResult.valid" class="success-content">
<p>{{ validationResult.message || '所有配置项验证通过,规则可以正常运行' }}</p>
<div class="success-tips">
<Icon icon="ep:check" class="tip-icon" />
<span class="tip-text">规则配置完整且有效</span>
</div>
</div>
<div v-else class="error-content">
<p>{{ validationResult.message || '配置验证失败,请检查以下问题' }}</p>
<div class="error-tips">
<div class="tip-item">
<Icon icon="ep:warning-filled" class="tip-icon error" />
<span class="tip-text">请确保所有必填项都已配置</span>
</div>
<div class="tip-item">
<Icon icon="ep:warning-filled" class="tip-icon error" />
<span class="tip-text">请检查触发器和执行器配置是否正确</span>
</div>
</div>
</div>
</template>
</el-alert>
</div>
</div>
</template>
<script setup lang="ts">
/** 验证结果组件 */
defineOptions({ name: 'ValidationResult' })
interface Props {
validationResult?: { valid: boolean; message?: string } | null
}
defineProps<Props>()
</script>
<style scoped>
.validation-result {
width: 100%;
}
.no-validation {
text-align: center;
padding: 20px 0;
}
.validation-content {
width: 100%;
}
.success-content,
.error-content {
margin-top: 8px;
}
.success-content p,
.error-content p {
margin: 0 0 8px 0;
font-size: 14px;
line-height: 1.5;
}
.success-tips,
.error-tips {
display: flex;
flex-direction: column;
gap: 4px;
}
.tip-item {
display: flex;
align-items: center;
gap: 6px;
}
.tip-icon {
font-size: 12px;
flex-shrink: 0;
}
.tip-icon:not(.error) {
color: var(--el-color-success);
}
.tip-icon.error {
color: var(--el-color-danger);
}
.tip-text {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.success-tips .tip-text {
color: var(--el-color-success-dark-2);
}
.error-tips .tip-text {
color: var(--el-color-danger-dark-2);
}
</style>

View File

@ -117,7 +117,6 @@ import {
ActionFormData,
IotRuleSceneActionTypeEnum as ActionTypeEnum
} from '@/api/iot/rule/scene/scene.types'
import { createDefaultActionData } from '../../utils/transform'
/** 执行器配置组件 */
defineOptions({ name: 'ActionSection' })
@ -136,6 +135,19 @@ const emit = defineEmits<Emits>()
const actions = useVModel(props, 'actions', emit)
/**
* 创建默认的执行器数据
*/
const createDefaultActionData = (): ActionFormData => {
return {
type: ActionTypeEnum.DEVICE_PROPERTY_SET, // 默认为设备属性设置
productId: undefined,
deviceId: undefined,
params: {},
alertConfigId: undefined
}
}
// 配置常量
const maxActions = 5

View File

@ -1,108 +0,0 @@
<!-- 预览区域组件 -->
<!-- TODO @puhui999是不是不用这个哈 -->
<template>
<el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
<template #header>
<div class="flex items-center justify-between">
<div class="flex items-center gap-8px">
<Icon icon="ep:view" class="text-[var(--el-color-primary)] text-18px" />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">配置预览</span>
</div>
<div class="flex items-center gap-8px">
<el-button type="primary" size="small" @click="handleValidate" :loading="validating">
<Icon icon="ep:check" />
验证配置
</el-button>
</div>
</div>
</template>
<div class="p-0">
<!-- 基础信息预览 -->
<div class="mb-20px">
<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)]">基础信息</span>
</div>
<div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ConfigPreview :form-data="formData" />
</div>
</div>
<!-- 触发器预览 -->
<div class="mb-20px">
<div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:lightning" class="text-[var(--el-color-warning)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发器配置</span>
<el-tag size="small" type="primary">{{ formData.triggers.length }}</el-tag>
</div>
<div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<TriggerPreview :triggers="formData.triggers" />
</div>
</div>
<!-- 执行器预览 -->
<div class="mb-20px">
<div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">执行器配置</span>
<el-tag size="small" type="success">{{ formData.actions.length }}</el-tag>
</div>
<div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ActionPreview :actions="formData.actions" />
</div>
</div>
<!-- 验证结果 -->
<div class="mb-20px">
<div class="flex items-center gap-8px mb-12px">
<Icon icon="ep:circle-check" class="text-[var(--el-color-primary)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">验证结果</span>
</div>
<div class="p-12px bg-[var(--el-fill-color-light)] rounded-6px">
<ValidationResult :validation-result="validationResult" />
</div>
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import ConfigPreview from '../previews/ConfigPreview.vue'
import TriggerPreview from '../previews/TriggerPreview.vue'
import ActionPreview from '../previews/ActionPreview.vue'
import ValidationResult from '../previews/ValidationResult.vue'
import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
/** 预览区域组件 */
defineOptions({ name: 'PreviewSection' })
interface Props {
formData: RuleSceneFormData
validationResult?: { valid: boolean; message?: string } | null
}
interface Emits {
(e: 'validate'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 状态
const validating = ref(false)
// 事件处理
const handleValidate = async () => {
validating.value = true
try {
// 延迟一下模拟验证过程
await new Promise((resolve) => setTimeout(resolve, 500))
emit('validate')
} finally {
validating.value = false
}
}
</script>

View File

@ -119,7 +119,6 @@ import {
TriggerFormData,
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
} from '@/api/iot/rule/scene/scene.types'
import { createDefaultTriggerData } from '../../utils/transform'
/** 触发器配置组件 */
defineOptions({ name: 'TriggerSection' })
@ -138,6 +137,22 @@ const emit = defineEmits<Emits>()
const triggers = useVModel(props, 'triggers', emit)
/**
* 创建默认的触发器数据
*/
const createDefaultTriggerData = (): TriggerFormData => {
return {
type: TriggerTypeEnum.DEVICE_PROPERTY_POST, // 默认为设备属性上报
productId: undefined,
deviceId: undefined,
identifier: undefined,
operator: undefined,
value: undefined,
cronExpression: undefined,
conditionGroups: []
}
}
// 配置常量
const maxTriggers = 5