perf: 【IoT 物联网】场景联动类型归纳优化,移除多余文件

This commit is contained in:
puhui999
2025-08-07 14:29:59 +08:00
parent 9f3eb14a0f
commit a8adda7dc5
20 changed files with 209 additions and 584 deletions

View File

@ -36,7 +36,7 @@ import { useVModel } from '@vueuse/core'
import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue'
import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
import { IotSceneRule } from '@/api/iot/rule/scene'
import { RuleSceneApi } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerTypeEnum,

View File

@ -123,7 +123,7 @@ import DeviceSelector from '../selectors/DeviceSelector.vue'
import PropertySelector from '../selectors/PropertySelector.vue'
import OperatorSelector from '../selectors/OperatorSelector.vue'
import ValueInput from '../inputs/ValueInput.vue'
import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
import type { TriggerCondition } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerConditionTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum

View File

@ -94,17 +94,18 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import { ConditionFormData, IotRuleSceneTriggerTimeOperatorEnum } from '@/views/iot/utils/constants'
import { IotRuleSceneTriggerTimeOperatorEnum } from '@/views/iot/utils/constants'
import type { TriggerCondition } from '@/api/iot/rule/scene'
/** 当前时间条件配置组件 */
defineOptions({ name: 'CurrentTimeConditionConfig' })
const props = defineProps<{
modelValue: ConditionFormData
modelValue: TriggerCondition
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: ConditionFormData): void
(e: 'update:modelValue', value: TriggerCondition): void
(e: 'validate', result: { valid: boolean; message: string }): void
}>()
@ -178,7 +179,7 @@ const needsSecondTimeInput = computed(() => {
})
// 事件处理
const updateConditionField = (field: keyof ConditionFormData, value: any) => {
const updateConditionField = (field: keyof TriggerCondition, value: any) => {
condition.value[field] = value
updateValidationResult()
}

View File

@ -82,7 +82,8 @@ import { useVModel } from '@vueuse/core'
import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.vue'
import JsonParamsInput from '../inputs/JsonParamsInput.vue'
import { Action, ThingModelProperty, ThingModelService } from '@/api/iot/rule/scene/scene.types'
import type { Action } from '@/api/iot/rule/scene'
import type { ThingModelProperty, ThingModelService } from '@/api/iot/thingmodel'
import {
IotRuleSceneActionTypeEnum,
IoTThingModelAccessModeEnum

View File

@ -84,7 +84,7 @@
import { useVModel } from '@vueuse/core'
import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.vue'
import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
import type { TriggerCondition } from '@/api/iot/rule/scene'
/** 设备状态条件配置组件 */
defineOptions({ name: 'DeviceStatusConditionConfig' })

View File

@ -27,49 +27,24 @@ import { useVModel } from '@vueuse/core'
import MainConditionConfig from './MainConditionConfig.vue'
import ConditionGroupContainerConfig from './ConditionGroupContainerConfig.vue'
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneTriggerTypeEnum as TriggerTypeEnum } from '@/views/iot/utils/constants'
import type { Trigger } from '@/api/iot/rule/scene'
/** 设备触发配置组件 */
defineOptions({ name: 'DeviceTriggerConfig' })
const props = defineProps<{
modelValue: TriggerFormData
modelValue: Trigger
index: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerFormData): void
(e: 'update:modelValue', value: Trigger): void
(e: 'validate', value: { valid: boolean; message: string }): void
(e: 'trigger-type-change', type: number): void
}>()
const trigger = useVModel(props, 'modelValue', emit)
// 初始化主条件
const initMainCondition = () => {
// TODO @puhui999: 等到编辑回显时联调
// if (!trigger.value.mainCondition) {
// trigger.value = {
// type: trigger.value.type, // 使用触发事件类型作为条件类型
// productId: undefined,
// deviceId: undefined,
// identifier: '',
// operator: '=',
// param: ''
// }
// }
}
// 监听触发器类型变化,自动初始化主条件
watch(
() => trigger.value.type,
() => {
initMainCondition()
},
{ immediate: true }
)
const handleTriggerTypeChange = (type: number) => {
trigger.value.type = type
emit('trigger-type-change', type)

View File

@ -35,24 +35,24 @@
<script setup lang="ts">
import MainConditionInnerConfig from './MainConditionInnerConfig.vue'
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneTriggerConditionTypeEnum } from '@/views/iot/utils/constants'
import { Trigger } from '@/api/iot/rule/scene'
/** 主条件配置组件 */
defineOptions({ name: 'MainConditionConfig' })
const props = defineProps<{
modelValue: TriggerFormData
defineProps<{
modelValue: Trigger
triggerType: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerFormData): void
(e: 'update:modelValue', value: Trigger): void
(e: 'validate', result: { valid: boolean; message: string }): void
(e: 'trigger-type-change', type: number): void
}>()
// 事件处理
const updateCondition = (condition: TriggerFormData) => {
const updateCondition = (condition: Trigger) => {
emit('update:modelValue', condition)
}

View File

@ -1,7 +1,6 @@
<template>
<div class="space-y-16px">
<!-- 触发事件类型选择 -->
<!-- TODO @puhui999事件上报时应该也是 json -->
<el-form-item label="触发事件类型" required>
<el-select
:model-value="triggerType"
@ -189,7 +188,7 @@ import OperatorSelector from '../selectors/OperatorSelector.vue'
import ValueInput from '../inputs/ValueInput.vue'
import JsonParamsInput from '../inputs/JsonParamsInput.vue'
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
import type { Trigger } from '@/api/iot/rule/scene'
import { IotRuleSceneTriggerTypeEnum, getTriggerTypeOptions } from '@/views/iot/utils/constants'
import { useVModel } from '@vueuse/core'
@ -197,12 +196,12 @@ import { useVModel } from '@vueuse/core'
defineOptions({ name: 'MainConditionInnerConfig' })
const props = defineProps<{
modelValue: TriggerFormData
modelValue: Trigger
triggerType: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: TriggerFormData): void
(e: 'update:modelValue', value: Trigger): void
(e: 'validate', result: { valid: boolean; message: string }): void
(e: 'trigger-type-change', value: number): void
}>()
@ -278,7 +277,7 @@ const getTriggerTypeText = (type: number) => {
const triggerTypeOptions = getTriggerTypeOptions()
// 事件处理
const updateConditionField = (field: keyof TriggerFormData, value: any) => {
const updateConditionField = (field: keyof Trigger, value: any) => {
;(condition.value as any)[field] = value
updateValidationResult()
}

View File

@ -83,7 +83,7 @@
import { nextTick } from 'vue'
import { useVModel } from '@vueuse/core'
import ConditionConfig from './ConditionConfig.vue'
import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
import type { TriggerCondition } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerConditionTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum

View File

@ -122,7 +122,7 @@ import { useVModel } from '@vueuse/core'
import ActionTypeSelector from '../selectors/ActionTypeSelector.vue'
import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
import AlertConfig from '../configs/AlertConfig.vue'
import { Action } from '@/api/iot/rule/scene/scene.types'
import type { Action } from '@/api/iot/rule/scene'
import {
IotRuleSceneActionTypeEnum as ActionTypeEnum,
isDeviceAction,

View File

@ -58,7 +58,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
import type { IotSceneRule } from '@/api/iot/rule/scene'
/** 基础信息配置组件 */
defineOptions({ name: 'BasicInfoSection' })

View File

@ -96,7 +96,7 @@
import { useVModel } from '@vueuse/core'
import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue'
import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue'
import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
import type { Trigger } from '@/api/iot/rule/scene'
import {
getTriggerTypeOptions,
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum,
@ -108,11 +108,11 @@ import {
defineOptions({ name: 'TriggerSection' })
const props = defineProps<{
triggers: TriggerFormData[]
triggers: Trigger[]
}>()
const emit = defineEmits<{
(e: 'update:triggers', value: TriggerFormData[]): void
(e: 'update:triggers', value: Trigger[]): void
}>()
const triggers = useVModel(props, 'triggers', emit)
@ -136,7 +136,7 @@ const getTriggerTagType = (type: number): string => {
// 事件处理函数
const addTrigger = () => {
const newTrigger: TriggerFormData = {
const newTrigger: Trigger = {
type: TriggerTypeEnum.DEVICE_STATE_UPDATE,
productId: undefined,
deviceId: undefined,
@ -160,7 +160,7 @@ const updateTriggerType = (index: number, type: number) => {
onTriggerTypeChange(index, type)
}
const updateTriggerDeviceConfig = (index: number, newTrigger: TriggerFormData) => {
const updateTriggerDeviceConfig = (index: number, newTrigger: Trigger) => {
triggers.value[index] = newTrigger
}

View File

@ -1,317 +0,0 @@
<!-- 产品设备选择器组件 -->
<template>
<div class="product-device-selector">
<el-row :gutter="16">
<!-- 产品选择 -->
<el-col :span="12">
<el-form-item label="选择产品" required>
<el-select
v-model="localProductId"
placeholder="请选择产品"
filterable
clearable
@change="handleProductChange"
class="w-full"
:loading="productLoading"
>
<el-option
v-for="product in productList"
:key="product.id"
:label="product.name"
:value="product.id"
>
<div class="flex items-center justify-between w-full py-4px">
<div class="flex-1">
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
{{ product.name }}
</div>
<div class="text-12px text-[var(--el-text-color-secondary)]">
{{ product.productKey }}
</div>
</div>
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
<!-- 设备选择模式 -->
<el-col :span="12">
<el-form-item label="设备选择模式" required>
<el-radio-group
v-model="deviceSelectionMode"
@change="handleDeviceSelectionModeChange"
:disabled="!localProductId"
>
<el-radio value="all">全部设备</el-radio>
<el-radio value="specific">选择设备</el-radio>
</el-radio-group>
<div
v-if="!localProductId"
class="text-12px text-[var(--el-text-color-placeholder)] mt-4px"
>
请先选择产品
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 具体设备选择 -->
<el-row v-if="deviceSelectionMode === 'specific'" :gutter="16">
<el-col :span="24">
<el-form-item label="选择设备" required>
<el-select
v-model="localDeviceId"
:placeholder="localProductId ? '请选择设备' : '请先选择产品'"
filterable
clearable
@change="handleDeviceChange"
class="w-full"
:loading="deviceLoading"
:disabled="!localProductId"
>
<el-option
v-for="device in deviceList"
:key="device.id"
:label="device.deviceName"
:value="device.id"
>
<div class="flex items-center justify-between w-full py-4px">
<div class="flex-1">
<div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
{{ device.deviceName }}
</div>
<div class="text-12px text-[var(--el-text-color-secondary)]">
{{ device.nickname || '无备注' }}
</div>
</div>
<el-tag size="small" :type="getDeviceStatusTag(device.state)">
{{ getDeviceStatusText(device.state) }}
</el-tag>
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 选择结果展示 -->
<div
v-if="localProductId && localDeviceId !== undefined"
class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
>
<div class="flex items-center gap-6px mb-8px">
<Icon icon="ep:check" class="text-[var(--el-color-success)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">已选择设备</span>
</div>
<div class="flex flex-col gap-6px ml-22px">
<div class="flex items-center gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-40px">产品</span>
<span class="text-12px text-[var(--el-text-color-primary)] font-500">
{{ selectedProduct?.name }}
</span>
<el-tag size="small" type="primary">{{ selectedProduct?.productKey }}</el-tag>
</div>
<div class="flex items-center gap-8px">
<span class="text-12px text-[var(--el-text-color-secondary)] min-w-40px">设备</span>
<span
v-if="deviceSelectionMode === 'all'"
class="text-12px text-[var(--el-text-color-primary)] font-500"
>全部设备</span
>
<span v-else class="text-12px text-[var(--el-text-color-primary)] font-500">
{{ selectedDevice?.deviceName }}
</span>
<el-tag v-if="deviceSelectionMode === 'all'" size="small" type="warning"> 全部</el-tag>
<el-tag v-else size="small" :type="getDeviceStatusTag(selectedDevice?.state)">
{{ getDeviceStatusText(selectedDevice?.state) }}
</el-tag>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import { ProductApi } from '@/api/iot/product/product'
import { DeviceApi } from '@/api/iot/device/device'
import { DICT_TYPE } from '@/utils/dict'
/** 产品设备选择器组件 */
defineOptions({ name: 'ProductDeviceSelector' })
interface Props {
productId?: number
deviceId?: number
}
interface Emits {
(e: 'update:productId', value?: number): void
(e: 'update:deviceId', value?: number): void
(e: 'change', value: { productId?: number; deviceId?: number }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const localProductId = useVModel(props, 'productId', emit)
const localDeviceId = useVModel(props, 'deviceId', emit)
// 设备选择模式
// 默认选择具体设备,这样用户可以看到设备选择器
const deviceSelectionMode = ref<'specific' | 'all'>('specific')
// 数据状态
const productLoading = ref(false)
const deviceLoading = ref(false)
const productList = ref<any[]>([])
const deviceList = ref<any[]>([])
// 计算属性
const selectedProduct = computed(() => {
return productList.value.find((p) => p.id === localProductId.value)
})
const selectedDevice = computed(() => {
return deviceList.value.find((d) => d.id === localDeviceId.value)
})
// TODO @puhui999字典下
// 设备状态映射
const getDeviceStatusText = (state?: number) => {
switch (state) {
case 0:
return '未激活'
case 1:
return '在线'
case 2:
return '离线'
default:
return '未知'
}
}
const getDeviceStatusTag = (state?: number) => {
switch (state) {
case 0:
return 'info'
case 1:
return 'success'
case 2:
return 'danger'
default:
return 'info'
}
}
// TODO @puhui999注释风格哈
// 事件处理
const handleProductChange = async (productId?: number) => {
localProductId.value = productId
localDeviceId.value = undefined
deviceList.value = []
if (productId) {
await getDeviceList(productId)
}
emitChange()
}
const handleDeviceChange = (deviceId?: number) => {
localDeviceId.value = deviceId
emitChange()
}
const handleDeviceSelectionModeChange = (mode: 'specific' | 'all') => {
deviceSelectionMode.value = mode
if (mode === 'all') {
// 全部设备时,设备 ID 设为 0
localDeviceId.value = 0
} else {
// 选择设备时,清空设备 ID
localDeviceId.value = undefined
}
emitChange()
}
const emitChange = () => {
emit('change', {
productId: localProductId.value,
deviceId: localDeviceId.value
})
}
// API 调用
const getProductList = async () => {
productLoading.value = true
try {
const data = await ProductApi.getSimpleProductList()
productList.value = data || []
} catch (error) {
console.error('获取产品列表失败:', error)
// 模拟数据
// TODO @puhui999移除下不太合理
productList.value = [
{ id: 1, name: '智能温度传感器', productKey: 'temp_sensor_001', status: 0 },
{ id: 2, name: '智能空调控制器', productKey: 'ac_controller_001', status: 0 },
{ id: 3, name: '智能门锁', productKey: 'smart_lock_001', status: 0 }
]
} finally {
productLoading.value = false
}
}
const getDeviceList = async (productId: number) => {
deviceLoading.value = true
try {
const data = await DeviceApi.getSimpleDeviceList(undefined, productId)
deviceList.value = data || []
} catch (error) {
console.error('获取设备列表失败:', error)
// 模拟数据
// TODO @puhui999移除下不太合理
deviceList.value = [
{ id: 1, deviceName: 'sensor_001', nickname: '客厅温度传感器', state: 1, productId },
{ id: 2, deviceName: 'sensor_002', nickname: '卧室温度传感器', state: 2, productId },
{ id: 3, deviceName: 'sensor_003', nickname: '厨房温度传感器', state: 1, productId }
]
} finally {
deviceLoading.value = false
}
}
// 初始化
onMounted(async () => {
await getProductList()
// 根据初始设备 ID 设置选择模式
if (localDeviceId.value === 0) {
deviceSelectionMode.value = 'all'
} else if (localDeviceId.value) {
deviceSelectionMode.value = 'specific'
}
if (localProductId.value) {
await getDeviceList(localProductId.value)
}
})
// 监听产品变化
watch(
() => localProductId.value,
async (newProductId) => {
if (newProductId && deviceList.value.length === 0) {
await getDeviceList(newProductId)
}
}
)
// TODO @puhui999是不是 unocss
</script>
<style scoped>
:deep(.el-select-dropdown__item) {
height: auto;
padding: 8px 20px;
}
</style>

View File

@ -160,12 +160,38 @@
import { useVModel } from '@vueuse/core'
import { InfoFilled } from '@element-plus/icons-vue'
import { IotRuleSceneTriggerTypeEnum, IoTThingModelTypeEnum } from '@/views/iot/utils/constants'
import type {
IotThingModelTSLResp,
ThingModelEvent,
ThingModelParam,
ThingModelProperty,
ThingModelService
} from '@/api/iot/thingmodel'
import { ThingModelApi } from '@/api/iot/thingmodel'
import type { IotThingModelTSLRespVO, PropertySelectorItem } from '@/api/iot/rule/scene/scene.types'
/** 属性选择器组件 */
defineOptions({ name: 'PropertySelector' })
/** 属性选择器内部使用的统一数据结构 */
export interface PropertySelectorItem {
identifier: string
name: string
description?: string
dataType: string
type: number // IoTThingModelTypeEnum
accessMode?: string
required?: boolean
unit?: string
range?: string
eventType?: string
callType?: string
inputParams?: ThingModelParam[]
outputParams?: ThingModelParam[]
property?: ThingModelProperty
event?: ThingModelEvent
service?: ThingModelService
}
const props = defineProps<{
modelValue?: string
triggerType: number
@ -183,7 +209,7 @@ const localValue = useVModel(props, 'modelValue', emit)
// 状态
const loading = ref(false)
const propertyList = ref<PropertySelectorItem[]>([])
const thingModelTSL = ref<IotThingModelTSLRespVO | null>(null)
const thingModelTSL = ref<IotThingModelTSLResp | null>(null)
// 计算属性
const propertyGroups = computed(() => {

View File

@ -239,7 +239,7 @@
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { ContentWrap } from '@/components/ContentWrap'
import RuleSceneForm from './form/RuleSceneForm.vue'
import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
import { IotSceneRule } from '@/api/iot/rule/scene'
import { RuleSceneApi } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerTypeEnum,