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
Commit
dde5911b
authored
Dec 14, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【功能完善】IoT:设备新增时,增加 deviceKey、serialNumber、picUrl 等字段
parent
8b59786f
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
169 additions
and
53 deletions
+169
-53
src/api/iot/device/index.ts
+5
-0
src/views/iot/device/device/DeviceForm.vue
+94
-12
src/views/iot/device/device/detail/DeviceDataDetail.vue
+0
-0
src/views/iot/device/device/detail/DeviceDetailsHeader.vue
+0
-0
src/views/iot/device/device/detail/DeviceDetailsInfo.vue
+0
-0
src/views/iot/device/device/detail/DeviceDetailsModel.vue
+0
-0
src/views/iot/device/device/detail/index.vue
+0
-0
src/views/iot/device/device/index.vue
+0
-0
src/views/iot/product/product/ProductForm.vue
+13
-2
src/views/iot/product/product/detail/ProductTopic.vue
+21
-17
src/views/iot/product/product/detail/index.vue
+6
-1
src/views/iot/product/product/index.vue
+30
-21
No files found.
src/api/iot/device/index.ts
View file @
dde5911b
...
...
@@ -91,6 +91,11 @@ export const DeviceApi = {
return
await
request
.
get
({
url
:
`/iot/device/count?productId=`
+
productId
})
},
// 获取设备的精简信息列表
getSimpleDeviceList
:
async
(
deviceType
?:
number
)
=>
{
return
await
request
.
get
({
url
:
`/iot/device/simple-list?`
,
params
:
{
deviceType
}
})
},
// 获取设备属性最新数据
getDevicePropertiesLatestData
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/iot/device/data/latest`
,
params
})
...
...
src/views/iot/device/DeviceForm.vue
→
src/views/iot/device/
device/
DeviceForm.vue
View file @
dde5911b
...
...
@@ -13,6 +13,7 @@
placeholder=
"请选择产品"
:disabled=
"formType === 'update'"
clearable
@
change=
"handleProductChange"
>
<el-option
v-for=
"product in products"
...
...
@@ -22,6 +23,19 @@
/>
</el-select>
</el-form-item>
<el-form-item
label=
"DeviceKey"
prop=
"deviceKey"
>
<el-input
v-model=
"formData.deviceKey"
placeholder=
"请输入 DeviceKey"
:disabled=
"formType === 'update'"
>
<template
#
append
>
<el-button
@
click=
"generateDeviceKey"
:disabled=
"formType === 'update'"
>
重新生成
</el-button>
</
template
>
</el-input>
</el-form-item>
<el-form-item
label=
"DeviceName"
prop=
"deviceName"
>
<el-input
v-model=
"formData.deviceName"
...
...
@@ -29,9 +43,30 @@
:disabled=
"formType === 'update'"
/>
</el-form-item>
<el-form-item
v-if=
"formData.deviceType === 1"
label=
"网关设备"
prop=
"gatewayId"
>
<el-select
v-model=
"formData.gatewayId"
placeholder=
"子设备可选择父设备"
clearable
>
<el-option
v-for=
"gateway in gatewayDevices"
:key=
"gateway.id"
:label=
"gateway.nickname || gateway.deviceName"
:value=
"gateway.id"
/>
</el-select>
</el-form-item>
<el-collapse>
<el-collapse-item
title=
"更多配置"
>
<el-form-item
label=
"备注名称"
prop=
"nickname"
>
<el-input
v-model=
"formData.nickname"
placeholder=
"请输入备注名称"
/>
</el-form-item>
<el-form-item
label=
"设备图片"
prop=
"picUrl"
>
<UploadImg
v-model=
"formData.picUrl"
:height=
"'120px'"
:width=
"'120px'"
/>
</el-form-item>
<el-form-item
label=
"设备序列号"
prop=
"serialNumber"
>
<el-input
v-model=
"formData.serialNumber"
placeholder=
"请输入设备序列号"
/>
</el-form-item>
</el-collapse-item>
</el-collapse>
</el-form>
<
template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading"
>
确 定
</el-button>
...
...
@@ -41,13 +76,15 @@
</template>
<
script
setup
lang=
"ts"
>
import
{
DeviceApi
,
DeviceVO
}
from
'@/api/iot/device'
import
{
ProductApi
}
from
'@/api/iot/product/product'
import
{
DeviceTypeEnum
,
ProductApi
,
ProductVO
}
from
'@/api/iot/product/product'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
generateRandomStr
}
from
'@/utils'
/** IoT 设备
表单 */
/** IoT 设备表单 */
defineOptions
({
name
:
'IoTDeviceForm'
})
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息
弹
窗
const
message
=
useMessage
()
// 消息窗
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
...
...
@@ -56,12 +93,26 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
const
formData
=
ref
({
id
:
undefined
,
productId
:
undefined
,
deviceKey
:
undefined
as
string
|
undefined
,
deviceName
:
undefined
,
nickname
:
undefined
nickname
:
undefined
,
picUrl
:
undefined
,
gatewayId
:
undefined
,
deviceType
:
undefined
as
number
|
undefined
,
serialNumber
:
undefined
})
const
formRules
=
reactive
({
productId
:
[{
required
:
true
,
message
:
'产品不能为空'
,
trigger
:
'blur'
}],
deviceKey
:
[
{
required
:
true
,
message
:
'DeviceKey 不能为空'
,
trigger
:
'blur'
},
{
pattern
:
/^
[
a-zA-Z0-9
]
+$/
,
message
:
'DeviceKey 只能包含字母和数字'
,
trigger
:
'blur'
}
],
deviceName
:
[
{
required
:
true
,
message
:
'DeviceName 不能为空'
,
trigger
:
'blur'
},
{
pattern
:
/^
[
a-zA-Z0-9_.
\-
:@
]{4,32}
$/
,
message
:
...
...
@@ -87,9 +138,18 @@ const formRules = reactive({
},
trigger
:
'blur'
}
],
serialNumber
:
[
{
pattern
:
/^
[
a-zA-Z0-9-_
]
+$/
,
message
:
'序列号只能包含字母、数字、中划线和下划线'
,
trigger
:
'blur'
}
]
})
const
formRef
=
ref
()
// 表单 Ref
const
products
=
ref
<
ProductVO
[]
>
([])
// 产品列表
const
gatewayDevices
=
ref
<
DeviceVO
[]
>
([])
// 网关设备列表
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
...
...
@@ -97,6 +157,7 @@ const open = async (type: string, id?: number) => {
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
...
...
@@ -105,7 +166,18 @@ const open = async (type: string, id?: number) => {
}
finally
{
formLoading
.
value
=
false
}
}
else
{
generateDeviceKey
()
}
// 加载网关设备列表
try
{
gatewayDevices
.
value
=
await
DeviceApi
.
getSimpleDeviceList
(
DeviceTypeEnum
.
GATEWAY
)
}
catch
(
error
)
{
console
.
error
(
'加载网关设备列表失败:'
,
error
)
}
// 加载产品列表
products
.
value
=
await
ProductApi
.
getSimpleProductList
()
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
...
...
@@ -138,19 +210,29 @@ const resetForm = () => {
formData
.
value
=
{
id
:
undefined
,
productId
:
undefined
,
deviceKey
:
undefined
,
deviceName
:
undefined
,
nickname
:
undefined
nickname
:
undefined
,
picUrl
:
undefined
,
gatewayId
:
undefined
,
deviceType
:
undefined
,
serialNumber
:
undefined
}
formRef
.
value
?.
resetFields
()
}
/** 查询字典下拉列表 */
const
products
=
ref
()
const
getProducts
=
async
()
=>
{
products
.
value
=
await
ProductApi
.
getSimpleProductList
()
/** 产品选择变化 */
const
handleProductChange
=
(
productId
:
number
)
=>
{
if
(
!
productId
)
{
formData
.
value
.
deviceType
=
undefined
return
}
const
product
=
products
.
value
?.
find
((
item
)
=>
item
.
id
===
productId
)
formData
.
value
.
deviceType
=
product
?.
deviceType
}
onMounted
(()
=>
{
getProducts
()
})
/** 生成 DeviceKey */
const
generateDeviceKey
=
()
=>
{
formData
.
value
.
deviceKey
=
generateRandomStr
(
16
)
}
</
script
>
src/views/iot/device/detail/DeviceDataDetail.vue
→
src/views/iot/device/de
vice/de
tail/DeviceDataDetail.vue
View file @
dde5911b
File moved
src/views/iot/device/detail/DeviceDetailsHeader.vue
→
src/views/iot/device/de
vice/de
tail/DeviceDetailsHeader.vue
View file @
dde5911b
File moved
src/views/iot/device/detail/DeviceDetailsInfo.vue
→
src/views/iot/device/de
vice/de
tail/DeviceDetailsInfo.vue
View file @
dde5911b
File moved
src/views/iot/device/detail/DeviceDetailsModel.vue
→
src/views/iot/device/de
vice/de
tail/DeviceDetailsModel.vue
View file @
dde5911b
File moved
src/views/iot/device/detail/index.vue
→
src/views/iot/device/de
vice/de
tail/index.vue
View file @
dde5911b
File moved
src/views/iot/device/index.vue
→
src/views/iot/device/
device/
index.vue
View file @
dde5911b
File moved
src/views/iot/product/product/ProductForm.vue
View file @
dde5911b
...
...
@@ -12,7 +12,13 @@
v-model=
"formData.productKey"
placeholder=
"请输入 ProductKey"
:readonly=
"formType === 'update'"
/>
>
<template
#
append
>
<el-button
@
click=
"generateProductKey"
:disabled=
"formType === 'update'"
>
重新生成
</el-button>
</
template
>
</el-input>
</el-form-item>
<el-form-item
label=
"产品名称"
prop=
"name"
>
<el-input
v-model=
"formData.name"
placeholder=
"请输入产品名称"
/>
...
...
@@ -184,7 +190,7 @@ const open = async (type: string, id?: number) => {
}
}
else
{
// 新增时,生成随机 productKey
formData
.
value
.
productKey
=
generateRandomStr
(
16
)
generateProductKey
(
)
}
// 加载分类列表
categoryList
.
value
=
await
ProductCategoryApi
.
getSimpleProductCategoryList
()
...
...
@@ -231,4 +237,9 @@ const resetForm = () => {
}
formRef
.
value
?.
resetFields
()
}
/** 生成 ProductKey */
const
generateProductKey
=
()
=>
{
formData
.
value
.
productKey
=
generateRandomStr
(
16
)
}
</
script
>
src/views/iot/product/product/detail/ProductTopic.vue
View file @
dde5911b
...
...
@@ -3,9 +3,9 @@
<el-tabs>
<el-tab-pane
label=
"基础通信 Topic"
>
<Table
:columns=
"
columns1
"
:data=
"
data1
"
:span-method=
"createSpanMethod(
data1
)"
:columns=
"
basicColumn
"
:data=
"
basicData
"
:span-method=
"createSpanMethod(
basicData
)"
align=
"left"
headerAlign=
"left"
border=
"true"
...
...
@@ -13,9 +13,9 @@
</el-tab-pane>
<el-tab-pane
label=
"物模型通信 Topic"
>
<Table
:columns=
"
columns2
"
:data=
"
data2
"
:span-method=
"createSpanMethod(
data2
)"
:columns=
"
functionColumn
"
:data=
"
functionData
"
:span-method=
"createSpanMethod(
functionData
)"
align=
"left"
headerAlign=
"left"
border=
"true"
...
...
@@ -29,23 +29,18 @@ import { ProductVO } from '@/api/iot/product/product'
const
props
=
defineProps
<
{
product
:
ProductVO
}
>
()
// 定义列
const
columns1
=
reactive
([
{
label
:
'功能'
,
field
:
'function'
,
width
:
150
},
{
label
:
'Topic 类'
,
field
:
'topicClass'
,
width
:
800
},
{
label
:
'操作权限'
,
field
:
'operationPermission'
,
width
:
100
},
{
label
:
'描述'
,
field
:
'description'
}
])
// TODO 芋艿:不确定未来会不会改,所以先写死
const
columns2
=
reactive
([
// 基础通信 Topic 列
const
basicColumn
=
reactive
([
{
label
:
'功能'
,
field
:
'function'
,
width
:
150
},
{
label
:
'Topic 类'
,
field
:
'topicClass'
,
width
:
800
},
{
label
:
'操作权限'
,
field
:
'operationPermission'
,
width
:
100
},
{
label
:
'描述'
,
field
:
'description'
}
])
//
TODO @haohao:这个,有没可能写到一个枚举里,方便后续维护? /Users/yunai/Java/yudao-ui-admin-vue3/src/views/ai/utils/constants.ts
const
data1
=
computed
(()
=>
{
//
基础通信 Topic 数据
const
basicData
=
computed
(()
=>
{
if
(
!
props
.
product
||
!
props
.
product
.
productKey
)
return
[]
return
[
{
...
...
@@ -147,7 +142,16 @@ const data1 = computed(() => {
]
})
const
data2
=
computed
(()
=>
{
// 物模型通信 Topic 列
const
functionColumn
=
reactive
([
{
label
:
'功能'
,
field
:
'function'
,
width
:
150
},
{
label
:
'Topic 类'
,
field
:
'topicClass'
,
width
:
800
},
{
label
:
'操作权限'
,
field
:
'operationPermission'
,
width
:
100
},
{
label
:
'描述'
,
field
:
'description'
}
])
// 物模型通信 Topic 数据
const
functionData
=
computed
(()
=>
{
if
(
!
props
.
product
||
!
props
.
product
.
productKey
)
return
[]
return
[
{
...
...
src/views/iot/product/product/detail/index.vue
View file @
dde5911b
...
...
@@ -36,7 +36,7 @@ const message = useMessage()
const
id
=
Number
(
route
.
params
.
id
)
// 编号
const
loading
=
ref
(
true
)
// 加载中
const
product
=
ref
<
ProductVO
>
({}
as
ProductVO
)
// 详情
const
activeTab
=
ref
(
'info'
)
// 默认
激活的
标签页
const
activeTab
=
ref
(
'info'
)
// 默认
为 info
标签页
/** 获取详情 */
const
getProductData
=
async
(
id
:
number
)
=>
{
...
...
@@ -66,6 +66,11 @@ onMounted(async () => {
return
}
await
getProductData
(
id
)
// 处理 tab 参数
const
{
tab
}
=
route
.
query
if
(
tab
)
{
activeTab
.
value
=
tab
as
string
}
// 查询设备数量
if
(
product
.
value
.
id
)
{
product
.
value
.
deviceCount
=
await
getDeviceCount
(
product
.
value
.
id
)
...
...
src/views/iot/product/product/index.vue
View file @
dde5911b
...
...
@@ -63,23 +63,15 @@
<!-- 卡片视图 -->
<ContentWrap>
<div
v-if=
"viewMode === 'card'"
class=
"flex flex-wrap gap-4"
>
<el-card
v-for=
"item in list"
:key=
"item.id"
class=
"w-[calc(25%-12px)] transition-colors"
:body-style=
"
{ padding: '0' }"
>
<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-card
class=
"h-full transition-colors"
:body-style=
"
{ padding: '0' }">
<!-- 内容区域 -->
<div
class=
"p-4"
>
<!-- 标题区域 -->
<div
class=
"flex items-center mb-3"
>
<div
class=
"mr-2.5 flex items-center"
>
<el-image
:src=
"item.icon || defaultIconUrl"
class=
"w-[35px] h-[35px]"
fit=
"contain"
/>
<el-image
:src=
"item.icon || defaultIconUrl"
class=
"w-[35px] h-[35px]"
/>
</div>
<div
class=
"text-[16px] font-600"
>
{{
item
.
name
}}
</div>
</div>
...
...
@@ -101,7 +93,7 @@
</div>
</div>
<div
class=
"w-[100px] h-[100px]"
>
<el-image
:src=
"item.picUrl || defaultPicUrl"
class=
"w-full h-full"
fit=
"cover
"
/>
<el-image
:src=
"item.picUrl || defaultPicUrl"
class=
"w-full h-full
"
/>
</div>
</div>
...
...
@@ -109,9 +101,9 @@
<el-divider
class=
"!my-3"
/>
<!-- 按钮组 -->
<div
class=
"flex items-center
"
>
<div
class=
"flex items-center px-0
"
>
<el-button
class=
"flex-1 !px-2 !h-[32
px]"
class=
"flex-1 !px-2 !h-[32px] text-[13
px]"
type=
"primary"
plain
@
click=
"openForm('update', item.id)"
...
...
@@ -121,7 +113,7 @@
编辑
</el-button>
<el-button
class=
"flex-1 !px-2 !h-[32px] !ml-[12
px]"
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13
px]"
type=
"warning"
plain
@
click=
"openDetail(item.id)"
...
...
@@ -130,7 +122,7 @@
详情
</el-button>
<el-button
class=
"flex-1 !px-2 !h-[32px] !ml-[12
px]"
class=
"flex-1 !px-2 !h-[32px] !ml-[10px] text-[13
px]"
type=
"success"
plain
@
click=
"openObjectModel(item)"
...
...
@@ -138,9 +130,9 @@
<Icon
icon=
"ep:scale-to-original"
class=
"mr-1"
/>
物模型
</el-button>
<div
class=
"mx-[12
px] h-[20px] w-[1px] bg-[#dcdfe6]"
></div>
<div
class=
"mx-[10
px] h-[20px] w-[1px] bg-[#dcdfe6]"
></div>
<el-button
class=
"!px-2 !h-[32
px]"
class=
"!px-2 !h-[32px] text-[13
px]"
type=
"danger"
plain
@
click=
"handleDelete(item.id)"
...
...
@@ -152,7 +144,8 @@
</div>
</div>
</el-card>
</div>
</el-col>
</el-row>
<!-- 列表视图 -->
<el-table
v-else
v-loading=
"loading"
:data=
"list"
:stripe=
"true"
:show-overflow-tooltip=
"true"
>
...
...
@@ -251,8 +244,11 @@ defineOptions({ name: 'IoTProduct' })
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
{
push
}
=
useRouter
()
const
route
=
useRoute
()
const
loading
=
ref
(
true
)
// 列表的加载中
const
activeName
=
ref
(
'info'
)
// 当前激活的标签页
const
list
=
ref
<
ProductVO
[]
>
([])
// 列表的数据
const
total
=
ref
(
0
)
// 列表的总页数
const
queryParams
=
reactive
({
...
...
@@ -296,11 +292,19 @@ const openForm = (type: string, id?: number) => {
}
/** 打开详情 */
const
{
push
}
=
useRouter
()
const
openDetail
=
(
id
:
number
)
=>
{
push
({
name
:
'IoTProductDetail'
,
params
:
{
id
}
})
}
/** 打开物模型 */
const
openObjectModel
=
(
item
:
ProductVO
)
=>
{
push
({
name
:
'IoTProductDetail'
,
params
:
{
id
:
item
.
id
},
query
:
{
tab
:
'function'
}
})
}
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
...
...
@@ -332,5 +336,10 @@ const handleExport = async () => {
/** 初始化 **/
onMounted
(()
=>
{
getList
()
// 处理 tab 参数
const
{
tab
}
=
route
.
query
if
(
tab
)
{
activeName
.
value
=
tab
as
string
}
})
</
script
>
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