fix(ts): 收尾 IOT 物模型类型,ts:check 清零

- 去掉 ThingModelProperty/Event/Service 重复空接口,dataSpecs 建为通用联合类型
- dataSpecsList 支持结构体/枚举/布尔项,补齐枚举项 identifier/accessMode
- 物模型表单编辑态用类型断言;DataDefinition/ThingModelProperty 对动态 dataSpecs 安全访问
- 场景规则动态字段改用 Object.assign,规避 keyof 写入被推成 never

Co-Authored-By: Codex <codex@openai.com>

ts:check 26 → 0
master
YunaiV 2026-06-21 07:06:20 -07:00
parent 983ac71559
commit 69444994ad
6 changed files with 79 additions and 68 deletions

View File

@ -18,47 +18,35 @@ export interface ThingModelData {
service?: ThingModelService // 服务 service?: ThingModelService // 服务
} }
/**
* ThingModelProperty
*/
export interface ThingModelProperty {
[key: string]: any
}
/**
* ThingModelEvent
*/
export interface ThingModelEvent {
[key: string]: any
}
/**
* ThingModelService
*/
export interface ThingModelService {
[key: string]: any
}
/** dataSpecs 数值型数据结构 */ /** dataSpecs 数值型数据结构 */
export interface DataSpecsNumberData { export interface DataSpecsNumberData {
dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE dataType: string // 数据类型,取值为 INT、FLOAT 或 DOUBLE
max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型 max?: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型 min?: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
step: string // 步长,必须与 dataType 设置一致,且为 STRING 类型 step?: string // 步长,必须与 dataType 设置一致,且为 STRING 类型
precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选 precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选
defaultValue?: string // 默认值,可选 defaultValue?: string // 默认值,可选
unit: string // 单位的符号 unit?: string // 单位的符号
unitName: string // 单位的名称 unitName?: string // 单位的名称
} }
/** dataSpecs 枚举型数据结构 */ /** dataSpecs 枚举型数据结构 */
export interface DataSpecsEnumOrBoolData { export interface DataSpecsEnumOrBoolData {
dataType: 'enum' | 'bool' dataType: string
defaultValue?: string // 默认值,可选 defaultValue?: string // 默认值,可选
name: string // 枚举项的名称 name: string // 枚举项的名称
value: number | undefined // 枚举值 value: number | undefined // 枚举值
} }
/** dataSpecs 通用数据结构 */
export type ThingModelDataSpecs =
| DataSpecsNumberData
| DataSpecsEnumOrBoolData
| ThingModelDateOrTextDataSpecs
| ThingModelArrayDataSpecs
| ThingModelStructDataSpecs
| Record<string, any>
/** 物模型TSL响应数据结构 */ /** 物模型TSL响应数据结构 */
export interface IotThingModelTSLResp { export interface IotThingModelTSLResp {
productId: number productId: number
@ -76,8 +64,9 @@ export interface ThingModelProperty {
required?: boolean required?: boolean
dataType: string dataType: string
description?: string description?: string
dataSpecs?: ThingModelProperty dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelProperty[] dataSpecsList?: ThingModelPropertyDataSpecs[]
value?: number
} }
/** 物模型事件 */ /** 物模型事件 */
@ -110,8 +99,8 @@ export interface ThingModelParam {
direction: string direction: string
paraOrder?: number paraOrder?: number
dataType: string dataType: string
dataSpecs?: ThingModelProperty dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelProperty[] dataSpecsList?: ThingModelPropertyDataSpecs[]
} }
/** 数值型数据规范 */ /** 数值型数据规范 */
@ -142,24 +131,26 @@ export interface ThingModelDateOrTextDataSpecs {
/** 数组型数据规范 */ /** 数组型数据规范 */
export interface ThingModelArrayDataSpecs { export interface ThingModelArrayDataSpecs {
dataType: 'array' dataType: string
size: number size?: number
childDataType: string childDataType?: string
dataSpecsList?: ThingModelProperty[] dataSpecsList?: ThingModelPropertyDataSpecs[]
} }
/** 结构体型数据规范 */ /** 结构体型数据规范 */
export interface ThingModelStructDataSpecs { export interface ThingModelStructDataSpecs {
dataType: 'struct' dataType: string
identifier: string identifier?: string
name: string name?: string
accessMode: string accessMode?: string
required?: boolean required?: boolean
childDataType: string childDataType?: string
dataSpecs?: ThingModelProperty dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelProperty[] dataSpecsList?: ThingModelPropertyDataSpecs[]
} }
export type ThingModelPropertyDataSpecs = ThingModelProperty | DataSpecsEnumOrBoolData
// IoT 产品物模型 API // IoT 产品物模型 API
export const ThingModelApi = { export const ThingModelApi = {
// 查询产品物模型分页 // 查询产品物模型分页

View File

@ -189,7 +189,7 @@ const timeValue2 = computed(() => {
* @param value 字段值 * @param value 字段值
*/ */
const updateConditionField = (field: keyof TriggerCondition, value: any) => { const updateConditionField = (field: keyof TriggerCondition, value: any) => {
condition.value[field] = value Object.assign(condition.value, { [field]: value })
emit('field-change', field) emit('field-change', field)
} }

View File

@ -313,7 +313,7 @@ const ensureDeviceStatusDefaults = () => {
* @param value 字段值 * @param value 字段值
*/ */
const updateConditionField = (field: keyof Trigger, value: any) => { const updateConditionField = (field: keyof Trigger, value: any) => {
condition.value[field] = value Object.assign(condition.value, { [field]: value })
nextTick(() => { nextTick(() => {
innerFormRef.value?.validateField(field as string).catch(() => {}) innerFormRef.value?.validateField(field as string).catch(() => {})
}) })

View File

@ -63,7 +63,14 @@ import { ProductVO } from '@/api/iot/product/product'
import ThingModelProperty from './ThingModelProperty.vue' import ThingModelProperty from './ThingModelProperty.vue'
import ThingModelService from './ThingModelService.vue' import ThingModelService from './ThingModelService.vue'
import ThingModelEvent from './ThingModelEvent.vue' import ThingModelEvent from './ThingModelEvent.vue'
import { ThingModelApi, ThingModelData, ThingModelFormRules } from '@/api/iot/thingmodel' import {
ThingModelApi,
ThingModelData,
ThingModelEvent as IotThingModelEvent,
ThingModelFormRules,
ThingModelProperty as IotThingModelProperty,
ThingModelService as IotThingModelService
} from '@/api/iot/thingmodel'
import { import {
IOT_PROVIDE_KEY, IOT_PROVIDE_KEY,
IoTDataSpecsDataTypeEnum, IoTDataSpecsDataTypeEnum,
@ -93,9 +100,9 @@ const formData = ref<ThingModelData>({
dataSpecs: { dataSpecs: {
dataType: IoTDataSpecsDataTypeEnum.INT dataType: IoTDataSpecsDataTypeEnum.INT
} }
}, } as IotThingModelProperty,
service: {}, service: {} as IotThingModelService,
event: {} event: {} as IotThingModelEvent
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
@ -118,20 +125,23 @@ const open = async (type: string, id?: number) => {
dataSpecs: { dataSpecs: {
dataType: IoTDataSpecsDataTypeEnum.INT dataType: IoTDataSpecsDataTypeEnum.INT
} }
} } as IotThingModelProperty
} }
// //
if (isEmpty(formData.value.service)) { if (isEmpty(formData.value.service)) {
formData.value.service = { inputParams: [], outputParams: [] } formData.value.service = {
inputParams: [],
outputParams: []
} as unknown as IotThingModelService
} else { } else {
formData.value.service.inputParams ??= [] formData.value.service!.inputParams ??= []
formData.value.service.outputParams ??= [] formData.value.service!.outputParams ??= []
} }
// //
if (isEmpty(formData.value.event)) { if (isEmpty(formData.value.event)) {
formData.value.event = { outputParams: [] } formData.value.event = { outputParams: [] } as unknown as IotThingModelEvent
} else { } else {
formData.value.event.outputParams ??= [] formData.value.event!.outputParams ??= []
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
@ -217,9 +227,9 @@ const resetForm = () => {
dataSpecs: { dataSpecs: {
dataType: IoTDataSpecsDataTypeEnum.INT dataType: IoTDataSpecsDataTypeEnum.INT
} }
}, } as IotThingModelProperty,
service: {}, service: {} as IotThingModelService,
event: {} event: {} as IotThingModelEvent
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }

View File

@ -22,7 +22,7 @@
IoTDataSpecsDataTypeEnum.INT, IoTDataSpecsDataTypeEnum.INT,
IoTDataSpecsDataTypeEnum.DOUBLE, IoTDataSpecsDataTypeEnum.DOUBLE,
IoTDataSpecsDataTypeEnum.FLOAT IoTDataSpecsDataTypeEnum.FLOAT
].includes(property.dataType || '') ].includes(property.dataType as any)
" "
v-model="property.dataSpecs" v-model="property.dataSpecs"
/> />
@ -60,7 +60,11 @@
label="数据长度" label="数据长度"
prop="property.dataSpecs.length" prop="property.dataSpecs.length"
> >
<el-input v-model="property.dataSpecs.length" class="w-255px!" placeholder="请输入文本字节长度"> <el-input
v-model="property.dataSpecs!['length']"
class="w-255px!"
placeholder="请输入文本字节长度"
>
<template #append>字节</template> <template #append>字节</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@ -134,7 +138,7 @@ const handleChange = (dataType: any) => {
IoTDataSpecsDataTypeEnum.ENUM, IoTDataSpecsDataTypeEnum.ENUM,
IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.BOOL,
IoTDataSpecsDataTypeEnum.STRUCT IoTDataSpecsDataTypeEnum.STRUCT
].includes(dataType) && (property.value.dataSpecs.dataType = dataType) ].includes(dataType) && (property.value.dataSpecs!.dataType = dataType)
switch (dataType) { switch (dataType) {
case IoTDataSpecsDataTypeEnum.ENUM: case IoTDataSpecsDataTypeEnum.ENUM:
property.value.dataSpecsList.push({ property.value.dataSpecsList.push({

View File

@ -8,14 +8,16 @@
IoTDataSpecsDataTypeEnum.INT, IoTDataSpecsDataTypeEnum.INT,
IoTDataSpecsDataTypeEnum.DOUBLE, IoTDataSpecsDataTypeEnum.DOUBLE,
IoTDataSpecsDataTypeEnum.FLOAT IoTDataSpecsDataTypeEnum.FLOAT
].includes(data.property.dataType) ].includes(data.property.dataType as any)
" "
> >
取值范围{{ `${data.property.dataSpecs.min}~${data.property.dataSpecs.max}` }} 取值范围{{
`${getDataSpecsValue(data.property, 'min')}~${getDataSpecsValue(data.property, 'max')}`
}}
</div> </div>
<!-- 非列表型文本 --> <!-- 非列表型文本 -->
<div v-if="IoTDataSpecsDataTypeEnum.TEXT === data.property.dataType"> <div v-if="IoTDataSpecsDataTypeEnum.TEXT === data.property.dataType">
数据长度{{ data.property.dataSpecs.length }} 数据长度{{ getDataSpecsValue(data.property, 'length') }}
</div> </div>
<!-- 列表型: 数组结构时间特殊 --> <!-- 列表型: 数组结构时间特殊 -->
<div <div
@ -24,7 +26,7 @@
IoTDataSpecsDataTypeEnum.ARRAY, IoTDataSpecsDataTypeEnum.ARRAY,
IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.STRUCT,
IoTDataSpecsDataTypeEnum.DATE IoTDataSpecsDataTypeEnum.DATE
].includes(data.property.dataType) ].includes(data.property.dataType as any)
" "
> >
- -
@ -32,7 +34,7 @@
<!-- 列表型: 布尔值枚举 --> <!-- 列表型: 布尔值枚举 -->
<div <div
v-if=" v-if="
[IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.ENUM].includes( ([IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.ENUM] as string[]).includes(
data.property.dataType data.property.dataType
) )
" "
@ -40,7 +42,7 @@
<div> <div>
{{ IoTDataSpecsDataTypeEnum.BOOL === data.property.dataType ? '布尔值' : '枚举值' }} {{ IoTDataSpecsDataTypeEnum.BOOL === data.property.dataType ? '布尔值' : '枚举值' }}
</div> </div>
<div v-for="item in data.property.dataSpecsList" :key="item.value"> <div v-for="item in data.property.dataSpecsList || []" :key="item.value">
{{ `${item.name}-${item.value}` }} {{ `${item.name}-${item.value}` }}
</div> </div>
</div> </div>
@ -56,7 +58,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ThingModelData } from '@/api/iot/thingmodel' import { ThingModelData, ThingModelProperty } from '@/api/iot/thingmodel'
import { import {
getEventTypeLabel, getEventTypeLabel,
getThingModelServiceCallTypeLabel, getThingModelServiceCallTypeLabel,
@ -68,6 +70,10 @@ import {
defineOptions({ name: 'DataDefinition' }) defineOptions({ name: 'DataDefinition' })
defineProps<{ data: ThingModelData }>() defineProps<{ data: ThingModelData }>()
const getDataSpecsValue = (property: ThingModelProperty, key: string) => {
return property.dataSpecs?.[key]
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>