2
0

fix:首页仪表盘

This commit is contained in:
zhongzm
2025-01-14 14:09:27 +08:00
parent 28200f4931
commit 134a417eb1
5 changed files with 279 additions and 172 deletions

View File

@@ -209,6 +209,15 @@ export function getDashboardOverview() {
method: 'get' method: 'get'
}); });
} }
/** 获取仪表盘站点列表 */
export function getDashboardSiteList(params: { pageNum: number; pageSize: number; name?: string }) {
return request<Api.DashboardSiteResponse>({
url: '/system/dashboard/page',
method: 'get',
params
});
}

77
src/typings/api.d.ts vendored
View File

@@ -608,44 +608,59 @@ declare namespace Api {
} }
} }
interface DashboardOverview { interface DashboardOverview {
cloud: { cloud: {
connected: boolean; connected: boolean;
}; };
sites: { siteNum: number;
total: number;
countries: number;
connected: number;
disconnected: number;
};
devices: { devices: {
gateways: { totalGatewayNum: number;
connected: number; connectedGatewayNum: number;
disconnected: number; disconnectedGatewayNum: number;
}; totalSwitchNum: number;
switches: { connectedSwitchNum: number;
connected: number; disconnectedSwitchNum: number;
disconnected: number; totalApNum: number;
}; connectedApNum: number;
olts: { disconnectedApNum: number;
connected: number; isolatedApNum: number;
disconnected: number;
};
ap: {
connected: number;
disconnected: number;
isolated: number;
};
}; };
client: { client: {
wiredUsers: number; totalClientNum: number;
wirelessUsers: number; wiredClientNum: number;
wirelessGuests: number; wirelessClientNum: number;
}; guestNum: number;
alerts: number;
users: {
registered: number;
online: number;
}; };
registerUserNum: number;
onlineUserNum: number;
}
interface DashboardSite {
siteId: string;
name: string;
region: string;
tagIds: string[];
timeZone: string;
scenario: string;
totalGatewayNum: number;
connectedGatewayNum: number;
disconnectedGatewayNum: number;
totalSwitchNum: number;
connectedSwitchNum: number;
disconnectedSwitchNum: number;
totalApNum: number;
connectedApNum: number;
disconnectedApNum: number;
isolatedApNum: number;
totalClientNum: number;
wiredClientNum: number;
wirelessClientNum: number;
guestNum: number;
}
interface DashboardSiteResponse {
rows: DashboardSite[];
total: number;
} }
} }

View File

@@ -153,6 +153,7 @@ declare global {
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentScope: typeof import('vue')['getCurrentScope']
const getDashboardOverview: typeof import('../service/api/auth')['getDashboardOverview'] const getDashboardOverview: typeof import('../service/api/auth')['getDashboardOverview']
const getDashboardSiteList: typeof import('../service/api/auth')['getDashboardSiteList']
const getData: typeof import('../service/api/dictData')['getData'] const getData: typeof import('../service/api/dictData')['getData']
const getDefaultHomeTab: typeof import('../store/modules/tab/shared')['getDefaultHomeTab'] const getDefaultHomeTab: typeof import('../store/modules/tab/shared')['getDefaultHomeTab']
const getDictDataType: typeof import('../service/api/dict')['getDictDataType'] const getDictDataType: typeof import('../service/api/dict')['getDictDataType']

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue'; import { ref, watch } from 'vue';
import type { TableColumnType } from 'ant-design-vue';
import { import {
EnvironmentOutlined, EnvironmentOutlined,
EditOutlined, EditOutlined,
@@ -10,28 +11,59 @@ import {
PlusOutlined PlusOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { getDashboardSiteList } from '@/service/api/auth';
defineOptions({ defineOptions({
name: 'CardData' name: 'CardData'
}); });
interface SiteData {
id: number;
name: string;
region: string;
alerts: number;
gateway: string;
switches: string;
olts: string;
eaps: string;
}
// 搜索和分页状态 // 搜索和分页状态
const searchValue = ref(''); const searchValue = ref('');
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const loading = ref(false);
const total = ref(0);
const siteData = ref<Api.DashboardSite[]>([]);
const fetchSiteList = async () => {
try {
loading.value = true;
const { data } = await getDashboardSiteList({
pageNum: currentPage.value,
pageSize: pageSize.value,
name: searchValue.value
});
console.log('API Response:', data);
if (data) {
siteData.value = data.rows || [];
total.value = data.total || 0;
console.log('Processed Site Data:', siteData.value);
console.log('Total:', total.value);
} else {
siteData.value = [];
total.value = 0;
}
}catch (error){
console.error('Failed to fetch site list:', error);
siteData.value = [];
total.value = 0;
}finally {
loading.value = false;
}
};
// 修改监听方式
watch([currentPage, pageSize, searchValue], () => {
fetchSiteList();
}, { immediate: true });
// 表格列定义 // 表格列定义
const columns = [ const columns: TableColumnType<Api.DashboardSite>[] = [
{ {
title: 'NAME', title: 'NAME',
key: 'name', key: 'name',
@@ -45,95 +77,69 @@ const columns = [
{ {
title: 'ALERTS', title: 'ALERTS',
key: 'alerts', key: 'alerts',
dataIndex: 'alerts',
width: 100 width: 100
}, },
{ {
title: 'GATEWAY', title: 'GATEWAY',
key: 'gateway', key: 'gateway',
dataIndex: 'gateway',
width: 100 width: 100
}, },
{ {
title: 'SWITCHES', title: 'SWITCHES',
key: 'switches', key: 'switches',
dataIndex: 'switches',
width: 100 width: 100
}, },
{ {
title: 'OLTS', title: 'OLTS',
key: 'olts', key: 'olts',
dataIndex: 'olts',
width: 100 width: 100
}, },
{ {
title: 'EAPS', title: 'EAPS',
key: 'eaps', key: 'eaps',
dataIndex: 'eaps',
width: 100 width: 100
}, },
{ {
title: 'ACTION', title: 'CLIENTS',
key: 'action', key: 'clients',
width: 150, width: 150
fixed: 'right'
}
];
const siteData = computed<SiteData[]>(() => [
{
id: 1,
name: 'wfc-dev-omada1-site1',
region: 'China mainland',
alerts: 0,
gateway: '-',
switches: '0 / 0',
olts: '0 / 0',
eaps: '1 / 1'
}, },
{ // {
id: 2, // title: 'ACTION',
name: 'wfc-dev-omada1-site2', // key: 'action',
region: 'China mainland', // width: 150,
alerts: 0, // fixed: 'right'
gateway: '-', // }
switches: '0 / 0', ];
olts: '0 / 0',
eaps: '0 / 1'
}
]);
// 按钮操作处理函数 // 按钮操作处理函数
const handleEdit = (record: SiteData) => { // const handleEdit = (record: Api.DashboardSite) => {
console.log('Edit:', record); // console.log('Edit:', record);
}; // };
//
const handleCopy = (record: SiteData) => { // const handleCopy = (record: Api.DashboardSite) => {
console.log('Copy:', record); // console.log('Copy:', record);
}; // };
//
const handleDelete = (record: SiteData) => { // const handleDelete = (record: Api.DashboardSite) => {
console.log('Delete:', record); // console.log('Delete:', record);
}; // };
//
const handleHome = (record: SiteData) => { // const handleHome = (record: Api.DashboardSite) => {
console.log('Home:', record); // console.log('Home:', record);
}; // };
// 分页处理函数 // 分页处理函数
// const handlePageChange = (page: number) => { const handlePageChange = (page: number, pageSize: number) => {
// currentPage.value = page; currentPage.value = page;
// }; if (pageSize !== undefined) {
handlePageSizeChange(pageSize);
}
};
// const handlePageSizeChange = (size: number) => { const handlePageSizeChange = (size: number) => {
// pageSize.value = size; pageSize.value = size;
// currentPage.value = 1; currentPage.value = 1;
// }; };
// const handleGoToPage = () => {
// // 处理跳转页面逻辑
// console.log('Go to page:', currentPage.value);
// };
</script> </script>
<template> <template>
@@ -141,7 +147,6 @@ const handleHome = (record: SiteData) => {
<div class="flex justify-between items-center mb-16px"> <div class="flex justify-between items-center mb-16px">
<div class="flex items-center gap-8px"> <div class="flex items-center gap-8px">
<span class="text-16px font-medium">Site List</span> <span class="text-16px font-medium">Site List</span>
<!-- <span class="text-12px text-gray-500">1-2 of 2 records</span>-->
</div> </div>
<div class="flex items-center gap-16px"> <div class="flex items-center gap-16px">
<AInput <AInput
@@ -154,65 +159,82 @@ const handleHome = (record: SiteData) => {
<search-outlined /> <search-outlined />
</template> </template>
</AInput> </AInput>
<AButton type="primary"> <!-- <AButton type="primary">-->
<template #icon> <!-- <template #icon>-->
<plus-outlined /> <!-- <plus-outlined />-->
</template> <!-- </template>-->
Import Site <!-- Import Site-->
</AButton> <!-- </AButton>-->
<AButton type="primary"> <!-- <AButton type="primary">-->
<template #icon> <!-- <template #icon>-->
<plus-outlined /> <!-- <plus-outlined />-->
</template> <!-- </template>-->
Add New Site <!-- Add New Site-->
</AButton> <!-- </AButton>-->
</div> </div>
</div> </div>
<ATable <ATable
:columns="columns" :columns="columns"
:data-source="siteData" :data-source="siteData"
:pagination="false" :pagination="{
current: currentPage,
pageSize: pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条`,
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange
}"
row-key="siteId"
:scroll="{ x: 1200 }" :scroll="{ x: 1200 }"
row-key="id" :loading="loading"
> >
<!-- 添加一个调试信息 -->
<template #headerCell="{ column }">
<span>{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<!-- 添加调试信息 -->
{{ console.log('Rendering cell:', column.key, record) }}
<template v-if="column.key === 'name'"> <template v-if="column.key === 'name'">
<div class="flex items-center gap-8px"> <div class="flex items-center gap-8px">
<environment-outlined class="text-16px" /> <environment-outlined class="text-16px" />
<span>{{ record.name }}</span> <span>{{ record.name }}</span>
</div> </div>
</template> </template>
<template v-else-if="column.key === 'region'">
<span>{{ record.region }}</span>
</template>
<template v-else-if="column.key === 'alerts'"> <template v-else-if="column.key === 'alerts'">
<ATag v-if="record.alerts > 0" color="error">{{ record.alerts }}</ATag> <span>0</span>
<span v-else>{{ record.alerts }}</span>
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'gateway'">
<div class="flex items-center gap-8px"> <span>{{ record.connectedGatewayNum }}/{{ record.disconnectedGatewayNum }}</span>
<edit-outlined class="cursor-pointer text-primary" @click="handleEdit(record)" />
<copy-outlined class="cursor-pointer text-primary" @click="handleCopy(record)" />
<delete-outlined class="cursor-pointer text-red-500" @click="handleDelete(record)" />
<home-outlined class="cursor-pointer text-primary" @click="handleHome(record)" />
</div>
</template> </template>
<template v-else-if="column.key === 'switches'">
<span>{{ record.connectedSwitchNum }}/{{ record.disconnectedSwitchNum }}</span>
</template>
<template v-else-if="column.key === 'olts'">
<span>0/0</span>
</template>
<template v-else-if="column.key === 'eaps'">
<span>{{ record.connectedApNum }}/{{ record.disconnectedApNum }}/{{ record.isolatedApNum }}</span>
</template>
<template v-else-if="column.key === 'clients'">
<span>{{ record.wiredClientNum }}/{{ record.wirelessClientNum }}/{{ record.guestNum }}</span>
</template>
<!-- <template v-else-if="column.key === 'action'">-->
<!-- <div class="flex items-center gap-8px">-->
<!-- <edit-outlined class="cursor-pointer text-primary" @click="handleEdit(record)" />-->
<!-- <copy-outlined class="cursor-pointer text-primary" @click="handleCopy(record)" />-->
<!-- <delete-outlined class="cursor-pointer text-red-500" @click="handleDelete(record)" />-->
<!-- <home-outlined class="cursor-pointer text-primary" @click="handleHome(record)" />-->
<!-- </div>-->
<!-- </template>-->
</template> </template>
</ATable> </ATable>
<div class="flex justify-between items-center mt-16px">
<div class="text-12px text-gray-500">
Showing 1-2 of 2 records
</div>
<div class="flex items-center gap-8px">
<ASelect v-model:value="pageSize" style="width: 100px">
<ASelectOption :value="10">10 / page</ASelectOption>
<ASelectOption :value="20">20 / page</ASelectOption>
<ASelectOption :value="50">50 / page</ASelectOption>
</ASelect>
<span class="text-12px text-gray-500">Go to page:</span>
<AInput v-model:value="currentPage" style="width: 50px" />
<AButton>Go</AButton>
</div>
</div>
</ACard> </ACard>
</template> </template>

View File

@@ -7,6 +7,8 @@ import {
UserAddOutlined, UserAddOutlined,
UserSwitchOutlined UserSwitchOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { computed, ref, onMounted } from 'vue';
import { getDashboardOverview } from '@/service/api/auth';
defineOptions({ defineOptions({
name: 'HeaderBanner' name: 'HeaderBanner'
@@ -14,25 +16,61 @@ defineOptions({
const overviewData = ref<Api.DashboardOverview>({ const overviewData = ref<Api.DashboardOverview>({
cloud: { connected: false }, cloud: { connected: false },
sites: { total: 0, countries: 0, connected: 0, disconnected: 0 }, siteNum: 0,
devices: { devices: {
gateways: { connected: 0, disconnected: 0 }, totalGatewayNum: 0,
switches: { connected: 0, disconnected: 0 }, connectedGatewayNum: 0,
olts: { connected: 0, disconnected: 0 }, disconnectedGatewayNum: 0,
ap: { connected: 0, disconnected: 0, isolated: 0 } totalSwitchNum: 0,
connectedSwitchNum: 0,
disconnectedSwitchNum: 0,
totalApNum: 0,
connectedApNum: 0,
disconnectedApNum: 0,
isolatedApNum: 0
}, },
client: { wiredUsers: 0, wirelessUsers: 0, wirelessGuests: 0 }, client: {
alerts: 0, totalClientNum: 0,
users: { registered: 0, online: 0 } wiredClientNum: 0,
wirelessClientNum: 0,
guestNum: 0
},
registerUserNum: 0,
onlineUserNum: 0
}); });
const fetchOverviewData = async () => { const fetchOverviewData = async () => {
// try { try {
// const data = await authStore.getDashboardOverview(); const { data } = await getDashboardOverview();
// overviewData.value = data; if (data) {
// } catch (error) { overviewData.value = {
// console.error('Failed to fetch overview data:', error); cloud: { connected: data.cloud?.connected || false },
// } siteNum: data.siteNum || 0,
devices: {
totalGatewayNum: data.devices?.totalGatewayNum || 0,
connectedGatewayNum: data.devices?.connectedGatewayNum || 0,
disconnectedGatewayNum: data.devices?.disconnectedGatewayNum || 0,
totalSwitchNum: data.devices?.totalSwitchNum || 0,
connectedSwitchNum: data.devices?.connectedSwitchNum || 0,
disconnectedSwitchNum: data.devices?.disconnectedSwitchNum || 0,
totalApNum: data.devices?.totalApNum || 0,
connectedApNum: data.devices?.connectedApNum || 0,
disconnectedApNum: data.devices?.disconnectedApNum || 0,
isolatedApNum: data.devices?.isolatedApNum || 0
},
client: {
totalClientNum: data.client?.totalClientNum || 0,
wiredClientNum: data.client?.wiredClientNum || 0,
wirelessClientNum: data.client?.wirelessClientNum || 0,
guestNum: data.client?.guestNum || 0
},
registerUserNum: data.registerUserNum || 0,
onlineUserNum: data.onlineUserNum || 0
};
}
} catch (error) {
console.error('Failed to fetch overview data:', error);
}
}; };
onMounted(() => { onMounted(() => {
@@ -40,18 +78,43 @@ onMounted(() => {
}); });
const deviceStatus = computed(() => ({ const deviceStatus = computed(() => ({
ap: overviewData.value.devices.ap, ap: {
client: overviewData.value.client, connected: overviewData.value.devices.connectedApNum,
alerts: overviewData.value.alerts, disconnected: overviewData.value.devices.disconnectedApNum,
users: overviewData.value.users isolated: overviewData.value.devices.isolatedApNum
},
client: {
wiredUsers: overviewData.value.client.wiredClientNum,
wirelessUsers: overviewData.value.client.wirelessClientNum,
wirelessGuests: overviewData.value.client.guestNum
},
users: {
registered: overviewData.value.registerUserNum,
online: overviewData.value.onlineUserNum
},
alerts: 0
})); }));
const siteInfo = computed(() => overviewData.value.sites); const siteInfo = computed(() => ({
total: overviewData.value.siteNum
}));
const otherDevices = computed(() => ({ const otherDevices = computed(() => ({
gateways: overviewData.value.devices.gateways, gateways: {
switches: overviewData.value.devices.switches, total: overviewData.value.devices.totalGatewayNum,
olts: overviewData.value.devices.olts connected: overviewData.value.devices.connectedGatewayNum,
disconnected: overviewData.value.devices.disconnectedGatewayNum
},
switches: {
total: overviewData.value.devices.totalSwitchNum,
connected: overviewData.value.devices.connectedSwitchNum,
disconnected: overviewData.value.devices.disconnectedSwitchNum
},
olts: {
total: 0,
connected: 0,
disconnected: 0
}
})); }));
</script> </script>
@@ -87,9 +150,6 @@ const otherDevices = computed(() => ({
<div class="border-t border-gray-100 my-4px"></div> <div class="border-t border-gray-100 my-4px"></div>
<div class="flex flex-col text-12px text-gray-500"> <div class="flex flex-col text-12px text-gray-500">
<span>Total Sites: {{ siteInfo.total }}</span> <span>Total Sites: {{ siteInfo.total }}</span>
<span>Countries: {{ siteInfo.countries }}</span>
<span>Connected: {{ siteInfo.connected }}</span>
<span>Disconnected: {{ siteInfo.disconnected }}</span>
</div> </div>
</div> </div>
@@ -130,7 +190,7 @@ const otherDevices = computed(() => ({
<div class="flex items-center gap-6px mb-6px"> <div class="flex items-center gap-6px mb-6px">
<div class="size-48px flex-center bg-gray-100 rounded-lg relative"> <div class="size-48px flex-center bg-gray-100 rounded-lg relative">
<group-outlined class="text-24px text-primary" /> <group-outlined class="text-24px text-primary" />
<span class="text-20px font-semibold absolute -right-2 -top-2 bg-primary text-white rounded-full w-6 h-6 flex-center">{{ otherDevices.olts.connected + otherDevices.olts.disconnected }}</span> <span class="text-20px font-semibold absolute -right-2 -top-2 bg-primary text-white rounded-full w-6 h-6 flex-center">{{ otherDevices.olts.total }}</span>
</div> </div>
<div class="text-16px font-medium">OLTs</div> <div class="text-16px font-medium">OLTs</div>
</div> </div>
@@ -213,8 +273,8 @@ const otherDevices = computed(() => ({
</div> </div>
<div class="border-t border-gray-100 my-4px"></div> <div class="border-t border-gray-100 my-4px"></div>
<div class="flex flex-col text-12px text-gray-500"> <div class="flex flex-col text-12px text-gray-500">
<span>Total: {{ deviceStatus.alerts }}</span> <span>Total: {{ deviceStatus.alerts || 0 }}</span>
<span>Active: {{ deviceStatus.alerts }}</span> <span>Active: {{ deviceStatus.alerts || 0 }}</span>
</div> </div>
</div> </div>
</div> </div>