2
0

fix:修改菜单布局

This commit is contained in:
zhongzm
2025-01-17 16:20:10 +08:00
parent ebd243040f
commit 63439ad22c
16 changed files with 1004 additions and 774 deletions

View File

@@ -1,7 +1,174 @@
<script setup lang="ts">
import access from '@/views/userInfo/access/index.vue'
import {ref} from 'vue'
import type {TableColumnsType} from 'ant-design-vue'
import {WifiOutlined} from '@ant-design/icons-vue'
import {useI18n} from "vue-i18n";
import { useRouter } from 'vue-router';
defineOptions({
name: 'access'
});
const { t } = useI18n();
const router = useRouter();
const handleBack = () => {
router.push('/endpoint/clientservice');
};
// 设备列表数据
const deviceList = ref<{
deviceName: string;
macAddress: string;
speed: string;
id: number;
}[]>([]);
const loading = ref(false);
const columns: TableColumnsType = [
{
title: t('page.access.devicename'),
dataIndex: 'deviceName',
key: 'deviceName',
width: '30%'
},
{
title: t('page.access.mac'),
dataIndex: 'macAddress',
key: 'macAddress',
width: '40%'
},
{
title: t('page.access.speed'),
dataIndex: 'speed',
key: 'speed',
width: '30%',
align: 'right'
}
];
// 格式化速度函数
function formatSpeed(bytesPerSecond: number): string {
// 处理 0 值、undefined 或 null 的情况
if (!bytesPerSecond || bytesPerSecond === 0) {
return '0 B/s';
}
if (bytesPerSecond < 1024) {
return `${Number(bytesPerSecond.toFixed(2))} B/s`;
} else if (bytesPerSecond < 1024 * 1024) {
return `${Number((bytesPerSecond / 1024).toFixed(2))} KB/s`;
} else if (bytesPerSecond < 1024 * 1024 * 1024) {
return `${Number((bytesPerSecond / (1024 * 1024)).toFixed(2))} MB/s`;
} else {
return `${Number((bytesPerSecond / (1024 * 1024 * 1024)).toFixed(2))} GB/s`;
}
}
// 获取设备列表数据
async function getDeviceList() {
loading.value = true;
try {
const { data, error } = await fetchCurrentDevices();
if (!error && data) {
// 将API返回的数据映射到表格所需的格式
deviceList.value = data.rows.map(item => ({
id: item.id,
deviceName: item.clientName,
macAddress: item.clientMac,
speed: formatSpeed(item.activity) // 如果需要显示设备类型作为速度
}));
}
} catch (err) {
console.error('Failed to fetch device list:', err);
} finally {
loading.value = false;
}
}
// 刷新设备列表
async function handleRefresh() {
await getDeviceList();
}
// 组件挂载时获取数据
onMounted(() => {
getDeviceList();
});
</script>
<template>
<access/>
<div class="device-list-container">
<a-card :bordered="false">
<template #title>
<div class="card-title">
<WifiOutlined/>
<span>{{ t('page.access.currentdevice') }}</span>
</div>
</template>
<template #extra>
<a-button @click="handleBack">
{{ t('page.login.common.back') }}
</a-button>
<a-button type="primary" :loading="loading" @click="handleRefresh">
<template #icon>
<span class="i-carbon:refresh"></span>
</template>
{{ t('page.access.refresh') }}
</a-button>
</template>
<a-table
:loading="loading"
:columns="columns"
:data-source="deviceList"
:pagination="{
total: deviceList.length,
pageSize: 10,
showSizeChanger: false,
showTotal: (total) => `共 ${total} 条`
}"
>
</a-table>
</a-card>
</div>
</template>
<style scoped></style>
<style scoped>
.device-list-container {
padding: 8px;
background-color: #f5f5f7;
min-height: 100vh;
}
.card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
padding: 0 12px;
}
:deep(.ant-card-body) {
padding: 12px;
}
:deep(.ant-table-wrapper) {
width: 100%;
}
:deep(.ant-table) {
width: 100%;
}
@media screen and (min-width: 768px) {
.device-list-container {
padding: 16px;
}
}
</style>

View File

@@ -1,7 +1,270 @@
<script setup lang="ts">
import cdrlrecords from '@/views/userInfo/cdrlrecords/index.vue'
import { ref, onMounted } from 'vue'
import type { TableColumnsType } from 'ant-design-vue'
import { HistoryOutlined } from '@ant-design/icons-vue'
import { useI18n } from "vue-i18n";
import { fetchCDRHistory } from '@/service/api/auth';
import {useRouter} from "vue-router";
defineOptions({
name: 'cdrlrecords'
});
const { t } = useI18n();
const router = useRouter();
const handleBack = () => {
router.push('/endpoint/clientservice');
};
// 定义记录类型
interface CDRRecord {
id: number;
deviceName: string;
macAddress: string;
trafficUp: string;
trafficDown: string;
startTime: string;
endTime: string;
}
// 定义API返回的原始数据类型
interface RawCDRRecord {
id: number;
clientName: string;
clientMac: string;
trafficUp: number; // 字节数
trafficDown: number; // 字节数
startTime: number; // 时间戳
endTime: number; // 时间戳
}
// CDR记录数据
const cdrData = ref<CDRRecord[]>([]);
const loading = ref(false);
const pagination = ref({
current: 1,
pageSize: 10,
total: 0
});
// 展开行控制
const expandedRowKeys = ref<number[]>([]);
// 处理展开行变化
function handleExpandChange(expanded: boolean, record: CDRRecord) {
if (expanded) {
expandedRowKeys.value = [record.id];
} else {
expandedRowKeys.value = [];
}
}
// 格式化流量数据的函数
const formatTraffic = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
}
// 格式化时间戳
function formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
const columns: TableColumnsType = [
{
title: t('page.cdrlrecords.devicename'),
dataIndex: 'deviceName',
key: 'deviceName',
width: '40%'
},
{
title: t('page.cdrlrecords.uptraffic'),
dataIndex: 'trafficUp',
key: 'trafficUp',
width: '30%',
align: 'right'
},
{
title: t('page.cdrlrecords.downtraffic'),
dataIndex: 'trafficDown',
key: 'trafficDown',
width: '30%',
align: 'right'
}
];
// 获取CDR记录数据
async function getCDRHistory(page = pagination.value.current, pageSize = pagination.value.pageSize) {
loading.value = true;
try {
const { data, error } = await fetchCDRHistory({
pageNum: page,
pageSize: pageSize
});
if (!error && data) {
// 将原始数据映射为显示所需的格式
cdrData.value = data.rows.map((item: RawCDRRecord) => ({
id: item.id,
deviceName: item.clientName,
macAddress: item.clientMac,
trafficUp: formatTraffic(item.trafficUp), // 格式化上传流量
trafficDown: formatTraffic(item.trafficDown), // 格式化下载流量
startTime: formatTimestamp(item.startTime), // 格式化开始时间
endTime: formatTimestamp(item.endTime) // 格式化结束时间
}));
pagination.value.total = data.total;
}
} catch (err) {
console.error('Failed to fetch CDR history:', err);
} finally {
loading.value = false;
}
}
// 处理分页变化
function handleTableChange(pag: any) {
pagination.value.current = pag.current;
pagination.value.pageSize = pag.pageSize;
getCDRHistory(pag.current, pag.pageSize);
}
// 刷新设备列表
async function handleRefresh() {
await getCDRHistory();
}
// 组件挂载时获取数据
onMounted(() => {
getCDRHistory();
});
</script>
<template>
<cdrlrecords/>
<div class="device-list-container">
<a-card :bordered="false">
<template #title>
<div class="card-title">
<HistoryOutlined />
<span>{{ t('page.cdrlrecords.cdr') }}</span>
</div>
</template>
<template #extra>
<a-button @click="handleBack">
{{ t('page.login.common.back') }}
</a-button>
<a-button type="primary" :loading="loading" @click="handleRefresh">
<template #icon>
<span class="i-carbon:refresh"></span>
</template>
{{ t('page.cdrlrecords.refresh') }}
</a-button>
</template>
<a-table
:loading="loading"
:columns="columns"
:data-source="cdrData"
:row-key="(record: CDRRecord) => record.id"
:expanded-row-keys="expandedRowKeys"
@expand="handleExpandChange"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
showTotal: (total: number) => `${total}`,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100']
}"
@change="handleTableChange"
>
<template #expandedRowRender="{ record }">
<div class="pl-4">
<div>{{ t('page.cdrlrecords.mac') }}: {{ record.macAddress }}</div>
<div>{{ t('page.cdrlrecords.uptime') }}: {{ record.startTime }}</div>
<div>{{ t('page.cdrlrecords.lasttime') }}: {{ record.endTime }}</div>
</div>
</template>
</a-table>
</a-card>
</div>
</template>
<style scoped></style>
<style scoped>
.device-list-container {
padding: 8px;
background-color: #f5f5f7;
min-height: 100vh;
}
.card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
padding: 0 12px;
}
:deep(.ant-card-body) {
padding: 12px;
}
:deep(.ant-table-wrapper) {
width: 100%;
}
:deep(.ant-table) {
width: 100%;
}
/* 使表格更紧凑的样式 */
:deep(.ant-table) .ant-table-thead > tr > th,
:deep(.ant-table) .ant-table-tbody > tr > td {
padding: 8px 4px;
font-size: 14px;
}
/* 在小屏幕下进一步压缩内边距 */
@media screen and (max-width: 768px) {
:deep(.ant-table) .ant-table-thead > tr > th,
:deep(.ant-table) .ant-table-tbody > tr > td {
padding: 4px 2px;
font-size: 13px;
}
}
/* 确保表头文字也能自动换行 */
:deep(.ant-table) .ant-table-thead > tr > th {
white-space: normal;
word-break: break-word;
}
/* 允许单元格内容自动换行 */
:deep(.ant-table) .ant-table-tbody > tr > td {
white-space: normal;
word-break: break-word;
}
@media screen and (min-width: 768px) {
.device-list-container {
padding: 16px;
}
}
.pl-4 {
padding-left: 1rem;
}
</style>

