review:【IoT 物联网】场景联动的部分 review

pull/794/MERGE
YunaiV 2025-07-18 10:00:06 +08:00
parent 2fb68bc441
commit a72c297e86
8 changed files with 82 additions and 81 deletions

View File

@ -2,6 +2,7 @@
* IoT
*/
// TODO @puhui999枚举挪到 views/iot/utils/constants.ts 里
// 枚举定义
const IotRuleSceneTriggerTypeEnum = {
DEVICE_STATE_UPDATE: 1, // 设备上下线变更
@ -24,6 +25,7 @@ const IotDeviceMessageTypeEnum = {
EVENT: 'event' // 事件
} as const
// TODO @puhui999这个貌似可以不要
const IotDeviceMessageIdentifierEnum = {
PROPERTY_SET: 'set', // 属性设置
SERVICE_INVOKE: '${identifier}' // 服务调用
@ -44,6 +46,7 @@ const IotRuleSceneTriggerConditionParameterOperatorEnum = {
NOT_NULL: { name: '非空', value: 'not null' } // 非空
} as const
// TODO @puhui999下面 IotAlertConfigReceiveTypeEnum、DeviceStateEnum 没用到,貌似可以删除下?
const IotAlertConfigReceiveTypeEnum = {
SMS: 1, // 短信
MAIL: 2, // 邮箱
@ -57,6 +60,7 @@ const DeviceStateEnum = {
OFFLINE: 2 // 离线
} as const
// TODO @puhui999这个全局已经有啦
// 通用状态枚举
const CommonStatusEnum = {
ENABLE: 0, // 开启
@ -64,6 +68,7 @@ const CommonStatusEnum = {
} as const
// 基础接口
// TODO @puhui999这个貌似可以不要
interface TenantBaseDO {
createTime?: Date // 创建时间
updateTime?: Date // 更新时间
@ -169,6 +174,7 @@ interface IotRuleScene extends TenantBaseDO {
}
// 工具类型
// TODO @puhui999这些在瞅瞅~
type TriggerType = (typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum]
type ActionType = (typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum]
type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum]

View File

@ -1,3 +1,4 @@
// TODO @puhui999:这些后续需要删除哈
# IoT场景联动规则表单设计思路文档
## 概述

View File

@ -1,3 +1,4 @@
// TODO @puhui999:这些后续需要删除哈
# IotThingModelTSLRespVO 数据结构文档
## 概述

View File

@ -1,5 +1,7 @@
// IoT物模型TSL数据类型定义
// TODO @puhui999看看这些里面是不是一些已经有了哈可以复用下~
/** 物模型TSL响应数据结构 */
export interface IotThingModelTSLRespVO {
productId: number

View File

@ -36,6 +36,7 @@
class="!w-240px"
/>
</el-form-item>
<!-- TODO @puhui999字典 -->
<el-form-item label="规则状态">
<el-select
v-model="queryParams.status"
@ -61,6 +62,7 @@
</el-card>
<!-- 统计卡片 -->
<!-- TODO @puhui999这种需要服用的 stats-contentstats-info 的属性到底 unocss 还是现有的 style css ~ -->
<el-row :gutter="16" class="stats-row">
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
@ -124,6 +126,7 @@
<template #default="{ row }">
<div class="rule-name-cell">
<span class="rule-name">{{ row.name }}</span>
<!-- TODO @puhui999字典 -->
<el-tag
:type="row.status === 0 ? 'success' : 'danger'"
size="small"
@ -137,7 +140,6 @@
</div>
</template>
</el-table-column>
<el-table-column label="触发条件" min-width="250">
<template #default="{ row }">
<div class="trigger-summary">
@ -153,7 +155,6 @@
</div>
</template>
</el-table-column>
<el-table-column label="执行动作" min-width="250">
<template #default="{ row }">
<div class="action-summary">
@ -169,7 +170,7 @@
</div>
</template>
</el-table-column>
<!-- TODO @puhui999貌似要新增一个字段 -->
<el-table-column label="最近触发" prop="lastTriggeredTime" width="180">
<template #default="{ row }">
<span v-if="row.lastTriggeredTime">
@ -178,13 +179,11 @@
<span v-else class="text-gray-400">未触发</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180">
<template #default="{ row }">
{{ formatDate(row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<div class="action-buttons">
@ -242,11 +241,7 @@
</div>
<!-- 表单对话框 -->
<RuleSceneForm
v-model="formVisible"
:rule-scene="currentRule"
@success="handleFormSuccess"
/>
<RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="handleFormSuccess" />
</ContentWrap>
</template>
@ -435,6 +430,7 @@ const handleSelectionChange = (selection: IotRuleScene[]) => {
selectedRows.value = selection
}
// TODO @puhui999batch UI
const handleBatchEnable = async () => {
try {
await ElMessageBox.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`, '提示', {

View File

@ -2,6 +2,8 @@
* IoT
*/
// TODO @puhui999这个貌似用不到
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
// 错误类型枚举

View File

@ -1,7 +1,6 @@
/**
* IoT
*/
import {
IotRuleScene,
TriggerConfig,
@ -12,6 +11,8 @@ import {
} from '@/api/iot/rule/scene/scene.types'
import { generateUUID } from '@/utils'
// TODO @puhui999这些是不是放到对应的界面会好一丢丢哈
/**
*
*/
@ -19,7 +20,7 @@ export function createDefaultFormData(): RuleSceneFormData {
return {
name: '',
description: '',
status: 0,
status: 0, // TODO @puhui999枚举值
triggers: [],
actions: []
}
@ -30,7 +31,7 @@ export function createDefaultFormData(): RuleSceneFormData {
*/
export function createDefaultTriggerData(): TriggerFormData {
return {
type: 2, // 默认为属性上报
type: 2, // 默认为属性上报 TODO @puhui999枚举值
productId: undefined,
deviceId: undefined,
identifier: undefined,
@ -46,7 +47,7 @@ export function createDefaultTriggerData(): TriggerFormData {
*/
export function createDefaultActionData(): ActionFormData {
return {
type: 1, // 默认为属性设置
type: 1, // 默认为属性设置 TODO @puhui999枚举值
productId: undefined,
deviceId: undefined,
params: {},
@ -58,7 +59,8 @@ export function createDefaultActionData(): ActionFormData {
* API
*/
export function transformFormToApi(formData: RuleSceneFormData): IotRuleScene {
// 这里需要根据实际API结构进行转换
// TODO @puhui999这个关注下
// 这里需要根据实际 API 结构进行转换
// 暂时返回基本结构
return {
id: formData.id,
@ -71,7 +73,7 @@ export function transformFormToApi(formData: RuleSceneFormData): IotRuleScene {
}
/**
* API
* API
*/
export function transformApiToForm(apiData: IotRuleScene): RuleSceneFormData {
return {
@ -94,6 +96,7 @@ export function transformApiToForm(apiData: IotRuleScene): RuleSceneFormData {
}
}
// TODO @puhui999貌似没用到
/**
*
*/
@ -144,6 +147,7 @@ export function createDefaultTriggerConfig(type?: number): TriggerConfig {
}
}
// TODO @puhui999貌似没用到
/**
*
*/
@ -174,6 +178,7 @@ export function createDefaultActionConfig(type?: number): ActionConfig {
}
}
// TODO @puhui999全局已经有类似的
/**
*
*/
@ -203,6 +208,7 @@ export function deepClone<T>(obj: T): T {
return obj
}
// TODO @puhui999貌似没用到
/**
*
*/
@ -297,6 +303,7 @@ export function formatCronExpression(cron: string): string {
return description || cron
}
// TODO @puhui999貌似没用到
/**
*
*/
@ -351,6 +358,7 @@ export function validateAndFixData(data: IotRuleScene): IotRuleScene {
return fixed
}
// TODO @puhui999貌似没用到
/**
* key
*/
@ -401,6 +409,5 @@ export function getRuleSceneSummary(ruleScene: IotRuleScene): {
return '未知执行类型'
}
}) || []
return { triggerSummary, actionSummary }
}

View File

@ -1,13 +1,18 @@
/**
* IoT
*/
import {
FormValidationRules,
IotRuleScene,
TriggerConfig,
ActionConfig
} from '@/api/iot/rule/scene/scene.types'
import {
IotRuleSceneTriggerTypeEnum,
IotRuleSceneActionTypeEnum
} from '@/api/iot/rule/scene/scene.types'
import { FormValidationRules, IotRuleScene, TriggerConfig, ActionConfig } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneTriggerTypeEnum, IotRuleSceneActionTypeEnum } from '@/api/iot/rule/scene/scene.types'
/**
*
*/
/** 基础表单验证规则 */
export const getBaseValidationRules = (): FormValidationRules => ({
name: [
{ required: true, message: '场景名称不能为空', trigger: 'blur' },
@ -30,55 +35,47 @@ export const getBaseValidationRules = (): FormValidationRules => ({
]
})
/**
* CRON
*/
/** 验证CRON表达式格式 */
export function validateCronExpression(cron: string): boolean {
if (!cron || cron.trim().length === 0) return false
// 基础的CRON表达式正则验证支持6位和7位格式
const cronRegex = /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))( (\*|([1-9][0-9]{3})|\*\/([1-9][0-9]{3})))?$/
// 基础的 CRON 表达式正则验证支持6位和7位格式
const cronRegex =
/^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))( (\*|([1-9][0-9]{3})|\*\/([1-9][0-9]{3})))?$/
return cronRegex.test(cron.trim())
}
/**
*
*/
/** 验证设备名称数组 */
export function validateDeviceNames(deviceNames: string[]): boolean {
return Array.isArray(deviceNames) &&
deviceNames.length > 0 &&
deviceNames.every(name => name && name.trim().length > 0)
return (
Array.isArray(deviceNames) &&
deviceNames.length > 0 &&
deviceNames.every((name) => name && name.trim().length > 0)
)
}
/**
*
*/
/** 验证比较值格式 */
export function validateCompareValue(operator: string, value: string): boolean {
if (!value || value.trim().length === 0) return false
const trimmedValue = value.trim()
switch (operator) {
case 'between':
case 'not between':
const betweenValues = trimmedValue.split(',')
return betweenValues.length === 2 &&
betweenValues.every(v => v.trim().length > 0) &&
!isNaN(Number(betweenValues[0].trim())) &&
!isNaN(Number(betweenValues[1].trim()))
return (
betweenValues.length === 2 &&
betweenValues.every((v) => v.trim().length > 0) &&
!isNaN(Number(betweenValues[0].trim())) &&
!isNaN(Number(betweenValues[1].trim()))
)
case 'in':
case 'not in':
const inValues = trimmedValue.split(',')
return inValues.length > 0 && inValues.every(v => v.trim().length > 0)
return inValues.length > 0 && inValues.every((v) => v.trim().length > 0)
case '>':
case '>=':
case '<':
case '<=':
return !isNaN(Number(trimmedValue))
case '=':
case '!=':
case 'like':
@ -88,14 +85,14 @@ export function validateCompareValue(operator: string, value: string): boolean {
}
}
/**
*
*/
export function validateTriggerConfig(trigger: TriggerConfig): { valid: boolean; message?: string } {
/** 验证触发器配置 */
export function validateTriggerConfig(trigger: TriggerConfig): {
valid: boolean
message?: string
} {
if (!trigger.type) {
return { valid: false, message: '触发类型不能为空' }
}
// 定时触发验证
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
if (!trigger.cronExpression) {
@ -106,32 +103,26 @@ export function validateTriggerConfig(trigger: TriggerConfig): { valid: boolean;
}
return { valid: true }
}
// 设备触发验证
if (!trigger.productKey) {
return { valid: false, message: '产品标识不能为空' }
}
if (!trigger.deviceNames || !validateDeviceNames(trigger.deviceNames)) {
return { valid: false, message: '设备名称不能为空' }
}
// 设备状态变更无需额外条件验证
if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
return { valid: true }
}
// 其他设备触发类型需要验证条件
if (!trigger.conditions || trigger.conditions.length === 0) {
return { valid: false, message: '触发条件不能为空' }
}
// 验证每个条件的参数
for (const condition of trigger.conditions) {
if (!condition.parameters || condition.parameters.length === 0) {
return { valid: false, message: '触发条件参数不能为空' }
}
for (const param of condition.parameters) {
if (!param.operator) {
return { valid: false, message: '操作符不能为空' }
@ -141,34 +132,32 @@ export function validateTriggerConfig(trigger: TriggerConfig): { valid: boolean;
}
}
}
return { valid: true }
}
/**
*
*/
/** 验证执行器配置 */
export function validateActionConfig(action: ActionConfig): { valid: boolean; message?: string } {
if (!action.type) {
return { valid: false, message: '执行类型不能为空' }
}
// 告警触发/恢复验证
if (action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER ||
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER) {
if (
action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER ||
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER
) {
if (!action.alertConfigId) {
return { valid: false, message: '告警配置ID不能为空' }
}
return { valid: true }
}
// 设备控制验证
if (action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE) {
if (
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
) {
if (!action.deviceControl) {
return { valid: false, message: '设备控制配置不能为空' }
}
const { deviceControl } = action
if (!deviceControl.productKey) {
return { valid: false, message: '产品标识不能为空' }
@ -185,34 +174,28 @@ export function validateActionConfig(action: ActionConfig): { valid: boolean; me
if (!deviceControl.params || Object.keys(deviceControl.params).length === 0) {
return { valid: false, message: '参数不能为空' }
}
return { valid: true }
}
return { valid: false, message: '未知的执行类型' }
}
/**
*
*/
// TODO @puhui999貌似没用到
/** 验证完整的场景联动规则 */
export function validateRuleScene(ruleScene: IotRuleScene): { valid: boolean; message?: string } {
// 基础字段验证
if (!ruleScene.name || ruleScene.name.trim().length === 0) {
return { valid: false, message: '场景名称不能为空' }
}
if (ruleScene.status !== 0 && ruleScene.status !== 1) {
return { valid: false, message: '场景状态必须为0或1' }
}
if (!ruleScene.triggers || ruleScene.triggers.length === 0) {
return { valid: false, message: '至少需要一个触发器' }
}
if (!ruleScene.actions || ruleScene.actions.length === 0) {
return { valid: false, message: '至少需要一个执行器' }
}
// 验证每个触发器
for (let i = 0; i < ruleScene.triggers.length; i++) {
const triggerResult = validateTriggerConfig(ruleScene.triggers[i])
@ -220,7 +203,6 @@ export function validateRuleScene(ruleScene: IotRuleScene): { valid: boolean; me
return { valid: false, message: `触发器${i + 1}: ${triggerResult.message}` }
}
}
// 验证每个执行器
for (let i = 0; i < ruleScene.actions.length; i++) {
const actionResult = validateActionConfig(ruleScene.actions[i])
@ -228,14 +210,16 @@ export function validateRuleScene(ruleScene: IotRuleScene): { valid: boolean; me
return { valid: false, message: `执行器${i + 1}: ${actionResult.message}` }
}
}
return { valid: true }
}
// TODO @puhui999下面 getOperatorOptions、getTriggerTypeOptions、getActionTypeOptions 三个貌似没用到?如果用到的话,要不放到 yudao-ui-admin-vue3/src/views/iot/utils/constants.ts 里
/**
*
*/
export function getOperatorOptions() {
// TODO @puhui999这个能不能从枚举计算出来减少后续添加枚举的维护
return [
{ value: '=', label: '等于' },
{ value: '!=', label: '不等于' },
@ -256,6 +240,7 @@ export function getOperatorOptions() {
*
*/
export function getTriggerTypeOptions() {
// TODO @puhui999这个能不能从枚举计算出来减少后续添加枚举的维护
return [
{ value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, label: '设备上下线变更' },
{ value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, label: '物模型属性上报' },
@ -269,6 +254,7 @@ export function getTriggerTypeOptions() {
*
*/
export function getActionTypeOptions() {
// TODO @puhui999这个能不能从枚举计算出来减少后续添加枚举的维护
return [
{ value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, label: '设备属性设置' },
{ value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE, label: '设备服务调用' },