fix:仪表盘界面(未接通)
This commit is contained in:
@@ -202,6 +202,14 @@ export function updateBillRule(data: Api.Billing.BillRuleUpdate) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** 获取仪表盘概览数据 */
|
||||||
|
export function getDashboardOverview() {
|
||||||
|
return request<Api.DashboardOverview>({
|
||||||
|
url: '/system/dashboard/overview',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
41
src/typings/api.d.ts
vendored
41
src/typings/api.d.ts
vendored
@@ -607,4 +607,45 @@ declare namespace Api {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DashboardOverview {
|
||||||
|
cloud: {
|
||||||
|
connected: boolean;
|
||||||
|
};
|
||||||
|
sites: {
|
||||||
|
total: number;
|
||||||
|
countries: number;
|
||||||
|
connected: number;
|
||||||
|
disconnected: number;
|
||||||
|
};
|
||||||
|
devices: {
|
||||||
|
gateways: {
|
||||||
|
connected: number;
|
||||||
|
disconnected: number;
|
||||||
|
};
|
||||||
|
switches: {
|
||||||
|
connected: number;
|
||||||
|
disconnected: number;
|
||||||
|
};
|
||||||
|
olts: {
|
||||||
|
connected: number;
|
||||||
|
disconnected: number;
|
||||||
|
};
|
||||||
|
ap: {
|
||||||
|
connected: number;
|
||||||
|
disconnected: number;
|
||||||
|
isolated: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
client: {
|
||||||
|
wiredUsers: number;
|
||||||
|
wirelessUsers: number;
|
||||||
|
wirelessGuests: number;
|
||||||
|
};
|
||||||
|
alerts: number;
|
||||||
|
users: {
|
||||||
|
registered: number;
|
||||||
|
online: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/typings/auto-imports.d.ts
vendored
1
src/typings/auto-imports.d.ts
vendored
@@ -152,6 +152,7 @@ declare global {
|
|||||||
const getCacheRouteNames: typeof import('../store/modules/route/shared')['getCacheRouteNames']
|
const getCacheRouteNames: typeof import('../store/modules/route/shared')['getCacheRouteNames']
|
||||||
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 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']
|
||||||
|
|||||||
5
src/typings/components.d.ts
vendored
5
src/typings/components.d.ts
vendored
@@ -45,7 +45,6 @@ declare module 'vue' {
|
|||||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
|
||||||
AStep: typeof import('ant-design-vue/es')['Step']
|
AStep: typeof import('ant-design-vue/es')['Step']
|
||||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||||
@@ -77,7 +76,11 @@ declare module 'vue' {
|
|||||||
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
||||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||||
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
||||||
|
IconMdiAccessPoint: typeof import('~icons/mdi/access-point')['default']
|
||||||
|
IconMdiAlert: typeof import('~icons/mdi/alert')['default']
|
||||||
|
IconMdiCloud: typeof import('~icons/mdi/cloud')['default']
|
||||||
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
|
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
|
||||||
|
IconMdiLaptop: typeof import('~icons/mdi/laptop')['default']
|
||||||
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
|
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
|
||||||
IconMdiSearch: typeof import('~icons/mdi/search')['default']
|
IconMdiSearch: typeof import('~icons/mdi/search')['default']
|
||||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||||
|
|||||||
@@ -1,109 +1,231 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { createReusableTemplate } from '@vueuse/core';
|
import {
|
||||||
import { $t } from '@/locales';
|
EnvironmentOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
PlusOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CardData'
|
name: 'CardData'
|
||||||
});
|
});
|
||||||
|
|
||||||
interface CardData {
|
interface SiteData {
|
||||||
key: string;
|
id: number;
|
||||||
title: string;
|
name: string;
|
||||||
value: number;
|
region: string;
|
||||||
unit: string;
|
alerts: number;
|
||||||
color: {
|
gateway: string;
|
||||||
start: string;
|
switches: string;
|
||||||
end: string;
|
olts: string;
|
||||||
};
|
eaps: string;
|
||||||
icon: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData = computed<CardData[]>(() => [
|
// 搜索和分页状态
|
||||||
|
const searchValue = ref('');
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
{
|
{
|
||||||
key: 'visitCount',
|
title: 'NAME',
|
||||||
title: $t('page.home.visitCount'),
|
key: 'name',
|
||||||
value: 9725,
|
dataIndex: 'name'
|
||||||
unit: '',
|
|
||||||
color: {
|
|
||||||
start: '#ec4786',
|
|
||||||
end: '#b955a4'
|
|
||||||
},
|
|
||||||
icon: 'ant-design:bar-chart-outlined'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'turnover',
|
title: 'COUNTRY/REGION',
|
||||||
title: $t('page.home.turnover'),
|
key: 'region',
|
||||||
value: 1026,
|
dataIndex: 'region'
|
||||||
unit: '$',
|
|
||||||
color: {
|
|
||||||
start: '#865ec0',
|
|
||||||
end: '#5144b4'
|
|
||||||
},
|
|
||||||
icon: 'ant-design:money-collect-outlined'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'downloadCount',
|
title: 'ALERTS',
|
||||||
title: $t('page.home.downloadCount'),
|
key: 'alerts',
|
||||||
value: 970925,
|
dataIndex: 'alerts',
|
||||||
unit: '',
|
width: 100
|
||||||
color: {
|
|
||||||
start: '#56cdf3',
|
|
||||||
end: '#719de3'
|
|
||||||
},
|
|
||||||
icon: 'carbon:document-download'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'dealCount',
|
title: 'GATEWAY',
|
||||||
title: $t('page.home.dealCount'),
|
key: 'gateway',
|
||||||
value: 9527,
|
dataIndex: 'gateway',
|
||||||
unit: '',
|
width: 100
|
||||||
color: {
|
|
||||||
start: '#fcbc25',
|
|
||||||
end: '#f68057'
|
|
||||||
},
|
},
|
||||||
icon: 'ant-design:trademark-circle-outlined'
|
{
|
||||||
|
title: 'SWITCHES',
|
||||||
|
key: 'switches',
|
||||||
|
dataIndex: 'switches',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OLTS',
|
||||||
|
key: 'olts',
|
||||||
|
dataIndex: 'olts',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'EAPS',
|
||||||
|
key: 'eaps',
|
||||||
|
dataIndex: 'eaps',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ACTION',
|
||||||
|
key: 'action',
|
||||||
|
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,
|
||||||
|
name: 'wfc-dev-omada1-site2',
|
||||||
|
region: 'China mainland',
|
||||||
|
alerts: 0,
|
||||||
|
gateway: '-',
|
||||||
|
switches: '0 / 0',
|
||||||
|
olts: '0 / 0',
|
||||||
|
eaps: '0 / 1'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
interface GradientBgProps {
|
// 按钮操作处理函数
|
||||||
gradientColor: string;
|
const handleEdit = (record: SiteData) => {
|
||||||
}
|
console.log('Edit:', record);
|
||||||
|
};
|
||||||
|
|
||||||
const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
|
const handleCopy = (record: SiteData) => {
|
||||||
|
console.log('Copy:', record);
|
||||||
|
};
|
||||||
|
|
||||||
function getGradientColor(color: CardData['color']) {
|
const handleDelete = (record: SiteData) => {
|
||||||
return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
|
console.log('Delete:', record);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleHome = (record: SiteData) => {
|
||||||
|
console.log('Home:', record);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页处理函数
|
||||||
|
// const handlePageChange = (page: number) => {
|
||||||
|
// currentPage.value = page;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const handlePageSizeChange = (size: number) => {
|
||||||
|
// pageSize.value = size;
|
||||||
|
// currentPage.value = 1;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const handleGoToPage = () => {
|
||||||
|
// // 处理跳转页面逻辑
|
||||||
|
// console.log('Go to page:', currentPage.value);
|
||||||
|
// };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ACard :bordered="false" size="small" class="card-wrapper">
|
<ACard :bordered="false" size="small" class="card-wrapper">
|
||||||
<!-- define component start: GradientBg -->
|
<div class="flex justify-between items-center mb-16px">
|
||||||
<DefineGradientBg v-slot="{ $slots, gradientColor }">
|
<div class="flex items-center gap-8px">
|
||||||
<div class="rd-8px px-16px pb-4px pt-8px text-white" :style="{ backgroundImage: gradientColor }">
|
<span class="text-16px font-medium">Site List</span>
|
||||||
<component :is="$slots.default" />
|
<!-- <span class="text-12px text-gray-500">1-2 of 2 records</span>-->
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-16px">
|
||||||
|
<AInput
|
||||||
|
v-model:value="searchValue"
|
||||||
|
placeholder="Search Site Name"
|
||||||
|
class="w-240px"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<search-outlined />
|
||||||
|
</template>
|
||||||
|
</AInput>
|
||||||
|
<AButton type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<plus-outlined />
|
||||||
|
</template>
|
||||||
|
Import Site
|
||||||
|
</AButton>
|
||||||
|
<AButton type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<plus-outlined />
|
||||||
|
</template>
|
||||||
|
Add New Site
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefineGradientBg>
|
|
||||||
<!-- define component end: GradientBg -->
|
|
||||||
|
|
||||||
<ARow :gutter="[16, 16]">
|
<ATable
|
||||||
<ACol v-for="item in cardData" :key="item.key" :span="24" :md="12" :lg="6">
|
:columns="columns"
|
||||||
<GradientBg :gradient-color="getGradientColor(item.color)" class="flex-1">
|
:data-source="siteData"
|
||||||
<h3 class="text-16px">{{ item.title }}</h3>
|
:pagination="false"
|
||||||
<div class="flex justify-between pt-12px">
|
:scroll="{ x: 1200 }"
|
||||||
<SvgIcon :icon="item.icon" class="text-32px" />
|
row-key="id"
|
||||||
<CountTo
|
>
|
||||||
:prefix="item.unit"
|
<template #bodyCell="{ column, record }">
|
||||||
:start-value="1"
|
<template v-if="column.key === 'name'">
|
||||||
:end-value="item.value"
|
<div class="flex items-center gap-8px">
|
||||||
class="text-30px text-white dark:text-dark"
|
<environment-outlined class="text-16px" />
|
||||||
/>
|
<span>{{ record.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'alerts'">
|
||||||
|
<ATag v-if="record.alerts > 0" color="error">{{ record.alerts }}</ATag>
|
||||||
|
<span v-else>{{ record.alerts }}</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>
|
||||||
|
</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>
|
</div>
|
||||||
</GradientBg>
|
|
||||||
</ACol>
|
|
||||||
</ARow>
|
|
||||||
</ACard>
|
</ACard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.card-wrapper {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-240px {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,62 +1,263 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import {
|
||||||
import { $t } from '@/locales';
|
EnvironmentOutlined,
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
SafetyOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
GroupOutlined,
|
||||||
|
UserAddOutlined,
|
||||||
|
UserSwitchOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'HeaderBanner'
|
name: 'HeaderBanner'
|
||||||
});
|
});
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const overviewData = ref<Api.DashboardOverview>({
|
||||||
|
cloud: { connected: false },
|
||||||
interface StatisticData {
|
sites: { total: 0, countries: 0, connected: 0, disconnected: 0 },
|
||||||
id: number;
|
devices: {
|
||||||
title: string;
|
gateways: { connected: 0, disconnected: 0 },
|
||||||
value: string;
|
switches: { connected: 0, disconnected: 0 },
|
||||||
}
|
olts: { connected: 0, disconnected: 0 },
|
||||||
|
ap: { connected: 0, disconnected: 0, isolated: 0 }
|
||||||
const statisticData = computed<StatisticData[]>(() => [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: $t('page.home.projectCount'),
|
|
||||||
value: '25'
|
|
||||||
},
|
},
|
||||||
{
|
client: { wiredUsers: 0, wirelessUsers: 0, wirelessGuests: 0 },
|
||||||
id: 1,
|
alerts: 0,
|
||||||
title: $t('page.home.todo'),
|
users: { registered: 0, online: 0 }
|
||||||
value: '4/16'
|
});
|
||||||
},
|
|
||||||
{
|
const fetchOverviewData = async () => {
|
||||||
id: 2,
|
// try {
|
||||||
title: $t('page.home.message'),
|
// const data = await authStore.getDashboardOverview();
|
||||||
value: '12'
|
// overviewData.value = data;
|
||||||
}
|
// } catch (error) {
|
||||||
]);
|
// console.error('Failed to fetch overview data:', error);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchOverviewData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceStatus = computed(() => ({
|
||||||
|
ap: overviewData.value.devices.ap,
|
||||||
|
client: overviewData.value.client,
|
||||||
|
alerts: overviewData.value.alerts,
|
||||||
|
users: overviewData.value.users
|
||||||
|
}));
|
||||||
|
|
||||||
|
const siteInfo = computed(() => overviewData.value.sites);
|
||||||
|
|
||||||
|
const otherDevices = computed(() => ({
|
||||||
|
gateways: overviewData.value.devices.gateways,
|
||||||
|
switches: overviewData.value.devices.switches,
|
||||||
|
olts: overviewData.value.devices.olts
|
||||||
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ACard :bordered="false" class="card-wrapper">
|
<ACard :bordered="false" class="card-wrapper">
|
||||||
|
<div class="text-16px font-bold mb-4px">Controller Overview</div>
|
||||||
<ARow :gutter="[16, 16]">
|
<ARow :gutter="[16, 16]">
|
||||||
<ACol :span="24" :md="18">
|
<ACol :span="24">
|
||||||
<div class="flex-y-center">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-8px">
|
||||||
<div class="size-72px shrink-0 overflow-hidden rd-1/2">
|
<!-- Connected Status -->
|
||||||
<img src="@/assets/imgs/soybean.jpg" class="size-full" />
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-blue-50 rounded-lg">
|
||||||
|
<icon-mdi-cloud class="text-primary text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Connected</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="text-12px text-gray-500">
|
||||||
|
Cloud Access
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sites -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-gray-100 rounded-lg relative">
|
||||||
|
<environment-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">{{ siteInfo.total }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Sites</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Total Sites: {{ siteInfo.total }}</span>
|
||||||
|
<span>Countries: {{ siteInfo.countries }}</span>
|
||||||
|
<span>Connected: {{ siteInfo.connected }}</span>
|
||||||
|
<span>Disconnected: {{ siteInfo.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gateways -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-gray-100 rounded-lg relative">
|
||||||
|
<safety-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.gateways.connected + otherDevices.gateways.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Gateways</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Connected: {{ otherDevices.gateways.connected }}</span>
|
||||||
|
<span>Disconnected: {{ otherDevices.gateways.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Switches -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-gray-100 rounded-lg relative">
|
||||||
|
<hdd-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.switches.connected + otherDevices.switches.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Switches</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Connected: {{ otherDevices.switches.connected }}</span>
|
||||||
|
<span>Disconnected: {{ otherDevices.switches.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- OLTs -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-gray-100 rounded-lg relative">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">OLTs</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Connected: {{ otherDevices.olts.connected }}</span>
|
||||||
|
<span>Disconnected: {{ otherDevices.olts.disconnected }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Register Users -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-blue-50 rounded-lg">
|
||||||
|
<user-add-outlined class="text-primary text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Register Users</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Total: {{ deviceStatus.users.registered }}</span>
|
||||||
|
<span>Online: {{ deviceStatus.users.online }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Online Users -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-green-50 rounded-lg">
|
||||||
|
<user-switch-outlined class="text-primary text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Online Users</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Active: {{ deviceStatus.users.online }}</span>
|
||||||
|
<span>Total: {{ deviceStatus.users.registered }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AP Status -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-green-50 rounded-lg">
|
||||||
|
<icon-mdi-access-point class="text-primary text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">AP</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Connected: {{ deviceStatus.ap.connected }}</span>
|
||||||
|
<span>Disconnected: {{ deviceStatus.ap.disconnected }}</span>
|
||||||
|
<span>Isolated: {{ deviceStatus.ap.isolated }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Client Status -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-purple-50 rounded-lg">
|
||||||
|
<icon-mdi-laptop class="text-primary text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Client</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Wired Users: {{ deviceStatus.client.wiredUsers }}</span>
|
||||||
|
<span>Wireless Users: {{ deviceStatus.client.wirelessUsers }}</span>
|
||||||
|
<span>Wireless Guests: {{ deviceStatus.client.wirelessGuests }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alerts -->
|
||||||
|
<div class="flex flex-col p-6px">
|
||||||
|
<div class="flex items-center gap-6px mb-6px">
|
||||||
|
<div class="size-48px flex-center bg-yellow-50 rounded-lg">
|
||||||
|
<icon-mdi-alert class="text-warning text-24px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-16px font-medium">Alerts</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 my-4px"></div>
|
||||||
|
<div class="flex flex-col text-12px text-gray-500">
|
||||||
|
<span>Total: {{ deviceStatus.alerts }}</span>
|
||||||
|
<span>Active: {{ deviceStatus.alerts }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-12px">
|
|
||||||
<h3 class="text-18px font-semibold">
|
|
||||||
{{ $t('page.home.greeting', { username: authStore.userInfo.username }) }}
|
|
||||||
</h3>
|
|
||||||
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ACol>
|
|
||||||
<ACol :span="24" :md="6">
|
|
||||||
<ASpace class="w-full justify-end" :size="24">
|
|
||||||
<AStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" />
|
|
||||||
</ASpace>
|
|
||||||
</ACol>
|
</ACol>
|
||||||
</ARow>
|
</ARow>
|
||||||
</ACard>
|
</ACard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.card-wrapper {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-48px {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col > span {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-50 {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-6px {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-8px {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-6px {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
8
src/views/device/dashboard/index.vue
Normal file
8
src/views/device/dashboard/index.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>仪表盘面板</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in New Issue
Block a user