View File

@@ -0,0 +1,132 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useI18n } from "vue-i18n";
import {
RightOutlined,
DesktopOutlined,
HistoryOutlined,
FileTextOutlined
} from '@ant-design/icons-vue';
const { t } = useI18n();
const router = useRouter();
const menuItems = [
{
icon: DesktopOutlined,
title: t('page.endpoint.access'),
path: '/endpoint/access'
},
{
icon: HistoryOutlined,
title: t('page.endpoint.records'),
path: '/endpoint/records'
},
{
icon: FileTextOutlined,
title: t('page.endpoint.cdrlrecords'),
path: '/endpoint/cdrlrecords'
}
];
const handleMenuClick = (path: string) => {
router.push(path);
};
</script>
<template>
<div class="client-service">
<div class="client-service-content">
<!-- 菜单列表 -->
<div class="menu-list">
<div
v-for="item in menuItems"
:key="item.title"
class="menu-item"
@click="handleMenuClick(item.path)"
>
<div class="menu-content">
<div class="icon-wrapper">
<component :is="item.icon" />
</div>
<span>{{ item.title }}</span>
</div>
<RightOutlined />
</div>
</div>
</div>
</div>
</template>
<style scoped>
.client-service {
width: 100%;
min-height: 100vh;
padding: 16px;
background-color: #f5f5f7;
}
.client-service-content {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.menu-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
padding: 16px;
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background: #fff;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.menu-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.menu-content {
display: flex;
align-items: center;
gap: 12px;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 8px;
background-color: #f0f5ff;
color: #1890ff;
}
/* 响应式布局调整 */
@media screen and (max-width: 768px) {
.menu-list {
grid-template-columns: 1fr;
gap: 12px;
padding: 12px;
}
.menu-item {
padding: 16px;
}
.client-service {
padding: 8px;
}
}
</style>

View File

