【代码优化】IoT: 场景联动
This commit is contained in:
@@ -1,15 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
import { IotRuleSceneTriggerConfig } from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
// IoT 场景联动 VO
|
||||
export interface RuleSceneVO {
|
||||
id?: number // 场景编号
|
||||
name: string // 场景名称
|
||||
description?: string // 场景描述
|
||||
status: number // 场景状态
|
||||
triggers: IotRuleSceneTriggerConfig[] // 触发器数组
|
||||
actions?: any[] // 执行器数组
|
||||
}
|
||||
import { IotRuleScene } from './scene.types'
|
||||
|
||||
// IoT 场景联动 API
|
||||
export const RuleSceneApi = {
|
||||
@@ -24,12 +14,12 @@ export const RuleSceneApi = {
|
||||
},
|
||||
|
||||
// 新增场景联动
|
||||
createRuleScene: async (data: RuleSceneVO) => {
|
||||
createRuleScene: async (data: IotRuleScene) => {
|
||||
return await request.post({ url: `/iot/rule-scene/create`, data })
|
||||
},
|
||||
|
||||
// 修改场景联动
|
||||
updateRuleScene: async (data: RuleSceneVO) => {
|
||||
updateRuleScene: async (data: IotRuleScene) => {
|
||||
return await request.put({ url: `/iot/rule-scene/update`, data })
|
||||
},
|
||||
|
||||
|
||||
@@ -1,222 +1,131 @@
|
||||
/**
|
||||
* 场景规则触发器配置
|
||||
* IoT 场景联动接口定义
|
||||
*/
|
||||
export interface IotRuleSceneTriggerConfig {
|
||||
/**
|
||||
* 触发类型
|
||||
* - 1: 设备触发
|
||||
* - 2: 定时触发
|
||||
*/
|
||||
type: number
|
||||
/** 产品标识 */
|
||||
productKey: string
|
||||
/** 设备名称数组 */
|
||||
deviceNames: string[]
|
||||
/** 触发条件数组。条件之间是"或"的关系 */
|
||||
conditions: IotRuleSceneTriggerCondition[]
|
||||
/** CRON 表达式。当 type = 2 时必填 */
|
||||
cronExpression?: string
|
||||
|
||||
// 枚举定义
|
||||
const IotRuleSceneTriggerTypeEnum = {
|
||||
DEVICE: 1, // 设备触发
|
||||
TIMER: 2 // 定时触发
|
||||
} as const
|
||||
|
||||
const IotRuleSceneActionTypeEnum = {
|
||||
DEVICE_CONTROL: 1, // 设备执行
|
||||
ALERT: 2, // 告警执行
|
||||
DATA_BRIDGE: 3 // 桥接执行
|
||||
} as const
|
||||
|
||||
const IotDeviceMessageTypeEnum = {
|
||||
PROPERTY: 'property', // 属性
|
||||
SERVICE: 'service', // 服务
|
||||
EVENT: 'event' // 事件
|
||||
} as const
|
||||
|
||||
const IotDeviceMessageIdentifierEnum = {
|
||||
PROPERTY_SET: 'set', // 属性设置
|
||||
SERVICE_INVOKE: '${identifier}' // 服务调用
|
||||
} as const
|
||||
|
||||
const IotRuleSceneTriggerConditionParameterOperatorEnum = {
|
||||
EQUALS: { name: '等于', value: '=' }, // 等于
|
||||
NOT_EQUALS: { name: '不等于', value: '!=' }, // 不等于
|
||||
GREATER_THAN: { name: '大于', value: '>' }, // 大于
|
||||
GREATER_THAN_OR_EQUALS: { name: '大于等于', value: '>=' }, // 大于等于
|
||||
LESS_THAN: { name: '小于', value: '<' }, // 小于
|
||||
LESS_THAN_OR_EQUALS: { name: '小于等于', value: '<=' }, // 小于等于
|
||||
IN: { name: '在...之中', value: 'in' }, // 在...之中
|
||||
NOT_IN: { name: '不在...之中', value: 'not in' }, // 不在...之中
|
||||
BETWEEN: { name: '在...之间', value: 'between' }, // 在...之间
|
||||
NOT_BETWEEN: { name: '不在...之间', value: 'not between' }, // 不在...之间
|
||||
LIKE: { name: '字符串匹配', value: 'like' }, // 字符串匹配
|
||||
NOT_NULL: { name: '非空', value: 'not null' } // 非空
|
||||
} as const
|
||||
|
||||
const IotAlertConfigReceiveTypeEnum = {
|
||||
SMS: 1, // 短信
|
||||
MAIL: 2, // 邮箱
|
||||
NOTIFY: 3 // 通知
|
||||
} as const
|
||||
|
||||
// 基础接口
|
||||
interface TenantBaseDO {
|
||||
createTime?: Date // 创建时间
|
||||
updateTime?: Date // 更新时间
|
||||
creator?: string // 创建者
|
||||
updater?: string // 更新者
|
||||
deleted?: boolean // 是否删除
|
||||
tenantId?: number // 租户编号
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发条件
|
||||
*/
|
||||
export interface IotRuleSceneTriggerCondition {
|
||||
/**
|
||||
* 消息类型
|
||||
* - property: 属性上报
|
||||
* - event: 事件上报
|
||||
*/
|
||||
type: string
|
||||
/** 消息标识符 */
|
||||
identifier?: string
|
||||
/** 参数数组。参数之间是"或"的关系 */
|
||||
parameters: IotRuleSceneTriggerConditionParameter[]
|
||||
// 触发条件参数
|
||||
interface TriggerConditionParameter {
|
||||
identifier: string // 标识符(属性、事件、服务)
|
||||
operator: string // 操作符
|
||||
value: string // 比较值
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发条件参数
|
||||
*/
|
||||
export interface IotRuleSceneTriggerConditionParameter {
|
||||
/** 标识符(属性、事件、服务) */
|
||||
identifier: string
|
||||
/**
|
||||
* 操作符
|
||||
*/
|
||||
operator: string
|
||||
/**
|
||||
* 比较值
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
|
||||
*/
|
||||
value: string
|
||||
// 触发条件
|
||||
interface TriggerCondition {
|
||||
type: string // 消息类型
|
||||
identifier: string // 消息标识符
|
||||
parameters: TriggerConditionParameter[] // 参数数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行器配置
|
||||
*/
|
||||
export interface IotRuleSceneActionConfig {
|
||||
/**
|
||||
* 执行类型
|
||||
* - 1: 设备控制
|
||||
* - 2: 数据桥接
|
||||
*/
|
||||
type: number
|
||||
/** 设备控制配置。当 type = 1 时必填 */
|
||||
deviceControl?: IotRuleSceneActionDeviceControl
|
||||
/** 数据桥接编号。当 type = 2 时必填 */
|
||||
dataBridgeId?: number
|
||||
// 触发器配置
|
||||
interface TriggerConfig {
|
||||
type: number // 触发类型
|
||||
productKey: string // 产品标识
|
||||
deviceNames: string[] // 设备名称数组
|
||||
conditions?: TriggerCondition[] // 触发条件数组
|
||||
cronExpression?: string // CRON 表达式
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行设备控制
|
||||
*/
|
||||
export interface IotRuleSceneActionDeviceControl {
|
||||
/** 产品标识 */
|
||||
productKey: string
|
||||
/** 设备名称数组 */
|
||||
deviceNames: string[]
|
||||
/**
|
||||
* 消息类型
|
||||
* - property: 属性
|
||||
* - service: 服务
|
||||
*/
|
||||
type: string
|
||||
/**
|
||||
* 消息标识符
|
||||
* - property_set: 属性设置
|
||||
* - service_invoke: 服务调用
|
||||
*/
|
||||
identifier: string
|
||||
/** 具体数据 */
|
||||
data: Record<string, any>
|
||||
// 执行设备控制
|
||||
interface ActionDeviceControl {
|
||||
productKey: string // 产品标识
|
||||
deviceNames: string[] // 设备名称数组
|
||||
type: string // 消息类型
|
||||
identifier: string // 消息标识符
|
||||
data: Record<string, any> // 具体数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则创建/更新请求
|
||||
*/
|
||||
export interface IotRuleSceneSaveReqVO {
|
||||
/** 场景规则编号 */
|
||||
id?: number
|
||||
/** 场景规则名称 */
|
||||
name: string
|
||||
/** 场景规则状态(0=禁用 1=启用) */
|
||||
status: number
|
||||
/** 触发器配置 */
|
||||
triggerConfig: IotRuleSceneTriggerConfig
|
||||
/** 执行动作配置数组 */
|
||||
actionConfigs: IotRuleSceneActionConfig[]
|
||||
/** 备注 */
|
||||
remark?: string
|
||||
// 告警执行配置
|
||||
interface ActionAlert {
|
||||
receiveType: number // 接收方式
|
||||
phoneNumbers?: string[] // 手机号列表
|
||||
emails?: string[] // 邮箱列表
|
||||
content: string // 通知内容
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则响应
|
||||
*/
|
||||
export interface IotRuleSceneRespVO {
|
||||
/** 场景规则编号 */
|
||||
id: number
|
||||
/** 场景规则名称 */
|
||||
name: string
|
||||
/** 场景规则状态(0=禁用 1=启用) */
|
||||
status: number
|
||||
/** 触发器配置 */
|
||||
triggerConfig: IotRuleSceneTriggerConfig
|
||||
/** 执行动作配置数组 */
|
||||
actionConfigs: IotRuleSceneActionConfig[]
|
||||
/** 备注 */
|
||||
remark?: string
|
||||
/** 创建时间 */
|
||||
createTime: Date
|
||||
// 执行器配置
|
||||
interface ActionConfig {
|
||||
type: number // 执行类型
|
||||
deviceControl?: ActionDeviceControl // 设备控制
|
||||
alert?: ActionAlert // 告警执行
|
||||
dataBridgeId?: number // 数据桥接编号
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则分页项
|
||||
*/
|
||||
export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
|
||||
/** 触发次数 */
|
||||
triggerCount: number
|
||||
/** 最后触发时间 */
|
||||
lastTriggerTime?: Date
|
||||
// 主接口
|
||||
interface IotRuleScene extends TenantBaseDO {
|
||||
id: number // 场景编号
|
||||
name: string // 场景名称
|
||||
description: string // 场景描述
|
||||
status: number // 场景状态
|
||||
triggers: TriggerConfig[] // 触发器数组
|
||||
actions: ActionConfig[] // 执行器数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则分页请求
|
||||
*/
|
||||
export interface IotRuleScenePageReqVO {
|
||||
/** 场景规则名称 */
|
||||
name?: string
|
||||
/** 场景规则状态(0=禁用 1=启用) */
|
||||
status?: number
|
||||
/** 创建时间 */
|
||||
createTime?: [Date, Date]
|
||||
/** 页码 */
|
||||
pageNo?: number
|
||||
/** 每页条数 */
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则类型枚举
|
||||
*/
|
||||
export enum IotRuleSceneTriggerTypeEnum {
|
||||
/** 设备触发 */
|
||||
DEVICE = 1,
|
||||
/** 定时触发 */
|
||||
TIMER = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则动作类型枚举
|
||||
*/
|
||||
export enum IotRuleSceneActionTypeEnum {
|
||||
/** 设备控制 */
|
||||
DEVICE_CONTROL = 1,
|
||||
/** 数据桥接 */
|
||||
DATA_BRIDGE = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备消息类型枚举
|
||||
*/
|
||||
export enum IotDeviceMessageTypeEnum {
|
||||
/** 属性 */
|
||||
PROPERTY = 'property',
|
||||
/** 事件 */
|
||||
EVENT = 'event',
|
||||
/** 服务 */
|
||||
SERVICE = 'service'
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备消息标识符枚举
|
||||
*/
|
||||
export enum IotDeviceMessageIdentifierEnum {
|
||||
/** 属性上报 */
|
||||
PROPERTY_REPORT = 'property_report',
|
||||
/** 属性设置 */
|
||||
PROPERTY_SET = 'property_set',
|
||||
/** 事件上报 */
|
||||
EVENT_REPORT = 'event_report',
|
||||
/** 服务调用 */
|
||||
SERVICE_INVOKE = 'service_invoke'
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发条件参数操作符枚举
|
||||
*/
|
||||
export enum IotRuleSceneTriggerConditionParameterOperatorEnum {
|
||||
/** 等于 */
|
||||
EQ = 'eq',
|
||||
/** 大于 */
|
||||
GT = 'gt',
|
||||
/** 大于等于 */
|
||||
GTE = 'gte',
|
||||
/** 小于 */
|
||||
LT = 'lt',
|
||||
/** 小于等于 */
|
||||
LTE = 'lte',
|
||||
/** 范围 */
|
||||
BETWEEN = 'between',
|
||||
/** 在列表中 */
|
||||
IN = 'in'
|
||||
export {
|
||||
IotRuleScene,
|
||||
TriggerConfig,
|
||||
TriggerCondition,
|
||||
TriggerConditionParameter,
|
||||
ActionConfig,
|
||||
ActionDeviceControl,
|
||||
ActionAlert,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum,
|
||||
IotAlertConfigReceiveTypeEnum
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const ThingModelApi = {
|
||||
// 获得产品物模型 TSL
|
||||
getThingModelTSLByProductId: async (productId: number) => {
|
||||
return await request.get({
|
||||
url: `/iot/thing-model/tsl-by-product-id?productId=${productId}`
|
||||
url: `/iot/thing-model/get-tsl?productId=${productId}`
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- TODO @puhui999:IoT 前缀去掉哈,文件名 -->
|
||||
<!-- IoT 设备选择,使用弹窗展示 -->
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- TODO @puhui999:IoT 前缀去掉哈,文件名 -->
|
||||
<!-- IoT 产品选择,使用弹窗展示 -->
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">
|
||||
@@ -40,13 +40,9 @@
|
||||
@update:model-value="(val) => (formData.triggers[index] = val)"
|
||||
class="mb-10px"
|
||||
>
|
||||
<el-button
|
||||
type="danger"
|
||||
round
|
||||
:icon="Delete"
|
||||
size="small"
|
||||
@click="removeTrigger(index)"
|
||||
/>
|
||||
<el-button type="danger" round size="small" @click="removeTrigger(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</device-listener>
|
||||
<!-- TODO @puhui999:可以使用 el-button,然后选个合适的样式哇 -->
|
||||
<el-text class="ml-10px!" type="primary" @click="addTrigger">添加触发器</el-text>
|
||||
@@ -67,14 +63,19 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
|
||||
import { RuleSceneApi } from '@/api/iot/rule/scene'
|
||||
import DeviceListener from './components/DeviceListener.vue'
|
||||
// TODO @puhui999:尽量用 icon 组件哈,项目里的
|
||||
import { Delete } from '@element-plus/icons-vue'
|
||||
import { IotRuleSceneTriggerConfig } from '@/api/iot/rule/scene/scene.types'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import {
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
IotRuleScene,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** IoT 规则场景(场景联动) 表单 */
|
||||
defineOptions({ name: 'RuleSceneForm' })
|
||||
/** IoT 场景联动表单 */
|
||||
defineOptions({ name: 'IotRuleSceneForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
@@ -83,10 +84,10 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<RuleSceneVO>({
|
||||
status: 0, // TODO @puhui999:使用枚举值
|
||||
triggers: [] as IotRuleSceneTriggerConfig[]
|
||||
} as RuleSceneVO)
|
||||
const formData = ref<IotRuleScene>({
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
triggers: [] as TriggerConfig[]
|
||||
} as IotRuleScene)
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '场景名称不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '场景状态不能为空', trigger: 'blur' }],
|
||||
@@ -98,12 +99,13 @@ const formRef = ref() // 表单 Ref
|
||||
/** 添加触发器 */
|
||||
const addTrigger = () => {
|
||||
formData.value.triggers.push({
|
||||
type: 1, // TODO @puhui999:使用枚举值
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE,
|
||||
productKey: '',
|
||||
deviceNames: [],
|
||||
conditions: [
|
||||
{
|
||||
type: 'property',
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
parameters: []
|
||||
}
|
||||
]
|
||||
@@ -142,7 +144,7 @@ const submitForm = async () => {
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as RuleSceneVO
|
||||
const data = formData.value as unknown as IotRuleScene
|
||||
if (formType.value === 'create') {
|
||||
await RuleSceneApi.createRuleScene(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
@@ -161,9 +163,9 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
status: 0, // TODO @puhui999:使用枚举值
|
||||
triggers: [] as IotRuleSceneTriggerConfig[]
|
||||
} as RuleSceneVO
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
triggers: [] as TriggerConfig[]
|
||||
} as IotRuleScene
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -1,29 +1,18 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-model="selectedOperator"
|
||||
class="condition-selector"
|
||||
clearable
|
||||
:placeholder="placeholder"
|
||||
>
|
||||
<!-- TODO puhui999: 考虑根据属性类型不同展示不同的可选条件 -->
|
||||
<!-- TODO @puhui999:可以在 scene.types.ts IotRuleSceneTriggerConditionParameterOperatorEnum 枚举下 -->
|
||||
<el-option label="等于" value="=" />
|
||||
<el-option label="不等于" value="!=" />
|
||||
<el-option label="大于" value=">" />
|
||||
<el-option label="大于等于" value=">=" />
|
||||
<el-option label="小于" value="<" />
|
||||
<el-option label="小于等于" value="<=" />
|
||||
<el-option label="在列表中" value="in" />
|
||||
<el-option label="不在列表中" value="not in" />
|
||||
<el-option label="在范围内" value="between" />
|
||||
<el-option label="不在范围内" value="not between" />
|
||||
<el-option label="包含" value="like" />
|
||||
<el-option label="非空" value="not null" />
|
||||
<el-select v-model="selectedOperator" class="w-1/1" clearable :placeholder="placeholder">
|
||||
<!-- 根据属性类型展示不同的可选条件 -->
|
||||
<el-option
|
||||
v-for="(item, key) in filteredOperators"
|
||||
:key="key"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { IotRuleSceneTriggerConditionParameterOperatorEnum } from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 条件选择器 */
|
||||
defineOptions({ name: 'ConditionSelector' })
|
||||
@@ -35,6 +24,10 @@ const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
dataType: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -44,11 +37,70 @@ const selectedOperator = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- TODO @puhui999:尽量用 unocss -->
|
||||
<style scoped>
|
||||
.condition-selector {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
// 根据数据类型过滤可用的操作符
|
||||
const filteredOperators = computed(() => {
|
||||
// 如果没有指定数据类型,返回所有操作符
|
||||
if (!props.dataType) {
|
||||
return IotRuleSceneTriggerConditionParameterOperatorEnum
|
||||
}
|
||||
|
||||
const operatorMap = new Map()
|
||||
|
||||
// 添加通用的操作符(所有类型都有非空操作符)
|
||||
operatorMap.set('NOT_NULL', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL)
|
||||
|
||||
// 根据数据类型添加特定的操作符
|
||||
switch (props.dataType) {
|
||||
case 'int':
|
||||
case 'float':
|
||||
case 'double':
|
||||
// 数值类型支持的所有操作符
|
||||
operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
|
||||
operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
|
||||
operatorMap.set('GREATER_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN)
|
||||
operatorMap.set('GREATER_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS)
|
||||
operatorMap.set('LESS_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN)
|
||||
operatorMap.set('LESS_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS)
|
||||
operatorMap.set('IN', IotRuleSceneTriggerConditionParameterOperatorEnum.IN)
|
||||
operatorMap.set('NOT_IN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN)
|
||||
operatorMap.set('BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN)
|
||||
operatorMap.set('NOT_BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)
|
||||
break
|
||||
case 'enum':
|
||||
// 枚举类型支持的操作符
|
||||
operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
|
||||
operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
|
||||
operatorMap.set('IN', IotRuleSceneTriggerConditionParameterOperatorEnum.IN)
|
||||
operatorMap.set('NOT_IN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN)
|
||||
break
|
||||
case 'bool':
|
||||
// 布尔类型支持的操作符
|
||||
operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
|
||||
operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
|
||||
break
|
||||
case 'text':
|
||||
// 文本类型支持的操作符
|
||||
operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
|
||||
operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
|
||||
operatorMap.set('LIKE', IotRuleSceneTriggerConditionParameterOperatorEnum.LIKE)
|
||||
break
|
||||
case 'date':
|
||||
// 日期类型支持的操作符
|
||||
operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
|
||||
operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
|
||||
operatorMap.set('GREATER_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN)
|
||||
operatorMap.set('GREATER_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS)
|
||||
operatorMap.set('LESS_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN)
|
||||
operatorMap.set('LESS_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS)
|
||||
operatorMap.set('BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN)
|
||||
operatorMap.set('NOT_BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)
|
||||
break
|
||||
// struct 和 array 类型只支持非空操作符,已在通用部分添加
|
||||
default:
|
||||
return IotRuleSceneTriggerConditionParameterOperatorEnum
|
||||
}
|
||||
|
||||
return Object.fromEntries(operatorMap)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="device-listener m-10px">
|
||||
<div class="device-listener-header h-50px flex items-center px-10px">
|
||||
<div class="m-10px">
|
||||
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">触发条件</span>
|
||||
<el-select
|
||||
@@ -20,8 +20,7 @@
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">产品</span>
|
||||
<el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
|
||||
<!-- TODO @puhui999:最终最好是,product ? product.name : '选择产品',减少取反 -->
|
||||
{{ !product ? '选择产品' : product.name }}
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="flex items-center mr-60px">
|
||||
@@ -31,7 +30,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 删除触发器 -->
|
||||
<div class="device-listener-delete">
|
||||
<div class="absolute top-auto right-16px bottom-auto">
|
||||
<el-tooltip content="删除触发器" placement="top">
|
||||
<slot></slot>
|
||||
</el-tooltip>
|
||||
@@ -39,7 +38,7 @@
|
||||
</div>
|
||||
<!-- 触发器条件 -->
|
||||
<div
|
||||
class="device-listener-condition flex p-10px"
|
||||
class="bg-[#dbe5f6] flex p-10px"
|
||||
v-for="(condition, index) in triggerConfig.conditions"
|
||||
:key="index"
|
||||
>
|
||||
@@ -51,15 +50,9 @@
|
||||
clearable
|
||||
placeholder=""
|
||||
>
|
||||
<!-- <el-option-->
|
||||
<!-- v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"-->
|
||||
<!-- :key="dict.value"-->
|
||||
<!-- :label="dict.label"-->
|
||||
<!-- :value="dict.value"-->
|
||||
<!-- />-->
|
||||
<el-option label="属性" value="property" />
|
||||
<el-option label="服务" value="service" />
|
||||
<el-option label="事件" value="event" />
|
||||
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
|
||||
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="">
|
||||
@@ -73,13 +66,13 @@
|
||||
>
|
||||
<el-tooltip content="删除参数" placement="top">
|
||||
<el-button
|
||||
class="device-listener-delete"
|
||||
type="danger"
|
||||
circle
|
||||
:icon="Delete"
|
||||
size="small"
|
||||
@click="removeConditionParameter(condition.parameters, index2)"
|
||||
/>
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</DeviceListenerCondition>
|
||||
</div>
|
||||
@@ -89,10 +82,11 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
:icon="Plus"
|
||||
size="small"
|
||||
@click="addConditionParameter(condition.parameters)"
|
||||
/>
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 删除条件 -->
|
||||
@@ -100,7 +94,9 @@
|
||||
class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
|
||||
>
|
||||
<el-tooltip content="删除条件" placement="top">
|
||||
<el-button type="danger" :icon="Delete" size="small" @click="removeCondition(index)" />
|
||||
<el-button type="danger" size="small" @click="removeCondition(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,8 +104,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 产品、设备的选择 -->
|
||||
<IoTProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<IoTDeviceTableSelect
|
||||
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<DeviceTableSelect
|
||||
ref="deviceTableSelectRef"
|
||||
multiple
|
||||
:product-id="product?.id"
|
||||
@@ -118,61 +114,63 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Delete, Plus } from '@element-plus/icons-vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import DeviceListenerCondition from './DeviceListenerCondition.vue'
|
||||
import IoTProductTableSelect from '@/views/iot/product/product/components/IoTProductTableSelect.vue'
|
||||
import IoTDeviceTableSelect from '@/views/iot/device/device/components/IoTDeviceTableSelect.vue'
|
||||
import {
|
||||
IotRuleSceneTriggerCondition,
|
||||
IotRuleSceneTriggerConditionParameter,
|
||||
IotRuleSceneTriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceVO } from '@/api/iot/device/device'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import DeviceListenerCondition from './DeviceListenerCondition.vue'
|
||||
import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
|
||||
import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
|
||||
import { ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceVO } from '@/api/iot/device/device'
|
||||
import { ThingModelApi } from '@/api/iot/thingmodel'
|
||||
import {
|
||||
IotDeviceMessageIdentifierEnum,
|
||||
IotDeviceMessageTypeEnum,
|
||||
TriggerCondition,
|
||||
TriggerConditionParameter,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** 场景联动之监听器组件 */
|
||||
defineOptions({ name: 'DeviceListener' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<IotRuleSceneTriggerConfig>
|
||||
const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 添加触发条件 */
|
||||
const addCondition = () => {
|
||||
triggerConfig.value.conditions.push({
|
||||
type: 'property',
|
||||
triggerConfig.value.conditions?.push({
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
parameters: []
|
||||
})
|
||||
}
|
||||
/** 移除触发条件 */
|
||||
const removeCondition = (index: number) => {
|
||||
triggerConfig.value.conditions.splice(index, 1)
|
||||
triggerConfig.value.conditions?.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加参数 */
|
||||
const addConditionParameter = (conditionParameters: IotRuleSceneTriggerConditionParameter[]) => {
|
||||
const addConditionParameter = (conditionParameters: TriggerConditionParameter[]) => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
conditionParameters.push({} as IotRuleSceneTriggerConditionParameter)
|
||||
conditionParameters.push({} as TriggerConditionParameter)
|
||||
}
|
||||
/** 移除参数 */
|
||||
const removeConditionParameter = (
|
||||
conditionParameters: IotRuleSceneTriggerConditionParameter[],
|
||||
conditionParameters: TriggerConditionParameter[],
|
||||
index: number
|
||||
) => {
|
||||
conditionParameters.splice(index, 1)
|
||||
}
|
||||
|
||||
const productTableSelectRef = ref<InstanceType<typeof IoTProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof IoTDeviceTableSelect>>()
|
||||
const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
|
||||
const product = ref<ProductVO>()
|
||||
const deviceList = ref<DeviceVO[]>([])
|
||||
/** 处理产品选择 */
|
||||
@@ -198,15 +196,14 @@ const openDeviceSelect = () => {
|
||||
|
||||
/** 获取产品物模型 */
|
||||
const thingModelTSL = ref<any>()
|
||||
const thingModels = computed(() => (condition: IotRuleSceneTriggerCondition) => {
|
||||
// TODO @puhui999:这里最好也用枚举
|
||||
const thingModels = computed(() => (condition: TriggerCondition) => {
|
||||
switch (condition.type) {
|
||||
case 'property':
|
||||
case IotDeviceMessageTypeEnum.PROPERTY:
|
||||
return thingModelTSL.value.properties
|
||||
// TODO puhui999: 服务和事件后续考虑
|
||||
case 'service':
|
||||
case IotDeviceMessageTypeEnum.SERVICE:
|
||||
return thingModelTSL.value.services
|
||||
case 'event':
|
||||
case IotDeviceMessageTypeEnum.EVENT:
|
||||
return thingModelTSL.value.events
|
||||
}
|
||||
return []
|
||||
@@ -218,24 +215,3 @@ const getThingModelTSL = async () => {
|
||||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product.value.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- TODO @puhui999:建议使用 unocss 哈 -->
|
||||
<style lang="scss" scoped>
|
||||
.device-listener {
|
||||
.device-listener-header {
|
||||
position: relative;
|
||||
background-color: #eff3f7;
|
||||
|
||||
.device-listener-delete {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
right: 16px;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.device-listener-condition {
|
||||
background-color: #dbe5f6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,10 +13,22 @@
|
||||
:value="thingModel.identifier"
|
||||
/>
|
||||
</el-select>
|
||||
<ConditionSelector v-model="conditionParameter.operator" class="!w-180px mr-10px" />
|
||||
<ConditionSelector
|
||||
v-model="conditionParameter.operator"
|
||||
:data-type="getDataType"
|
||||
class="!w-180px mr-10px"
|
||||
/>
|
||||
<!-- TODO puhui999: 输入值范围校验? -->
|
||||
<el-input v-model="conditionParameter.value" class="!w-240px mr-10px" placeholder="请输入值">
|
||||
<template #append> {{ getUnitName }} </template>
|
||||
<el-input
|
||||
v-if="
|
||||
conditionParameter.operator !==
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value
|
||||
"
|
||||
v-model="conditionParameter.value"
|
||||
class="!w-240px mr-10px"
|
||||
placeholder="请输入值"
|
||||
>
|
||||
<template v-if="getUnitName" #append> {{ getUnitName }} </template>
|
||||
</el-input>
|
||||
<!-- 按钮插槽 -->
|
||||
<slot></slot>
|
||||
@@ -25,18 +37,28 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ConditionSelector from './ConditionSelector.vue'
|
||||
import { IotRuleSceneTriggerConditionParameter } from '@/api/iot/rule/scene/scene.types'
|
||||
import {
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum,
|
||||
TriggerConditionParameter
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
defineOptions({ name: 'DeviceListenerCondition' })
|
||||
const props = defineProps<{ modelValue: any; thingModels: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const conditionParameter = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits
|
||||
) as Ref<IotRuleSceneTriggerConditionParameter>
|
||||
const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter>
|
||||
|
||||
/** 获得物模型属性类型 */
|
||||
const getDataType = computed(() => {
|
||||
const model = props.thingModels?.find(
|
||||
(item: any) => item.identifier === conditionParameter.value.identifier
|
||||
)
|
||||
// 属性
|
||||
if (model?.dataSpecs) {
|
||||
return model.dataSpecs.dataType
|
||||
}
|
||||
return ''
|
||||
})
|
||||
/** 获得属性单位 */
|
||||
const getUnitName = computed(() => {
|
||||
const model = props.thingModels?.find(
|
||||
@@ -46,11 +68,12 @@ const getUnitName = computed(() => {
|
||||
if (model?.dataSpecs) {
|
||||
return model.dataSpecs.unitName
|
||||
}
|
||||
// TODO puhui999: 先不考虑服务和事件的情况
|
||||
// 服务和事件
|
||||
// if (model?.outputParams) {
|
||||
// return model.dataSpecs.unitName
|
||||
// }
|
||||
return '单位'
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -110,23 +110,24 @@
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<IoTRuleSceneForm ref="formRef" @success="getList" />
|
||||
<RuleSceneForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
|
||||
import IoTRuleSceneForm from './IoTRuleSceneForm.vue'
|
||||
import { RuleSceneApi } from '@/api/iot/rule/scene'
|
||||
import RuleSceneForm from './RuleSceneForm.vue'
|
||||
import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
|
||||
|
||||
/** IoT 规则场景(场景联动) 列表 */
|
||||
/** IoT 场景联动 列表 */
|
||||
defineOptions({ name: 'IotRuleScene' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<RuleSceneVO[]>([]) // 列表的数据
|
||||
const list = ref<IotRuleScene[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
|
||||
Reference in New Issue
Block a user