!626 【代码完善】IOT: ThingModel StructDataSpecs 组件
Merge pull request !626 from puhui999/feature/iotpull/628/MERGE
commit
eb5d350b09
|
@ -3,7 +3,7 @@ import request from '@/config/axios'
|
||||||
/**
|
/**
|
||||||
* IoT 产品物模型
|
* IoT 产品物模型
|
||||||
*/
|
*/
|
||||||
export interface ThinkModelData {
|
export interface ThingModelData {
|
||||||
id?: number // 物模型功能编号
|
id?: number // 物模型功能编号
|
||||||
identifier?: string // 功能标识
|
identifier?: string // 功能标识
|
||||||
name?: string // 功能名称
|
name?: string // 功能名称
|
||||||
|
@ -12,29 +12,29 @@ export interface ThinkModelData {
|
||||||
productKey?: string // 产品标识
|
productKey?: string // 产品标识
|
||||||
dataType: string // 数据类型,与 dataSpecs 的 dataType 保持一致
|
dataType: string // 数据类型,与 dataSpecs 的 dataType 保持一致
|
||||||
type: ProductFunctionTypeEnum // 功能类型
|
type: ProductFunctionTypeEnum // 功能类型
|
||||||
property: ThinkModelProperty // 属性
|
property: ThingModelProperty // 属性
|
||||||
event?: ThinkModelEvent // 事件
|
event?: ThingModelEvent // 事件
|
||||||
service?: ThinkModelService // 服务
|
service?: ThingModelService // 服务
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThinkModelProperty 类型
|
* ThingModelProperty 类型
|
||||||
*/
|
*/
|
||||||
export interface ThinkModelProperty {
|
export interface ThingModelProperty {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThinkModelEvent 类型
|
* ThingModelEvent 类型
|
||||||
*/
|
*/
|
||||||
export interface ThinkModelEvent {
|
export interface ThingModelEvent {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThinkModelService 类型
|
* ThingModelService 类型
|
||||||
*/
|
*/
|
||||||
export interface ThinkModelService {
|
export interface ThingModelService {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,38 +52,37 @@ export enum ProductFunctionAccessModeEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IoT 产品物模型 API
|
// IoT 产品物模型 API
|
||||||
export const ThinkModelApi = {
|
export const ThingModelApi = {
|
||||||
// 查询产品物模型分页
|
// 查询产品物模型分页
|
||||||
// TODO @puhui999:product 前缀,是不是去掉哈。
|
getThingModelPage: async (params: any) => {
|
||||||
getThinkModelPage: async (params: any) => {
|
return await request.get({ url: `/iot/product-thing-model/page`, params })
|
||||||
return await request.get({ url: `/iot/product-think-model/page`, params })
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获得产品物模型
|
// 获得产品物模型
|
||||||
getThinkModelListByProductId: async (params: any) => {
|
getThingModelListByProductId: async (params: any) => {
|
||||||
return await request.get({
|
return await request.get({
|
||||||
url: `/iot/product-think-model/list-by-product-id`,
|
url: `/iot/product-thing-model/list-by-product-id`,
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询产品物模型详情
|
// 查询产品物模型详情
|
||||||
getThinkModel: async (id: number) => {
|
getThingModel: async (id: number) => {
|
||||||
return await request.get({ url: `/iot/product-think-model/get?id=` + id })
|
return await request.get({ url: `/iot/product-thing-model/get?id=` + id })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 新增产品物模型
|
// 新增产品物模型
|
||||||
createThinkModel: async (data: ThinkModelData) => {
|
createThingModel: async (data: ThingModelData) => {
|
||||||
return await request.post({ url: `/iot/product-think-model/create`, data })
|
return await request.post({ url: `/iot/product-thing-model/create`, data })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 修改产品物模型
|
// 修改产品物模型
|
||||||
updateThinkModel: async (data: ThinkModelData) => {
|
updateThingModel: async (data: ThingModelData) => {
|
||||||
return await request.put({ url: `/iot/product-think-model/update`, data })
|
return await request.put({ url: `/iot/product-thing-model/update`, data })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除产品物模型
|
// 删除产品物模型
|
||||||
deleteThinkModel: async (id: number) => {
|
deleteThingModel: async (id: number) => {
|
||||||
return await request.delete({ url: `/iot/product-think-model/delete?id=` + id })
|
return await request.delete({ url: `/iot/product-thing-model/delete?id=` + id })
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -236,7 +236,7 @@ export enum DICT_TYPE {
|
||||||
IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式
|
IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式
|
||||||
IOT_PROTOCOL_TYPE = 'iot_protocol_type', // IOT 接入网关协议
|
IOT_PROTOCOL_TYPE = 'iot_protocol_type', // IOT 接入网关协议
|
||||||
IOT_DEVICE_STATUS = 'iot_device_status', // IOT 设备状态
|
IOT_DEVICE_STATUS = 'iot_device_status', // IOT 设备状态
|
||||||
IOT_PRODUCT_THINK_MODEL_TYPE = 'iot_product_think_model_type', // IOT 产品功能类型
|
IOT_PRODUCT_THING_MODEL_TYPE = 'iot_product_thing_model_type', // IOT 产品功能类型
|
||||||
IOT_DATA_TYPE = 'iot_data_type', // IOT 数据类型
|
IOT_DATA_TYPE = 'iot_data_type', // IOT 数据类型
|
||||||
IOT_UNIT_TYPE = 'iot_unit_type', // IOT 单位类型
|
IOT_UNIT_TYPE = 'iot_unit_type', // IOT 单位类型
|
||||||
IOT_RW_TYPE = 'iot_rw_type', // IOT 读写类型
|
IOT_RW_TYPE = 'iot_rw_type', // IOT 读写类型
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
<el-tab-pane label="Topic 类列表" name="topic">
|
<el-tab-pane label="Topic 类列表" name="topic">
|
||||||
<ProductTopic v-if="activeTab === 'topic'" :product="product" />
|
<ProductTopic v-if="activeTab === 'topic'" :product="product" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="功能定义" lazy name="thinkModel">
|
<el-tab-pane label="功能定义" lazy name="thingModel">
|
||||||
<IoTProductThinkModel ref="thinkModelRef" />
|
<IoTProductThingModel ref="thingModelRef" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="消息解析" name="message" />
|
<el-tab-pane label="消息解析" name="message" />
|
||||||
<el-tab-pane label="服务端订阅" name="subscription" />
|
<el-tab-pane label="服务端订阅" name="subscription" />
|
||||||
|
@ -22,7 +22,7 @@ import { DeviceApi } from '@/api/iot/device/device'
|
||||||
import ProductDetailsHeader from './ProductDetailsHeader.vue'
|
import ProductDetailsHeader from './ProductDetailsHeader.vue'
|
||||||
import ProductDetailsInfo from './ProductDetailsInfo.vue'
|
import ProductDetailsInfo from './ProductDetailsInfo.vue'
|
||||||
import ProductTopic from './ProductTopic.vue'
|
import ProductTopic from './ProductTopic.vue'
|
||||||
import IoTProductThinkModel from '@/views/iot/thinkmodel/index.vue'
|
import IoTProductThingModel from '@/views/iot/thingmodel/index.vue'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
||||||
|
|
|
@ -97,7 +97,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2.5 last:mb-0">
|
<div class="mb-2.5 last:mb-0">
|
||||||
<span class="text-[#717c8e] mr-2.5">产品标识</span>
|
<span class="text-[#717c8e] mr-2.5">产品标识</span>
|
||||||
<span class="text-[#0b1d30] whitespace-normal break-all">{{ item.productKey }}</span>
|
<span class="text-[#0b1d30] whitespace-normal break-all">
|
||||||
|
{{ item.productKey }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[100px] h-[100px]">
|
<div class="w-[100px] h-[100px]">
|
||||||
|
@ -309,7 +311,7 @@ const openObjectModel = (item: ProductVO) => {
|
||||||
push({
|
push({
|
||||||
name: 'IoTProductDetail',
|
name: 'IoTProductDetail',
|
||||||
params: { id: item.id },
|
params: { id: item.id },
|
||||||
query: { tab: 'thinkModel' }
|
query: { tab: 'thingModel' }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
prop="property.dataType"
|
prop="property.dataType"
|
||||||
>
|
>
|
||||||
<el-select v-model="property.dataType" placeholder="请选择数据类型" @change="handleChange">
|
<el-select v-model="property.dataType" placeholder="请选择数据类型" @change="handleChange">
|
||||||
|
<!-- ARRAY 和 STRUCT 类型数据相互嵌套时,最多支持递归嵌套2层(父和子) -->
|
||||||
<el-option
|
<el-option
|
||||||
v-for="option in dataTypeOptions"
|
v-for="option in getDataTypeOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 数值型配置 -->
|
<!-- 数值型配置 -->
|
||||||
<ThinkModelNumberTypeDataSpecs
|
<ThingModelNumberDataSpecs
|
||||||
v-if="
|
v-if="
|
||||||
[DataSpecsDataType.INT, DataSpecsDataType.DOUBLE, DataSpecsDataType.FLOAT].includes(
|
[DataSpecsDataType.INT, DataSpecsDataType.DOUBLE, DataSpecsDataType.FLOAT].includes(
|
||||||
property.dataType || ''
|
property.dataType || ''
|
||||||
|
@ -23,17 +24,12 @@
|
||||||
v-model="property.dataSpecs"
|
v-model="property.dataSpecs"
|
||||||
/>
|
/>
|
||||||
<!-- 枚举型配置 -->
|
<!-- 枚举型配置 -->
|
||||||
<ThinkModelEnumTypeDataSpecs
|
<ThingModelEnumDataSpecs
|
||||||
v-if="property.dataType === DataSpecsDataType.ENUM"
|
v-if="property.dataType === DataSpecsDataType.ENUM"
|
||||||
v-model="property.dataSpecsList"
|
v-model="property.dataSpecsList"
|
||||||
/>
|
/>
|
||||||
<!-- 布尔型配置 -->
|
<!-- 布尔型配置 -->
|
||||||
<el-form-item
|
<el-form-item v-if="property.dataType === DataSpecsDataType.BOOL" label="布尔值">
|
||||||
v-if="property.dataType === DataSpecsDataType.BOOL"
|
|
||||||
:rules="[{ required: true, message: '请输入布尔值名称', trigger: 'blur' }]"
|
|
||||||
label="布尔值"
|
|
||||||
prop="property.dataSpecsList"
|
|
||||||
>
|
|
||||||
<template v-for="(item, index) in property.dataSpecsList" :key="item.value">
|
<template v-for="(item, index) in property.dataSpecsList" :key="item.value">
|
||||||
<div class="flex items-center justify-start w-1/1 mb-5px">
|
<div class="flex items-center justify-start w-1/1 mb-5px">
|
||||||
<span>{{ item.value }}</span>
|
<span>{{ item.value }}</span>
|
||||||
|
@ -58,10 +54,6 @@
|
||||||
<!-- 文本型配置 -->
|
<!-- 文本型配置 -->
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="property.dataType === DataSpecsDataType.TEXT"
|
v-if="property.dataType === DataSpecsDataType.TEXT"
|
||||||
:rules="[
|
|
||||||
{ required: true, message: '请输入文本字节长度', trigger: 'blur' },
|
|
||||||
{ validator: validateTextLength, trigger: 'blur' }
|
|
||||||
]"
|
|
||||||
label="数据长度"
|
label="数据长度"
|
||||||
prop="property.dataSpecs.length"
|
prop="property.dataSpecs.length"
|
||||||
>
|
>
|
||||||
|
@ -74,16 +66,16 @@
|
||||||
<el-input class="w-255px!" disabled placeholder="String类型的UTC时间戳(毫秒)" />
|
<el-input class="w-255px!" disabled placeholder="String类型的UTC时间戳(毫秒)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 数组型配置-->
|
<!-- 数组型配置-->
|
||||||
<ThinkModelArrayTypeDataSpecs
|
<ThingModelArrayDataSpecs
|
||||||
v-if="property.dataType === DataSpecsDataType.ARRAY"
|
v-if="property.dataType === DataSpecsDataType.ARRAY"
|
||||||
v-model="property.dataSpecs"
|
v-model="property.dataSpecs"
|
||||||
/>
|
/>
|
||||||
<!-- TODO puhui999: Struct 属性待完善 -->
|
<!-- Struct 型配置-->
|
||||||
<el-form-item
|
<ThingModelStructDataSpecs
|
||||||
:rules="[{ required: true, message: '请选择读写类型', trigger: 'change' }]"
|
v-if="property.dataType === DataSpecsDataType.STRUCT"
|
||||||
label="读写类型"
|
v-model="property.dataSpecsList"
|
||||||
prop="property.accessMode"
|
/>
|
||||||
>
|
<el-form-item v-if="!isStructDataSpecs" label="读写类型" prop="property.accessMode">
|
||||||
<el-radio-group v-model="property.accessMode">
|
<el-radio-group v-model="property.accessMode">
|
||||||
<el-radio label="rw">读写</el-radio>
|
<el-radio label="rw">读写</el-radio>
|
||||||
<el-radio label="r">只读</el-radio>
|
<el-radio label="r">只读</el-radio>
|
||||||
|
@ -102,22 +94,29 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { DataSpecsDataType, dataTypeOptions } from './config'
|
import { DataSpecsDataType, dataTypeOptions, validateBoolName } from './config'
|
||||||
import {
|
import {
|
||||||
ThinkModelArrayTypeDataSpecs,
|
ThingModelArrayDataSpecs,
|
||||||
ThinkModelEnumTypeDataSpecs,
|
ThingModelEnumDataSpecs,
|
||||||
ThinkModelNumberTypeDataSpecs
|
ThingModelNumberDataSpecs,
|
||||||
|
ThingModelStructDataSpecs
|
||||||
} from './dataSpecs'
|
} from './dataSpecs'
|
||||||
import { ThinkModelProperty } from '@/api/iot/thinkmodel'
|
import { ThingModelProperty } from '@/api/iot/thingmodel'
|
||||||
import { isEmpty } from '@/utils/is'
|
|
||||||
|
|
||||||
/** IoT 物模型数据 */
|
/** IoT 物模型数据 */
|
||||||
defineOptions({ name: 'ThinkModelDataSpecs' })
|
defineOptions({ name: 'ThingModelDataSpecs' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
||||||
const property = useVModel(props, 'modelValue', emits) as Ref<ThinkModelProperty>
|
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
|
||||||
|
const getDataTypeOptions = computed(() => {
|
||||||
|
return !props.isStructDataSpecs
|
||||||
|
? dataTypeOptions
|
||||||
|
: dataTypeOptions.filter(
|
||||||
|
(item) =>
|
||||||
|
!([DataSpecsDataType.STRUCT, DataSpecsDataType.ARRAY] as any[]).includes(item.value)
|
||||||
|
)
|
||||||
|
}) // 获得数据类型列表
|
||||||
/** 属性值的数据类型切换时初始化相关数据 */
|
/** 属性值的数据类型切换时初始化相关数据 */
|
||||||
const handleChange = (dataType: any) => {
|
const handleChange = (dataType: any) => {
|
||||||
property.value.dataSpecsList = []
|
property.value.dataSpecsList = []
|
||||||
|
@ -143,45 +142,6 @@ const handleChange = (dataType: any) => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @puhui999:一些校验的规则,是不是写到 utils 里。
|
|
||||||
/** 校验布尔值名称 */
|
|
||||||
const validateBoolName = (_: any, value: string, callback: any) => {
|
|
||||||
if (isEmpty(value)) {
|
|
||||||
callback(new Error('布尔值名称不能为空'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 检查开头字符
|
|
||||||
if (!/^[\u4e00-\u9fa5a-zA-Z0-9]/.test(value)) {
|
|
||||||
callback(new Error('布尔值名称必须以中文、英文字母或数字开头'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 检查整体格式
|
|
||||||
if (!/^[\u4e00-\u9fa5a-zA-Z0-9][a-zA-Z0-9\u4e00-\u9fa5_-]*$/.test(value)) {
|
|
||||||
callback(new Error('布尔值名称只能包含中文、英文字母、数字、下划线和短划线'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 检查长度(一个中文算一个字符)
|
|
||||||
if (value.length > 20) {
|
|
||||||
callback(new Error('布尔值名称长度不能超过20个字符'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 校验文本长度 */
|
|
||||||
const validateTextLength = (_: any, value: any, callback: any) => {
|
|
||||||
if (isEmpty(value)) {
|
|
||||||
callback(new Error('文本长度不能为空'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (isNaN(Number(value))) {
|
|
||||||
callback(new Error('文本长度必须是数字'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
|
@ -4,13 +4,13 @@
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="ThingModelFormRules"
|
||||||
label-width="100px"
|
label-width="100px"
|
||||||
>
|
>
|
||||||
<el-form-item label="功能类型" prop="type">
|
<el-form-item label="功能类型" prop="type">
|
||||||
<el-radio-group v-model="formData.type">
|
<el-radio-group v-model="formData.type">
|
||||||
<el-radio-button
|
<el-radio-button
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_THINK_MODEL_TYPE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_THING_MODEL_TYPE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<el-input v-model="formData.identifier" placeholder="请输入标识符" />
|
<el-input v-model="formData.identifier" placeholder="请输入标识符" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 属性配置 -->
|
<!-- 属性配置 -->
|
||||||
<ThinkModelDataSpecs
|
<ThingModelDataSpecs
|
||||||
v-if="formData.type === ProductFunctionTypeEnum.PROPERTY"
|
v-if="formData.type === ProductFunctionTypeEnum.PROPERTY"
|
||||||
v-model="formData.property"
|
v-model="formData.property"
|
||||||
/>
|
/>
|
||||||
|
@ -40,15 +40,15 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ProductVO } from '@/api/iot/product/product'
|
import { ProductVO } from '@/api/iot/product/product'
|
||||||
import ThinkModelDataSpecs from './ThinkModelDataSpecs.vue'
|
import ThingModelDataSpecs from './ThingModelDataSpecs.vue'
|
||||||
import { ProductFunctionTypeEnum, ThinkModelApi, ThinkModelData } from '@/api/iot/thinkmodel'
|
import { ProductFunctionTypeEnum, ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||||
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
||||||
import { DataSpecsDataType } from './config'
|
import { DataSpecsDataType, ThingModelFormRules } from './config'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
|
||||||
/** IoT 物模型数据表单 */
|
/** IoT 物模型数据表单 */
|
||||||
defineOptions({ name: 'IoTProductThinkModelForm' })
|
defineOptions({ name: 'IoTProductThingModelForm' })
|
||||||
|
|
||||||
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
|
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
const formData = ref<ThinkModelData>({
|
const formData = ref<ThingModelData>({
|
||||||
type: ProductFunctionTypeEnum.PROPERTY,
|
type: ProductFunctionTypeEnum.PROPERTY,
|
||||||
dataType: DataSpecsDataType.INT,
|
dataType: DataSpecsDataType.INT,
|
||||||
property: {
|
property: {
|
||||||
|
@ -69,43 +69,7 @@ const formData = ref<ThinkModelData>({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
|
||||||
name: [
|
|
||||||
{ required: true, message: '功能名称不能为空', trigger: 'blur' },
|
|
||||||
{
|
|
||||||
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/,
|
|
||||||
message:
|
|
||||||
'支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符',
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }],
|
|
||||||
identifier: [
|
|
||||||
{ required: true, message: '标识符不能为空', trigger: 'blur' },
|
|
||||||
{
|
|
||||||
pattern: /^[a-zA-Z0-9_]{1,50}$/,
|
|
||||||
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
|
|
||||||
trigger: 'blur'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
validator: (_: any, value: string, callback: any) => {
|
|
||||||
const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value']
|
|
||||||
if (reservedKeywords.includes(value)) {
|
|
||||||
callback(
|
|
||||||
new Error(
|
|
||||||
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else if (/^\d+$/.test(value)) {
|
|
||||||
callback(new Error('标识符不能是纯数字'))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
|
@ -117,7 +81,7 @@ const open = async (type: string, id?: number) => {
|
||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
formData.value = await ThinkModelApi.getThinkModel(id)
|
formData.value = await ThingModelApi.getThingModel(id)
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
|
@ -131,7 +95,7 @@ const submitForm = async () => {
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = cloneDeep(formData.value) as ThinkModelData
|
const data = cloneDeep(formData.value) as ThingModelData
|
||||||
// 信息补全
|
// 信息补全
|
||||||
data.productId = product!.value.id
|
data.productId = product!.value.id
|
||||||
data.productKey = product!.value.productKey
|
data.productKey = product!.value.productKey
|
||||||
|
@ -140,10 +104,10 @@ const submitForm = async () => {
|
||||||
data.property.identifier = data.identifier
|
data.property.identifier = data.identifier
|
||||||
data.property.name = data.name
|
data.property.name = data.name
|
||||||
if (formType.value === 'create') {
|
if (formType.value === 'create') {
|
||||||
await ThinkModelApi.createThinkModel(data)
|
await ThingModelApi.createThingModel(data)
|
||||||
message.success(t('common.createSuccess'))
|
message.success(t('common.createSuccess'))
|
||||||
} else {
|
} else {
|
||||||
await ThinkModelApi.updateThinkModel(data)
|
await ThingModelApi.updateThingModel(data)
|
||||||
message.success(t('common.updateSuccess'))
|
message.success(t('common.updateSuccess'))
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
|
@ -0,0 +1,151 @@
|
||||||
|
import {isEmpty} from '@/utils/is'
|
||||||
|
|
||||||
|
/** dataSpecs 数值型数据结构 */
|
||||||
|
export interface DataSpecsNumberDataVO {
|
||||||
|
dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE
|
||||||
|
max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
|
||||||
|
min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
|
||||||
|
step: string // 步长,必须与 dataType 设置一致,且为 STRING 类型
|
||||||
|
precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选
|
||||||
|
defaultValue?: string // 默认值,可选
|
||||||
|
unit: string // 单位的符号
|
||||||
|
unitName: string // 单位的名称
|
||||||
|
}
|
||||||
|
|
||||||
|
/** dataSpecs 枚举型数据结构 */
|
||||||
|
export interface DataSpecsEnumOrBoolDataVO {
|
||||||
|
dataType: 'enum' | 'bool'
|
||||||
|
defaultValue?: string // 默认值,可选
|
||||||
|
name: string // 枚举项的名称
|
||||||
|
value: number | undefined // 枚举值
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 属性值的数据类型 */
|
||||||
|
export const DataSpecsDataType = {
|
||||||
|
INT: 'int',
|
||||||
|
FLOAT: 'float',
|
||||||
|
DOUBLE: 'double',
|
||||||
|
ENUM: 'enum',
|
||||||
|
BOOL: 'bool',
|
||||||
|
TEXT: 'text',
|
||||||
|
DATE: 'date',
|
||||||
|
STRUCT: 'struct',
|
||||||
|
ARRAY: 'array'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 物体模型数据类型配置项 */
|
||||||
|
export const dataTypeOptions = [
|
||||||
|
{ value: DataSpecsDataType.INT, label: 'int32 (整数型)' },
|
||||||
|
{ value: DataSpecsDataType.FLOAT, label: 'float (单精度浮点型)' },
|
||||||
|
{ value: DataSpecsDataType.DOUBLE, label: 'double (双精度浮点型)' },
|
||||||
|
{ value: DataSpecsDataType.ENUM, label: 'enum(枚举型)' },
|
||||||
|
{ value: DataSpecsDataType.BOOL, label: 'bool (布尔型)' },
|
||||||
|
{ value: DataSpecsDataType.TEXT, label: 'text (文本型)' },
|
||||||
|
{ value: DataSpecsDataType.DATE, label: 'date (时间型)' },
|
||||||
|
{ value: DataSpecsDataType.STRUCT, label: 'struct (结构体)' },
|
||||||
|
{ value: DataSpecsDataType.ARRAY, label: 'array (数组)' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 获得物体模型数据类型配置项名称 */
|
||||||
|
export const getDataTypeOptionsLabel = (value: string) => {
|
||||||
|
return dataTypeOptions.find((option) => option.value === value)?.label
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 公共校验规则 */
|
||||||
|
export const ThingModelFormRules = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '功能名称不能为空', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/,
|
||||||
|
message:
|
||||||
|
'支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }],
|
||||||
|
identifier: [
|
||||||
|
{ required: true, message: '标识符不能为空', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9_]{1,50}$/,
|
||||||
|
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (_: any, value: string, callback: any) => {
|
||||||
|
const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value']
|
||||||
|
if (reservedKeywords.includes(value)) {
|
||||||
|
callback(
|
||||||
|
new Error(
|
||||||
|
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (/^\d+$/.test(value)) {
|
||||||
|
callback(new Error('标识符不能是纯数字'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'property.dataSpecs.childDataType': [{ required: true, message: '元素类型不能为空' }],
|
||||||
|
'property.dataSpecs.size': [
|
||||||
|
{ required: true, message: '元素个数不能为空' },
|
||||||
|
{
|
||||||
|
validator: (_: any, value: any, callback: any) => {
|
||||||
|
if (isEmpty(value)) {
|
||||||
|
callback(new Error('元素个数不能为空'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNaN(Number(value))) {
|
||||||
|
callback(new Error('元素个数必须是数字'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'property.dataSpecs.length': [
|
||||||
|
{ required: true, message: '请输入文本字节长度', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (_: any, value: any, callback: any) => {
|
||||||
|
if (isEmpty(value)) {
|
||||||
|
callback(new Error('文本长度不能为空'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNaN(Number(value))) {
|
||||||
|
callback(new Error('文本长度必须是数字'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'property.accessMode': [{ required: true, message: '请选择读写类型', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
/** 校验布尔值名称 */
|
||||||
|
export const validateBoolName = (_: any, value: string, callback: any) => {
|
||||||
|
if (isEmpty(value)) {
|
||||||
|
callback(new Error('布尔值名称不能为空'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查开头字符
|
||||||
|
if (!/^[\u4e00-\u9fa5a-zA-Z0-9]/.test(value)) {
|
||||||
|
callback(new Error('布尔值名称必须以中文、英文字母或数字开头'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查整体格式
|
||||||
|
if (!/^[\u4e00-\u9fa5a-zA-Z0-9][a-zA-Z0-9\u4e00-\u9fa5_-]*$/.test(value)) {
|
||||||
|
callback(new Error('布尔值名称只能包含中文、英文字母、数字、下划线和短划线'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查长度(一个中文算一个字符)
|
||||||
|
if (value.length > 20) {
|
||||||
|
callback(new Error('布尔值名称长度不能超过20个字符'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback()
|
||||||
|
}
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<el-form-item
|
<el-form-item label="元素类型" prop="property.dataSpecs.childDataType">
|
||||||
:rules="[{ required: true, message: '元素类型不能为空' }]"
|
<el-radio-group v-model="dataSpecs.childDataType" @change="handleChange">
|
||||||
label="元素类型"
|
|
||||||
prop="property.dataSpecs.childDataType"
|
|
||||||
>
|
|
||||||
<el-radio-group v-model="dataSpecs.childDataType">
|
|
||||||
<template v-for="item in dataTypeOptions" :key="item.value">
|
<template v-for="item in dataTypeOptions" :key="item.value">
|
||||||
<el-radio
|
<el-radio
|
||||||
v-if="
|
v-if="
|
||||||
|
@ -19,43 +15,35 @@
|
||||||
</template>
|
</template>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item label="元素个数" prop="property.dataSpecs.size">
|
||||||
:rules="[
|
|
||||||
{ required: true, message: '元素个数不能为空' },
|
|
||||||
{ validator: validateSize, trigger: 'blur' }
|
|
||||||
]"
|
|
||||||
label="元素个数"
|
|
||||||
prop="property.dataSpecs.size"
|
|
||||||
>
|
|
||||||
<el-input v-model="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
<el-input v-model="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- Struct 型配置-->
|
||||||
|
<ThingModelStructDataSpecs
|
||||||
|
v-if="dataSpecs.childDataType === DataSpecsDataType.STRUCT"
|
||||||
|
v-model="dataSpecs.dataSpecsList"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { DataSpecsDataType, dataTypeOptions } from '../config'
|
import { DataSpecsDataType, dataTypeOptions } from '../config'
|
||||||
import { isEmpty } from '@/utils/is'
|
import ThingModelStructDataSpecs from './ThingModelStructDataSpecs.vue'
|
||||||
|
|
||||||
// TODO @puhui999:参数校验,是不是还是定义一个变量,统一管,好阅读点哈?
|
|
||||||
|
|
||||||
/** 数组型的 dataSpecs 配置组件 */
|
/** 数组型的 dataSpecs 配置组件 */
|
||||||
defineOptions({ name: 'ThinkModelArrayTypeDataSpecs' })
|
defineOptions({ name: 'ThingModelArrayDataSpecs' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
const props = defineProps<{ modelValue: any }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
||||||
const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>
|
const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>
|
||||||
|
|
||||||
/** 校验元素个数 */
|
/** 元素类型改变时间。当值为 struct 时,对 dataSpecs 中的 dataSpecsList 进行初始化 */
|
||||||
const validateSize = (_: any, value: any, callback: any) => {
|
const handleChange = (val: string) => {
|
||||||
if (isEmpty(value)) {
|
if (val !== DataSpecsDataType.STRUCT) {
|
||||||
callback(new Error('元素个数不能为空'))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isNaN(Number(value))) {
|
|
||||||
callback(new Error('元素个数必须是数字'))
|
dataSpecs.value.dataSpecsList = []
|
||||||
return
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:rules="[{ required: true, validator: validateEnumList, trigger: 'change' }]"
|
:rules="[{ required: true, validator: validateEnumList, trigger: 'change' }]"
|
||||||
label="枚举项"
|
label="枚举项"
|
||||||
prop="property.dataSpecsList"
|
|
||||||
>
|
>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
@ -48,7 +47,7 @@ import { DataSpecsDataType, DataSpecsEnumOrBoolDataVO } from '../config'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
/** 枚举型的 dataSpecs 配置组件 */
|
/** 枚举型的 dataSpecs 配置组件 */
|
||||||
defineOptions({ name: 'ThinkModelEnumTypeDataSpecs' })
|
defineOptions({ name: 'ThingModelEnumDataSpecs' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
const props = defineProps<{ modelValue: any }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
@ -113,7 +112,6 @@ const validateEnumName = (_: any, value: string, callback: any) => {
|
||||||
callback(new Error('枚举描述长度不能超过20个字符'))
|
callback(new Error('枚举描述长度不能超过20个字符'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +145,6 @@ const validateEnumList = (_: any, __: any, callback: any) => {
|
||||||
callback(new Error('存在重复的枚举值'))
|
callback(new Error('存在重复的枚举值'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -62,7 +62,7 @@ import { UnifyUnitSpecsDTO } from '@/views/iot/utils/constants'
|
||||||
import { DataSpecsNumberDataVO } from '../config'
|
import { DataSpecsNumberDataVO } from '../config'
|
||||||
|
|
||||||
/** 数值型的 dataSpecs 配置组件 */
|
/** 数值型的 dataSpecs 配置组件 */
|
||||||
defineOptions({ name: 'ThinkModelNumberTypeDataSpecs' })
|
defineOptions({ name: 'ThingModelNumberDataSpecs' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
const props = defineProps<{ modelValue: any }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
|
@ -0,0 +1,161 @@
|
||||||
|
<template>
|
||||||
|
<!-- struct 数据展示 -->
|
||||||
|
<el-form-item
|
||||||
|
:rules="[{ required: true, validator: validateList, trigger: 'change' }]"
|
||||||
|
label="JSON 对象"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in dataSpecsList"
|
||||||
|
:key="index"
|
||||||
|
class="w-1/1 struct-item flex justify-between px-10px mb-10px"
|
||||||
|
>
|
||||||
|
<span>参数名称:{{ item.name }}</span>
|
||||||
|
<div class="btn">
|
||||||
|
<el-button link type="primary" @click="openStructForm(item)">编辑</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button link type="danger" @click="deleteStructItem(index)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button link type="primary" @click="openStructForm(null)">+新增参数</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- struct 表单 -->
|
||||||
|
<Dialog v-model="dialogVisible" :title="dialogTitle" append-to-body>
|
||||||
|
<el-form
|
||||||
|
ref="structFormRef"
|
||||||
|
v-loading="formLoading"
|
||||||
|
:model="formData"
|
||||||
|
:rules="ThingModelFormRules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="参数名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入功能名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="标识符" prop="identifier">
|
||||||
|
<el-input v-model="formData.identifier" placeholder="请输入标识符" />
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 属性配置 -->
|
||||||
|
<ThingModelDataSpecs v-model="formData.property" is-struct-data-specs />
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import ThingModelDataSpecs from '../ThingModelDataSpecs.vue'
|
||||||
|
import { DataSpecsDataType, ThingModelFormRules } from '../config'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
|
/** Struct 型的 dataSpecs 配置组件 */
|
||||||
|
defineOptions({ name: 'ThingModelStructDataSpecs' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: any }>()
|
||||||
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('新增参数') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const structFormRef = ref() // 表单 ref
|
||||||
|
const formData = ref<any>({
|
||||||
|
property: {
|
||||||
|
dataType: DataSpecsDataType.INT,
|
||||||
|
dataSpecs: {
|
||||||
|
dataType: DataSpecsDataType.INT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 打开 struct 表单 */
|
||||||
|
const openStructForm = (val: any) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
resetForm()
|
||||||
|
if (isEmpty(val)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑时回显数据
|
||||||
|
formData.value = {
|
||||||
|
identifier: val.identifier,
|
||||||
|
name: val.name,
|
||||||
|
description: val.description,
|
||||||
|
property: {
|
||||||
|
dataType: val.childDataType,
|
||||||
|
dataSpecs: val.dataSpecs,
|
||||||
|
dataSpecsList: val.dataSpecsList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** 删除 struct 项 */
|
||||||
|
const deleteStructItem = (index: number) => {
|
||||||
|
dataSpecsList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加参数 */
|
||||||
|
const submitForm = async () => {
|
||||||
|
await structFormRef.value.validate()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = unref(formData)
|
||||||
|
// 构建数据对象
|
||||||
|
const item = {
|
||||||
|
identifier: data.identifier,
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
dataType: DataSpecsDataType.STRUCT,
|
||||||
|
childDataType: data.property.dataType,
|
||||||
|
dataSpecs:
|
||||||
|
!!data.property.dataSpecs && Object.keys(data.property.dataSpecs).length > 1
|
||||||
|
? data.property.dataSpecs
|
||||||
|
: undefined,
|
||||||
|
dataSpecsList: data.property.dataSpecsList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找是否已有相同 identifier 的项
|
||||||
|
const existingIndex = dataSpecsList.value.findIndex(
|
||||||
|
(spec) => spec.identifier === data.identifier
|
||||||
|
)
|
||||||
|
if (existingIndex > -1) {
|
||||||
|
// 更新已有项
|
||||||
|
dataSpecsList.value[existingIndex] = item
|
||||||
|
} else {
|
||||||
|
// 添加新项
|
||||||
|
dataSpecsList.value.push(item)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// 隐藏对话框
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
property: {
|
||||||
|
dataType: DataSpecsDataType.INT,
|
||||||
|
dataSpecs: {
|
||||||
|
dataType: DataSpecsDataType.INT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
structFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验 struct 不能为空 */
|
||||||
|
const validateList = (_: any, __: any, callback: any) => {
|
||||||
|
if (isEmpty(dataSpecsList.value)) {
|
||||||
|
callback(new Error('struct 不能为空'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.struct-item {
|
||||||
|
background-color: #e4f2fd;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ThingModelEnumDataSpecs from './ThingModelEnumDataSpecs.vue'
|
||||||
|
import ThingModelNumberDataSpecs from './ThingModelNumberDataSpecs.vue'
|
||||||
|
import ThingModelArrayDataSpecs from './ThingModelArrayDataSpecs.vue'
|
||||||
|
import ThingModelStructDataSpecs from './ThingModelStructDataSpecs.vue'
|
||||||
|
|
||||||
|
export {
|
||||||
|
ThingModelEnumDataSpecs,
|
||||||
|
ThingModelNumberDataSpecs,
|
||||||
|
ThingModelArrayDataSpecs,
|
||||||
|
ThingModelStructDataSpecs
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- TODO 目录,应该是 thinkModel 哈。 -->
|
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
|
@ -17,7 +16,7 @@
|
||||||
placeholder="请选择功能类型"
|
placeholder="请选择功能类型"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_THINK_MODEL_TYPE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_THING_MODEL_TYPE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
<el-table-column align="center" label="功能类型" prop="type">
|
<el-table-column align="center" label="功能类型" prop="type">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_THINK_MODEL_TYPE" :value="scope.row.type" />
|
<dict-tag :type="DICT_TYPE.IOT_PRODUCT_THING_MODEL_TYPE" :value="scope.row.type" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" label="功能名称" prop="name" />
|
<el-table-column align="center" label="功能名称" prop="name" />
|
||||||
|
@ -97,23 +96,23 @@
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ThinkModelForm ref="formRef" @success="getList" />
|
<ThingModelForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ThinkModelApi, ThinkModelData } from '@/api/iot/thinkmodel'
|
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import ThinkModelForm from './ThinkModelForm.vue'
|
import ThingModelForm from './ThingModelForm.vue'
|
||||||
import { ProductVO } from '@/api/iot/product/product'
|
import { ProductVO } from '@/api/iot/product/product'
|
||||||
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
||||||
import { getDataTypeOptionsLabel } from '@/views/iot/thinkmodel/config'
|
import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductThinkModel' })
|
defineOptions({ name: 'IoTProductThingModel' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ThinkModelData[]>([]) // 列表的数据
|
const list = ref<ThingModelData[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
|
@ -131,7 +130,7 @@ const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
queryParams.productId = product?.value?.id || -1
|
queryParams.productId = product?.value?.id || -1
|
||||||
const data = await ThinkModelApi.getThinkModelPage(queryParams)
|
const data = await ThingModelApi.getThingModelPage(queryParams)
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.total
|
total.value = data.total
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -163,7 +162,7 @@ const handleDelete = async (id: number) => {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
// 发起删除
|
// 发起删除
|
||||||
await ThinkModelApi.deleteThinkModel(id)
|
await ThingModelApi.deleteThingModel(id)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
|
@ -1,50 +0,0 @@
|
||||||
/** dataSpecs 数值型数据结构 */
|
|
||||||
export interface DataSpecsNumberDataVO {
|
|
||||||
dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE
|
|
||||||
max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
|
|
||||||
min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
|
|
||||||
step: string // 步长,必须与 dataType 设置一致,且为 STRING 类型
|
|
||||||
precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选
|
|
||||||
defaultValue?: string // 默认值,可选
|
|
||||||
unit: string // 单位的符号
|
|
||||||
unitName: string // 单位的名称
|
|
||||||
}
|
|
||||||
|
|
||||||
/** dataSpecs 枚举型数据结构 */
|
|
||||||
export interface DataSpecsEnumOrBoolDataVO {
|
|
||||||
dataType: 'enum' | 'bool'
|
|
||||||
defaultValue?: string // 默认值,可选
|
|
||||||
name: string // 枚举项的名称
|
|
||||||
value: number | undefined // 枚举值
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 属性值的数据类型 */
|
|
||||||
export const DataSpecsDataType = {
|
|
||||||
INT: 'int',
|
|
||||||
FLOAT: 'float',
|
|
||||||
DOUBLE: 'double',
|
|
||||||
ENUM: 'enum',
|
|
||||||
BOOL: 'bool',
|
|
||||||
TEXT: 'text',
|
|
||||||
DATE: 'date',
|
|
||||||
STRUCT: 'struct',
|
|
||||||
ARRAY: 'array'
|
|
||||||
} as const
|
|
||||||
|
|
||||||
/** 物体模型数据类型配置项 */
|
|
||||||
export const dataTypeOptions = [
|
|
||||||
{ value: DataSpecsDataType.INT, label: 'int32 (整数型)' },
|
|
||||||
{ value: DataSpecsDataType.FLOAT, label: 'float (单精度浮点型)' },
|
|
||||||
{ value: DataSpecsDataType.DOUBLE, label: 'double (双精度浮点型)' },
|
|
||||||
{ value: DataSpecsDataType.ENUM, label: 'enum(枚举型)' },
|
|
||||||
{ value: DataSpecsDataType.BOOL, label: 'bool (布尔型)' },
|
|
||||||
{ value: DataSpecsDataType.TEXT, label: 'text (文本型)' },
|
|
||||||
{ value: DataSpecsDataType.DATE, label: 'date (时间型)' },
|
|
||||||
{ value: DataSpecsDataType.STRUCT, label: 'struct (结构体)' },
|
|
||||||
{ value: DataSpecsDataType.ARRAY, label: 'array (数组)' }
|
|
||||||
]
|
|
||||||
|
|
||||||
/** 获得物体模型数据类型配置项名称 */
|
|
||||||
export const getDataTypeOptionsLabel = (value: string) => {
|
|
||||||
return dataTypeOptions.find((option) => option.value === value)?.label
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import ThinkModelEnumTypeDataSpecs from './ThinkModelEnumTypeDataSpecs.vue'
|
|
||||||
import ThinkModelNumberTypeDataSpecs from './ThinkModelNumberTypeDataSpecs.vue'
|
|
||||||
import ThinkModelArrayTypeDataSpecs from './ThinkModelArrayTypeDataSpecs.vue'
|
|
||||||
|
|
||||||
export { ThinkModelEnumTypeDataSpecs, ThinkModelNumberTypeDataSpecs, ThinkModelArrayTypeDataSpecs }
|
|
Loading…
Reference in New Issue