@@ -1,7 +1,213 @@
<script setup lang="ts">
import records from '@/views/userInfo/records/index.vue'
import { ref, onMounted } from 'vue'
import type { TableColumnsType } from 'ant-design-vue'
import { HistoryOutlined } from '@ant-design/icons-vue'
import { useI18n } from "vue-i18n";
import { fetchHistoricalDevices } from '@/service/api/auth';
import {useRouter} from "vue-router";
defineOptions({
name: 'records'
});
const router = useRouter();
const { t } = useI18n();
const handleBack = () => {
router.push('/endpoint/clientservice');
};
// 格式化时间戳
function formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
// 格式化流量
function formatDataUsage(bytes: number): string {
if (bytes < 1024) {
return `${bytes}B`;
} else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(2)}KB`;
} else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
} else {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
}
}
// 定义记录类型
interface DeviceRecord {
id: number;
deviceName: string;
macAddress: string;
dataUsage: string;
connectionTime: string;
disconnectionTime: string;
}
// 设备列表数据
const deviceList = ref<DeviceRecord[]>([]);
const loading = ref(false);
const columns: TableColumnsType = [
{
title: t('page.records.clientname'),
dataIndex: 'deviceName',
key: 'deviceName',
width: '30%'
},
{
title: t('page.records.clientmac'),
dataIndex: 'macAddress',
key: 'macAddress',
width: '40%'
},
{
title: t('page.records.dataUsage'),
dataIndex: 'dataUsage',
key: 'dataUsage',
width: '30%',
align: 'right'
}
];
// 添加展开行控制
const expandedRowKeys = ref<number[]>([]);
// 处理展开行变化
function handleExpandChange(expanded: boolean, record: DeviceRecord) {
if (expanded) {
expandedRowKeys.value = [record.id];
} else {
expandedRowKeys.value = [];
}
}
// 获取历史设备列表数据
async function getHistoricalDeviceList() {
loading.value = true;
try {
const { data, error } = await fetchHistoricalDevices();
if (!error && data) {
deviceList.value = data.rows.map(item => ({
id: item.id,
deviceName: item.clientName,
macAddress: item.clientMac,
dataUsage: formatDataUsage(item.duration),
connectionTime: formatTimestamp(item.startTime),
disconnectionTime: formatTimestamp(item.endTime)
}));
}
} catch (err) {
console.error('Failed to fetch historical device list:', err);
} finally {
loading.value = false;
}
}
// 刷新设备列表
async function handleRefresh() {
await getHistoricalDeviceList();
}
// 组件挂载时获取数据
onMounted(() => {
getHistoricalDeviceList();
});
</script>
<template>
<records/>
<div class="device-list-container">
<a-card :bordered="false">
<template #title>
<div class="card-title">
<HistoryOutlined />
<span>{{ t('page.records.clienthistory') }}</span>
</div>
</template>
<template #extra>
<a-button @click="handleBack">
{{ t('page.login.common.back') }}
</a-button>
<a-button type="primary" :loading="loading" @click="handleRefresh">
<template #icon>
<span class="i-carbon:refresh"></span>
</template>
{{ t('page.records.refresh') }}
</a-button>
</template>
<a-table
:loading="loading"
:columns="columns"
:data-source="deviceList"
:row-key="(record: DeviceRecord) => record.id"
:expanded-row-keys="expandedRowKeys"
@expand="handleExpandChange"
:pagination="{
total: deviceList.length,
pageSize: 10,
showSizeChanger: false,
showTotal: (total) => `${total}`
}"
>
<template #expandedRowRender="{ record }">
<div class="pl-4">
<div>{{ t('page.records.starttime') }}: {{ record.connectionTime }}</div>
<div>{{ t('page.records.endtime') }}: {{ record.disconnectionTime }}</div>
</div>
</template>
</a-table>
</a-card>
</div>
</template>
<style scoped></style>
<style scoped>
.device-list-container {
padding: 8px;
background-color: #f5f5f7;
min-height: 100vh;
}
.card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
.pl-4 {
padding-left: 1rem;
}
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
padding: 0 12px;
}
:deep(.ant-card-body) {
padding: 12px;
}
:deep(.ant-table-wrapper) {
width: 100%;
}
:deep(.ant-table) {
width: 100%;
}
@media screen and (min-width: 768px) {
.device-list-container {
padding: 16px;
}
}
</style>