!812 perf: 【IoT 物联网】优化场景联动 review 提到的逻辑

Merge pull request !812 from puhui999/feature/iot
pull/815/MERGE
芋道源码 2025-08-16 12:30:51 +00:00 committed by Gitee
commit 96a9154e52
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
11 changed files with 315 additions and 423 deletions

View File

@ -46,28 +46,28 @@ export interface Action {
export const RuleSceneApi = {
// 查询场景联动分页
getRuleScenePage: async (params: any) => {
return await request.get({ url: `/iot/rule-scene/page`, params })
return await request.get({ url: `/iot/scene-rule/page`, params })
},
// 查询场景联动详情
getRuleScene: async (id: number) => {
return await request.get({ url: `/iot/rule-scene/get?id=` + id })
return await request.get({ url: `/iot/scene-rule/get?id=` + id })
},
// 新增场景联动
createRuleScene: async (data: IotSceneRule) => {
return await request.post({ url: `/iot/rule-scene/create`, data })
return await request.post({ url: `/iot/scene-rule/create`, data })
},
// 修改场景联动
updateRuleScene: async (data: IotSceneRule) => {
return await request.put({ url: `/iot/rule-scene/update`, data })
return await request.put({ url: `/iot/scene-rule/update`, data })
},
// 修改场景联动
updateRuleSceneStatus: async (id: number, status: number) => {
return await request.put({
url: `/iot/rule-scene/update-status`,
url: `/iot/scene-rule/update-status`,
data: {
id,
status
@ -77,11 +77,11 @@ export const RuleSceneApi = {
// 删除场景联动
deleteRuleScene: async (id: number) => {
return await request.delete({ url: `/iot/rule-scene/delete?id=` + id })
return await request.delete({ url: `/iot/scene-rule/delete?id=` + id })
},
// 获取场景联动简单列表
getSimpleRuleSceneList: async () => {
return await request.get({ url: `/iot/rule-scene/simple-list` })
return await request.get({ url: `/iot/scene-rule/simple-list` })
}
}

View File

@ -98,7 +98,7 @@
:isWrite="true"
:clickMap="true"
:center="formData.location"
@locateChange="handleLocationChange"
@locate-change="handleLocationChange"
ref="mapRef"
class="h-full w-full"
/>

View File

@ -63,7 +63,7 @@
class="w-full"
>
<el-option
v-for="option in getStatusOperatorOptions()"
v-for="option in statusOperatorOptions"
:key="option.value"
:label="option.label"
:value="option.value"
@ -82,7 +82,7 @@
class="w-full"
>
<el-option
v-for="option in getDeviceStatusOptions()"
v-for="option in deviceStatusOptions"
:key="option.value"
:label="option.label"
:value="option.value"
@ -163,8 +163,7 @@ import {
IotRuleSceneTriggerConditionTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum,
getConditionTypeOptions,
getDeviceStatusOptions,
getStatusOperatorOptions
IoTDeviceStatusEnum
} from '@/views/iot/utils/constants'
/** 单个条件配置组件 */
@ -179,6 +178,30 @@ const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerCondition): void
}>()
/** 获取设备状态选项 */
const deviceStatusOptions = [
{
value: IoTDeviceStatusEnum.ONLINE.value,
label: IoTDeviceStatusEnum.ONLINE.label
},
{
value: IoTDeviceStatusEnum.OFFLINE.value,
label: IoTDeviceStatusEnum.OFFLINE.label
}
]
/** 获取状态操作符选项 */
const statusOperatorOptions = [
{
value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
},
{
value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name
}
]
const condition = useVModel(props, 'modelValue', emit)
const propertyType = ref<string>('string') //

View File

@ -65,7 +65,6 @@
:model-value="condition.operator"
@update:model-value="(value) => updateConditionField('operator', value)"
:property-type="propertyType"
@change="handleOperatorChange"
/>
</el-form-item>
</el-col>
@ -188,8 +187,8 @@ import {
IotRuleSceneTriggerTypeEnum,
triggerTypeOptions,
getTriggerTypeLabel,
deviceStatusChangeOptions,
IotRuleSceneTriggerConditionParameterOperatorEnum
IotRuleSceneTriggerConditionParameterOperatorEnum,
IoTDeviceStatusEnum
} from '@/views/iot/utils/constants'
import { useVModel } from '@vueuse/core'
@ -206,6 +205,18 @@ const emit = defineEmits<{
(e: 'trigger-type-change', value: number): void
}>()
/** 获取设备状态变更选项(用于触发器配置) */
const deviceStatusChangeOptions = [
{
label: IoTDeviceStatusEnum.ONLINE.label,
value: IoTDeviceStatusEnum.ONLINE.value
},
{
label: IoTDeviceStatusEnum.OFFLINE.label,
value: IoTDeviceStatusEnum.OFFLINE.value
}
]
const condition = useVModel(props, 'modelValue', emit)
const propertyType = ref('') //
const propertyConfig = ref<any>(null) //
@ -326,12 +337,4 @@ const handlePropertyChange = (propertyInfo: any) => {
}
}
}
// TODO @puhui999
/**
* 处理操作符变化事件
*/
const handleOperatorChange = () => {
//
}
</script>

View File

@ -1,148 +1,139 @@
<!-- JSON参数输入组件 - 通用版本 -->
<template>
<div class="w-full min-w-0">
<!-- 参数配置 -->
<div v-if="hasConfig" class="space-y-12px">
<!-- JSON 输入框 -->
<div class="relative">
<el-input
v-model="paramsJson"
type="textarea"
:rows="4"
:placeholder="placeholder"
@input="handleParamsChange"
:class="{ 'is-error': jsonError }"
/>
<!-- 查看详细示例弹出层 -->
<div class="absolute top-8px right-8px">
<el-popover
placement="left-start"
:width="450"
trigger="click"
:show-arrow="true"
:offset="8"
popper-class="json-params-detail-popover"
>
<template #reference>
<el-button
type="info"
:icon="InfoFilled"
circle
size="small"
:title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
/>
</template>
<!-- 参数配置 -->
<div class="w-full space-y-12px">
<!-- JSON 输入框 -->
<div class="relative">
<el-input
v-model="paramsJson"
type="textarea"
:rows="4"
:placeholder="placeholder"
@input="handleParamsChange"
:class="{ 'is-error': jsonError }"
/>
<!-- 查看详细示例弹出层 -->
<div class="absolute top-8px right-8px">
<el-popover
placement="left-start"
:width="450"
trigger="click"
:show-arrow="true"
:offset="8"
popper-class="json-params-detail-popover"
>
<template #reference>
<el-button
type="info"
:icon="InfoFilled"
circle
size="small"
:title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
/>
</template>
<!-- 弹出层内容 -->
<div class="json-params-detail-content">
<div class="flex items-center gap-8px mb-16px">
<Icon :icon="titleIcon" class="text-[var(--el-color-primary)] text-18px" />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">
{{ title }}
</span>
</div>
<!-- 弹出层内容 -->
<div class="json-params-detail-content">
<div class="flex items-center gap-8px mb-16px">
<Icon :icon="titleIcon" class="text-[var(--el-color-primary)] text-18px" />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">
{{ title }}
</span>
</div>
<div class="space-y-16px">
<!-- 参数列表 -->
<div v-if="paramsList.length > 0">
<div class="flex items-center gap-8px mb-8px">
<Icon :icon="paramsIcon" class="text-[var(--el-color-primary)] text-14px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
{{ paramsLabel }}
</span>
</div>
<div class="ml-22px space-y-8px">
<div
v-for="param in paramsList"
:key="param.identifier"
class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
>
<div class="flex-1">
<div class="text-12px font-500 text-[var(--el-text-color-primary)]">
{{ param.name }}
<el-tag v-if="param.required" size="small" type="danger" class="ml-4px">
{{ JSON_PARAMS_INPUT_CONSTANTS.REQUIRED_TAG }}
</el-tag>
</div>
<div class="text-11px text-[var(--el-text-color-secondary)]">
{{ param.identifier }}
</div>
</div>
<div class="flex items-center gap-8px">
<el-tag :type="getParamTypeTag(param.dataType)" size="small">
{{ getParamTypeName(param.dataType) }}
<div class="space-y-16px">
<!-- 参数列表 -->
<div v-if="paramsList.length > 0">
<div class="flex items-center gap-8px mb-8px">
<Icon :icon="paramsIcon" class="text-[var(--el-color-primary)] text-14px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">
{{ paramsLabel }}
</span>
</div>
<div class="ml-22px space-y-8px">
<div
v-for="param in paramsList"
:key="param.identifier"
class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
>
<div class="flex-1">
<div class="text-12px font-500 text-[var(--el-text-color-primary)]">
{{ param.name }}
<el-tag v-if="param.required" size="small" type="danger" class="ml-4px">
{{ JSON_PARAMS_INPUT_CONSTANTS.REQUIRED_TAG }}
</el-tag>
<span class="text-11px text-[var(--el-text-color-secondary)]">
{{ getExampleValue(param) }}
</span>
</div>
<div class="text-11px text-[var(--el-text-color-secondary)]">
{{ param.identifier }}
</div>
</div>
</div>
<div class="mt-12px ml-22px">
<div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
{{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
<div class="flex items-center gap-8px">
<el-tag :type="getParamTypeTag(param.dataType)" size="small">
{{ getParamTypeName(param.dataType) }}
</el-tag>
<span class="text-11px text-[var(--el-text-color-secondary)]">
{{ getExampleValue(param) }}
</span>
</div>
<pre
class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-primary)]"
>
<code>{{ generateExampleJson() }}</code>
</pre>
</div>
</div>
<!-- 无参数提示 -->
<div v-else>
<div class="text-center py-16px">
<p class="text-14px text-[var(--el-text-color-secondary)]">{{
emptyMessage
}}</p>
<div class="mt-12px ml-22px">
<div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
{{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
</div>
<pre
class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-primary)]"
>
<code>{{ generateExampleJson() }}</code>
</pre>
</div>
</div>
<!-- 无参数提示 -->
<div v-else>
<div class="text-center py-16px">
<p class="text-14px text-[var(--el-text-color-secondary)]">{{ emptyMessage }}</p>
</div>
</div>
</div>
</el-popover>
</div>
</div>
<!-- 验证状态和错误提示 -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-8px">
<Icon
:icon="
jsonError
? JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.ERROR
: JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.SUCCESS
"
:class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
class="text-14px"
/>
<span
:class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
class="text-12px"
>
{{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
</span>
</div>
<!-- 快速填充按钮 -->
<div v-if="paramsList.length > 0" class="flex items-center gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)]">{{
JSON_PARAMS_INPUT_CONSTANTS.QUICK_FILL_LABEL
}}</span>
<el-button size="small" type="primary" plain @click="fillExampleJson">
{{ JSON_PARAMS_INPUT_CONSTANTS.EXAMPLE_DATA_BUTTON }}
</el-button>
<el-button size="small" type="danger" plain @click="clearParams">{{
JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
}}</el-button>
</div>
</div>
</el-popover>
</div>
</div>
<!-- 无配置提示 -->
<div v-else class="text-center py-20px">
<p class="text-14px text-[var(--el-text-color-secondary)]">{{ noConfigMessage }}</p>
<!-- 验证状态和错误提示 -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-8px">
<Icon
:icon="
jsonError
? JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.ERROR
: JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.SUCCESS
"
:class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
class="text-14px"
/>
<span
:class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
class="text-12px"
>
{{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
</span>
</div>
<!-- 快速填充按钮 -->
<div v-if="paramsList.length > 0" class="flex items-center gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)]">{{
JSON_PARAMS_INPUT_CONSTANTS.QUICK_FILL_LABEL
}}</span>
<el-button size="small" type="primary" plain @click="fillExampleJson">
{{ JSON_PARAMS_INPUT_CONSTANTS.EXAMPLE_DATA_BUTTON }}
</el-button>
<el-button size="small" type="danger" plain @click="clearParams">{{
JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
}}</el-button>
</div>
</div>
</div>
</template>
@ -162,7 +153,7 @@ import {
/** JSON参数输入组件 - 通用版本 */
defineOptions({ name: 'JsonParamsInput' })
export interface JsonParamsConfig {
interface JsonParamsConfig {
//
service?: {
name: string
@ -207,19 +198,6 @@ const localValue = useVModel(props, 'modelValue', emit, {
const paramsJson = ref('') // JSON
const jsonError = ref('') // JSON
//
const hasConfig = computed(() => {
// TODO @puhui999:
console.log(props.config)
// return !!(
// props.config?.service ||
// props.config?.event ||
// props.config?.properties ||
// props.config?.custom
// )
return true
})
//
const paramsList = computed(() => {
switch (props.type) {

View File

@ -1,5 +1,4 @@
<!-- 执行器配置组件 -->
<!-- TODO @puhui999每个执行器的 UI 风格应该和触发器配置有点像 -->
<template>
<el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
<template #header>
@ -7,15 +6,10 @@
<div class="flex items-center gap-8px">
<Icon icon="ep:setting" class="text-[var(--el-color-primary)] text-18px" />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">执行器配置</span>
<el-tag size="small" type="info">{{ actions.length }}/{{ maxActions }}</el-tag>
<el-tag size="small" type="info">{{ actions.length }} 个执行器</el-tag>
</div>
<div class="flex items-center gap-8px">
<el-button
type="primary"
size="small"
@click="addAction"
:disabled="actions.length >= maxActions"
>
<el-button type="primary" size="small" @click="addAction">
<Icon icon="ep:plus" />
添加执行器
</el-button>
@ -35,27 +29,37 @@
</div>
<!-- 执行器列表 -->
<div v-else class="space-y-16px">
<div v-else class="space-y-24px">
<div
v-for="(action, index) in actions"
:key="`action-${index}`"
class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
class="border-2 border-blue-200 rounded-8px bg-blue-50 shadow-sm hover:shadow-md transition-shadow"
>
<div class="flex items-center justify-between mb-16px">
<div class="flex items-center gap-8px">
<Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
<span>执行器 {{ index + 1 }}</span>
<el-tag :type="getActionTypeTag(action.type)" size="small">
<!-- 执行器头部 - 蓝色主题 -->
<div
class="flex items-center justify-between p-16px bg-gradient-to-r from-blue-50 to-sky-50 border-b border-blue-200 rounded-t-6px"
>
<div class="flex items-center gap-12px">
<div class="flex items-center gap-8px text-16px font-600 text-blue-700">
<div
class="w-24px h-24px bg-blue-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
>
{{ index + 1 }}
</div>
<span>执行器 {{ index + 1 }}</span>
</div>
<el-tag :type="getActionTypeTag(action.type)" size="small" class="font-500">
{{ getActionTypeLabel(action.type) }}
</el-tag>
</div>
<div>
<div class="flex items-center gap-8px">
<el-button
v-if="actions.length > 1"
type="danger"
size="small"
text
@click="removeAction(index)"
v-if="actions.length > 1"
class="hover:bg-red-50"
>
<Icon icon="ep:delete" />
删除
@ -63,7 +67,8 @@
</div>
</div>
<div class="space-y-16px">
<!-- 执行器内容区域 -->
<div class="p-16px space-y-16px">
<!-- 执行类型选择 -->
<div class="w-full">
<el-form-item label="执行类型" required>
@ -93,14 +98,14 @@
<!-- 告警配置 - 只有恢复告警时才显示 -->
<AlertConfig
v-if="action.type === ActionTypeEnum.ALERT_RECOVER"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER"
:model-value="action.alertConfigId"
@update:model-value="(value) => updateActionAlertConfig(index, value)"
/>
<!-- 触发告警提示 - 触发告警时显示 -->
<div
v-if="action.type === ActionTypeEnum.ALERT_TRIGGER"
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
class="border border-[var(--el-border-color-light)] rounded-6px p-16px bg-[var(--el-fill-color-blank)]"
>
<div class="flex items-center gap-8px mb-8px">
@ -117,14 +122,11 @@
</div>
<!-- 添加提示 -->
<div v-if="actions.length > 0 && actions.length < maxActions" class="text-center py-16px">
<div v-if="actions.length > 0" class="text-center py-16px">
<el-button type="primary" plain @click="addAction">
<Icon icon="ep:plus" />
继续添加执行器
</el-button>
<span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]">
最多可添加 {{ maxActions }} 个执行器
</span>
</div>
</div>
</el-card>
@ -136,13 +138,9 @@ import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
import AlertConfig from '../configs/AlertConfig.vue'
import type { Action } from '@/api/iot/rule/scene'
import {
IotRuleSceneActionTypeEnum as ActionTypeEnum,
isDeviceAction,
isAlertAction,
getActionTypeLabel,
getActionTypeOptions,
getActionTypeTag,
SCENE_RULE_CONFIG
IotRuleSceneActionTypeEnum
} from '@/views/iot/utils/constants'
/** 执行器配置组件 */
@ -158,7 +156,34 @@ const emit = defineEmits<{
const actions = useVModel(props, 'actions', emit)
const maxActions = SCENE_RULE_CONFIG.MAX_ACTIONS //
/** 获取执行器标签类型(用于 el-tag 的 type 属性) */
const getActionTypeTag = (type: number): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
const actionTypeTags = {
[IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
[IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
[IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'danger',
[IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning'
} as const
return actionTypeTags[type] || 'info'
}
/** 判断是否为设备执行器类型 */
const isDeviceAction = (type: number): boolean => {
const deviceActionTypes = [
IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
] as number[]
return deviceActionTypes.includes(type)
}
/** 判断是否为告警执行器类型 */
const isAlertAction = (type: number): boolean => {
const alertActionTypes = [
IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
IotRuleSceneActionTypeEnum.ALERT_RECOVER
] as number[]
return alertActionTypes.includes(type)
}
/**
* 创建默认的执行器数据
@ -166,7 +191,7 @@ const maxActions = SCENE_RULE_CONFIG.MAX_ACTIONS // 最大执行器数量
*/
const createDefaultActionData = (): Action => {
return {
type: ActionTypeEnum.DEVICE_PROPERTY_SET, //
type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, //
productId: undefined,
deviceId: undefined,
identifier: undefined, // 使
@ -179,10 +204,6 @@ const createDefaultActionData = (): Action => {
* 添加执行器
*/
const addAction = () => {
if (actions.value.length >= maxActions) {
return
}
const newAction = createDefaultActionData()
actions.value.push(newAction)
}

View File

@ -67,7 +67,7 @@
<!-- 定时触发配置 -->
<div
v-else-if="triggerItem.type === TriggerTypeEnum.TIMER"
v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
class="flex flex-col gap-16px"
>
<div
@ -119,8 +119,7 @@ import { Crontab } from '@/components/Crontab'
import type { Trigger } from '@/api/iot/rule/scene'
import {
getTriggerTypeLabel,
getTriggerTagType,
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum,
IotRuleSceneTriggerTypeEnum,
isDeviceTrigger
} from '@/views/iot/utils/constants'
@ -137,10 +136,18 @@ const emit = defineEmits<{
const triggers = useVModel(props, 'triggers', emit)
/** 获取触发器标签类型(用于 el-tag 的 type 属性) */
const getTriggerTagType = (type: number): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
return 'warning'
}
return isDeviceTrigger(type) ? 'success' : 'info'
}
/** 添加触发器 */
const addTrigger = () => {
const newTrigger: Trigger = {
type: TriggerTypeEnum.DEVICE_STATE_UPDATE,
type: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
productId: undefined,
deviceId: undefined,
identifier: undefined,

View File

@ -24,12 +24,7 @@
<div class="text-12px text-[var(--el-text-color-secondary)]">{{ device.deviceKey }}</div>
</div>
<div class="flex items-center gap-4px" v-if="device.id > 0">
<el-tag size="small" :type="getDeviceEnableStatusTagType(device.status)">
{{ getDeviceEnableStatusText(device.status) }}
</el-tag>
<el-tag size="small" :type="getDeviceActiveStatus(device.activeTime).tagType">
{{ getDeviceActiveStatus(device.activeTime).text }}
</el-tag>
<dict-tag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
</div>
</div>
</el-option>
@ -38,12 +33,8 @@
<script setup lang="ts">
import { DeviceApi } from '@/api/iot/device/device'
import {
getDeviceEnableStatusText,
getDeviceEnableStatusTagType,
getDeviceActiveStatus,
DEVICE_SELECTOR_OPTIONS
} from '@/views/iot/utils/constants'
import { DEVICE_SELECTOR_OPTIONS } from '@/views/iot/utils/constants'
import { DICT_TYPE } from '@/utils/dict'
/** 设备选择器组件 */
defineOptions({ name: 'DeviceSelector' })

View File

@ -222,7 +222,9 @@ const availableOperators = computed(() => {
if (!props.propertyType) {
return allOperators
}
return allOperators.filter((op) => op.supportedTypes.includes(props.propertyType!))
return allOperators.filter((op) =>
(op.supportedTypes as any[]).includes(props.propertyType || '')
)
})
//
@ -243,10 +245,12 @@ watch(
() => props.propertyType,
() => {
//
if (localValue.value && selectedOperator.value) {
if (!selectedOperator.value.supportedTypes.includes(props.propertyType || '')) {
localValue.value = ''
}
if (
localValue.value &&
selectedOperator.value &&
!(selectedOperator.value.supportedTypes as any[]).includes(props.propertyType || '')
) {
localValue.value = ''
}
}
)

View File

@ -262,7 +262,6 @@ const handleChange = (value: string) => {
}
}
// TODO @puhui999
/**
* 获取物模型TSL数据
*/
@ -297,62 +296,62 @@ const parseThingModelData = () => {
const tsl = thingModelTSL.value
const properties: PropertySelectorItem[] = []
// TODO @puhui999if return
if (tsl) {
//
if (tsl.properties && Array.isArray(tsl.properties)) {
tsl.properties.forEach((prop) => {
properties.push({
identifier: prop.identifier,
name: prop.name,
description: prop.description,
dataType: prop.dataType,
type: IoTThingModelTypeEnum.PROPERTY,
accessMode: prop.accessMode,
required: prop.required,
unit: getPropertyUnit(prop),
range: getPropertyRange(prop),
property: prop
})
if (!tsl) {
propertyList.value = properties
return
}
//
if (tsl.properties && Array.isArray(tsl.properties)) {
tsl.properties.forEach((prop) => {
properties.push({
identifier: prop.identifier,
name: prop.name,
description: prop.description,
dataType: prop.dataType,
type: IoTThingModelTypeEnum.PROPERTY,
accessMode: prop.accessMode,
required: prop.required,
unit: getPropertyUnit(prop),
range: getPropertyRange(prop),
property: prop
})
}
//
if (tsl.events && Array.isArray(tsl.events)) {
tsl.events.forEach((event) => {
properties.push({
identifier: event.identifier,
name: event.name,
description: event.description,
dataType: 'struct',
type: IoTThingModelTypeEnum.EVENT,
eventType: event.type,
required: event.required,
outputParams: event.outputParams,
event: event
})
})
}
//
if (tsl.services && Array.isArray(tsl.services)) {
tsl.services.forEach((service) => {
properties.push({
identifier: service.identifier,
name: service.name,
description: service.description,
dataType: 'struct',
type: IoTThingModelTypeEnum.SERVICE,
callType: service.callType,
required: service.required,
inputParams: service.inputParams,
outputParams: service.outputParams,
service: service
})
})
}
})
}
//
if (tsl.events && Array.isArray(tsl.events)) {
tsl.events.forEach((event) => {
properties.push({
identifier: event.identifier,
name: event.name,
description: event.description,
dataType: 'struct',
type: IoTThingModelTypeEnum.EVENT,
eventType: event.type,
required: event.required,
outputParams: event.outputParams,
event: event
})
})
}
//
if (tsl.services && Array.isArray(tsl.services)) {
tsl.services.forEach((service) => {
properties.push({
identifier: service.identifier,
name: service.name,
description: service.description,
dataType: 'struct',
type: IoTThingModelTypeEnum.SERVICE,
callType: service.callType,
required: service.required,
inputParams: service.inputParams,
outputParams: service.outputParams,
service: service
})
})
}
propertyList.value = properties
}
@ -396,7 +395,7 @@ const getPropertyRange = (property: any) => {
return undefined
}
/** *
/** 监听产品变化 */
watch(
() => props.productId,
() => {
@ -410,7 +409,6 @@ watch(
() => props.triggerType,
() => {
localValue.value = ''
// el-popover
}
)
</script>

View File

@ -55,13 +55,6 @@ export const IotDeviceMessageMethodEnum = {
}
}
// IoT 产品物模型类型枚举类
export const IotThingModelTypeEnum = {
PROPERTY: 1, // 属性
SERVICE: 2, // 服务
EVENT: 3 // 事件
}
// IoT 产品物模型服务调用方式枚举
export const IoTThingModelServiceCallTypeEnum = {
ASYNC: {
@ -331,60 +324,12 @@ export const getActionTypeOptions = () => [
}
]
/** 判断是否为设备执行器类型 */
export const isDeviceAction = (type: number): boolean => {
const deviceActionTypes = [
IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
] as number[]
return deviceActionTypes.includes(type)
}
/** 判断是否为告警执行器类型 */
export const isAlertAction = (type: number): boolean => {
const alertActionTypes = [
IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
IotRuleSceneActionTypeEnum.ALERT_RECOVER
] as number[]
return alertActionTypes.includes(type)
}
/** 获取执行器类型标签 */
export const getActionTypeLabel = (type: number): string => {
const option = getActionTypeOptions().find((opt) => opt.value === type)
return option?.label || '未知类型'
}
/** 获取执行器标签类型(用于 el-tag 的 type 属性) */
// TODO @puhui999这种跟界面相关的可以拿到对应组件里
export const getActionTypeTag = (
type: number
): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
const actionTypeTags = {
[IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
[IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
[IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'danger',
[IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning'
} as const
return actionTypeTags[type] || 'info'
}
// TODO @puhui999建议不设置最大值哈。
/** 场景联动规则配置常量 */
export const SCENE_RULE_CONFIG = {
MAX_ACTIONS: 5, // 最大执行器数量
MAX_TRIGGERS: 10, // 最大触发器数量
MAX_CONDITIONS: 20 // 最大条件数量
} as const
// TODO @puhui999下面这个要去掉么
/** IoT 设备消息类型枚举 */
export const IotDeviceMessageTypeEnum = {
PROPERTY: 'property', // 属性
SERVICE: 'service', // 服务
EVENT: 'event' // 事件
} as const
/** IoT 场景联动触发条件参数操作符枚举 */
export const IotRuleSceneTriggerConditionParameterOperatorEnum = {
EQUALS: { name: '等于', value: '=' }, // 等于
@ -424,42 +369,41 @@ export const getConditionTypeOptions = () => [
}
]
/** 设备状态枚举 */
/** 设备状态枚举 - 统一的设备状态管理 */
export const IoTDeviceStatusEnum = {
// 在线状态
ONLINE: {
label: '在线',
value: 'online'
value: 'online',
tagType: 'success'
},
OFFLINE: {
label: '离线',
value: 'offline'
}
} as const
/** 设备启用状态枚举 */
// TODO @puhui999这个是不是和 IoTDeviceStatusEnum 合并下;额外增加一个 value2
export const IoTDeviceEnableStatusEnum = {
value: 'offline',
tagType: 'danger'
},
// 启用状态
ENABLED: {
label: '正常',
value: 0,
value2: 'enabled',
tagType: 'success'
},
DISABLED: {
label: '禁用',
value: 1,
value2: 'disabled',
tagType: 'danger'
}
} as const
/** 设备激活状态枚举 */
// TODO @puhui999这个是不是搞到界面里。label 就是 IoTDeviceStatusEnum然后 tag 界面里处理;;或者也可以在想想,= = 主要设备状态有 3 个枚举,嘿嘿~
export const IoTDeviceActiveStatusEnum = {
},
// 激活状态
ACTIVATED: {
label: '已激活',
value2: 'activated',
tagType: 'success'
},
NOT_ACTIVATED: {
label: '未激活',
value2: 'not_activated',
tagType: 'info'
}
} as const
@ -472,72 +416,6 @@ export const DEVICE_SELECTOR_OPTIONS = {
}
} as const
/** 获取设备状态选项 */
export const getDeviceStatusOptions = () => [
{
value: IoTDeviceStatusEnum.ONLINE.value,
label: IoTDeviceStatusEnum.ONLINE.label
},
{
value: IoTDeviceStatusEnum.OFFLINE.value,
label: IoTDeviceStatusEnum.OFFLINE.label
}
]
/** 获取状态操作符选项 */
export const getStatusOperatorOptions = () => [
{
value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
},
{
value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name
}
]
/** 获取设备状态变更选项(用于触发器配置) */
export const deviceStatusChangeOptions = [
{
label: IoTDeviceStatusEnum.ONLINE.label,
value: IoTDeviceStatusEnum.ONLINE.value
},
{
label: IoTDeviceStatusEnum.OFFLINE.label,
value: IoTDeviceStatusEnum.OFFLINE.value
}
]
/** 获取设备启用状态文本 */
export const getDeviceEnableStatusText = (status: number): string => {
// TODO @puhui999设备有 3 个状态,上线、离线,未激活;
const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
return statusItem?.label || '未知'
}
/** 获取设备启用状态标签类型 */
// TODO @puhui999这个是不是可以直接在界面里处理或者也可以在想想= = 主要设备状态有 3 个枚举,嘿嘿~
export const getDeviceEnableStatusTagType = (
status: number
): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
return statusItem?.tagType || 'info'
}
/** 获取设备激活状态文本和标签类型 */
// TODO @puhui999这个是不是可以直接在界面里处理或者也可以在想想= = 主要设备状态有 3 个枚举,嘿嘿~
export const getDeviceActiveStatus = (activeTime?: string | null) => {
const isActivated = !!activeTime
return {
text: isActivated
? IoTDeviceActiveStatusEnum.ACTIVATED.label
: IoTDeviceActiveStatusEnum.NOT_ACTIVATED.label,
tagType: isActivated
? IoTDeviceActiveStatusEnum.ACTIVATED.tagType
: IoTDeviceActiveStatusEnum.NOT_ACTIVATED.tagType
}
}
/** IoT 场景联动触发时间操作符枚举 */
export const IotRuleSceneTriggerTimeOperatorEnum = {
BEFORE_TIME: { name: '在时间之前', value: 'before_time' }, // 在时间之前
@ -555,17 +433,6 @@ export const getTriggerTypeLabel = (type: number): string => {
return option?.label || '未知类型'
}
// TODO @puhui999这种跟界面相关的可以拿到对应组件里
/** 获取触发器标签类型(用于 el-tag 的 type 属性) */
export const getTriggerTagType = (
type: number
): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
return 'warning'
}
return isDeviceTrigger(type) ? 'success' : 'info'
}
// ========== JSON 参数输入组件相关常量 ==========
/** JSON 参数输入组件类型枚举 */