iot-fontend/src/views/iot/device/components/device-detail.vue
Kven a045b8fc2d feat(iot): 优化设备详情和地图展示
- 移除了不必要的导入语句
- 优化了设备上报记录的展示逻辑,添加了详情弹窗
-修复了设备地图组件的样式问题
- 优化了产品 TSL 数据的请求逻辑,提高了数据加载效率
- 改进了设备列表中的在线状态展示,使用标签进行区分
2025-03-11 22:03:44 +08:00

352 lines
9.8 KiB
Vue

<template>
<div class="container">
<Breadcrumb :items="['物联网管理', '设备管理', '设备详情']" />
<a-card class="general-card" title=" ">
<a-descriptions v-model="renderData" size="large">
<template #title
><h3 style="margin-top: -15px"
><a-button
class="nav-btn"
type="outline"
:shape="'circle'"
@click="() => $router.go(-1)"
>
<icon-undo />
</a-button>
设备详情</h3
>
</template>
<a-descriptions-item label="设备名称">{{
renderData.name
}}</a-descriptions-item>
<a-descriptions-item label="硬件版本">{{
renderData.hardwareVersion
}}</a-descriptions-item>
<a-descriptions-item label="固件版本">{{
renderData.firmwareVersion
}}</a-descriptions-item>
<a-descriptions-item label="所属产品">{{
renderData.productName
}}</a-descriptions-item>
<!-- <a-descriptions-item label="备注">{{renderData.remark }}</a-descriptions-item>-->
<a-descriptions-item label="创建时间">{{
dayjs(renderData.createTime).format('YYYY-MM-DD HH:mm:ss')
}}</a-descriptions-item>
</a-descriptions>
</a-card>
<a-card class="general-card" style="margin-top: 10px">
<a-tabs
:active-key="activeKey"
@tab-click="handleMenuClick"
style="padding-top: 20px"
>
<a-tab-pane key="1" tab="参数" title="扩展属性">
<a-table :columns="columns" :data="renderData.params" />
</a-tab-pane>
<a-tab-pane key="2" tab="基本信息" title="基本信息">
<a-card class="general-card" title=" ">
<a-descriptions size="large">
<template #title>
<h3 style="margin-top: -15px">所属产品详情</h3>
</template>
<a-descriptions-item label="产品名称">{{
renderData.productName
}}</a-descriptions-item>
<a-descriptions-item label="产品类型">{{
renderData.productType
}}</a-descriptions-item>
<a-descriptions-item label="产品型号">{{
renderData.model
}}</a-descriptions-item>
<a-descriptions-item label="通讯协议">{{
renderData.link
}}</a-descriptions-item>
<a-descriptions-item label="备注">{{
renderData.remark
}}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{
dayjs(renderData.createTime).format('YYYY-MM-DD HH:mm:ss')
}}</a-descriptions-item>
</a-descriptions>
</a-card>
</a-tab-pane>
<a-tab-pane key="3" tab="执行服务" title="执行服务">
<a-table :columns="deviceServeColumns" :data="deviceServerData" >
<template #operations="{ record }">
<a-button
type="text"
status="success"
@click="openServeForm(record)"
>
<icon-plus />执行
</a-button>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="4" tab="上报记录" title="上报记录">
<a-table :columns="deviceReportColumns" :data="deviceReportData" >
<template #recordTime="{ record }">
<div>{{ dayjs(record.recordTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
</template>
<template #content="{ record }">
<div v-if="record.content">
<span v-for="(value, key, index) in record.content" :key="key">
<template v-if="index === 0">
<strong>{{ key }}:</strong> {{ value }}
</template>
</span>
<span v-if="Object.keys(record.content).length > 1">. . . . . .</span>
</div>
</template>
<template #operations="{ record }">
<a-button type="text" @click="openDetailModal(record)">
<template #icon><icon-list /></template>
查看详情
</a-button>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="5" tab="设备地图" title="设备地图">
<DeviceMap v-if="renderData.id" :renderData=renderData />
</a-tab-pane>
</a-tabs>
</a-card>
<a-modal
width="1100px"
height="500px"
:visible="visible"
@cancel="handleCancel"
>
<template #title>执行服务</template>
<dynamicForm
:fields="fields"
@update:form-data="handleFormDataUpdate"
/>
<template #footer>
<a-button class="editor-button" @click="handleCancel">取消</a-button>
<a-button class="editor-button" type="primary" @click="handleSubmit">确定</a-button>
</template>
</a-modal>
<a-modal
:visible="detailVisible"
@cancel="closeDetailModal"
title="上报记录详情"
>
<template #footer>
<a-button @click="closeDetailModal">关闭</a-button>
</template>
<div v-if="selectedRecord">
<p><strong>上报时间:</strong> {{ dayjs(selectedRecord.recordTime).format('YYYY-MM-DD HH:mm:ss') }}</p>
<div v-if="selectedRecord.content">
<div v-for="(value, key) in selectedRecord.content" :key="key">
<strong>{{ key }}:</strong> {{ value }}
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs';
import { useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { queryDeviceDetail, queryDeviceRecord, sendCommand } from '@/api/device';
import { queryServeDetail, queryServeList } from '@/api/tsl';
import useVisible from '@/hooks/visible';
import dynamicForm from './dynamic-form.vue';
import DeviceMap from './device-map.vue';
const route = useRoute();
const { visible, setVisible } = useVisible();
const id = Number(route.params.id);
const columns = [
{
title: '参数名称',
dataIndex: 'name',
slotName: 'name',
},
{
title: '参数标识',
dataIndex: 'identifier',
slotName: 'identifier',
},
{
title: '数据类型',
dataIndex: 'dataType',
slotName: 'dataType',
},
{
title: '参数类型',
dataIndex: 'type',
slotName: 'type',
},
];
const deviceReportColumns = [
{
title: '上报时间',
dataIndex: 'recordTime',
slotName: 'recordTime',
},
{
title: '内容',
dataIndex: 'content',
slotName: 'content',
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
}
];
const deviceServeColumns = [
{
title: ' 服务名称',
dataIndex: 'name',
slotName: 'name',
},
{
title: '备注',
dataIndex: 'remark',
slotName: 'remark',
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
},
];
const activeKey = ref('1');
const detailVisible = ref(false);
const selectedRecord = ref<any>(null);
const openDetailModal = (record: any) => {
selectedRecord.value = record;
detailVisible.value = true;
};
const closeDetailModal = () => {
detailVisible.value = false;
selectedRecord.value = null;
};
const renderData = ref(
{
clientId: 0,
productId: 0,
}
);
const deviceReportData = ref([]);
const deviceServerData = ref([]);
const fields = ref([]);
const fetchData = async (Id: number) => {
const res = await queryDeviceDetail(Id);
renderData.value = res.data;
const res1 = await queryDeviceRecord({
clientId: renderData.value.clientId,
size: 10,
current: 1,
});
deviceReportData.value = res1.data.records;
const res2 = await queryServeList({
productId: renderData.value.productId,
size: 10,
current: 1,
})
deviceServerData.value = res2.data.records;
};
const handleMenuClick = (e: any) => {
activeKey.value = e;
};
const dynamicFormData = ref();
// 关闭
const handleCancel = async () => {
setVisible(false);
};
const openServeForm = async (record: object) => {
setVisible(true);
const res = await queryServeDetail(record.id);
fields.value = res.data.inputs;
};
const handleFormDataUpdate = (newFormData: any) => {
dynamicFormData.value = newFormData; // 更新 formData
};
// 确定
const handleSubmit = async () => {
try {
const { qos, ...rest } = dynamicFormData.value;
const params = {
deviceId: id,
qos,
paras: rest,
};
const res = await sendCommand(params);
if (res.status === 200) {
Message.success({
content: '执行成功',
duration: 5 * 1000,
});
setVisible(false);
} else {
Message.error({
content: '执行失败',
duration: 5 * 1000,
});
}
} catch (error) {
Message.error({
content: '表单不能为空',
duration: 5 * 1000,
});
}
};
onMounted(() => {
fetchData(id);
});
</script>
<style scoped>
.container {
padding: 0 20px 20px 20px;
}
h1 {
font-size: 24px;
margin-bottom: 10px;
}
.meta span {
margin-right: 20px;
}
.attachments li {
margin-bottom: 10px;
}
.attachments a {
display: flex;
align-items: center;
font-size: 14px;
color: #1890ff;
text-decoration: none;
}
.attachments a:hover {
text-decoration: underline;
}
.nav-btn {
border-color: rgb(var(--gray-2));
color: rgb(var(--gray-8));
font-size: 16px;
}
</style>