Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
773b8dea
authored
Dec 16, 2024
by
芋道源码
Committed by
Gitee
Dec 16, 2024
Browse files
Options
Browse Files
Download
Plain Diff
!620 【功能完善】IOT: 产品物模型
Merge pull request !620 from puhui999/feature/iot
parents
a5565bb5
e36e4858
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1170 additions
and
341 deletions
+1170
-341
src/api/iot/thinkmodelfunction/index.ts
+49
-29
src/views/iot/product/product/detail/ThingModel/ThingModelDataSpecs.vue
+110
-0
src/views/iot/product/product/detail/ThingModel/ThingModelForm.vue
+171
-0
src/views/iot/product/product/detail/ThingModel/config.ts
+50
-0
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelArrayTypeDataSpecs.vue
+34
-0
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelEnumTypeDataSpecs.vue
+56
-0
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelNumberTypeDataSpecs.vue
+49
-0
src/views/iot/product/product/detail/ThingModel/dataSpecs/index.ts
+5
-0
src/views/iot/product/product/detail/ThingModel/index.vue
+50
-30
src/views/iot/product/product/detail/ThinkModelFunctionForm.vue
+0
-232
src/views/iot/product/product/detail/index.vue
+6
-3
src/views/iot/product/product/index.vue
+55
-47
src/views/iot/utils/constants.ts
+535
-0
No files found.
src/api/iot/thinkmodelfunction/index.ts
View file @
773b8dea
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
// IoT 产品物模型 VO
/**
export
interface
ThinkModelFunctionVO
{
* IoT 产品物模型
id
:
number
// 物模型功能编号
*/
identifier
:
string
// 功能标识
export
interface
ThingModelData
{
name
:
string
// 功能名称
id
?:
number
// 物模型功能编号
description
:
string
// 功能描述
identifier
?:
string
// 功能标识
productId
:
number
// 产品编号
name
?:
string
// 功能名称
productKey
:
string
// 产品标识
description
?:
string
// 功能描述
type
:
number
// 功能类型
productId
?:
number
// 产品编号
property
:
string
// 属性
productKey
?:
string
// 产品标识
event
:
string
// 事件
dataType
:
string
// 数据类型,与 dataSpecs 的 dataType 保持一致
service
:
string
// 服务
type
:
ProductFunctionTypeEnum
// 功能类型
property
:
ThingModelProperty
// 属性
event
?:
ThingModelEvent
// 事件
service
?:
ThingModelService
// 服务
}
/**
* ThingModelProperty 类型
*/
export
interface
ThingModelProperty
{
[
key
:
string
]:
any
}
/**
* ThingModelEvent 类型
*/
export
interface
ThingModelEvent
{
[
key
:
string
]:
any
}
/**
* ThingModelService 类型
*/
export
interface
ThingModelService
{
[
key
:
string
]:
any
}
}
// IOT 产品功能(物模型)类型枚举类
// IOT 产品功能(物模型)类型枚举类
...
@@ -30,39 +54,35 @@ export enum ProductFunctionAccessModeEnum {
...
@@ -30,39 +54,35 @@ export enum ProductFunctionAccessModeEnum {
// IoT 产品物模型 API
// IoT 产品物模型 API
export
const
ThinkModelFunctionApi
=
{
export
const
ThinkModelFunctionApi
=
{
// 查询产品物模型分页
// 查询产品物模型分页
get
ThinkModelFunction
Page
:
async
(
params
:
any
)
=>
{
get
ProductThingModel
Page
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/iot/
think-model-function
/page`
,
params
})
return
await
request
.
get
({
url
:
`/iot/
product-thing-model
/page`
,
params
})
},
},
// 获得产品物模型
// 获得产品物模型
get
ThinkModelFunction
ListByProductId
:
async
(
params
:
any
)
=>
{
get
ProductThingModel
ListByProductId
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
return
await
request
.
get
({
url
:
`/iot/
think-model-function
/list-by-product-id`
,
url
:
`/iot/
product-thing-model
/list-by-product-id`
,
params
params
})
})
},
},
// 查询产品物模型详情
// 查询产品物模型详情
get
ThinkModelFunction
:
async
(
id
:
number
)
=>
{
get
ProductThingModel
:
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
`/iot/
think-model-function
/get?id=`
+
id
})
return
await
request
.
get
({
url
:
`/iot/
product-thing-model
/get?id=`
+
id
})
},
},
// 新增产品物模型
// 新增产品物模型
create
ThinkModelFunction
:
async
(
data
:
ThinkModelFunctionVO
)
=>
{
create
ProductThingModel
:
async
(
data
:
ThingModelData
)
=>
{
return
await
request
.
post
({
url
:
`/iot/
think-model-function
/create`
,
data
})
return
await
request
.
post
({
url
:
`/iot/
product-thing-model
/create`
,
data
})
},
},
// 修改产品物模型
// 修改产品物模型
update
ThinkModelFunction
:
async
(
data
:
ThinkModelFunctionVO
)
=>
{
update
ProductThingModel
:
async
(
data
:
ThingModelData
)
=>
{
return
await
request
.
put
({
url
:
`/iot/
think-model-function
/update`
,
data
})
return
await
request
.
put
({
url
:
`/iot/
product-thing-model
/update`
,
data
})
},
},
// 删除产品物模型
// 删除产品物模型
deleteThinkModelFunction
:
async
(
id
:
number
)
=>
{
deleteProductThingModel
:
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
`/iot/think-model-function/delete?id=`
+
id
})
return
await
request
.
delete
({
url
:
`/iot/product-thing-model/delete?id=`
+
id
})
},
// 导出产品物模型 Excel
exportThinkModelFunction
:
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
`/iot/think-model-function/export-excel`
,
params
})
}
}
}
}
src/views/iot/product/product/detail/ThingModel/ThingModelDataSpecs.vue
0 → 100644
View file @
773b8dea
<
template
>
<el-form-item
label=
"数据类型"
prop=
"dataType"
>
<el-select
v-model=
"property.dataType"
placeholder=
"请选择数据类型"
@
change=
"handleChange"
>
<el-option
v-for=
"option in dataTypeOptions"
:key=
"option.value"
:label=
"option.label"
:value=
"option.value"
/>
</el-select>
</el-form-item>
<!-- 数值型配置 -->
<ThingModelNumberTypeDataSpecs
v-if=
"
[DataSpecsDataType.INT, DataSpecsDataType.DOUBLE, DataSpecsDataType.FLOAT].includes(
property.dataType || ''
)
"
v-model=
"property.dataSpecs"
/>
<!-- 枚举型配置 -->
<ThingModelEnumTypeDataSpecs
v-if=
"property.dataType === DataSpecsDataType.ENUM"
v-model=
"property.dataSpecsList"
/>
<!-- 布尔型配置 -->
<el-form-item
v-if=
"property.dataType === DataSpecsDataType.BOOL"
label=
"布尔值"
prop=
"bool"
>
<template
v-for=
"item in property.dataSpecsList"
:key=
"item.value"
>
<div
class=
"flex items-center justify-start w-1/1 mb-5px"
>
<span>
{{
item
.
value
}}
</span>
<span
class=
"mx-2"
>
-
</span>
<el-input
v-model=
"item.name"
:placeholder=
"`如:$
{item.value === 0 ? '关' : '开'}`"
class="w-255px!"
/>
</div>
</
template
>
</el-form-item>
<!-- 文本型配置 -->
<el-form-item
v-if=
"property.dataType === DataSpecsDataType.TEXT"
label=
"数据长度"
prop=
"text"
>
<el-input
v-model=
"property.dataSpecs.length"
class=
"w-255px!"
placeholder=
"请输入文本字节长度"
>
<
template
#
append
>
字节
</
template
>
</el-input>
</el-form-item>
<!-- 时间型配置 -->
<el-form-item
v-if=
"property.dataType === DataSpecsDataType.DATE"
label=
"时间格式"
prop=
"date"
>
<el-input
class=
"w-255px!"
disabled
placeholder=
"String类型的UTC时间戳(毫秒)"
/>
</el-form-item>
<!-- 数组型配置-->
<ThingModelArrayTypeDataSpecs
v-if=
"property.dataType === DataSpecsDataType.ARRAY"
v-model=
"property.dataSpecs"
/>
<!-- TODO puhui999: Struct 属性待完善 -->
<el-form-item
label=
"读写类型"
prop=
"accessMode"
>
<el-radio-group
v-model=
"property.accessMode"
>
<el-radio
label=
"rw"
>
读写
</el-radio>
<el-radio
label=
"r"
>
只读
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"属性描述"
prop=
"description"
>
<el-input
v-model=
"property.description"
placeholder=
"请输入属性描述"
type=
"textarea"
/>
</el-form-item>
</template>
<
script
lang=
"ts"
setup
>
import
{
useVModel
}
from
'@vueuse/core'
import
{
DataSpecsDataType
,
dataTypeOptions
}
from
'./config'
import
{
ThingModelArrayTypeDataSpecs
,
ThingModelEnumTypeDataSpecs
,
ThingModelNumberTypeDataSpecs
}
from
'./dataSpecs'
import
{
ThingModelProperty
}
from
'@/api/iot/thinkmodelfunction'
/** 物模型数据 */
defineOptions
({
name
:
'ThingModelDataSpecs'
})
const
props
=
defineProps
<
{
modelValue
:
any
}
>
()
const
emits
=
defineEmits
([
'update:modelValue'
])
const
property
=
useVModel
(
props
,
'modelValue'
,
emits
)
as
Ref
<
ThingModelProperty
>
/** 属性值的数据类型切换时初始化相关数据 */
const
handleChange
=
(
dataType
:
any
)
=>
{
property
.
value
.
dataSpecsList
=
[]
property
.
value
.
dataSpecs
=
{}
property
.
value
.
dataSpecs
.
dataType
=
dataType
switch
(
dataType
)
{
case
DataSpecsDataType
.
ENUM
:
property
.
value
.
dataSpecsList
.
push
({
dataType
:
DataSpecsDataType
.
ENUM
,
name
:
''
,
// 枚举项的名称
value
:
undefined
// 枚举值
})
break
case
DataSpecsDataType
.
BOOL
:
for
(
let
i
=
0
;
i
<
2
;
i
++
)
{
property
.
value
.
dataSpecsList
.
push
({
dataType
:
DataSpecsDataType
.
BOOL
,
name
:
''
,
// 布尔值的名称
value
:
i
// 布尔值
})
}
break
}
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/iot/product/product/detail/ThingModel/ThingModelForm.vue
0 → 100644
View file @
773b8dea
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
>
<el-form
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:rules=
"formRules"
label-width=
"100px"
>
<el-form-item
label=
"功能类型"
prop=
"type"
>
<el-radio-group
v-model=
"formData.type"
>
<el-radio-button
:value=
"1"
>
属性
</el-radio-button>
<el-radio-button
:value=
"2"
>
服务
</el-radio-button>
<el-radio-button
:value=
"3"
>
事件
</el-radio-button>
</el-radio-group>
</el-form-item>
<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-if=
"formData.type === ProductFunctionTypeEnum.PROPERTY"
v-model=
"formData.property"
/>
</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
{
ProductVO
}
from
'@/api/iot/product/product'
import
ThingModelDataSpecs
from
'./ThingModelDataSpecs.vue'
import
{
ProductFunctionTypeEnum
,
ThingModelData
,
ThinkModelFunctionApi
}
from
'@/api/iot/thinkmodelfunction'
import
{
IOT_PROVIDE_KEY
}
from
'@/views/iot/utils/constants'
import
{
DataSpecsDataType
}
from
'./config'
import
{
cloneDeep
}
from
'lodash-es'
defineOptions
({
name
:
'IoTProductThingModelForm'
})
const
product
=
inject
<
Ref
<
ProductVO
>>
(
IOT_PROVIDE_KEY
.
PRODUCT
)
// 注入产品信息
const
{
t
}
=
useI18n
()
const
message
=
useMessage
()
const
dialogVisible
=
ref
(
false
)
const
dialogTitle
=
ref
(
''
)
const
formLoading
=
ref
(
false
)
const
formType
=
ref
(
''
)
const
formData
=
ref
<
ThingModelData
>
({
type
:
ProductFunctionTypeEnum
.
PROPERTY
,
dataType
:
DataSpecsDataType
.
INT
,
property
:
{
dataType
:
DataSpecsDataType
.
INT
,
dataSpecs
:
{
dataType
:
DataSpecsDataType
.
INT
}
}
})
// TODO puhui999: 表单校验待完善
const
formRules
=
reactive
({
name
:
[
{
required
:
true
,
message
:
'功能名称不能为空'
,
trigger
:
'blur'
},
{
pattern
:
/^
[\u
4e00-
\u
9fa5a-zA-Z0-9
][\u
4e00-
\u
9fa5a-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
:
(
rule
,
value
,
callback
)
=>
{
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.dataType.type'
:
[{
required
:
true
,
message
:
'数据类型不能为空'
,
trigger
:
'blur'
}],
'property.accessMode'
:
[{
required
:
true
,
message
:
'读写类型不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
if
(
id
)
{
formLoading
.
value
=
true
try
{
formData
.
value
=
await
ThinkModelFunctionApi
.
getProductThingModel
(
id
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
,
close
:
()
=>
(
dialogVisible
.
value
=
false
)
})
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
const
submitForm
=
async
()
=>
{
await
formRef
.
value
.
validate
()
formLoading
.
value
=
true
try
{
const
data
=
cloneDeep
(
formData
.
value
)
as
ThingModelData
// 信息补全
data
.
productId
=
product
!
.
value
.
id
data
.
productKey
=
product
!
.
value
.
productKey
data
.
description
=
data
.
property
.
description
data
.
dataType
=
data
.
property
.
dataType
data
.
property
.
identifier
=
data
.
identifier
data
.
property
.
name
=
data
.
name
if
(
formType
.
value
===
'create'
)
{
await
ThinkModelFunctionApi
.
createProductThingModel
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
ThinkModelFunctionApi
.
updateProductThingModel
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
}
finally
{
dialogVisible
.
value
=
false
// 确保关闭弹框
emit
(
'success'
)
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
type
:
ProductFunctionTypeEnum
.
PROPERTY
,
dataType
:
DataSpecsDataType
.
INT
,
property
:
{
dataType
:
DataSpecsDataType
.
INT
,
dataSpecs
:
{
dataType
:
DataSpecsDataType
.
INT
}
}
}
formRef
.
value
?.
resetFields
()
}
</
script
>
src/views/iot/product/product/detail/ThingModel/config.ts
0 → 100644
View file @
773b8dea
/** 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
}
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelArrayTypeDataSpecs.vue
0 → 100644
View file @
773b8dea
<
template
>
<el-form-item
label=
"元素类型"
prop=
"childDataType"
>
<el-radio-group
v-model=
"dataSpecs.childDataType"
>
<template
v-for=
"item in dataTypeOptions"
:key=
"item.value"
>
<el-radio
:value=
"item.value"
v-if=
"
!(
[DataSpecsDataType.ENUM, DataSpecsDataType.ARRAY, DataSpecsDataType.DATE] as any[]
).includes(item.value)
"
>
{{
item
.
label
}}
</el-radio>
</
template
>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"元素个数"
prop=
"size"
>
<el-input
v-model=
"dataSpecs.size"
placeholder=
"请输入数组中的元素个数"
/>
</el-form-item>
</template>
<
script
lang=
"ts"
setup
>
import
{
useVModel
}
from
'@vueuse/core'
import
{
DataSpecsDataType
,
dataTypeOptions
}
from
'../config'
/** 数组型的 dataSpecs 配置组件 */
defineOptions
({
name
:
'ThingModelArrayTypeDataSpecs'
})
const
props
=
defineProps
<
{
modelValue
:
any
}
>
()
const
emits
=
defineEmits
([
'update:modelValue'
])
const
dataSpecs
=
useVModel
(
props
,
'modelValue'
,
emits
)
as
Ref
<
any
>
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelEnumTypeDataSpecs.vue
0 → 100644
View file @
773b8dea
<
template
>
<el-form-item
label=
"枚举项"
prop=
"enum"
>
<div
class=
"flex flex-col"
>
<div
class=
"flex items-center"
>
<span
class=
"flex-1"
>
参数值
</span>
<span
class=
"flex-1"
>
参数描述
</span>
</div>
<div
v-for=
"(item, index) in dataSpecsList"
:key=
"index"
class=
"flex items-center justify-between mb-5px"
>
<el-input
v-model=
"item.value"
placeholder=
"请输入枚举值,如'0'"
/>
<span
class=
"mx-2"
>
~
</span>
<el-input
v-model=
"item.name"
placeholder=
"对该枚举项的描述"
/>
<el-button
link
type=
"primary"
class=
"ml-10px"
@
click=
"deleteEnum(index)"
>
删除
</el-button>
</div>
<el-button
link
type=
"primary"
@
click=
"addEnum"
>
+添加枚举项
</el-button>
</div>
</el-form-item>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
useVModel
}
from
'@vueuse/core'
import
{
DataSpecsDataType
,
DataSpecsEnumOrBoolDataVO
}
from
'@/views/iot/product/product/detail/ThingModel/config'
/** 枚举型的 dataSpecs 配置组件 */
defineOptions
({
name
:
'ThingModelEnumTypeDataSpecs'
})
const
props
=
defineProps
<
{
modelValue
:
any
}
>
()
const
emits
=
defineEmits
([
'update:modelValue'
])
const
dataSpecsList
=
useVModel
(
props
,
'modelValue'
,
emits
)
as
Ref
<
DataSpecsEnumOrBoolDataVO
[]
>
const
message
=
useMessage
()
/** 添加枚举项 */
const
addEnum
=
()
=>
{
dataSpecsList
.
value
.
push
({
dataType
:
DataSpecsDataType
.
ENUM
,
name
:
''
,
// 枚举项的名称
value
:
undefined
// 枚举值
})
}
/** 删除枚举项 */
const
deleteEnum
=
(
index
:
number
)
=>
{
if
(
dataSpecsList
.
value
.
length
===
1
)
{
message
.
warning
(
'至少需要一个枚举项'
)
return
}
dataSpecsList
.
value
.
splice
(
index
,
1
)
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/iot/product/product/detail/ThingModel/dataSpecs/ThingModelNumberTypeDataSpecs.vue
0 → 100644
View file @
773b8dea
<
template
>
<el-form-item
label=
"取值范围"
prop=
"max"
>
<div
class=
"flex items-center justify-between"
>
<el-input
v-model=
"dataSpecs.min"
placeholder=
"请输入最小值"
/>
<span
class=
"mx-2"
>
~
</span>
<el-input
v-model=
"dataSpecs.max"
placeholder=
"请输入最大值"
/>
</div>
</el-form-item>
<el-form-item
label=
"步长"
prop=
"step"
>
<el-input
v-model=
"dataSpecs.step"
placeholder=
"请输入步长"
/>
</el-form-item>
<el-form-item
label=
"单位"
prop=
"unit"
>
<el-select
:model-value=
"dataSpecs.unit ? dataSpecs.unitName + '-' + dataSpecs.unit : ''"
filterable
placeholder=
"请选择单位"
style=
"width: 240px"
@
change=
"unitChange"
>
<el-option
v-for=
"(item, index) in UnifyUnitSpecsDTO"
:key=
"index"
:label=
"item.Name + '-' + item.Symbol"
:value=
"item.Name + '-' + item.Symbol"
/>
</el-select>
</el-form-item>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
useVModel
}
from
'@vueuse/core'
import
{
UnifyUnitSpecsDTO
}
from
'@/views/iot/utils/constants'
import
{
DataSpecsNumberDataVO
}
from
'../config'
/** 数值型的 dataSpecs 配置组件 */
defineOptions
({
name
:
'ThingModelNumberTypeDataSpecs'
})
const
props
=
defineProps
<
{
modelValue
:
any
}
>
()
const
emits
=
defineEmits
([
'update:modelValue'
])
const
dataSpecs
=
useVModel
(
props
,
'modelValue'
,
emits
)
as
Ref
<
DataSpecsNumberDataVO
>
/** 单位发生变化时触发 */
const
unitChange
=
(
UnitSpecs
:
string
)
=>
{
const
[
unitName
,
unit
]
=
UnitSpecs
.
split
(
'-'
)
dataSpecs
.
value
.
unitName
=
unitName
dataSpecs
.
value
.
unit
=
unit
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/iot/product/product/detail/ThingModel/dataSpecs/index.ts
0 → 100644
View file @
773b8dea
import
ThingModelEnumTypeDataSpecs
from
'./ThingModelEnumTypeDataSpecs.vue'
import
ThingModelNumberTypeDataSpecs
from
'./ThingModelNumberTypeDataSpecs.vue'
import
ThingModelArrayTypeDataSpecs
from
'./ThingModelArrayTypeDataSpecs.vue'
export
{
ThingModelEnumTypeDataSpecs
,
ThingModelNumberTypeDataSpecs
,
ThingModelArrayTypeDataSpecs
}
src/views/iot/product/product/detail/Thin
kModelFunction
.vue
→
src/views/iot/product/product/detail/Thin
gModel/index
.vue
View file @
773b8dea
...
@@ -2,18 +2,18 @@
...
@@ -2,18 +2,18 @@
<ContentWrap>
<ContentWrap>
<!-- 搜索工作栏 -->
<!-- 搜索工作栏 -->
<el-form
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
ref=
"queryFormRef"
:inline=
"true"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<el-form-item
label=
"功能类型"
prop=
"name"
>
<el-form-item
label=
"功能类型"
prop=
"name"
>
<el-select
<el-select
v-model=
"queryParams.type"
v-model=
"queryParams.type"
placeholder=
"请选择功能类型"
clearable
class=
"!w-240px"
class=
"!w-240px"
clearable
placeholder=
"请选择功能类型"
>
>
<el-option
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_FUNCTION_TYPE)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.IOT_PRODUCT_FUNCTION_TYPE)"
...
@@ -24,46 +24,62 @@
...
@@ -24,46 +24,62 @@
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
><Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"resetQuery"
><Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
<el-button
type=
"primary
"
v-hasPermi=
"[`iot:product-thing-model:create`]
"
plain
plain
type=
"primary"
@
click=
"openForm('create')"
@
click=
"openForm('create')"
v-hasPermi=
"['iot:think-model-function:create']"
>
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加功能
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
添加功能
</el-button>
</el-button>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
</ContentWrap>
</ContentWrap>
<ContentWrap>
<ContentWrap>
<el-tabs>
<el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
:s
tripe=
"true"
:show-overflow-tooltip
=
"true"
>
<el-table
v-loading=
"loading"
:data=
"list"
:s
how-overflow-tooltip=
"true"
:stripe
=
"true"
>
<el-table-column
label=
"功能类型"
align=
"center
"
prop=
"type"
>
<el-table-column
align=
"center"
label=
"功能类型
"
prop=
"type"
>
<template
#
default=
"scope"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.IOT_PRODUCT_FUNCTION_TYPE"
:value=
"scope.row.type"
/>
<dict-tag
:type=
"DICT_TYPE.IOT_PRODUCT_FUNCTION_TYPE"
:value=
"scope.row.type"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"功能名称"
align=
"center"
prop=
"name"
/>
<el-table-column
align=
"center"
label=
"功能名称"
prop=
"name"
/>
<el-table-column
label=
"标识符"
align=
"center"
prop=
"identifier"
/>
<el-table-column
align=
"center"
label=
"标识符"
prop=
"identifier"
/>
<el-table-column
label=
"数据类型"
align=
"center"
prop=
"identifier"
/>
<el-table-column
align=
"center"
label=
"数据类型"
prop=
"identifier"
>
<el-table-column
label=
"数据定义"
align=
"center"
prop=
"identifier"
/>
<
template
#
default=
"{ row }"
>
<el-table-column
label=
"操作"
align=
"center"
>
{{
dataTypeOptionsLabel
(
row
.
property
.
dataType
)
??
'-'
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"数据定义"
prop=
"identifier"
>
<
template
#
default=
"{ row }"
>
<!-- TODO puhui999: 数据定义展示待完善 -->
{{
row
.
property
.
dataSpecs
??
row
.
property
.
dataSpecsList
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"操作"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
v-hasPermi=
"[`iot:product-thing-model:update`]"
link
link
type=
"primary"
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"[`iot:think-model-function:update`]"
>
>
编辑
编辑
</el-button>
</el-button>
<el-button
<el-button
v-hasPermi=
"['iot:product-thing-model:delete']"
link
link
type=
"danger"
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['iot:think-model-function:delete']"
>
>
删除
删除
</el-button>
</el-button>
...
@@ -72,29 +88,31 @@
...
@@ -72,29 +88,31 @@
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</el-tabs>
</el-tabs>
</ContentWrap>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<!-- 表单弹窗:添加/修改 -->
<Thin
kModelFunctionForm
ref=
"formRef"
:product=
"product
"
@
success=
"getList"
/>
<Thin
gModelForm
ref=
"formRef
"
@
success=
"getList"
/>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
ProductVO
}
from
'@/api/iot/product/product'
import
{
ThingModelData
,
ThinkModelFunctionApi
}
from
'@/api/iot/thinkmodelfunction'
import
{
ThinkModelFunctionApi
,
ThinkModelFunctionVO
}
from
'@/api/iot/thinkmodelfunction'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
ThinkModelFunctionForm
from
'./ThinkModelFunctionForm.vue'
import
ThingModelForm
from
'./ThingModelForm.vue'
import
{
ProductVO
}
from
'@/api/iot/product/product'
import
{
IOT_PROVIDE_KEY
}
from
'@/views/iot/utils/constants'
import
{
getDataTypeOptionsLabel
}
from
'@/views/iot/product/product/detail/ThingModel/config'
const
props
=
defineProps
<
{
product
:
ProductVO
}
>
(
)
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
<
Thin
kModelFunctionVO
[]
>
([])
// 列表的数据
const
list
=
ref
<
Thin
gModelData
[]
>
([])
// 列表的数据
const
total
=
ref
(
0
)
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的总页数
const
queryParams
=
reactive
({
const
queryParams
=
reactive
({
pageNo
:
1
,
pageNo
:
1
,
...
@@ -104,13 +122,15 @@ const queryParams = reactive({
...
@@ -104,13 +122,15 @@ const queryParams = reactive({
})
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
queryFormRef
=
ref
()
// 搜索的表单
const
product
=
inject
<
Ref
<
ProductVO
>>
(
IOT_PROVIDE_KEY
.
PRODUCT
)
// 注入产品信息
const
dataTypeOptionsLabel
=
computed
(()
=>
(
value
:
string
)
=>
getDataTypeOptionsLabel
(
value
))
// 解析数据类型
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
try
{
try
{
queryParams
.
productId
=
pro
ps
.
product
.
id
queryParams
.
productId
=
pro
duct
?.
value
?.
id
||
-
1
const
data
=
await
ThinkModelFunctionApi
.
get
ThinkModelFunction
Page
(
queryParams
)
const
data
=
await
ThinkModelFunctionApi
.
get
ProductThingModel
Page
(
queryParams
)
list
.
value
=
data
.
list
list
.
value
=
data
.
list
total
.
value
=
data
.
total
total
.
value
=
data
.
total
}
finally
{
}
finally
{
...
@@ -142,7 +162,7 @@ const handleDelete = async (id: number) => {
...
@@ -142,7 +162,7 @@ const handleDelete = async (id: number) => {
// 删除的二次确认
// 删除的二次确认
await
message
.
delConfirm
()
await
message
.
delConfirm
()
// 发起删除
// 发起删除
await
ThinkModelFunctionApi
.
delete
ThinkModelFunction
(
id
)
await
ThinkModelFunctionApi
.
delete
ProductThingModel
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新列表
// 刷新列表
await
getList
()
await
getList
()
...
...
src/views/iot/product/product/detail/ThinkModelFunctionForm.vue
deleted
100644 → 0
View file @
a5565bb5
<
template
>
<Dialog
:title=
"dialogTitle"
v-model=
"dialogVisible"
>
<el-form
ref=
"formRef"
:model=
"formData"
:rules=
"formRules"
label-width=
"100px"
v-loading=
"formLoading"
>
<el-form-item
label=
"功能类型"
prop=
"type"
>
<el-radio-group
v-model=
"formData.type"
>
<el-radio-button
:value=
"1"
>
属性
</el-radio-button>
<el-radio-button
:value=
"2"
>
服务
</el-radio-button>
<el-radio-button
:value=
"3"
>
事件
</el-radio-button>
</el-radio-group>
</el-form-item>
<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=
"请输入标识符"
:disabled=
"formType === 'update'"
/>
</el-form-item>
<el-form-item
label=
"数据类型"
prop=
"type"
>
<el-select
v-model=
"formData.property.dataType.type"
placeholder=
"请选择数据类型"
:disabled=
"formType === 'update'"
>
<el-option
key=
"int"
label=
"int32 (整数型)"
value=
"int"
/>
<el-option
key=
"float"
label=
"float (单精度浮点型)"
value=
"float"
/>
<el-option
key=
"double"
label=
"double (双精度浮点型)"
value=
"double"
/>
<!--
<el-option
key=
"text"
label=
"text (文本型)"
value=
"text"
/>
-->
<!--
<el-option
key=
"date"
label=
"date (日期型)"
value=
"date"
/>
-->
<!--
<el-option
key=
"bool"
label=
"bool (布尔型)"
value=
"bool"
/>
-->
<!--
<el-option
key=
"enum"
label=
"enum (枚举型)"
value=
"enum"
/>
-->
<!--
<el-option
key=
"struct"
label=
"struct (结构体)"
value=
"struct"
/>
-->
<!--
<el-option
key=
"array"
label=
"array (数组)"
value=
"array"
/>
-->
</el-select>
</el-form-item>
<el-form-item
label=
"取值范围"
prop=
"max"
>
<el-input
v-model=
"formData.property.dataType.specs.min"
placeholder=
"请输入最小值"
/>
<span
class=
"mx-2"
>
~
</span>
<el-input
v-model=
"formData.property.dataType.specs.max"
placeholder=
"请输入最大值"
/>
</el-form-item>
<el-form-item
label=
"步长"
prop=
"step"
>
<el-input
v-model=
"formData.property.dataType.specs.step"
placeholder=
"请输入步长"
/>
</el-form-item>
<el-form-item
label=
"单位"
prop=
"unit"
>
<el-input
v-model=
"formData.property.dataType.specs.unit"
placeholder=
"请输入单位"
/>
</el-form-item>
<el-form-item
label=
"读写类型"
prop=
"accessMode"
>
<el-radio-group
v-model=
"formData.property.accessMode"
>
<el-radio
label=
"rw"
>
读写
</el-radio>
<el-radio
label=
"r"
>
只读
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"属性描述"
prop=
"property.description"
>
<el-input
type=
"textarea"
v-model=
"formData.property.description"
placeholder=
"请输入属性描述"
/>
</el-form-item>
</el-form>
<template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
ProductVO
}
from
'@/api/iot/product/product'
import
{
ProductFunctionAccessModeEnum
,
ProductFunctionTypeEnum
,
ThinkModelFunctionApi
,
ThinkModelFunctionVO
}
from
'@/api/iot/thinkmodelfunction'
const
props
=
defineProps
<
{
product
:
ProductVO
}
>
()
defineOptions
({
name
:
'ThinkModelFunctionForm'
})
const
{
t
}
=
useI18n
()
const
message
=
useMessage
()
const
dialogVisible
=
ref
(
false
)
const
dialogTitle
=
ref
(
''
)
const
formLoading
=
ref
(
false
)
const
formType
=
ref
(
''
)
const
formData
=
ref
({
id
:
undefined
,
productId
:
undefined
,
productKey
:
undefined
,
identifier
:
undefined
,
name
:
undefined
,
description
:
undefined
,
type
:
ProductFunctionTypeEnum
.
PROPERTY
,
property
:
{
identifier
:
undefined
,
name
:
undefined
,
accessMode
:
ProductFunctionAccessModeEnum
.
READ_WRITE
,
required
:
true
,
dataType
:
{
type
:
undefined
,
specs
:
{
min
:
undefined
,
max
:
undefined
,
step
:
undefined
,
unit
:
undefined
}
},
description
:
undefined
// 添加这一行
}
})
const
formRules
=
reactive
({
name
:
[
{
required
:
true
,
message
:
'功能名称不能为空'
,
trigger
:
'blur'
},
{
pattern
:
/^
[\u
4e00-
\u
9fa5a-zA-Z0-9
][\u
4e00-
\u
9fa5a-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
:
(
rule
,
value
,
callback
)
=>
{
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.dataType.type'
:
[{
required
:
true
,
message
:
'数据类型不能为空'
,
trigger
:
'blur'
}],
'property.accessMode'
:
[{
required
:
true
,
message
:
'读写类型不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
if
(
id
)
{
formLoading
.
value
=
true
try
{
formData
.
value
=
await
ThinkModelFunctionApi
.
getThinkModelFunction
(
id
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
,
close
:
()
=>
(
dialogVisible
.
value
=
false
)
})
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
const
submitForm
=
async
()
=>
{
await
formRef
.
value
.
validate
()
formLoading
.
value
=
true
try
{
const
data
=
formData
.
value
as
unknown
as
ThinkModelFunctionVO
data
.
productId
=
props
.
product
.
id
data
.
productKey
=
props
.
product
.
productKey
if
(
formType
.
value
===
'create'
)
{
await
ThinkModelFunctionApi
.
createThinkModelFunction
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
ThinkModelFunctionApi
.
updateThinkModelFunction
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
}
finally
{
dialogVisible
.
value
=
false
// 确保关闭弹框
emit
(
'success'
)
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
id
:
undefined
,
productId
:
undefined
,
productKey
:
undefined
,
identifier
:
undefined
,
name
:
undefined
,
description
:
undefined
,
type
:
ProductFunctionTypeEnum
.
PROPERTY
,
property
:
{
identifier
:
undefined
,
name
:
undefined
,
accessMode
:
ProductFunctionAccessModeEnum
.
READ_WRITE
,
required
:
true
,
dataType
:
{
type
:
undefined
,
specs
:
{
min
:
undefined
,
max
:
undefined
,
step
:
undefined
,
unit
:
undefined
}
},
description
:
undefined
// 确保重置 description 字段
}
}
formRef
.
value
?.
resetFields
()
}
</
script
>
src/views/iot/product/product/detail/index.vue
View file @
773b8dea
...
@@ -8,8 +8,8 @@
...
@@ -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=
"功能定义"
name=
"function
"
>
<el-tab-pane
label=
"功能定义"
lazy
name=
"thingModel
"
>
<
ThinkModelFunction
v-if=
"activeTab === 'function'"
:product=
"product
"
/>
<
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,9 +22,10 @@ import { DeviceApi } from '@/api/iot/device/device'
...
@@ -22,9 +22,10 @@ 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
ThinkModelFunction
from
'./ThinkModelFunction
.vue'
import
IoTProductThingModel
from
'./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'
defineOptions
({
name
:
'IoTProductDetail'
})
defineOptions
({
name
:
'IoTProductDetail'
})
...
@@ -38,6 +39,8 @@ const loading = ref(true) // 加载中
...
@@ -38,6 +39,8 @@ const loading = ref(true) // 加载中
const
product
=
ref
<
ProductVO
>
({}
as
ProductVO
)
// 详情
const
product
=
ref
<
ProductVO
>
({}
as
ProductVO
)
// 详情
const
activeTab
=
ref
(
'info'
)
// 默认为 info 标签页
const
activeTab
=
ref
(
'info'
)
// 默认为 info 标签页
provide
(
IOT_PROVIDE_KEY
.
PRODUCT
,
product
)
// 提供产品信息给产品信息详情页的所有子组件
/** 获取详情 */
/** 获取详情 */
const
getProductData
=
async
(
id
:
number
)
=>
{
const
getProductData
=
async
(
id
:
number
)
=>
{
loading
.
value
=
true
loading
.
value
=
true
...
...
src/views/iot/product/product/index.vue
View file @
773b8dea
...
@@ -2,49 +2,57 @@
...
@@ -2,49 +2,57 @@
<ContentWrap>
<ContentWrap>
<!-- 搜索工作栏 -->
<!-- 搜索工作栏 -->
<el-form
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
ref=
"queryFormRef"
:inline=
"true"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<el-form-item
label=
"产品名称"
prop=
"name"
>
<el-form-item
label=
"产品名称"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
placeholder=
"请输入产品名称
"
class=
"!w-240px
"
clearable
clearable
placeholder=
"请输入产品名称"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"ProductKey"
prop=
"productKey"
>
<el-form-item
label=
"ProductKey"
prop=
"productKey"
>
<el-input
<el-input
v-model=
"queryParams.productKey"
v-model=
"queryParams.productKey"
placeholder=
"请输入产品标识
"
class=
"!w-240px
"
clearable
clearable
placeholder=
"请输入产品标识"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
><Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"resetQuery"
><Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
<el-button
type=
"primary
"
v-hasPermi=
"['iot:product:create']
"
plain
plain
type=
"primary"
@
click=
"openForm('create')"
@
click=
"openForm('create')"
v-hasPermi=
"['iot:product:create']"
>
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新增
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
</el-button>
<el-button
<el-button
type=
"success"
v-hasPermi=
"['iot:product:export']"
:loading=
"exportLoading"
plain
plain
type=
"success"
@
click=
"handleExport"
@
click=
"handleExport"
:loading=
"exportLoading"
v-hasPermi=
"['iot:product:export']"
>
>
<Icon
icon=
"ep:download"
class=
"mr-5px"
/>
导出
<Icon
class=
"mr-5px"
icon=
"ep:download"
/>
导出
</el-button>
</el-button>
</el-form-item>
</el-form-item>
<!-- 视图切换按钮 -->
<!-- 视图切换按钮 -->
...
@@ -64,8 +72,8 @@
...
@@ -64,8 +72,8 @@
<!-- 卡片视图 -->
<!-- 卡片视图 -->
<ContentWrap>
<ContentWrap>
<el-row
v-if=
"viewMode === 'card'"
:gutter=
"16"
>
<el-row
v-if=
"viewMode === 'card'"
:gutter=
"16"
>
<el-col
v-for=
"item in list"
:key=
"item.id"
:
xs=
"24"
:sm=
"12"
:md=
"12"
:lg=
"6
"
class=
"mb-4"
>
<el-col
v-for=
"item in list"
:key=
"item.id"
:
lg=
"6"
:md=
"12"
:sm=
"12"
:xs=
"24
"
class=
"mb-4"
>
<el-card
class=
"h-full transition-colors"
:body-style=
"
{ padding: '0' }
">
<el-card
:body-style=
"
{ padding: '0' }" class="h-full transition-colors
">
<!-- 内容区域 -->
<!-- 内容区域 -->
<div
class=
"p-4"
>
<div
class=
"p-4"
>
<!-- 标题区域 -->
<!-- 标题区域 -->
...
@@ -103,41 +111,41 @@
...
@@ -103,41 +111,41 @@
<!-- 按钮组 -->
<!-- 按钮组 -->
<div
class=
"flex items-center px-0"
>
<div
class=
"flex items-center px-0"
>
<el-button
<el-button
v-hasPermi=
"['iot:product:update']"
class=
"flex-1 !px-2 !h-[32px] text-[13px]"
class=
"flex-1 !px-2 !h-[32px] text-[13px]"
type=
"primary"
plain
plain
type=
"primary"
@
click=
"openForm('update', item.id)"
@
click=
"openForm('update', item.id)"
v-hasPermi=
"['iot:product:update']"
>
>
<Icon
icon=
"ep:edit-pen"
class=
"mr-1
"
/>
<Icon
class=
"mr-1"
icon=
"ep:edit-pen
"
/>
编辑
编辑
</el-button>
</el-button>
<el-button
<el-button
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
type=
"warning"
plain
plain
type=
"warning"
@
click=
"openDetail(item.id)"
@
click=
"openDetail(item.id)"
>
>
<Icon
icon=
"ep:view"
class=
"mr-1
"
/>
<Icon
class=
"mr-1"
icon=
"ep:view
"
/>
详情
详情
</el-button>
</el-button>
<el-button
<el-button
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
type=
"success"
plain
plain
type=
"success"
@
click=
"openObjectModel(item)"
@
click=
"openObjectModel(item)"
>
>
<Icon
icon=
"ep:scale-to-original"
class=
"mr-1
"
/>
<Icon
class=
"mr-1"
icon=
"ep:scale-to-original
"
/>
物模型
物模型
</el-button>
</el-button>
<div
class=
"mx-[10px] h-[20px] w-[1px] bg-[#dcdfe6]"
></div>
<div
class=
"mx-[10px] h-[20px] w-[1px] bg-[#dcdfe6]"
></div>
<el-button
<el-button
v-hasPermi=
"['iot:product:delete']"
:disabled=
"item.status === 1"
class=
"!px-2 !h-[32px] text-[13px]"
class=
"!px-2 !h-[32px] text-[13px]"
type=
"danger"
plain
plain
type=
"danger"
@
click=
"handleDelete(item.id)"
@
click=
"handleDelete(item.id)"
v-hasPermi=
"['iot:product:delete']"
:disabled=
"item.status === 1"
>
>
<Icon
icon=
"ep:delete"
/>
<Icon
icon=
"ep:delete"
/>
</el-button>
</el-button>
...
@@ -148,68 +156,68 @@
...
@@ -148,68 +156,68 @@
</el-row>
</el-row>
<!-- 列表视图 -->
<!-- 列表视图 -->
<el-table
v-else
v-loading=
"loading"
:data=
"list"
:s
tripe=
"true"
:show-overflow-tooltip
=
"true"
>
<el-table
v-else
v-loading=
"loading"
:data=
"list"
:s
how-overflow-tooltip=
"true"
:stripe
=
"true"
>
<el-table-column
label=
"ID"
align=
"center
"
prop=
"id"
/>
<el-table-column
align=
"center"
label=
"ID
"
prop=
"id"
/>
<el-table-column
label=
"ProductKey"
align=
"center
"
prop=
"productKey"
/>
<el-table-column
align=
"center"
label=
"ProductKey
"
prop=
"productKey"
/>
<el-table-column
label=
"品类"
align=
"center
"
prop=
"categoryName"
/>
<el-table-column
align=
"center"
label=
"品类
"
prop=
"categoryName"
/>
<el-table-column
label=
"设备类型"
align=
"center
"
prop=
"deviceType"
>
<el-table-column
align=
"center"
label=
"设备类型
"
prop=
"deviceType"
>
<template
#
default=
"scope"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
:value=
"scope.row.deviceType"
/>
<dict-tag
:type=
"DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
:value=
"scope.row.deviceType"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"产品图标"
align=
"center
"
prop=
"icon"
>
<el-table-column
align=
"center"
label=
"产品图标
"
prop=
"icon"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-image
<el-image
v-if=
"scope.row.icon"
v-if=
"scope.row.icon"
:preview-src-list=
"[scope.row.icon]"
:src=
"scope.row.icon"
:src=
"scope.row.icon"
class=
"w-40px h-40px"
class=
"w-40px h-40px"
:preview-src-list=
"[scope.row.icon]"
/>
/>
<span
v-else
>
-
</span>
<span
v-else
>
-
</span>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"产品图片"
align=
"center
"
prop=
"picture"
>
<el-table-column
align=
"center"
label=
"产品图片
"
prop=
"picture"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-image
<el-image
v-if=
"scope.row.picUrl"
v-if=
"scope.row.picUrl"
:preview-src-list=
"[scope.row.picture]"
:src=
"scope.row.picUrl"
:src=
"scope.row.picUrl"
class=
"w-40px h-40px"
class=
"w-40px h-40px"
:preview-src-list=
"[scope.row.picture]"
/>
/>
<span
v-else
>
-
</span>
<span
v-else
>
-
</span>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
<el-table-column
label=
"创建时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"创建时间"
prop=
"createTime"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
width=
"180px"
/>
/>
<el-table-column
label=
"操作"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"操作
"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
v-hasPermi=
"['iot:product:query']"
link
link
type=
"primary"
type=
"primary"
@
click=
"openDetail(scope.row.id)"
@
click=
"openDetail(scope.row.id)"
v-hasPermi=
"['iot:product:query']"
>
>
查看
查看
</el-button>
</el-button>
<el-button
<el-button
v-hasPermi=
"['iot:product:update']"
link
link
type=
"primary"
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['iot:product:update']"
>
>
编辑
编辑
</el-button>
</el-button>
<el-button
<el-button
v-hasPermi=
"['iot:product:delete']"
:disabled=
"scope.row.status === 1"
link
link
type=
"danger"
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['iot:product:delete']"
:disabled=
"scope.row.status === 1"
>
>
删除
删除
</el-button>
</el-button>
...
@@ -219,9 +227,9 @@
...
@@ -219,9 +227,9 @@
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
...
@@ -230,7 +238,7 @@
...
@@ -230,7 +238,7 @@
<ProductForm
ref=
"formRef"
@
success=
"getList"
/>
<ProductForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
ProductApi
,
ProductVO
}
from
'@/api/iot/product/product'
import
{
ProductApi
,
ProductVO
}
from
'@/api/iot/product/product'
import
ProductForm
from
'./ProductForm.vue'
import
ProductForm
from
'./ProductForm.vue'
...
@@ -301,7 +309,7 @@ const openObjectModel = (item: ProductVO) => {
...
@@ -301,7 +309,7 @@ const openObjectModel = (item: ProductVO) => {
push
({
push
({
name
:
'IoTProductDetail'
,
name
:
'IoTProductDetail'
,
params
:
{
id
:
item
.
id
},
params
:
{
id
:
item
.
id
},
query
:
{
tab
:
'
function
'
}
query
:
{
tab
:
'
thingModel
'
}
})
})
}
}
...
...
src/views/iot/utils/constants.ts
0 → 100644
View file @
773b8dea
/** iot 依赖注入 KEY */
export
const
IOT_PROVIDE_KEY
=
{
PRODUCT
:
'IOT_PRODUCT'
}
// TODO puhui999: 物模型数字数据类型单位类型,后面改成字典获取
export
const
UnifyUnitSpecsDTO
=
[
{
Symbol
:
'L/min'
,
Name
:
'升每分钟'
},
{
Symbol
:
'mg/kg'
,
Name
:
'毫克每千克'
},
{
Symbol
:
'NTU'
,
Name
:
'浊度'
},
{
Symbol
:
'pH'
,
Name
:
'PH值'
},
{
Symbol
:
'dS/m'
,
Name
:
'土壤EC值'
},
{
Symbol
:
'W/㎡'
,
Name
:
'太阳总辐射'
},
{
Symbol
:
'mm/hour'
,
Name
:
'降雨量'
},
{
Symbol
:
'var'
,
Name
:
'乏'
},
{
Symbol
:
'cP'
,
Name
:
'厘泊'
},
{
Symbol
:
'aw'
,
Name
:
'饱和度'
},
{
Symbol
:
'pcs'
,
Name
:
'个'
},
{
Symbol
:
'cst'
,
Name
:
'厘斯'
},
{
Symbol
:
'bar'
,
Name
:
'巴'
},
{
Symbol
:
'ppt'
,
Name
:
'纳克每升'
},
{
Symbol
:
'ppb'
,
Name
:
'微克每升'
},
{
Symbol
:
'uS/cm'
,
Name
:
'微西每厘米'
},
{
Symbol
:
'N/C'
,
Name
:
'牛顿每库仑'
},
{
Symbol
:
'V/m'
,
Name
:
'伏特每米'
},
{
Symbol
:
'ml/min'
,
Name
:
'滴速'
},
{
Symbol
:
'mmHg'
,
Name
:
'毫米汞柱'
},
{
Symbol
:
'mmol/L'
,
Name
:
'血糖'
},
{
Symbol
:
'mm/s'
,
Name
:
'毫米每秒'
},
{
Symbol
:
'turn/m'
,
Name
:
'转每分钟'
},
{
Symbol
:
'count'
,
Name
:
'次'
},
{
Symbol
:
'gear'
,
Name
:
'档'
},
{
Symbol
:
'stepCount'
,
Name
:
'步'
},
{
Symbol
:
'Nm3/h'
,
Name
:
'标准立方米每小时'
},
{
Symbol
:
'kV'
,
Name
:
'千伏'
},
{
Symbol
:
'kVA'
,
Name
:
'千伏安'
},
{
Symbol
:
'kVar'
,
Name
:
'千乏'
},
{
Symbol
:
'uw/cm2'
,
Name
:
'微瓦每平方厘米'
},
{
Symbol
:
'只'
,
Name
:
'只'
},
{
Symbol
:
'%RH'
,
Name
:
'相对湿度'
},
{
Symbol
:
'm³/s'
,
Name
:
'立方米每秒'
},
{
Symbol
:
'kg/s'
,
Name
:
'公斤每秒'
},
{
Symbol
:
'r/min'
,
Name
:
'转每分钟'
},
{
Symbol
:
't/h'
,
Name
:
'吨每小时'
},
{
Symbol
:
'KCL/h'
,
Name
:
'千卡每小时'
},
{
Symbol
:
'L/s'
,
Name
:
'升每秒'
},
{
Symbol
:
'Mpa'
,
Name
:
'兆帕'
},
{
Symbol
:
'm³/h'
,
Name
:
'立方米每小时'
},
{
Symbol
:
'kvarh'
,
Name
:
'千乏时'
},
{
Symbol
:
'μg/L'
,
Name
:
'微克每升'
},
{
Symbol
:
'kcal'
,
Name
:
'千卡路里'
},
{
Symbol
:
'GB'
,
Name
:
'吉字节'
},
{
Symbol
:
'MB'
,
Name
:
'兆字节'
},
{
Symbol
:
'KB'
,
Name
:
'千字节'
},
{
Symbol
:
'B'
,
Name
:
'字节'
},
{
Symbol
:
'μg/(d㎡·d)'
,
Name
:
'微克每平方分米每天'
},
{
Symbol
:
''
,
Name
:
'无'
},
{
Symbol
:
'ppm'
,
Name
:
'百万分率'
},
{
Symbol
:
'pixel'
,
Name
:
'像素'
},
{
Symbol
:
'Lux'
,
Name
:
'照度'
},
{
Symbol
:
'grav'
,
Name
:
'重力加速度'
},
{
Symbol
:
'dB'
,
Name
:
'分贝'
},
{
Symbol
:
'%'
,
Name
:
'百分比'
},
{
Symbol
:
'lm'
,
Name
:
'流明'
},
{
Symbol
:
'bit'
,
Name
:
'比特'
},
{
Symbol
:
'g/mL'
,
Name
:
'克每毫升'
},
{
Symbol
:
'g/L'
,
Name
:
'克每升'
},
{
Symbol
:
'mg/L'
,
Name
:
'毫克每升'
},
{
Symbol
:
'μg/m³'
,
Name
:
'微克每立方米'
},
{
Symbol
:
'mg/m³'
,
Name
:
'毫克每立方米'
},
{
Symbol
:
'g/m³'
,
Name
:
'克每立方米'
},
{
Symbol
:
'kg/m³'
,
Name
:
'千克每立方米'
},
{
Symbol
:
'nF'
,
Name
:
'纳法'
},
{
Symbol
:
'pF'
,
Name
:
'皮法'
},
{
Symbol
:
'μF'
,
Name
:
'微法'
},
{
Symbol
:
'F'
,
Name
:
'法拉'
},
{
Symbol
:
'Ω'
,
Name
:
'欧姆'
},
{
Symbol
:
'μA'
,
Name
:
'微安'
},
{
Symbol
:
'mA'
,
Name
:
'毫安'
},
{
Symbol
:
'kA'
,
Name
:
'千安'
},
{
Symbol
:
'A'
,
Name
:
'安培'
},
{
Symbol
:
'mV'
,
Name
:
'毫伏'
},
{
Symbol
:
'V'
,
Name
:
'伏特'
},
{
Symbol
:
'ms'
,
Name
:
'毫秒'
},
{
Symbol
:
's'
,
Name
:
'秒'
},
{
Symbol
:
'min'
,
Name
:
'分钟'
},
{
Symbol
:
'h'
,
Name
:
'小时'
},
{
Symbol
:
'day'
,
Name
:
'日'
},
{
Symbol
:
'week'
,
Name
:
'周'
},
{
Symbol
:
'month'
,
Name
:
'月'
},
{
Symbol
:
'year'
,
Name
:
'年'
},
{
Symbol
:
'kn'
,
Name
:
'节'
},
{
Symbol
:
'km/h'
,
Name
:
'千米每小时'
},
{
Symbol
:
'm/s'
,
Name
:
'米每秒'
},
{
Symbol
:
'″'
,
Name
:
'秒'
},
{
Symbol
:
'′'
,
Name
:
'分'
},
{
Symbol
:
'°'
,
Name
:
'度'
},
{
Symbol
:
'rad'
,
Name
:
'弧度'
},
{
Symbol
:
'Hz'
,
Name
:
'赫兹'
},
{
Symbol
:
'μW'
,
Name
:
'微瓦'
},
{
Symbol
:
'mW'
,
Name
:
'毫瓦'
},
{
Symbol
:
'kW'
,
Name
:
'千瓦特'
},
{
Symbol
:
'W'
,
Name
:
'瓦特'
},
{
Symbol
:
'cal'
,
Name
:
'卡路里'
},
{
Symbol
:
'kW·h'
,
Name
:
'千瓦时'
},
{
Symbol
:
'Wh'
,
Name
:
'瓦时'
},
{
Symbol
:
'eV'
,
Name
:
'电子伏'
},
{
Symbol
:
'kJ'
,
Name
:
'千焦'
},
{
Symbol
:
'J'
,
Name
:
'焦耳'
},
{
Symbol
:
'℉'
,
Name
:
'华氏度'
},
{
Symbol
:
'K'
,
Name
:
'开尔文'
},
{
Symbol
:
't'
,
Name
:
'吨'
},
{
Symbol
:
'°C'
,
Name
:
'摄氏度'
},
{
Symbol
:
'mPa'
,
Name
:
'毫帕'
},
{
Symbol
:
'hPa'
,
Name
:
'百帕'
},
{
Symbol
:
'kPa'
,
Name
:
'千帕'
},
{
Symbol
:
'Pa'
,
Name
:
'帕斯卡'
},
{
Symbol
:
'mg'
,
Name
:
'毫克'
},
{
Symbol
:
'g'
,
Name
:
'克'
},
{
Symbol
:
'kg'
,
Name
:
'千克'
},
{
Symbol
:
'N'
,
Name
:
'牛'
},
{
Symbol
:
'mL'
,
Name
:
'毫升'
},
{
Symbol
:
'L'
,
Name
:
'升'
},
{
Symbol
:
'mm³'
,
Name
:
'立方毫米'
},
{
Symbol
:
'cm³'
,
Name
:
'立方厘米'
},
{
Symbol
:
'km³'
,
Name
:
'立方千米'
},
{
Symbol
:
'm³'
,
Name
:
'立方米'
},
{
Symbol
:
'h㎡'
,
Name
:
'公顷'
},
{
Symbol
:
'c㎡'
,
Name
:
'平方厘米'
},
{
Symbol
:
'm㎡'
,
Name
:
'平方毫米'
},
{
Symbol
:
'k㎡'
,
Name
:
'平方千米'
},
{
Symbol
:
'㎡'
,
Name
:
'平方米'
},
{
Symbol
:
'nm'
,
Name
:
'纳米'
},
{
Symbol
:
'μm'
,
Name
:
'微米'
},
{
Symbol
:
'mm'
,
Name
:
'毫米'
},
{
Symbol
:
'cm'
,
Name
:
'厘米'
},
{
Symbol
:
'dm'
,
Name
:
'分米'
},
{
Symbol
:
'km'
,
Name
:
'千米'
},
{
Symbol
:
'm'
,
Name
:
'米'
}
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment