perf: 【IoT 物联网】场景联动执行器和触发器的参数值类型都调整为了字符串类型

pull/806/head
puhui999 2025-08-05 21:26:01 +08:00
parent 9917683f0a
commit d81c544ad9
6 changed files with 281 additions and 113 deletions

View File

@ -175,7 +175,7 @@ interface Action {
productId?: number // 产品编号 productId?: number // 产品编号
deviceId?: number // 设备编号 deviceId?: number // 设备编号
identifier?: string // 物模型标识符(服务调用时使用) identifier?: string // 物模型标识符(服务调用时使用)
params?: Record<string, any> // 请求参数 params?: string // 请求参数
alertConfigId?: number // 告警配置编号 alertConfigId?: number // 告警配置编号
} }

View File

@ -82,8 +82,12 @@ import { useVModel } from '@vueuse/core'
import ProductSelector from '../selectors/ProductSelector.vue' import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.vue' import DeviceSelector from '../selectors/DeviceSelector.vue'
import JsonParamsInput from '../inputs/JsonParamsInput.vue' import JsonParamsInput from '../inputs/JsonParamsInput.vue'
import { Action, ThingModelService } from '@/api/iot/rule/scene/scene.types' import { Action, ThingModelProperty, ThingModelService } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneActionTypeEnum } from '@/views/iot/utils/constants' import {
IotRuleSceneActionTypeEnum,
IoTThingModelAccessModeEnum
} from '@/views/iot/utils/constants'
import { ThingModelApi } from '@/api/iot/thingmodel'
/** 设备控制配置组件 */ /** 设备控制配置组件 */
defineOptions({ name: 'DeviceControlConfig' }) defineOptions({ name: 'DeviceControlConfig' })
@ -99,7 +103,7 @@ const emit = defineEmits<{
const action = useVModel(props, 'modelValue', emit) const action = useVModel(props, 'modelValue', emit)
// //
const thingModelProperties = ref<any[]>([]) // const thingModelProperties = ref<ThingModelProperty[]>([]) //
const loadingThingModel = ref(false) // const loadingThingModel = ref(false) //
const selectedService = ref<ThingModelService | null>(null) // const selectedService = ref<ThingModelService | null>(null) //
const serviceList = ref<ThingModelService[]>([]) // const serviceList = ref<ThingModelService[]>([]) //
@ -108,21 +112,16 @@ const loadingServices = ref(false) // 服务加载状态
// //
const paramsValue = computed({ const paramsValue = computed({
get: () => { get: () => {
// params JSON
if (action.value.params && typeof action.value.params === 'object') { if (action.value.params && typeof action.value.params === 'object') {
return JSON.stringify(action.value.params, null, 2) return JSON.stringify(action.value.params, null, 2)
} }
return '' // params
return action.value.params || ''
}, },
set: (value: string) => { set: (value: string) => {
try { // JSON
if (value.trim()) { action.value.params = value.trim() || ''
action.value.params = JSON.parse(value)
} else {
action.value.params = {}
}
} catch (error) {
console.error('JSON解析错误:', error)
}
} }
}) })
@ -151,7 +150,7 @@ const handleProductChange = (productId?: number) => {
if (action.value.productId !== productId) { if (action.value.productId !== productId) {
action.value.deviceId = undefined action.value.deviceId = undefined
action.value.identifier = undefined // action.value.identifier = undefined //
action.value.params = {} action.value.params = '' //
selectedService.value = null // selectedService.value = null //
serviceList.value = [] // serviceList.value = [] //
} }
@ -173,7 +172,7 @@ const handleProductChange = (productId?: number) => {
const handleDeviceChange = (deviceId?: number) => { const handleDeviceChange = (deviceId?: number) => {
// //
if (action.value.deviceId !== deviceId) { if (action.value.deviceId !== deviceId) {
action.value.params = {} action.value.params = '' //
} }
} }
@ -187,7 +186,7 @@ const handleServiceChange = (serviceIdentifier?: string) => {
selectedService.value = service selectedService.value = service
// //
action.value.params = {} action.value.params = ''
// //
if (service && service.inputParams && service.inputParams.length > 0) { if (service && service.inputParams && service.inputParams.length > 0) {
@ -195,12 +194,29 @@ const handleServiceChange = (serviceIdentifier?: string) => {
service.inputParams.forEach((param) => { service.inputParams.forEach((param) => {
defaultParams[param.identifier] = getDefaultValueForParam(param) defaultParams[param.identifier] = getDefaultValueForParam(param)
}) })
action.value.params = defaultParams // JSON
action.value.params = JSON.stringify(defaultParams, null, 2)
} }
} }
/** /**
* 加载物模型属性 * 获取物模型TSL数据
* @param productId 产品ID
* @returns 物模型TSL数据
*/
const getThingModelTSL = async (productId: number) => {
if (!productId) return null
try {
return await ThingModelApi.getThingModelTSLByProductId(productId)
} catch (error) {
console.error('获取物模型TSL数据失败:', error)
return null
}
}
/**
* 加载物模型属性可写属性
* @param productId 产品ID * @param productId 产品ID
*/ */
const loadThingModelProperties = async (productId: number) => { const loadThingModelProperties = async (productId: number) => {
@ -211,39 +227,22 @@ const loadThingModelProperties = async (productId: number) => {
try { try {
loadingThingModel.value = true loadingThingModel.value = true
// TODO: API const tslData = await getThingModelTSL(productId)
// const response = await ProductApi.getThingModel(productId)
// 使
thingModelProperties.value = [
{
identifier: 'BatteryLevel',
name: '电池电量',
dataType: 'int',
description: '设备电池电量百分比'
},
{
identifier: 'WaterLeachState',
name: '漏水状态',
dataType: 'bool',
description: '设备漏水检测状态'
},
{
identifier: 'Temperature',
name: '温度',
dataType: 'float',
description: '环境温度值'
},
{
identifier: 'Humidity',
name: '湿度',
dataType: 'float',
description: '环境湿度值'
}
]
// if (!tslData?.properties) {
thingModelProperties.value = []
return
}
// accessMode 'w'
thingModelProperties.value = tslData.properties.filter(
(property: ThingModelProperty) =>
property.accessMode &&
(property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value ||
property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value)
)
} catch (error) { } catch (error) {
console.error('加载物模型失败:', error) console.error('加载物模型属性失败:', error)
thingModelProperties.value = [] thingModelProperties.value = []
} finally { } finally {
loadingThingModel.value = false loadingThingModel.value = false
@ -260,11 +259,16 @@ const loadServiceList = async (productId: number) => {
return return
} }
loadingServices.value = true
try { try {
const { ThingModelApi } = await import('@/api/iot/thingmodel') loadingServices.value = true
const tslData = await ThingModelApi.getThingModelTSLByProductId(productId) const tslData = await getThingModelTSL(productId)
serviceList.value = tslData?.services || []
if (!tslData?.services) {
serviceList.value = []
return
}
serviceList.value = tslData.services
} catch (error) { } catch (error) {
console.error('加载服务列表失败:', error) console.error('加载服务列表失败:', error)
serviceList.value = [] serviceList.value = []
@ -316,43 +320,67 @@ const getDefaultValueForParam = (param: any) => {
} }
} }
//
const isInitialized = ref(false)
/**
* 初始化组件数据
*/
const initializeComponent = async () => {
if (isInitialized.value) return
const currentAction = action.value
if (!currentAction) return
//
if (currentAction.productId && isPropertySetAction.value) {
await loadThingModelProperties(currentAction.productId)
}
//
if (currentAction.productId && isServiceInvokeAction.value && currentAction.identifier) {
// TSL
await loadServiceFromTSL(currentAction.productId, currentAction.identifier)
}
isInitialized.value = true
}
/** /**
* 组件初始化 * 组件初始化
*/ */
onMounted(() => { onMounted(() => {
// initializeComponent()
if (action.value.productId && isPropertySetAction.value) {
loadThingModelProperties(action.value.productId)
}
//
if (action.value.productId && isServiceInvokeAction.value && action.value.identifier) {
// TSL
loadServiceFromTSL(action.value.productId, action.value.identifier)
}
}) })
// action.value //
watch( watch(
() => action.value, () => [action.value.productId, action.value.type, action.value.identifier],
async (newAction) => { async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => {
if (newAction) { //
// if (!isInitialized.value) return
if (isServiceInvokeAction.value && newAction.productId) {
if (newAction.identifier) { //
// if (newProductId !== oldProductId) {
await loadServiceFromTSL(newAction.productId, newAction.identifier) if (newProductId && isPropertySetAction.value) {
} else { await loadThingModelProperties(newProductId as number)
// } else if (newProductId && isServiceInvokeAction.value) {
await loadServiceList(newAction.productId) await loadServiceList(newProductId as number)
} }
} else if (isServiceInvokeAction.value) { }
//
selectedService.value = null //
serviceList.value = [] if (
newIdentifier !== oldIdentifier &&
newProductId &&
isServiceInvokeAction.value &&
newIdentifier
) {
const service = serviceList.value.find((s: any) => s.identifier === newIdentifier)
if (service) {
selectedService.value = service
}
} }
} }
},
{ deep: true, immediate: true }
) )
</script> </script>

View File

@ -59,8 +59,14 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 操作符选择 - 服务调用不需要操作符 --> <!-- 操作符选择 - 服务调用和事件上报不需要操作符 -->
<el-col v-if="triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE" :span="6"> <el-col
v-if="
triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
"
:span="6"
>
<el-form-item label="操作符" required> <el-form-item label="操作符" required>
<OperatorSelector <OperatorSelector
:model-value="condition.operator" :model-value="condition.operator"
@ -72,7 +78,14 @@
</el-col> </el-col>
<!-- 值输入 --> <!-- 值输入 -->
<el-col :span="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE ? 18 : 12"> <el-col
:span="
triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE ||
triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
? 18
: 12
"
>
<el-form-item <el-form-item
:label=" :label="
triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
@ -84,20 +97,18 @@
<!-- 服务调用参数配置 --> <!-- 服务调用参数配置 -->
<JsonParamsInput <JsonParamsInput
v-if="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE" v-if="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE"
:model-value="condition.value" v-model="conditionValueAsString"
@update:model-value="(value) => updateConditionField('value', value)"
type="service" type="service"
:config="{ service: propertyConfig }" :config="serviceConfig"
placeholder="请输入JSON格式的服务参数" placeholder="请输入JSON格式的服务参数"
@validate="handleValueValidate" @validate="handleValueValidate"
/> />
<!-- 事件上报参数配置 --> <!-- 事件上报参数配置 -->
<JsonParamsInput <JsonParamsInput
v-else-if="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST" v-else-if="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST"
:model-value="condition.value" v-model="conditionValueAsString"
@update:model-value="(value) => updateConditionField('value', value)"
type="event" type="event"
:config="{ event: propertyConfig }" :config="eventConfig"
placeholder="请输入JSON格式的事件参数" placeholder="请输入JSON格式的事件参数"
@validate="handleValueValidate" @validate="handleValueValidate"
/> />
@ -217,6 +228,52 @@ const isDeviceStatusTrigger = computed(() => {
return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE
}) })
// - JsonParamsInput
const serviceConfig = computed(() => {
if (
propertyConfig.value &&
props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
) {
return {
service: {
name: propertyConfig.value.name || '服务',
inputParams: propertyConfig.value.inputParams || []
}
}
}
return undefined
})
// - JsonParamsInput
const eventConfig = computed(() => {
if (propertyConfig.value && props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
return {
event: {
name: propertyConfig.value.name || '事件',
outputParams: propertyConfig.value.outputParams || []
}
}
}
return undefined
})
// JsonParamsInput
const conditionValueAsString = computed({
get: () => {
const value = condition.value.value
if (value === null || value === undefined) {
return ''
}
if (typeof value === 'object') {
return JSON.stringify(value, null, 2)
}
return String(value)
},
set: (newValue: string) => {
condition.value.value = newValue
}
})
// //
// TODO @puhui999 // TODO @puhui999
const getTriggerTypeText = (type: number) => { const getTriggerTypeText = (type: number) => {
@ -264,6 +321,14 @@ const handlePropertyChange = (propertyInfo: any) => {
if (propertyInfo) { if (propertyInfo) {
propertyType.value = propertyInfo.type propertyType.value = propertyInfo.type
propertyConfig.value = propertyInfo.config propertyConfig.value = propertyInfo.config
// '='
if (
props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
) {
condition.value.operator = '='
}
} }
updateValidationResult() updateValidationResult()
} }
@ -306,9 +371,10 @@ const updateValidationResult = () => {
return return
} }
// //
if ( if (
props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE && props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST &&
!condition.value.operator !condition.value.operator
) { ) {
isValid.value = false isValid.value = false
@ -336,8 +402,9 @@ watch(
condition.value.productId, condition.value.productId,
condition.value.deviceId, condition.value.deviceId,
condition.value.identifier, condition.value.identifier,
// //
props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
? condition.value.operator ? condition.value.operator
: null, : null,
condition.value.value condition.value.value

View File

@ -169,7 +169,6 @@ interface Props {
interface Emits { interface Emits {
(e: 'update:modelValue', value: string): void (e: 'update:modelValue', value: string): void
(e: 'validate', result: { valid: boolean; message: string }): void (e: 'validate', result: { valid: boolean; message: string }): void
} }
@ -441,38 +440,91 @@ const generateExampleJson = () => {
return JSON.stringify(example, null, 2) return JSON.stringify(example, null, 2)
} }
// //
onMounted(() => { const isInitialized = ref(false)
//
const initializeData = () => {
if (isInitialized.value) return
if (localValue.value) { if (localValue.value) {
try { try {
// modelValue 使
if (localValue.value.trim()) {
try {
// JSON
const parsed = JSON.parse(localValue.value)
paramsJson.value = JSON.stringify(parsed, null, 2)
} catch {
// JSON使
paramsJson.value = localValue.value paramsJson.value = localValue.value
}
} else {
paramsJson.value = ''
}
jsonError.value = '' jsonError.value = ''
} catch (error) { } catch (error) {
console.error('初始化参数失败:', error) console.error('初始化参数失败:', error)
jsonError.value = '初始参数格式错误' jsonError.value = '初始参数格式错误'
} }
} }
isInitialized.value = true
}
//
onMounted(() => {
initializeData()
}) })
// //
watch( watch(
() => localValue.value, () => localValue.value,
(newValue) => { (newValue, oldValue) => {
if (newValue !== paramsJson.value) { //
paramsJson.value = newValue || '' if (newValue === oldValue) return
try {
let newJsonString = ''
if (newValue && newValue.trim()) {
try {
// JSON
const parsed = JSON.parse(newValue)
newJsonString = JSON.stringify(parsed, null, 2)
} catch {
// JSON使
newJsonString = newValue
} }
} }
// JSON
if (newJsonString !== paramsJson.value) {
paramsJson.value = newJsonString
jsonError.value = ''
}
} catch (error) {
console.error('数据回显失败:', error)
jsonError.value = '数据格式错误'
}
},
{ immediate: true }
) )
// //
watch( watch(
() => props.config, () => props.config,
() => { (newConfig, oldConfig) => {
// //
if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) {
//
if (!localValue.value) {
paramsJson.value = '' paramsJson.value = ''
localValue.value = ''
jsonError.value = '' jsonError.value = ''
} }
}
}
) )
</script> </script>

View File

@ -288,7 +288,9 @@ const handleChange = (value: string) => {
} }
} }
// TSL /**
* 获取物模型TSL数据
*/
const getThingModelTSL = async () => { const getThingModelTSL = async () => {
if (!props.productId) { if (!props.productId) {
thingModelTSL.value = null thingModelTSL.value = null
@ -298,8 +300,15 @@ const getThingModelTSL = async () => {
loading.value = true loading.value = true
try { try {
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(props.productId) const tslData = await ThingModelApi.getThingModelTSLByProductId(props.productId)
if (tslData) {
thingModelTSL.value = tslData
parseThingModelData() parseThingModelData()
} else {
// TSL
await getThingModelList()
}
} catch (error) { } catch (error) {
console.error('获取物模型TSL失败:', error) console.error('获取物模型TSL失败:', error)
// TSL // TSL
@ -309,7 +318,9 @@ const getThingModelTSL = async () => {
} }
} }
// /**
* 获取物模型列表备用方案
*/
const getThingModelList = async () => { const getThingModelList = async () => {
if (!props.productId) { if (!props.productId) {
propertyList.value = [] propertyList.value = []

View File

@ -109,9 +109,19 @@ export const IoTThingModelAccessModeEnum = {
READ_ONLY: { READ_ONLY: {
label: '只读', label: '只读',
value: 'r' value: 'r'
},
WRITE_ONLY: {
label: '只写',
value: 'w'
} }
} as const } as const
/** 获取访问模式标签 */
export const getAccessModeLabel = (value: string): string => {
const mode = Object.values(IoTThingModelAccessModeEnum).find((mode) => mode.value === value)
return mode?.label || value
}
/** 属性值的数据类型 */ /** 属性值的数据类型 */
export const IoTDataSpecsDataTypeEnum = { export const IoTDataSpecsDataTypeEnum = {
INT: 'int', INT: 'int',