【代码优化】IoT: 场景联动

pull/754/head
puhui999 2025-03-25 16:19:56 +08:00
parent cbdcffb20c
commit 416c7f42ab
10 changed files with 305 additions and 354 deletions

View File

@ -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 })
},

View File

@ -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
}

View File

@ -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}`
})
},

View File

@ -1,4 +1,3 @@
<!-- TODO @puhui999IoT 前缀去掉哈文件名 -->
<!-- IoT 设备选择使用弹窗展示 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">

View File

@ -1,4 +1,3 @@
<!-- TODO @puhui999IoT 前缀去掉哈文件名 -->
<!-- IoT 产品选择使用弹窗展示 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">

View File

@ -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) // 12
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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,