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
6aaf6112
authored
Sep 23, 2024
by
安浩浩
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【新增】 IOT 产品管理,Topic 类列表
parent
93a0789e
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
275 additions
and
100 deletions
+275
-100
src/api/iot/device/index.ts
+3
-3
src/api/iot/product/index.ts
+1
-0
src/views/iot/device/detail/DeviceDetailsHeader.vue
+4
-43
src/views/iot/device/detail/DeviceDetailsInfo.vue
+4
-43
src/views/iot/product/detail/ProductDetailsHeader.vue
+10
-4
src/views/iot/product/detail/ProductTopic.vue
+229
-0
src/views/iot/product/detail/index.vue
+24
-7
No files found.
src/api/iot/device/index.ts
View file @
6aaf6112
...
...
@@ -67,8 +67,8 @@ export const DeviceApi = {
return
await
request
.
delete
({
url
:
`/iot/device/delete?id=`
+
id
})
},
//
导出设备 Excel
exportDevice
:
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
`/iot/device/export-excel`
,
params
})
//
获取设备数量
getDeviceCount
:
async
(
productId
:
number
)
=>
{
return
await
request
.
get
({
url
:
`/iot/device/count?productId=`
+
productId
})
}
}
src/api/iot/product/index.ts
View file @
6aaf6112
...
...
@@ -14,6 +14,7 @@ export interface ProductVO {
netType
:
number
// 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他
protocolType
:
number
// 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee
dataFormat
:
number
// 数据格式, 0: 透传模式, 1: Alink JSON
deviceCount
:
number
// 设备数量
}
// iot 产品 API
...
...
src/views/iot/device/detail/DeviceDetailsHeader.vue
View file @
6aaf6112
...
...
@@ -58,49 +58,10 @@ const emit = defineEmits(['refresh'])
*
* @param text 需要复制的文本
*/
const
copyToClipboard
=
async
(
text
:
string
)
=>
{
if
(
!
navigator
.
clipboard
)
{
// 浏览器不支持 Clipboard API,使用回退方法
const
textarea
=
document
.
createElement
(
'textarea'
)
textarea
.
value
=
text
// 防止页面滚动
textarea
.
style
.
position
=
'fixed'
textarea
.
style
.
top
=
'0'
textarea
.
style
.
left
=
'0'
textarea
.
style
.
width
=
'2em'
textarea
.
style
.
height
=
'2em'
textarea
.
style
.
padding
=
'0'
textarea
.
style
.
border
=
'none'
textarea
.
style
.
outline
=
'none'
textarea
.
style
.
boxShadow
=
'none'
textarea
.
style
.
background
=
'transparent'
document
.
body
.
appendChild
(
textarea
)
textarea
.
focus
()
textarea
.
select
()
try
{
const
successful
=
document
.
execCommand
(
'copy'
)
if
(
successful
)
{
message
.
success
(
'复制成功!'
)
}
else
{
message
.
error
(
'复制失败,请手动复制'
)
}
}
catch
(
err
)
{
console
.
error
(
'Fallback: Oops, unable to copy'
,
err
)
message
.
error
(
'复制失败,请手动复制'
)
}
document
.
body
.
removeChild
(
textarea
)
return
}
try
{
await
navigator
.
clipboard
.
writeText
(
text
)
message
.
success
(
'复制成功!'
)
}
catch
(
err
)
{
console
.
error
(
'Async: Could not copy text: '
,
err
)
message
.
error
(
'复制失败,请手动复制'
)
}
const
copyToClipboard
=
(
text
:
string
)
=>
{
navigator
.
clipboard
.
writeText
(
text
).
then
(()
=>
{
message
.
success
(
'复制成功'
)
})
}
/**
...
...
src/views/iot/device/detail/DeviceDetailsInfo.vue
View file @
6aaf6112
...
...
@@ -103,49 +103,10 @@ const emit = defineEmits(['refresh'])
const
activeNames
=
ref
([
'basicInfo'
])
// 复制到剪贴板方法
const
copyToClipboard
=
async
(
text
:
string
)
=>
{
if
(
!
navigator
.
clipboard
)
{
// 浏览器不支持 Clipboard API,使用回退方法
const
textarea
=
document
.
createElement
(
'textarea'
)
textarea
.
value
=
text
// 防止页面滚动
textarea
.
style
.
position
=
'fixed'
textarea
.
style
.
top
=
'0'
textarea
.
style
.
left
=
'0'
textarea
.
style
.
width
=
'2em'
textarea
.
style
.
height
=
'2em'
textarea
.
style
.
padding
=
'0'
textarea
.
style
.
border
=
'none'
textarea
.
style
.
outline
=
'none'
textarea
.
style
.
boxShadow
=
'none'
textarea
.
style
.
background
=
'transparent'
document
.
body
.
appendChild
(
textarea
)
textarea
.
focus
()
textarea
.
select
()
try
{
const
successful
=
document
.
execCommand
(
'copy'
)
if
(
successful
)
{
message
.
success
(
'复制成功!'
)
}
else
{
message
.
error
(
'复制失败,请手动复制'
)
}
}
catch
(
err
)
{
console
.
error
(
'Fallback: Oops, unable to copy'
,
err
)
message
.
error
(
'复制失败,请手动复制'
)
}
document
.
body
.
removeChild
(
textarea
)
return
}
try
{
await
navigator
.
clipboard
.
writeText
(
text
)
message
.
success
(
'复制成功!'
)
}
catch
(
err
)
{
console
.
error
(
'Async: Could not copy text: '
,
err
)
message
.
error
(
'复制失败,请手动复制'
)
}
const
copyToClipboard
=
(
text
:
string
)
=>
{
navigator
.
clipboard
.
writeText
(
text
).
then
(()
=>
{
message
.
success
(
'复制成功'
)
})
}
// 定义 MQTT 弹框的可见性
...
...
src/views/iot/product/detail/ProductDetailsHeader.vue
View file @
6aaf6112
...
...
@@ -45,8 +45,8 @@
</el-descriptions>
<el-descriptions
:column=
"5"
direction=
"horizontal"
>
<el-descriptions-item
label=
"设备数"
>
0
<el-button
@
click=
"goToManagement(product.
productKey
)"
>
前往管理
</el-button>
{{
product
.
deviceCount
}}
<el-button
@
click=
"goToManagement(product.
id
)"
>
前往管理
</el-button>
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
...
...
@@ -63,8 +63,11 @@ const copyToClipboard = (text: string) => {
message
.
success
(
'复制成功'
)
})
}
const
goToManagement
=
(
productKey
:
string
)
=>
{
message
.
warning
(
'暂未开放'
)
// 路由跳转到设备管理
const
{
currentRoute
,
push
}
=
useRouter
()
const
goToManagement
=
(
productId
:
string
)
=>
{
push
({
name
:
'IoTDevice'
,
query
:
{
productId
}
})
}
// 操作修改
...
...
@@ -93,6 +96,9 @@ const confirmUnpublish = async (id: number) => {
}
}
// 定义 Props
const
{
product
}
=
defineProps
<
{
product
:
ProductVO
}
>
()
// 定义 Emits
const
emit
=
defineEmits
([
'refresh'
])
</
script
>
src/views/iot/product/detail/ProductTopic.vue
0 → 100644
View file @
6aaf6112
<
template
>
<ContentWrap>
<el-tabs>
<el-tab-pane
label=
"基础通信 Topic"
>
<Table
:columns=
"columns1"
:data=
"data1"
:span-method=
"objectSpanMethod"
/>
</el-tab-pane>
<el-tab-pane
label=
"物模型通信 Topic"
>
<Table
:columns=
"columns2"
:data=
"data2"
:span-method=
"objectSpanMethod"
/>
</el-tab-pane>
</el-tabs>
</ContentWrap>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ProductVO
}
from
'@/api/iot/product'
const
{
product
}
=
defineProps
<
{
product
:
ProductVO
}
>
()
// 定义列
const
columns1
=
reactive
([
{
label
:
'功能'
,
field
:
'function'
,
width
:
150
},
{
label
:
'Topic 类'
,
field
:
'topicClass'
,
width
:
500
},
{
label
:
'操作权限'
,
field
:
'operationPermission'
,
width
:
100
},
{
label
:
'描述'
,
field
:
'description'
}
])
const
columns2
=
reactive
([
{
label
:
'功能'
,
field
:
'function'
,
width
:
150
},
{
label
:
'Topic 类'
,
field
:
'topicClass'
,
width
:
500
},
{
label
:
'操作权限'
,
field
:
'operationPermission'
,
width
:
150
},
{
label
:
'描述'
,
field
:
'description'
}
])
// 定义数据
const
data1
=
reactive
([
{
function
:
'OTA 升级'
,
topicClass
:
'/ota/device/inform/'
+
product
.
productKey
+
'/${deviceName}'
,
operationPermission
:
'发布'
,
description
:
'设备上报固件升级信息'
},
{
function
:
'OTA 升级'
,
topicClass
:
'/ota/device/upgrade/'
+
product
.
productKey
+
'/${deviceName}'
,
operationPermission
:
'订阅'
,
description
:
'固件升级信息下行'
},
{
function
:
'OTA 升级'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/ota/firmware/get'
,
operationPermission
:
'发布'
,
description
:
'设备上报固件升级进度'
},
{
function
:
'OTA 升级'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/ota/firmware/get'
,
operationPermission
:
'发布'
,
description
:
'设备主动拉取固件升级信息'
},
{
function
:
'设备标签'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/deviceinfo/update'
,
operationPermission
:
'发布'
,
description
:
'设备上报标签数据'
},
{
function
:
'设备标签'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/deviceinfo/update_reply'
,
operationPermission
:
'订阅'
,
description
:
'云端响应标签上报'
},
{
function
:
'设备标签'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/deviceinfo/delete'
,
operationPermission
:
'订阅'
,
description
:
'设备删除标签信息'
},
{
function
:
'设备标签'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/deviceinfo/delete_reply'
,
operationPermission
:
'订阅'
,
description
:
'云端响应标签删除'
},
{
function
:
'时钟同步'
,
topicClass
:
'/ext/ntp/'
+
product
.
productKey
+
'/${deviceName}/request'
,
operationPermission
:
'发布'
,
description
:
'NTP 时钟同步请求'
},
{
function
:
'时钟同步'
,
topicClass
:
'/ext/ntp/'
+
product
.
productKey
+
'/${deviceName}/response'
,
operationPermission
:
'订阅'
,
description
:
'NTP 时钟同步响应'
},
{
function
:
'设备影子'
,
topicClass
:
'/shadow/update/'
+
product
.
productKey
+
'/${deviceName}'
,
operationPermission
:
'发布'
,
description
:
'设备影子发布'
},
{
function
:
'设备影子'
,
topicClass
:
'/shadow/get/'
+
product
.
productKey
+
'/${deviceName}'
,
operationPermission
:
'订阅'
,
description
:
'设备接收影子变更'
},
{
function
:
'配置更新'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/config/push'
,
operationPermission
:
'订阅'
,
description
:
'云端主动下推配置信息'
},
{
function
:
'配置更新'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/config/get'
,
operationPermission
:
'发布'
,
description
:
'设备端查询配置信息'
},
{
function
:
'配置更新'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/config/get_reply'
,
operationPermission
:
'订阅'
,
description
:
'云端响应配置信息'
},
{
function
:
'广播'
,
topicClass
:
'/broadcast/'
+
product
.
productKey
+
'/${identifier}'
,
operationPermission
:
'订阅'
,
description
:
'广播 Topic,identifier 为用户自定义字符串'
}
])
const
data2
=
reactive
([
{
function
:
'属性上报'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/event/property/post'
,
operationPermission
:
'发布'
,
description
:
'设备属性上报'
},
{
function
:
'属性上报'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/event/property/post_reply'
,
operationPermission
:
'订阅'
,
description
:
'云端响应属性上报'
},
{
function
:
'属性设置'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/service/property/set'
,
operationPermission
:
'订阅'
,
description
:
'设备属性设置'
},
{
function
:
'事件上报'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/event/${tsl.event.identifier}/post'
,
operationPermission
:
'发布'
,
description
:
'设备事件上报'
},
{
function
:
'事件上报'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/event/${tsl.event.identifier}/post_reply'
,
operationPermission
:
'订阅'
,
description
:
'云端响应事件上报'
},
{
function
:
'服务调用'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/service/${tsl.service.identifier}'
,
operationPermission
:
'订阅'
,
description
:
'设备服务调用'
},
{
function
:
'服务调用'
,
topicClass
:
'/sys/'
+
product
.
productKey
+
'/${deviceName}/thing/service/${tsl.service.identifier}_reply'
,
operationPermission
:
'发布'
,
description
:
'设备端响应服务调用'
}
])
// 合并单元格
const
objectSpanMethod
=
({
row
,
column
,
rowIndex
,
columnIndex
}:
SpanMethodProps
)
=>
{
if
(
columnIndex
===
0
||
columnIndex
===
2
)
{
if
(
rowIndex
%
2
===
0
)
{
return
{
rowspan
:
2
,
colspan
:
1
}
}
else
{
return
{
rowspan
:
0
,
colspan
:
0
}
}
}
}
</
script
>
src/views/iot/product/detail/index.vue
View file @
6aaf6112
<
template
>
<ProductDetailsHeader
:loading=
"loading"
:product=
"product"
@
refresh=
"getProductData(id)"
/>
<ProductDetailsHeader
:loading=
"loading"
:product=
"product"
@
refresh=
"
() =>
getProductData(id)"
/>
<el-col>
<el-tabs>
<el-tab-pane
label=
"产品信息"
>
<ProductDetailsInfo
:product=
"product"
/>
</el-tab-pane>
<el-tab-pane
label=
"Topic 类列表"
/>
<el-tab-pane
label=
"Topic 类列表"
>
<ProductTopic
:product=
"product"
/>
</el-tab-pane>
<el-tab-pane
label=
"物模型"
>
<!--
<ProductDetailsModel
:product=
"product"
/>
-->
</el-tab-pane>
...
...
@@ -15,10 +17,10 @@
</el-col>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
ProductApi
,
ProductVO
}
from
'@/api/iot/product'
import
ProductDetailsHeader
from
'@/views/iot/product/detail/ProductDetailsHeader.vue'
import
ProductDetailsInfo
from
'@/views/iot/product/detail/ProductDetailsInfo.vue'
import
{
DeviceApi
}
from
'@/api/iot/device'
defineOptions
({
name
:
'IoTProductDetail'
})
...
...
@@ -33,17 +35,25 @@ const getProductData = async (id: number) => {
loading
.
value
=
true
try
{
product
.
value
=
await
ProductApi
.
getProduct
(
id
)
console
.
log
(
product
.
value
)
console
.
log
(
'Product data:'
,
product
.
value
)
}
finally
{
loading
.
value
=
false
}
}
/** 获取物模型 */
// 查询设备数量
const
getDeviceCount
=
async
(
productId
:
string
)
=>
{
try
{
const
count
=
await
DeviceApi
.
getDeviceCount
(
productId
)
console
.
log
(
'Device count response:'
,
count
)
return
count
}
catch
(
error
)
{
console
.
error
(
'Error fetching device count:'
,
error
)
return
0
}
}
/** 初始化 */
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
currentRoute
}
=
useRouter
()
// 路由
onMounted
(
async
()
=>
{
if
(
!
id
)
{
message
.
warning
(
'参数错误,产品不能为空!'
)
...
...
@@ -51,5 +61,12 @@ onMounted(async () => {
return
}
await
getProductData
(
id
)
// 查询设备数量
if
(
product
.
value
.
id
)
{
product
.
value
.
deviceCount
=
await
getDeviceCount
(
product
.
value
.
id
)
console
.
log
(
'Device count:'
,
product
.
value
.
deviceCount
)
}
else
{
console
.
error
(
'Product ID is undefined'
)
}
})
</
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