Skip to content

Commit ce7dc92

Browse files
author
puhui999
committed
【代码完善】IOT: ThingModel StructDataSpecs 组件
1 parent d5f3d40 commit ce7dc92

File tree

11 files changed

+254
-96
lines changed

11 files changed

+254
-96
lines changed

src/views/iot/product/product/detail/index.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<el-tab-pane label="Topic 类列表" name="topic">
99
<ProductTopic v-if="activeTab === 'topic'" :product="product" />
1010
</el-tab-pane>
11-
<el-tab-pane label="功能定义" lazy name="thinkModel">
12-
<IoTProductThinkModel ref="thinkModelRef" />
11+
<el-tab-pane label="功能定义" lazy name="thingModel">
12+
<IoTProductThingModel ref="thingModelRef" />
1313
</el-tab-pane>
1414
<el-tab-pane label="消息解析" name="message" />
1515
<el-tab-pane label="服务端订阅" name="subscription" />
@@ -22,7 +22,7 @@ import { DeviceApi } from '@/api/iot/device/device'
2222
import ProductDetailsHeader from './ProductDetailsHeader.vue'
2323
import ProductDetailsInfo from './ProductDetailsInfo.vue'
2424
import ProductTopic from './ProductTopic.vue'
25-
import IoTProductThinkModel from '@/views/iot/thingmodel/index.vue'
25+
import IoTProductThingModel from '@/views/iot/thingmodel/index.vue'
2626
import { useTagsViewStore } from '@/store/modules/tagsView'
2727
import { useRouter } from 'vue-router'
2828
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'

src/views/iot/product/product/index.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@
9797
</div>
9898
<div class="mb-2.5 last:mb-0">
9999
<span class="text-[#717c8e] mr-2.5">产品标识</span>
100-
<span class="text-[#0b1d30] whitespace-normal break-all">{{ item.productKey }}</span>
100+
<span class="text-[#0b1d30] whitespace-normal break-all">
101+
{{ item.productKey }}
102+
</span>
101103
</div>
102104
</div>
103105
<div class="w-[100px] h-[100px]">
@@ -309,7 +311,7 @@ const openObjectModel = (item: ProductVO) => {
309311
push({
310312
name: 'IoTProductDetail',
311313
params: { id: item.id },
312-
query: { tab: 'thinkModel' }
314+
query: { tab: 'thingModel' }
313315
})
314316
}
315317

