Skip to content

Commit 773b8de

Browse files
YunaiVgitee-org
authored andcommitted
!620 【功能完善】IOT: 产品物模型
Merge pull request !620 from puhui999/feature/iot
2 parents a5565bb + e36e485 commit 773b8de

File tree

13 files changed

+1170
-341
lines changed

13 files changed

+1170
-341
lines changed
Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
import request from '@/config/axios'
22

3-
// IoT 产品物模型 VO
4-
export interface ThinkModelFunctionVO {
5-
id: number // 物模型功能编号
6-
identifier: string // 功能标识
7-
name: string // 功能名称
8-
description: string // 功能描述
9-
productId: number // 产品编号
10-
productKey: string // 产品标识
11-
type: number // 功能类型
12-
property: string // 属性
13-
event: string // 事件
14-
service: string // 服务
3+
/**
4+
* IoT 产品物模型
5+
*/
6+
export interface ThingModelData {
7+
id?: number // 物模型功能编号
8+
identifier?: string // 功能标识
9+
name?: string // 功能名称
10+
description?: string // 功能描述
11+
productId?: number // 产品编号
12+
productKey?: string // 产品标识
13+
dataType: string // 数据类型,与 dataSpecs 的 dataType 保持一致
14+
type: ProductFunctionTypeEnum // 功能类型
15+
property: ThingModelProperty // 属性
16+
event?: ThingModelEvent // 事件
17+
service?: ThingModelService // 服务
18+
}
19+
20+
/**
21+
* ThingModelProperty 类型
22+
*/
23+
export interface ThingModelProperty {
24+
[key: string]: any
25+
}
26+
27+
/**
28+
* ThingModelEvent 类型
29+
*/
30+
export interface ThingModelEvent {
31+
[key: string]: any
32+
}
33+
34+
/**
35+
* ThingModelService 类型
36+
*/
37+
export interface ThingModelService {
38+
[key: string]: any
1539
}
1640

1741
// IOT 产品功能(物模型)类型枚举类
@@ -30,39 +54,35 @@ export enum ProductFunctionAccessModeEnum {
3054
// IoT 产品物模型 API
3155
export const ThinkModelFunctionApi = {
3256
// 查询产品物模型分页
33-
getThinkModelFunctionPage: async (params: any) => {
34-
return await request.get({ url: `/iot/think-model-function/page`, params })
57+
getProductThingModelPage: async (params: any) => {
58+
return await request.get({ url: `/iot/product-thing-model/page`, params })
3559
},
60+
3661
// 获得产品物模型
37-
getThinkModelFunctionListByProductId: async (params: any) => {
62+
getProductThingModelListByProductId: async (params: any) => {
3863
return await request.get({
39-
url: `/iot/think-model-function/list-by-product-id`,
64+
url: `/iot/product-thing-model/list-by-product-id`,
4065
params
4166
})
4267
},
4368

4469
// 查询产品物模型详情
45-
getThinkModelFunction: async (id: number) => {
46-
return await request.get({ url: `/iot/think-model-function/get?id=` + id })
70+
getProductThingModel: async (id: number) => {
71+
return await request.get({ url: `/iot/product-thing-model/get?id=` + id })
4772
},
4873

4974
// 新增产品物模型
50-
createThinkModelFunction: async (data: ThinkModelFunctionVO) => {
51-
return await request.post({ url: `/iot/think-model-function/create`, data })
75+
createProductThingModel: async (data: ThingModelData) => {
76+
return await request.post({ url: `/iot/product-thing-model/create`, data })
5277
},
5378

5479
// 修改产品物模型
55-
updateThinkModelFunction: async (data: ThinkModelFunctionVO) => {
56-
return await request.put({ url: `/iot/think-model-function/update`, data })
80+
updateProductThingModel: async (data: ThingModelData) => {
81+
return await request.put({ url: `/iot/product-thing-model/update`, data })
5782
},
5883

5984
// 删除产品物模型
60-
deleteThinkModelFunction: async (id: number) => {
61-
return await request.delete({ url: `/iot/think-model-function/delete?id=` + id })
62-
},
63-
64-
// 导出产品物模型 Excel
65-
exportThinkModelFunction: async (params) => {
66-
return await request.download({ url: `/iot/think-model-function/export-excel`, params })
85+
deleteProductThingModel: async (id: number) => {
86+
return await request.delete({ url: `/iot/product-thing-model/delete?id=` + id })
6787
}
6888
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<template>
2+
<el-form-item label="数据类型" prop="dataType">
3+
<el-select v-model="property.dataType" placeholder="请选择数据类型" @change="handleChange">
4+
<el-option
5+
v-for="option in dataTypeOptions"
6+
:key="option.value"
7+
:label="option.label"
8+
:value="option.value"
9+
/>
10+
</el-select>
11+
</el-form-item>
12+
<!-- 数值型配置 -->
13+
<ThingModelNumberTypeDataSpecs
14+
v-if="
15+
[DataSpecsDataType.INT, DataSpecsDataType.DOUBLE, DataSpecsDataType.FLOAT].includes(
16+
property.dataType || ''
17+
)
18+
"
19+
v-model="property.dataSpecs"
20+
/>
21+
<!-- 枚举型配置 -->
22+
<ThingModelEnumTypeDataSpecs
23+
v-if="property.dataType === DataSpecsDataType.ENUM"
24+
v-model="property.dataSpecsList"
25+
/>
26+
<!-- 布尔型配置 -->
27+
<el-form-item v-if="property.dataType === DataSpecsDataType.BOOL" label="布尔值" prop="bool">
28+
<template v-for="item in property.dataSpecsList" :key="item.value">
29+
<div class="flex items-center justify-start w-1/1 mb-5px">
30+
<span>{{ item.value }}</span>
31+
<span class="mx-2">-</span>
32+
<el-input
33+
v-model="item.name"
34+
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
35+
class="w-255px!"
36+
/>
37+
</div>
38+
</template>
39+
</el-form-item>
40+
<!-- 文本型配置 -->
41+
<el-form-item v-if="property.dataType === DataSpecsDataType.TEXT" label="数据长度" prop="text">
42+
<el-input v-model="property.dataSpecs.length" class="w-255px!" placeholder="请输入文本字节长度">
43+
<template #append>字节</template>
44+
</el-input>
45+
</el-form-item>
46+
<!-- 时间型配置 -->
47+
<el-form-item v-if="property.dataType === DataSpecsDataType.DATE" label="时间格式" prop="date">
48+
<el-input class="w-255px!" disabled placeholder="String类型的UTC时间戳(毫秒)" />
49+
</el-form-item>
50+
<!-- 数组型配置-->
51+
<ThingModelArrayTypeDataSpecs
52+
v-if="property.dataType === DataSpecsDataType.ARRAY"
53+
v-model="property.dataSpecs"
54+
/>
55+
<!-- TODO puhui999: Struct 属性待完善 -->
56+
<el-form-item label="读写类型" prop="accessMode">
57+
<el-radio-group v-model="property.accessMode">
58+
<el-radio label="rw">读写</el-radio>
59+
<el-radio label="r">只读</el-radio>
60+
</el-radio-group>
61+
</el-form-item>
62+
<el-form-item label="属性描述" prop="description">
63+
<el-input v-model="property.description" placeholder="请输入属性描述" type="textarea" />
64+
</el-form-item>
65+
</template>
66+
67+
<script lang="ts" setup>
68+
import { useVModel } from '@vueuse/core'
69+
import { DataSpecsDataType, dataTypeOptions } from './config'
70+
import {
71+
ThingModelArrayTypeDataSpecs,
72+
ThingModelEnumTypeDataSpecs,
73+
ThingModelNumberTypeDataSpecs
74+
} from './dataSpecs'
75+
import { ThingModelProperty } from '@/api/iot/thinkmodelfunction'
76+
77+
/** 物模型数据 */
78+
defineOptions({ name: 'ThingModelDataSpecs' })
79+
const props = defineProps<{ modelValue: any }>()
80+
const emits = defineEmits(['update:modelValue'])
81+
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
82+
83+
/** 属性值的数据类型切换时初始化相关数据 */
84+
const handleChange = (dataType: any) => {
85+
property.value.dataSpecsList = []
86+
property.value.dataSpecs = {}
87+
88+
property.value.dataSpecs.dataType = dataType
89+
switch (dataType) {
90+
case DataSpecsDataType.ENUM:
91+
property.value.dataSpecsList.push({
92+
dataType: DataSpecsDataType.ENUM,
93+
name: '', // 枚举项的名称
94+
value: undefined // 枚举值
95+
})
96+
break
97+
case DataSpecsDataType.BOOL:
98+
for (let i = 0; i < 2; i++) {
99+
property.value.dataSpecsList.push({
100+
dataType: DataSpecsDataType.BOOL,
101+
name: '', // 布尔值的名称
102+
value: i // 布尔值
103+
})
104+
}
105+
break
106+
}
107+
}
108+
</script>
109+
110+
<style lang="scss" scoped></style>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<template>
2+
<Dialog v-model="dialogVisible" :title="dialogTitle">
3+
<el-form
4+
ref="formRef"
5+
v-loading="formLoading"
6+
:model="formData"
7+
:rules="formRules"
8+
label-width="100px"
9+
>
10+
<el-form-item label="功能类型" prop="type">
11+
<el-radio-group v-model="formData.type">
12+
<el-radio-button :value="1"> 属性</el-radio-button>
13+
<el-radio-button :value="2"> 服务</el-radio-button>
14+
<el-radio-button :value="3"> 事件</el-radio-button>
15+
</el-radio-group>
16+
</el-form-item>
17+
<el-form-item label="功能名称" prop="name">
18+
<el-input v-model="formData.name" placeholder="请输入功能名称" />
19+
</el-form-item>
20+
<el-form-item label="标识符" prop="identifier">
21+
<el-input v-model="formData.identifier" placeholder="请输入标识符" />
22+
</el-form-item>
23+
<!-- 属性配置 -->
24+
<ThingModelDataSpecs
25+
v-if="formData.type === ProductFunctionTypeEnum.PROPERTY"
26+
v-model="formData.property"
27+
/>
28+
</el-form>
29+
30+
<template #footer>
31+
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
32+
<el-button @click="dialogVisible = false">取 消</el-button>
33+
</template>
34+
</Dialog>
35+
</template>
36+
37+
<script lang="ts" setup>
38+
import { ProductVO } from '@/api/iot/product/product'
39+
import ThingModelDataSpecs from './ThingModelDataSpecs.vue'
40+
import {
41+
ProductFunctionTypeEnum,
42+
ThingModelData,
43+
ThinkModelFunctionApi
44+
} from '@/api/iot/thinkmodelfunction'
45+
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
46+
import { DataSpecsDataType } from './config'
47+
import { cloneDeep } from 'lodash-es'
48+
49+
defineOptions({ name: 'IoTProductThingModelForm' })
50+
51+
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
52+
53+
const { t } = useI18n()
54+
const message = useMessage()
55+
56+
const dialogVisible = ref(false)
57+
const dialogTitle = ref('')
58+
const formLoading = ref(false)
59+
const formType = ref('')
60+
const formData = ref<ThingModelData>({
61+
type: ProductFunctionTypeEnum.PROPERTY,
62+
dataType: DataSpecsDataType.INT,
63+
property: {
64+
dataType: DataSpecsDataType.INT,
65+
dataSpecs: {
66+
dataType: DataSpecsDataType.INT
67+
}
68+
}
69+
})
70+
// TODO puhui999: 表单校验待完善
71+
const formRules = reactive({
72+
name: [
73+
{ required: true, message: '功能名称不能为空', trigger: 'blur' },
74+
{
75+
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/,
76+
message:
77+
'支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符',
78+
trigger: 'blur'
79+
}
80+
],
81+
type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }],
82+
identifier: [
83+
{ required: true, message: '标识符不能为空', trigger: 'blur' },
84+
{
85+
pattern: /^[a-zA-Z0-9_]{1,50}$/,
86+
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
87+
trigger: 'blur'
88+
},
89+
{
90+
validator: (rule, value, callback) => {
91+
const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value']
92+
if (reservedKeywords.includes(value)) {
93+
callback(
94+
new Error(
95+
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义'
96+
)
97+
)
98+
} else if (/^\d+$/.test(value)) {
99+
callback(new Error('标识符不能是纯数字'))
100+
} else {
101+
callback()
102+
}
103+
},
104+
trigger: 'blur'
105+
}
106+
],
107+
'property.dataType.type': [{ required: true, message: '数据类型不能为空', trigger: 'blur' }],
108+
'property.accessMode': [{ required: true, message: '读写类型不能为空', trigger: 'blur' }]
109+
})
110+
const formRef = ref()
111+
112+
/** 打开弹窗 */
113+
const open = async (type: string, id?: number) => {
114+
dialogVisible.value = true
115+
dialogTitle.value = t('action.' + type)
116+
formType.value = type
117+
resetForm()
118+
if (id) {
119+
formLoading.value = true
120+
try {
121+
formData.value = await ThinkModelFunctionApi.getProductThingModel(id)
122+
} finally {
123+
formLoading.value = false
124+
}
125+
}
126+
}
127+
defineExpose({ open, close: () => (dialogVisible.value = false) })
128+
129+
/** 提交表单 */
130+
const emit = defineEmits(['success'])
131+
const submitForm = async () => {
132+
await formRef.value.validate()
133+
formLoading.value = true
134+
try {
135+
const data = cloneDeep(formData.value) as ThingModelData
136+
// 信息补全
137+
data.productId = product!.value.id
138+
data.productKey = product!.value.productKey
139+
data.description = data.property.description
140+
data.dataType = data.property.dataType
141+
data.property.identifier = data.identifier
142+
data.property.name = data.name
143+
if (formType.value === 'create') {
144+
await ThinkModelFunctionApi.createProductThingModel(data)
145+
message.success(t('common.createSuccess'))
146+
} else {
147+
await ThinkModelFunctionApi.updateProductThingModel(data)
148+
message.success(t('common.updateSuccess'))
149+
}
150+
} finally {
151+
dialogVisible.value = false // 确保关闭弹框
152+
emit('success')
153+
formLoading.value = false
154+
}
155+
}
156+
157+
/** 重置表单 */
158+
const resetForm = () => {
159+
formData.value = {
160+
type: ProductFunctionTypeEnum.PROPERTY,
161+
dataType: DataSpecsDataType.INT,
162+
property: {
163+
dataType: DataSpecsDataType.INT,
164+
dataSpecs: {
165+
dataType: DataSpecsDataType.INT
166+
}
167+
}
168+
}
169+
formRef.value?.resetFields()
170+
}
171+
</script>

0 commit comments

Comments
 (0)