9 changed files with 354 additions and 8 deletions
@ -0,0 +1,103 @@
|
||||
<script lang="ts" setup> |
||||
import { Card, Empty } from 'ant-design-vue' |
||||
import { FieldTimeOutlined, SyncOutlined } from '@ant-design/icons-vue' |
||||
import { useDeviceInfo } from './composables/useDeviceInfo' |
||||
import { useDeviceProperties } from './composables/useDeviceProperties' |
||||
import { Description } from '@/components/Description' |
||||
import { useModelService } from '@/views/product/components/composables/useModelService' |
||||
|
||||
const { data, scheam } = useDeviceInfo() |
||||
|
||||
const { |
||||
modelServiceList, |
||||
selectedModelId, |
||||
setSelectedModelId, |
||||
} = useModelService(() => data.value?.productId) |
||||
|
||||
const { |
||||
isLoading, |
||||
deviceProperties, |
||||
reloadReviceProperties, |
||||
} = useDeviceProperties(() => selectedModelId.value, () => data.value?.deviceSn) |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<Description :schema="scheam" :data="data" :column="2" :label-style="{ width: '130px' }" /> |
||||
|
||||
<Card mt="15px"> |
||||
<div flex="~ items-center justify-between"> |
||||
<div font-bold> |
||||
最新上报数据 |
||||
</div> |
||||
<div flex="~ items-center"> |
||||
<span v-if="deviceProperties" text="gray-400 center" mr="12px"> |
||||
更新时间: {{ deviceProperties.updateTime }} |
||||
</span> |
||||
<a-button size="small" @click="reloadReviceProperties"> |
||||
<SyncOutlined /> |
||||
刷新 |
||||
</a-button> |
||||
</div> |
||||
</div> |
||||
|
||||
<div v-loading="isLoading" flex="~ gap-12px" mt="12px"> |
||||
<div w="20%" border="0 r-1 solid gray-50"> |
||||
<div |
||||
v-for="item in modelServiceList" :key="item.id" |
||||
flex="~ items-center justify-between" |
||||
class="box-border h-60px cursor-pointer pl-10px hover:bg-gray-100" |
||||
border="0 b-1 solid gray-50" |
||||
:class="selectedModelId === item.id ? 'bg-gray-100' : ''" |
||||
@click="setSelectedModelId(item.id)" |
||||
> |
||||
<div> |
||||
<div truncate> |
||||
{{ item.serviceId }} |
||||
</div> |
||||
<div mt="5px" text="12px gray-500" truncate :title="item.description"> |
||||
{{ item.description }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div |
||||
v-if="deviceProperties && deviceProperties.properties" |
||||
w-0 flex-1 |
||||
grid="~ cols-4 rows-[160px] auto-rows-[160px] gap-12px" |
||||
> |
||||
<div |
||||
v-for="item in deviceProperties.properties" |
||||
:key="item.identifier" |
||||
p="15px" box-border rounded |
||||
shadow="hover:lg" transition="shadow" |
||||
flex="~ col justify-between" |
||||
style="background: linear-gradient(to right, rgba(243, 244, 246, 1), rgba(209, 216, 224, 1));" |
||||
> |
||||
<div> |
||||
<div font-500 text="16px"> |
||||
{{ item.name }} |
||||
</div> |
||||
<div text="gray-500"> |
||||
{{ item.identifier }} |
||||
</div> |
||||
</div> |
||||
<div text="22px center"> |
||||
"{{ item.unit || 'null' }}" |
||||
</div> |
||||
<div text="right gray-500 hover:gray-800"> |
||||
<span class="cursor-pointer"> |
||||
<FieldTimeOutlined /> |
||||
历史 |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div v-else text="center" flex-1> |
||||
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> |
||||
</div> |
||||
</div> |
||||
</Card> |
||||
</div> |
||||
</template> |
@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup> |
||||
import { h } from 'vue' |
||||
import { Alert, Tag } from 'ant-design-vue' |
||||
import { SyncOutlined } from '@ant-design/icons-vue' |
||||
import { useRoute } from 'vue-router' |
||||
import { BasicTable, useTable } from '@/components/Table' |
||||
import { getDeviceTopicList } from '@/api/device-manage/device' |
||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
||||
|
||||
const route = useRoute() |
||||
const { getSystemEnumLabel } = useSystemEnumStore() |
||||
const [register, { reload }] = useTable({ |
||||
api(params) { |
||||
return getDeviceTopicList({ ...params, deviceId: route.params.id as string }) |
||||
}, |
||||
columns: [ |
||||
{ |
||||
title: 'Topic', |
||||
dataIndex: 'topic', |
||||
}, |
||||
{ |
||||
title: '操作权限', |
||||
dataIndex: 'topicType', |
||||
customRender({ value }) { |
||||
return h(Tag, () => getSystemEnumLabel('eProductTopicType', value)) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '描述', |
||||
dataIndex: 'topicDesc', |
||||
}, |
||||
], |
||||
bordered: true, |
||||
inset: true, |
||||
canResize: false, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div flex="~ items-center justify-between"> |
||||
<Alert |
||||
type="info" |
||||
show-icon |
||||
class="py-4px text-13px" |
||||
message="Topic用以将设备数据分类上报,进而分别进行处理。除了系统预置的Topic,您也可以自定义Topic,然后在设备侧开发时选择数据上报的Topic。" |
||||
/> |
||||
<a-button size="small" @click="reload"> |
||||
<SyncOutlined /> |
||||
刷新 |
||||
</a-button> |
||||
</div> |
||||
<BasicTable mt="12px" @register="register" /> |
||||
</template> |
@ -0,0 +1,90 @@
|
||||
import { h } from 'vue' |
||||
import { Button, Tag } from 'ant-design-vue' |
||||
import { EyeOutlined } from '@ant-design/icons-vue' |
||||
import { useRoute } from 'vue-router' |
||||
import { useAsyncState } from '@vueuse/core' |
||||
import type { DescItem } from '@/components/Description' |
||||
import { getDeviceDetail } from '@/api/device-manage/device' |
||||
|
||||
export function useDeviceInfo() { |
||||
const scheam: DescItem[] = [ |
||||
{ |
||||
field: 'productName', |
||||
label: '所属产品', |
||||
}, |
||||
{ |
||||
field: 'deviceName', |
||||
label: '设备名称', |
||||
}, |
||||
{ |
||||
field: 'deviceSn', |
||||
label: '设备序列号', |
||||
}, |
||||
{ |
||||
field: 'deviceSecret', |
||||
label: '设备密钥', |
||||
}, |
||||
{ |
||||
field: 'isActive', |
||||
label: '设备状态', |
||||
render(value) { |
||||
return h(Tag, { color: value ? 'green' : 'default' }, () => value ? '已激活' : '未激活') |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'isOnline', |
||||
label: '在线状态', |
||||
render(value) { |
||||
return h(Tag, { color: value ? 'green' : 'default' }, () => value ? '在线' : '离线') |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'createTime', |
||||
label: '创建时间', |
||||
}, |
||||
{ |
||||
field: 'activeTime', |
||||
label: '激活时间', |
||||
}, |
||||
{ |
||||
field: 'lastOnlineTime', |
||||
label: '最后上线时间', |
||||
}, |
||||
{ |
||||
field: 'lastOfflineTime', |
||||
label: '最后离线时间', |
||||
}, |
||||
{ |
||||
field: 'mqtt', |
||||
label: 'MQTT连接参数', |
||||
render: () => h(Button, { |
||||
size: 'small', |
||||
onClick: openMqttParamsModal, |
||||
}, () => [h(EyeOutlined), '查看参数']), |
||||
}, |
||||
{ |
||||
field: 'report', |
||||
label: '上报示例', |
||||
render: () => h(Button, { |
||||
size: 'small', |
||||
onClick: openReportExampleModal, |
||||
}, () => [h(EyeOutlined), '查看示例']), |
||||
}, |
||||
{ |
||||
field: 'deviceDesc', |
||||
label: '设备描述', |
||||
}, |
||||
] |
||||
|
||||
const route = useRoute() |
||||
const { state: data } = useAsyncState(() => getDeviceDetail(route.params.id as string), undefined) |
||||
|
||||
function openMqttParamsModal() {} |
||||
|
||||
function openReportExampleModal() {} |
||||
|
||||
return { |
||||
data, |
||||
scheam, |
||||
} |
||||
} |
@ -0,0 +1,27 @@
|
||||
import { useAsyncState } from '@vueuse/core' |
||||
import { type MaybeRefOrGetter, toValue, watchEffect } from 'vue' |
||||
import { getDeviceProperties } from '@/api/device-manage/device' |
||||
|
||||
export function useDeviceProperties(modelId: MaybeRefOrGetter<string | undefined>, deviceSn: MaybeRefOrGetter<string | undefined>) { |
||||
const { state, execute, isLoading } = useAsyncState( |
||||
() => getDeviceProperties(toValue(modelId)!, toValue(deviceSn)!), |
||||
undefined, |
||||
{ |
||||
immediate: false, |
||||
resetOnExecute: false, |
||||
}, |
||||
) |
||||
|
||||
watchEffect(() => { |
||||
if (!toValue(modelId) || !toValue(deviceSn)) |
||||
return |
||||
|
||||
execute() |
||||
}) |
||||
|
||||
return { |
||||
isLoading, |
||||
deviceProperties: state, |
||||
reloadReviceProperties: execute, |
||||
} |
||||
} |
@ -0,0 +1,2 @@
|
||||
export { default as DeviceInfo } from './DeviceInfo.vue' |
||||
export { default as TopicList } from './TopicList.vue' |
@ -1,5 +1,30 @@
|
||||
<script lang='ts' setup> |
||||
import { Card, Tabs } from 'ant-design-vue' |
||||
import { DeviceInfo, TopicList } from './components' |
||||
|
||||
defineOptions({ name: 'DeviceDetail' }) |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
Detail |
||||
<div p="12px"> |
||||
<Card title="设备详情"> |
||||
<Tabs> |
||||
<Tabs.TabPane key="1" tab="设备信息"> |
||||
<DeviceInfo /> |
||||
</Tabs.TabPane> |
||||
<Tabs.TabPane key="2" tab="已订阅 Topic"> |
||||
<TopicList /> |
||||
</Tabs.TabPane> |
||||
<Tabs.TabPane key="3" tab="云端下发"> |
||||
TODO |
||||
</Tabs.TabPane> |
||||
</Tabs> |
||||
</Card> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="less"> |
||||
:deep(.ant-card-body) { |
||||
padding-top: 10px; |
||||
} |
||||
</style> |
||||
|
Loading…
Reference in new issue