admin-vue3/src/views/iot/rule/scene/form/RuleSceneForm.vue

340 lines
9.2 KiB
Vue

<template>
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
size="80%"
direction="rtl"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px">
<!-- 基础信息配置 -->
<BasicInfoSection v-model="formData" :rules="formRules" />
<!-- 触发器配置 -->
<TriggerSection v-model:triggers="formData.triggers" />
<!-- 执行器配置 -->
<ActionSection v-model:actions="formData.actions" />
</el-form>
<template #footer>
<div class="drawer-footer">
<el-button :disabled="submitLoading" type="primary" @click="handleSubmit">
<Icon icon="ep:check" />
确 定
</el-button>
<el-button @click="handleClose">
<Icon icon="ep:close" />
取 消
</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue'
import { IotSceneRule } from '@/api/iot/rule/scene'
import { RuleSceneApi } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerTypeEnum,
IotRuleSceneActionTypeEnum,
isDeviceTrigger
} from '@/views/iot/utils/constants'
import { ElMessage } from 'element-plus'
import { CommonStatusEnum } from '@/utils/constants'
/** IoT - */
defineOptions({ name: 'RuleSceneForm' })
/** */
const props = defineProps<{
/** */
modelValue: boolean
/** */
ruleScene?: IotSceneRule
}>()
/** 组件事件定义 */
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}>()
const drawerVisible = useVModel(props, 'modelValue', emit) // 抽屉显示状态
/**
* 创建默认的表单数据
* @returns 默认表单数据对象
*/
const createDefaultFormData = (): IotSceneRule => {
return {
name: '',
description: '',
status: CommonStatusEnum.ENABLE, // 默认启用状态
triggers: [
{
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
productId: undefined,
deviceId: undefined,
identifier: undefined,
operator: undefined,
value: undefined,
cronExpression: undefined,
conditionGroups: [] // 空的条件组数组
}
],
actions: []
}
}
const formRef = ref() // 表单引用
const formData = ref<IotSceneRule>(createDefaultFormData()) // 表单数据
/**
* 触发器校验器
* @param _rule 校验规则(未使用)
* @param value 校验值
* @param callback 回调函数
*/
const validateTriggers = (_rule: any, value: any, callback: any) => {
if (!value || !Array.isArray(value) || value.length === 0) {
callback(new Error('至少需要一个触发器'))
return
}
for (let i = 0; i < value.length; i++) {
const trigger = value[i]
// 校验触发器类型
if (!trigger.type) {
callback(new Error(`触发器 ${i + 1}: 触发器类型不能为空`))
return
}
// 校验设备触发器
if (isDeviceTrigger(trigger.type)) {
if (!trigger.productId) {
callback(new Error(`触发器 ${i + 1}: 产品不能为空`))
return
}
if (!trigger.deviceId) {
callback(new Error(`触发器 ${i + 1}: 设备不能为空`))
return
}
if (!trigger.identifier) {
callback(new Error(`触发器 ${i + 1}: 物模型标识符不能为空`))
return
}
if (!trigger.operator) {
callback(new Error(`触发器 ${i + 1}: 操作符不能为空`))
return
}
if (trigger.value === undefined || trigger.value === null || trigger.value === '') {
callback(new Error(`触发器 ${i + 1}: 参数值不能为空`))
return
}
}
// 校验定时触发器
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
if (!trigger.cronExpression) {
callback(new Error(`触发器 ${i + 1}: CRON表达式不能为空`))
return
}
}
}
callback()
}
/**
* 执行器校验器
* @param _rule 校验规则(未使用)
* @param value 校验值
* @param callback 回调函数
*/
const validateActions = (_rule: any, value: any, callback: any) => {
if (!value || !Array.isArray(value) || value.length === 0) {
callback(new Error('至少需要一个执行器'))
return
}
for (let i = 0; i < value.length; i++) {
const action = value[i]
// 校验执行器类型
if (!action.type) {
callback(new Error(`执行器 ${i + 1}: 执行器类型不能为空`))
return
}
// 校验设备控制执行器
if (
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
) {
if (!action.productId) {
callback(new Error(`执行器 ${i + 1}: 产品不能为空`))
return
}
if (!action.deviceId) {
callback(new Error(`执行器 ${i + 1}: 设备不能为空`))
return
}
// 服务调用需要验证服务标识符
if (action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE) {
if (!action.identifier) {
callback(new Error(`执行器 ${i + 1}: 服务不能为空`))
return
}
}
if (!action.params || Object.keys(action.params).length === 0) {
callback(new Error(`执行器 ${i + 1}: 参数配置不能为空`))
return
}
}
// 校验告警执行器
if (
action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER ||
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER
) {
if (!action.alertConfigId) {
callback(new Error(`执行器 ${i + 1}: 告警配置不能为空`))
return
}
}
}
callback()
}
const formRules = reactive({
// 表单校验规则
name: [
{ required: true, message: '场景名称不能为空', trigger: 'blur' },
{ type: 'string', min: 1, max: 50, message: '场景名称长度应在1-50个字符之间', trigger: 'blur' }
],
status: [
{ required: true, message: '场景状态不能为空', trigger: 'change' },
{
type: 'enum',
enum: [CommonStatusEnum.ENABLE, CommonStatusEnum.DISABLE],
message: '状态值必须为启用或禁用',
trigger: 'change'
}
],
description: [
{ type: 'string', max: 200, message: '场景描述不能超过200个字符', trigger: 'blur' }
],
triggers: [{ required: true, validator: validateTriggers, trigger: 'change' }],
actions: [{ required: true, validator: validateActions, trigger: 'change' }]
})
const submitLoading = ref(false) // 提交加载状态
const isEdit = ref(false) // 是否为编辑模式
// 计算属性:抽屉标题
const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则'))
/**
* 提交表单
*/
const handleSubmit = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
submitLoading.value = true
try {
if (isEdit.value) {
// 更新场景联动规则
await RuleSceneApi.updateRuleScene(formData.value)
ElMessage.success('更新成功')
} else {
// 创建场景联动规则
await RuleSceneApi.createRuleScene(formData.value)
ElMessage.success('创建成功')
}
// 关闭抽屉并触发成功事件
drawerVisible.value = false
emit('success')
} catch (error) {
console.error('保存失败:', error)
ElMessage.error(isEdit.value ? '更新失败' : '创建失败')
} finally {
submitLoading.value = false
}
}
/**
* 处理抽屉关闭事件
*/
const handleClose = () => {
drawerVisible.value = false
}
/**
* 初始化表单数据
*/
const initFormData = () => {
if (props.ruleScene) {
// 编辑模式:数据结构已对齐,直接使用后端数据
isEdit.value = true
formData.value = {
...props.ruleScene,
// 确保触发器数组不为空
triggers: props.ruleScene.triggers?.length
? props.ruleScene.triggers
: [
{
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
productId: undefined,
deviceId: undefined,
identifier: undefined,
operator: undefined,
value: undefined,
cronExpression: undefined,
conditionGroups: []
}
],
// 确保执行器数组不为空
actions: props.ruleScene.actions || []
}
} else {
// 新增模式:使用默认数据
isEdit.value = false
formData.value = createDefaultFormData()
}
}
// 监听抽屉显示
watch(drawerVisible, async (visible) => {
if (visible) {
initFormData()
// 重置表单验证状态
await nextTick()
formRef.value?.clearValidate()
}
})
// 监听编辑数据变化
watch(
() => props.ruleScene,
() => {
if (drawerVisible.value) {
initFormData()
}
},
{ deep: true }
)
</script>