src/views/iot/thingmodel/ThingModelDataSpecs.vue

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
prop="property.dataType"
66
>
77
<el-select v-model="property.dataType" placeholder="请选择数据类型" @change="handleChange">
8+
<!-- ARRAY 和 STRUCT 类型数据相互嵌套时,最多支持递归嵌套2层(父和子) -->
89
<el-option
9-
v-for="option in dataTypeOptions"
10+
v-for="option in getDataTypeOptions"
1011
:key="option.value"
1112
:label="option.label"
1213
:value="option.value"
1314
/>
1415
</el-select>
1516
</el-form-item>
1617
<!-- 数值型配置 -->
17-
<ThingModelNumberTypeDataSpecs
18+
<ThingModelNumberDataSpecs
1819
v-if="
1920
[DataSpecsDataType.INT, DataSpecsDataType.DOUBLE, DataSpecsDataType.FLOAT].includes(
2021
property.dataType || ''
@@ -23,7 +24,7 @@
2324
v-model="property.dataSpecs"
2425
/>
2526
<!-- 枚举型配置 -->
26-
<ThingModelEnumTypeDataSpecs
27+
<ThingModelEnumDataSpecs
2728
v-if="property.dataType === DataSpecsDataType.ENUM"
2829
v-model="property.dataSpecsList"
2930
/>
@@ -74,12 +75,17 @@
7475
<el-input class="w-255px!" disabled placeholder="String类型的UTC时间戳(毫秒)" />
7576
</el-form-item>
7677
<!-- 数组型配置-->
77-
<ThingModelArrayTypeDataSpecs
78+
<ThingModelArrayDataSpecs
7879
v-if="property.dataType === DataSpecsDataType.ARRAY"
7980
v-model="property.dataSpecs"
8081
/>
81-
<!-- TODO puhui999: Struct 属性待完善 -->
82+
<!-- Struct 型配置-->
83+
<ThingModelStructDataSpecs
84+
v-if="property.dataType === DataSpecsDataType.STRUCT"
85+
v-model="property.dataSpecsList"
86+
/>
8287
<el-form-item
88+
v-if="!isStructDataSpecs"
8389
:rules="[{ required: true, message: '请选择读写类型', trigger: 'change' }]"
8490
label="读写类型"
8591
prop="property.accessMode"
@@ -104,20 +110,28 @@
104110
import { useVModel } from '@vueuse/core'
105111
import { DataSpecsDataType, dataTypeOptions } from './config'
106112
import {
107-
ThingModelArrayTypeDataSpecs,
108-
ThingModelEnumTypeDataSpecs,
109-
ThingModelNumberTypeDataSpecs
113+
ThingModelArrayDataSpecs,
114+
ThingModelEnumDataSpecs,
115+
ThingModelNumberDataSpecs
110116
} from './dataSpecs'
111-
import { ThingModelProperty } from 'src/api/iot/thingmodel'
117+
import ThingModelStructDataSpecs from './ThingModelStructDataSpecs.vue'
118+
import { ThingModelProperty } from '@/api/iot/thingmodel'
112119
import { isEmpty } from '@/utils/is'
113120
114121
/** IoT 物模型数据 */
115122
defineOptions({ name: 'ThingModelDataSpecs' })
116123
117-
const props = defineProps<{ modelValue: any }>()
124+
const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean }>()
118125
const emits = defineEmits(['update:modelValue'])
119126
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
120-
127+
const getDataTypeOptions = computed(() => {
128+
return !props.isStructDataSpecs
129+
? dataTypeOptions
130+
: dataTypeOptions.filter(
131+
(item) =>
132+
!([DataSpecsDataType.STRUCT, DataSpecsDataType.ARRAY] as any[]).includes(item.value)
133+
)
134+
}) // 获得数据类型列表
121135
/** 属性值的数据类型切换时初始化相关数据 */
122136
const handleChange = (dataType: any) => {
123137
property.value.dataSpecsList = []

src/views/iot/thingmodel/ThingModelForm.vue

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
ref="formRef"
55
v-loading="formLoading"
66
:model="formData"
7-
:rules="formRules"
7+
:rules="ThingModelFormRules"
88
label-width="100px"
99
>
1010
<el-form-item label="功能类型" prop="type">
@@ -41,9 +41,9 @@
4141
<script lang="ts" setup>
4242
import { ProductVO } from '@/api/iot/product/product'
4343
import ThingModelDataSpecs from './ThingModelDataSpecs.vue'
44-
import { ProductFunctionTypeEnum, ThingModelApi, ThingModelData } from 'src/api/iot/thingmodel'
44+
import { ProductFunctionTypeEnum, ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
4545
import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
46-
import { DataSpecsDataType } from './config'
46+
import { DataSpecsDataType, ThingModelFormRules } from './config'
4747
import { cloneDeep } from 'lodash-es'
4848
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
4949
@@ -69,43 +69,7 @@ const formData = ref<ThingModelData>({
6969
}
7070
}
7171
})
72-
const formRules = reactive({
73-
name: [
74-
{ required: true, message: '功能名称不能为空', trigger: 'blur' },
75-
{
76-
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/,
77-
message:
78-
'支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符',
79-
trigger: 'blur'
80-
}
81-
],
82-
type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }],
83-
identifier: [
84-
{ required: true, message: '标识符不能为空', trigger: 'blur' },
85-
{
86-
pattern: /^[a-zA-Z0-9_]{1,50}$/,
87-
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
88-
trigger: 'blur'
89-
},
90-
{
91-
validator: (_: any, value: string, callback: any) => {
92-
const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value']
93-
if (reservedKeywords.includes(value)) {
94-
callback(
95-
new Error(
96-
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义'
97-
)
98-
)
99-
} else if (/^\d+$/.test(value)) {
100-
callback(new Error('标识符不能是纯数字'))
101-
} else {
102-
callback()
103-
}
104-
},
105-
trigger: 'blur'
106-
}
107-
]
108-
})
72+
10973
const formRef = ref() // 表单 Ref
11074
11175
/** 打开弹窗 */
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<template>
2+
<!-- struct 数据展示 -->
3+
<el-form-item label="JSON 对象">
4+
<div
5+
v-for="(item, index) in dataSpecsList"
6+
:key="index"
7+
class="w-1/1 struct-item flex justify-between px-10px mb-10px"
8+
>
9+
<span>参数名称:{{ item.name }}</span>
10+
<div class="btn">
11+
<el-button link type="primary" @click="openStructForm(item)">编辑</el-button>
12+
<el-divider direction="vertical" />
13+
<el-button link type="danger" @click="deleteStructItem(index)">删除</el-button>
14+
</div>
15+
</div>
16+
<el-button link type="primary" @click="openStructForm(null)">+新增参数</el-button>
17+
</el-form-item>
18+
19+
<!-- struct 表单 -->
20+
<Dialog v-model="dialogVisible" :title="dialogTitle" append-to-body>
21+
<el-form
22+
ref="structFormRef"
23+
v-loading="formLoading"
24+
:model="formData"
25+
:rules="ThingModelFormRules"
26+
label-width="100px"
27+
>
28+
<el-form-item label="参数名称" prop="name">
29+
<el-input v-model="formData.name" placeholder="请输入功能名称" />
30+
</el-form-item>
31+
<el-form-item label="标识符" prop="identifier">
32+
<el-input v-model="formData.identifier" placeholder="请输入标识符" />
33+
</el-form-item>
34+
<!-- 属性配置 -->
35+
<ThingModelDataSpecs v-model="formData.property" is-struct-data-specs />
36+
</el-form>
37+
38+
<template #footer>
39+
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
40+
<el-button @click="dialogVisible = false">取 消</el-button>
41+
</template>
42+
</Dialog>
43+
</template>
44+
45+
<script lang="ts" setup>
46+
import { useVModel } from '@vueuse/core'
47+
import ThingModelDataSpecs from '@/views/iot/thingmodel/ThingModelDataSpecs.vue'
48+
import { DataSpecsDataType, ThingModelFormRules } from '@/views/iot/thingmodel/config'
49+
import { isEmpty } from '@/utils/is'
50+
51+
/** Struct 型的 dataSpecs 配置组件 */
52+
defineOptions({ name: 'ThingModelStructDataSpecs' })
53+
54+
const props = defineProps<{ modelValue: any }>()
55+
const emits = defineEmits(['update:modelValue'])
56+
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>
57+
const dialogVisible = ref(false) // 弹窗的是否展示
58+
const dialogTitle = ref('新增参数') // 弹窗的标题
59+
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
60+
const structFormRef = ref() // 表单 ref
61+
const formData = ref<any>({
62+
property: {
63+
dataType: DataSpecsDataType.INT,
64+
dataSpecs: {
65+
dataType: DataSpecsDataType.INT
66+
}
67+
}
68+
})
69+
70+
/** 打开 struct 表单 */
71+
const openStructForm = (val: any) => {
72+
dialogVisible.value = true
73+
resetForm()
74+
if (isEmpty(val)) {
75+
return
76+
}
77+
// 编辑时回显数据
78+
formData.value = {
79+
identifier: val.identifier,
80+
name: val.name,
81+
description: val.description,
82+
property: {
83+
dataType: val.childDataType,
84+
dataSpecs: val.dataSpecs,
85+
dataSpecsList: val.dataSpecsList
86+
}
87+
}
88+
}
89+
/** 删除 struct 项 */
90+
const deleteStructItem = (index: number) => {
91+
dataSpecsList.value.splice(index, 1)
92+
}
93+
94+
/** 添加参数 */
95+
const submitForm = async () => {
96+
await structFormRef.value.validate()
97+
98+
try {
99+
const data = unref(formData)
100+
// 构建数据对象
101+
const item = {
102+
identifier: data.identifier,
103+
name: data.name,
104+
description: data.description,
105+
dataType: DataSpecsDataType.STRUCT,
106+
childDataType: data.property.dataType,
107+
dataSpecs:
108+
!!data.property.dataSpecs && Object.keys(data.property.dataSpecs).length > 1
109+
? data.property.dataSpecs
110+
: undefined,
111+
dataSpecsList: data.property.dataSpecsList
112+
}
113+
114+
// 查找是否已有相同 identifier 的项
115+
const existingIndex = dataSpecsList.value.findIndex(
116+
(spec) => spec.identifier === data.identifier
117+
)
118+
if (existingIndex > -1) {
119+
// 更新已有项
120+
dataSpecsList.value[existingIndex] = item
121+
} else {
122+
// 添加新项
123+
dataSpecsList.value.push(item)
124+
}
125+
} finally {
126+
// 隐藏对话框
127+
dialogVisible.value = false
128+
}
129+
}
130+
131+
/** 重置表单 */
132+
const resetForm = () => {
133+
formData.value = {
134+
property: {
135+
dataType: DataSpecsDataType.INT,
136+
dataSpecs: {
137+
dataType: DataSpecsDataType.INT
138+
}
139+
}
140+
}
141+
structFormRef.value?.resetFields()
142+
}
143+
</script>
144+
145+
<style lang="scss" scoped>
146+
.struct-item {
147+
background-color: #e4f2fd;
148+
}
149+
</style>

0 commit comments

Comments
 (0)