前端解析物联网物模型(Thing Model),核心是把后端 / 平台返回的JSON 格式标准模型(属性、服务、事件),解析为前端可渲染、可交互、可校验的结构。
一、物模型标准结构(常见 TSL)
主流(阿里云 / 腾讯云 / 移远等)格式:
{ "productKey": "xxx", "properties": { "temperature": { "name": "温度", "type": "float", "accessMode": "r", "unit": "℃", "min": -40, "max": 125 }, "power_switch": { "name": "电源", "type": "bool", "accessMode": "rw" } }, "services": { "setTemperature": { "name": "设置温度", "input": { "temperature": { "type": "float", "min": 16, "max": 30 } } } }, "events": { "overtemp": { "name": "超温报警", "output": { "temp": "float" } } } }二、前端解析完整流程
1. 加载与校验
- 用
fetch/axios获取模型 JSON - 用JSON Schema / Zod / AJV做格式校验
import { z } from 'zod' const PropSchema = z.object({ name: z.string(), type: z.enum(['bool','int','float','enum','text']), accessMode: z.enum(['r','rw','w']), unit: z.string().optional(), min: z.number().optional(), max: z.number().optional() })2. 扁平化 / 结构化(便于遍历)
function parseThingModel(tsl) { const properties = Object.entries(tsl.properties || {}).map(([id, def]) => ({ id, ...def, dataType: def.type // 统一字段名 })) const services = Object.entries(tsl.services || {}).map(([id, def]) => ({ id, ...def })) const events = Object.entries(tsl.events || {}).map(([id, def]) => ({ id, ...def })) return { properties, services, events } }3. 数据类型映射(前端组件 / 校验)
const typeMap = { bool: { component: 'Switch', valueType: Boolean }, int: { component: 'InputNumber', valueType: Number }, float: { component: 'InputNumber', step: 0.1, valueType: Number }, enum: { component: 'Select', options: [] }, text: { component: 'Input' } } // 给每个属性附加前端信息 properties = properties.map(p => ({ ...p, ...typeMap[p.type], rules: genRules(p) // 生成必填/范围校验 }))4. 动态表单 / UI 渲染
- 用
v-for/map遍历properties - 按
dataType/component渲染对应控件 - 绑定
min/max/required/rules
5. 设备数据(上报 / 下发)适配
- 上报解析:设备原始数据 → 按物模型
identifier映射
// 原始上报 { temperature: 25.6, power_switch: 1 } function parseDeviceData(raw, tslProps) { const res = {} tslProps.forEach(({ id, type }) => { let val = raw[id] if (type === 'bool') val = Boolean(val) if (type === 'float') val = parseFloat(val) res[id] = val }) return res }- 下发参数校验:用物模型
min/max/required校验
6. 事件 / 服务调用
- 事件:展示告警列表、时间线
- 服务:弹出参数表单,按
service.input动态生成
三、常用工具与库
- 校验:Zod、AJV、Yup
- 表单:Formily(动态表单)、AntD/Vant 组件库
- 状态:Pinia/Vuex 存解析后的模型
- 可视化:ECharts 折线图(属性趋势)
四、最佳实践
- 缓存模型:避免重复请求
- 版本兼容:处理字段增减
- 类型安全:TS 定义
Property/Service/Event接口 - 国际化:
name支持多语言 - 错误边界:解析异常捕获提示
要不要我给你一套可直接运行的Vue3 + TS + 物模型解析 + 动态表单完整示例代码(含解析、校验、渲染、下发)?
要
我给你一套可直接复制运行的:Vue3 + TypeScript + 物模型解析 + 动态表单渲染 + 下发校验
结构:
- 物模型类型定义(TS)
- 解析工具函数
- 动态表单组件
- 模拟上报 / 下发逻辑
1. types/thingModel.ts(类型定义)
// 物模型属性类型 export type PropType = 'bool' | 'int' | 'float' | 'enum' | 'text' export interface TSLProperty { name: string type: PropType accessMode: 'r' | 'w' | 'rw' unit?: string min?: number max?: number enumList?: { label: string; value: number | string }[] } export interface TSLService { name: string input: Record<string, TSLProperty> } export interface TSL { properties: Record<string, TSLProperty> services: Record<string, TSLService> events?: Record<string, any> } // 解析后用于前端渲染 export interface RenderProp extends TSLProperty { id: string component: 'switch' | 'input-number' | 'input' | 'select' rules: any[] }2. utils/thingModelParser.ts(解析器)
import type { TSL, TSLProperty, RenderProp } from '@/types/thingModel' // 根据类型映射前端组件 function getComponent(type: TSLProperty['type']) { switch (type) { case 'bool': return 'switch' case 'int': case 'float': return 'input-number' case 'enum': return 'select' case 'text': return 'input' default: return 'input' } } // 生成表单校验规则 function genRules(prop: TSLProperty) { const rules: any[] = [] if (prop.accessMode.includes('w')) { rules.push({ required: true, message: `${prop.name}不能为空` }) } if ((prop.type === 'int' || prop.type === 'float') && prop.min !== undefined) { rules.push({ min: prop.min, message: `${prop.name}不能小于 ${prop.min}` }) } if ((prop.type === 'int' || prop.type === 'float') && prop.max !== undefined) { rules.push({ max: prop.max, message: `${prop.name}不能大于 ${prop.max}` }) } return rules } // 解析 TSL 为可渲染结构 export function parseTSL(tsl: TSL): RenderProp[] { return Object.entries(tsl.properties).map(([id, prop]) => ({ id, ...prop, component: getComponent(prop.type), rules: genRules(prop) })) } // 下发前校验 export function validateValue(prop: RenderProp, value: any): boolean { if (prop.type === 'int' && !Number.isInteger(value)) return false if ((prop.type === 'int' || prop.type === 'float') && prop.min !== undefined && value < prop.min) return false if ((prop.type === 'int' || prop.type === 'float') && prop.max !== undefined && value > prop.max) return false return true }3. 模拟物模型数据(demoTSL.ts)
import type { TSL } from '@/types/thingModel' export const demoTSL: TSL = { properties: { temp: { name: '温度', type: 'float', accessMode: 'rw', unit: '℃', min: -20, max: 80 }, power: { name: '电源开关', type: 'bool', accessMode: 'rw' }, mode: { name: '工作模式', type: 'enum', accessMode: 'rw', enumList: [ { label: '自动', value: 0 }, { label: '制冷', value: 1 }, { label: '制热', value: 2 } ] } }, services: {}, events: {} }4. Vue 组件:ThingModelForm.vue
<template> <div class="p-4 max-w-2xl"> <h2 class="mb-4 text-lg font-bold">物模型动态表单</h2> <a-form layout="vertical" :model="formData"> <a-form-item v-for="prop in renderProps" :key="prop.id" :label="`${prop.name}${prop.unit ? '(' + prop.unit + ')' : ''}`" :rules="prop.rules" :name="prop.id" > <!-- 开关 --> <a-switch v-if="prop.component === 'switch'" v-model:checked="formData[prop.id]" /> <!-- 数字输入 --> <a-input-number v-else-if="prop.component === 'input-number'" v-model:value="formData[prop.id]" :min="prop.min" :max="prop.max" :step="prop.type === 'float' ? 0.1 : 1" style="width: 100%" /> <!-- 下拉枚举 --> <a-select v-else-if="prop.component === 'select'" v-model:value="formData[prop.id]" style="width: 100%" > <a-select-option v-for="opt in prop.enumList" :key="opt.value" :value="opt.value" > {{ opt.label }} </a-select-option> </a-select> <!-- 普通输入 --> <a-input v-else v-model:value="formData[prop.id]" /> </a-form-item> </a-form> <div class="mt-4"> <a-button type="primary" @click="handleSubmit">下发控制</a-button> </div> <div class="mt-6 p-3 bg-gray-50 rounded"> <pre class="text-xs">{{ JSON.stringify(formData, null, 2) }}</pre> </div> </div> </template> <script setup lang="ts"> import { ref, reactive, onMounted } from 'vue' import { message } from 'ant-design-vue' import { demoTSL } from './demoTSL' import { parseTSL, validateValue } from './utils/thingModelParser' import type { RenderProp } from '@/types/thingModel' // 解析物模型 const renderProps = ref<RenderProp[]>([]) onMounted(() => { renderProps.value = parseTSL(demoTSL) initFormData() }) // 表单数据 const formData = reactive<Record<string, any>>({}) // 初始化默认值 function initFormData() { renderProps.value.forEach(p => { switch (p.type) { case 'bool': formData[p.id] = false break case 'int': case 'float': formData[p.id] = p.min || 0 break case 'enum': formData[p.id] = p.enumList?.[0]?.value || 0 break default: formData[p.id] = '' } }) } // 下发 function handleSubmit() { const payload = { ...formData } // 逐个校验 for (const prop of renderProps.value) { const valid = validateValue(prop, formData[prop.id]) if (!valid) { message.error(`${prop.name} 数值不合法`) return } } // 模拟下发 console.log('下发物模型数据:', payload) message.success('下发成功') } </script>5. 你能直接用它做什么?
- 解析任意标准 TSL 物模型
- 自动生成表单、校验、单位、范围
- 支持 bool /int/float /enum/text
- 下发前自动校验
- 可直接对接设备上报 / 下发接口
如果本文对你有帮助,欢迎点赞、收藏、评论,如有疑问或补充,欢迎在评论区交流探讨~
日常深耕嵌入式、物联网、协议开发相关技术,有技术答疑、项目合作、毕设指导需求,均可私信私聊!