fix:上网记录界面添加条件查询,排序,添加时长字段
This commit is contained in:
@@ -650,6 +650,7 @@ const local: any = {
|
||||
cdrlrecords:"Internet records"
|
||||
},
|
||||
cdrlrecords:{
|
||||
total:'Total',
|
||||
devicename:"Device name",
|
||||
uptraffic:"Traffic Up",
|
||||
downtraffic:"Traffic Down",
|
||||
@@ -657,7 +658,12 @@ const local: any = {
|
||||
lasttime:"Last seen Time",
|
||||
cdr:"CDR Records",
|
||||
refresh:"Refresh",
|
||||
mac:"MAC"
|
||||
mac:"MAC",
|
||||
duration:'Duration',
|
||||
sec:'s',
|
||||
min:'min',
|
||||
hour:'hour',
|
||||
day:'day'
|
||||
},
|
||||
records:{
|
||||
clientID:"Client ID",
|
||||
|
||||
@@ -650,6 +650,13 @@ const local:any = {
|
||||
cdrlrecords:"上网记录"
|
||||
},
|
||||
cdrlrecords:{
|
||||
total:'共',
|
||||
searchdevice:'输入设备名称',
|
||||
timerange:'开始时间范围',
|
||||
starttime:'开始时间',
|
||||
endtime:'结束时间',
|
||||
search:'查询',
|
||||
reset:'重置',
|
||||
devicename:"设备名称",
|
||||
uptraffic:"上传流量",
|
||||
downtraffic:"下载流量",
|
||||
@@ -657,7 +664,12 @@ const local:any = {
|
||||
lasttime:"断开时间",
|
||||
cdr:"上网记录",
|
||||
refresh:"刷新",
|
||||
mac:"mac地址"
|
||||
mac:"mac地址",
|
||||
duration:'时长',
|
||||
sec:'秒',
|
||||
min:'分',
|
||||
hour:'时',
|
||||
day:'天'
|
||||
},
|
||||
records:{
|
||||
clientID:"设备ID",
|
||||
|
||||
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@@ -40,6 +40,7 @@ declare module 'vue' {
|
||||
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, watch } 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";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
defineOptions({
|
||||
name: 'cdrlrecords'
|
||||
});
|
||||
@@ -17,11 +19,11 @@ const handleBack = () => {
|
||||
interface CDRRecord {
|
||||
id: number;
|
||||
clientName: string;
|
||||
clientName: string;
|
||||
trafficUp: string;
|
||||
trafficDown: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
duration:number;
|
||||
}
|
||||
|
||||
// 定义API返回的原始数据类型
|
||||
@@ -48,7 +50,6 @@ const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 处理展开行变化
|
||||
function handleExpandChange(expanded: boolean, record: CDRRecord) {
|
||||
console.log('Expanded: ', expanded, 'Record: ', record);
|
||||
if (expanded) {
|
||||
expandedRowKeys.value = [record.id];
|
||||
} else {
|
||||
@@ -63,7 +64,22 @@ const formatTraffic = (bytes: number): string => {
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
|
||||
}
|
||||
//格式化时间
|
||||
const formatTime = (totalSeconds: number): string => {
|
||||
const days = Math.floor(totalSeconds / (24 * 60 * 60));
|
||||
const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
|
||||
const seconds = Math.floor(totalSeconds % 60);
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (days > 0) parts.push(`${days}${t('page.cdrlrecords.day')}`);
|
||||
if (hours > 0) parts.push(`${hours}${t('page.cdrlrecords.hour')}`);
|
||||
if (minutes > 0) parts.push(`${minutes}${t('page.cdrlrecords.min')}`);
|
||||
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}${t('page.cdrlrecords.sec')}`);
|
||||
|
||||
return parts.join('');
|
||||
};
|
||||
// 格式化时间戳
|
||||
function formatTimestamp(timestamp: number): string {
|
||||
const date = new Date(timestamp);
|
||||
@@ -77,52 +93,215 @@ function formatTimestamp(timestamp: number): string {
|
||||
});
|
||||
}
|
||||
|
||||
const searchForm = ref({
|
||||
clientName: '',
|
||||
timeRange: [undefined, undefined]
|
||||
});
|
||||
|
||||
const { columns, data, loading, getData, mobilePagination, searchParams } = useTable({
|
||||
apiFn: fetchCDRHistory,
|
||||
apiParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 5,
|
||||
},
|
||||
rowKey: 'id',
|
||||
columns: () => [
|
||||
// 定义查询参数接口
|
||||
interface SearchParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
clientName?: string;
|
||||
startTimeS?: number;
|
||||
startTimeE?: number;
|
||||
orderBy?: string;
|
||||
orderType?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
// 表格数据和分页
|
||||
const tableData = ref([]);
|
||||
const tablePagination = ref({
|
||||
current: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number) => `${t('page.cdrlrecords.total')} ${total} `
|
||||
});
|
||||
const tableLoading = ref(false);
|
||||
|
||||
// 添加排序状态
|
||||
const sortState = ref({
|
||||
field: '',
|
||||
order: undefined as 'ascend' | 'descend' | undefined
|
||||
});
|
||||
|
||||
// 添加排序字段映射
|
||||
const sortFieldMapping = {
|
||||
duration: 'duration', // 根据后端API的实际字段名进行映射
|
||||
clientName: 'clientName',
|
||||
trafficUp: 'trafficUp',
|
||||
trafficDown: 'trafficDown'
|
||||
};
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = async () => {
|
||||
const params: SearchParams = {
|
||||
pageNum: tablePagination.value.current,
|
||||
pageSize: tablePagination.value.pageSize
|
||||
};
|
||||
|
||||
if (searchForm.value.clientName) {
|
||||
params.clientName = searchForm.value.clientName;
|
||||
}
|
||||
|
||||
if (searchForm.value.timeRange[0]) {
|
||||
params.startTimeS = dayjs(searchForm.value.timeRange[0]).startOf('day').valueOf();
|
||||
}
|
||||
|
||||
if (searchForm.value.timeRange[1]) {
|
||||
params.startTimeE = dayjs(searchForm.value.timeRange[1]).endOf('day').valueOf();
|
||||
}
|
||||
|
||||
// 添加排序参数,使用映射后的字段名
|
||||
if (sortState.value.field && sortState.value.order) {
|
||||
const mappedField = sortFieldMapping[sortState.value.field];
|
||||
if (mappedField) {
|
||||
params.orderBy = mappedField;
|
||||
params.orderType = sortState.value.order === 'ascend' ? 'asc' : 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
const res = await fetchCDRHistory(params);
|
||||
|
||||
// 检查响应数据的具体结构
|
||||
if (res.data) {
|
||||
let records = [];
|
||||
let total = 0;
|
||||
|
||||
// 检查数据结构
|
||||
if (res.data.rows && Array.isArray(res.data.rows)) {
|
||||
records = res.data.rows;
|
||||
total = res.data.total || records.length;
|
||||
}
|
||||
|
||||
if (records.length > 0) {
|
||||
// 处理数据,确保所有必要的字段都存在
|
||||
const processedData = records.map((record: any) => ({
|
||||
id: record.id || Math.random(),
|
||||
clientName: record.clientName || '',
|
||||
clientMac: record.clientMac || '',
|
||||
trafficUp: Number(record.trafficUp || 0),
|
||||
trafficDown: Number(record.trafficDown || 0),
|
||||
startTime: Number(record.startTime || 0),
|
||||
endTime: Number(record.endTime || 0),
|
||||
duration: Number(record.duration || 0),
|
||||
key: record.id || Math.random()
|
||||
}));
|
||||
|
||||
tableData.value = processedData;
|
||||
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
total: total,
|
||||
current: params.pageNum,
|
||||
pageSize: params.pageSize
|
||||
};
|
||||
} else {
|
||||
tableData.value = [];
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
total: 0,
|
||||
current: 1
|
||||
};
|
||||
}
|
||||
} else {
|
||||
tableData.value = [];
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
total: 0,
|
||||
current: 1
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
tableData.value = [];
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
total: 0,
|
||||
current: 1
|
||||
};
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听分页和排序变化
|
||||
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
|
||||
// 只有分页变化时才重新请求数据
|
||||
if (pagination.current !== tablePagination.value.current ||
|
||||
pagination.pageSize !== tablePagination.value.pageSize) {
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize
|
||||
};
|
||||
// 只在分页变化时重新获取数据
|
||||
handleSearch();
|
||||
}
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
searchForm.value = {
|
||||
clientName: '',
|
||||
timeRange: [undefined, undefined]
|
||||
};
|
||||
// 重置排序状态
|
||||
sortState.value = {
|
||||
field: '',
|
||||
order: undefined
|
||||
};
|
||||
tablePagination.value.current = 1;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
// 定义表格列
|
||||
const tableColumns = [
|
||||
{
|
||||
title: t('page.cdrlrecords.devicename'),
|
||||
dataIndex: 'clientName',
|
||||
key: 'clientName',
|
||||
width: '40%'
|
||||
width: '25%'
|
||||
},
|
||||
{
|
||||
title: t('page.cdrlrecords.duration'),
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
width: '25%',
|
||||
align: 'right',
|
||||
sorter: (a, b) => a.duration - b.duration,
|
||||
defaultSortOrder: undefined,
|
||||
customRender: ({ text }: { text: number }) => formatTime(text)
|
||||
},
|
||||
{
|
||||
title: t('page.cdrlrecords.uptraffic'),
|
||||
dataIndex: 'trafficUp',
|
||||
key: 'trafficUp',
|
||||
width: '30%',
|
||||
width: '25%',
|
||||
align: 'right',
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
return formatTraffic(record);
|
||||
},
|
||||
customRender: ({ text }: { text: number }) => formatTraffic(text)
|
||||
},
|
||||
{
|
||||
title: t('page.cdrlrecords.downtraffic'),
|
||||
dataIndex: 'trafficDown',
|
||||
key: 'trafficDown',
|
||||
width: '30%',
|
||||
width: '25%',
|
||||
align: 'right',
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
return formatTraffic(record);
|
||||
},
|
||||
customRender: ({ text }: { text: number }) => formatTraffic(text)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
// 监听数据变化
|
||||
watch(tableData, (newData) => {
|
||||
}, { deep: true });
|
||||
|
||||
</script>
|
||||
|
||||
@@ -137,25 +316,67 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<a-space size="middle">
|
||||
<a-button @click="handleBack">
|
||||
{{ t('page.login.common.back') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="getData">
|
||||
<a-button type="primary" :loading="tableLoading" @click="handleSearch">
|
||||
<template #icon>
|
||||
<span class="i-carbon:refresh"></span>
|
||||
</template>
|
||||
{{ t('page.cdrlrecords.refresh') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<a-form layout="inline" class="search-form-container">
|
||||
<a-form-item :label="t('page.cdrlrecords.devicename')" class="form-item">
|
||||
<a-input
|
||||
v-model:value="searchForm.clientName"
|
||||
:placeholder="t('page.cdrlrecords.searchdevice')"
|
||||
allow-clear
|
||||
class="device-input"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('page.cdrlrecords.timerange')" class="form-item date-picker-group">
|
||||
<a-date-picker
|
||||
v-model:value="searchForm.timeRange[0]"
|
||||
:placeholder="t('page.cdrlrecords.starttime')"
|
||||
class="date-picker"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
<span class="date-separator">-</span>
|
||||
<a-date-picker
|
||||
v-model:value="searchForm.timeRange[1]"
|
||||
:placeholder="t('page.cdrlrecords.endtime')"
|
||||
class="date-picker"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="form-item button-group">
|
||||
<a-space size="middle">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
{{ t('page.cdrlrecords.search') }}
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
{{ t('page.cdrlrecords.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="tableLoading"
|
||||
:columns="tableColumns"
|
||||
:data-source="tableData"
|
||||
row-key="id"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
@expand="handleExpandChange"
|
||||
:pagination="mobilePagination"
|
||||
:pagination="tablePagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div class="pl-4">
|
||||
@@ -237,4 +458,98 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
||||
.pl-4 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-form-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.device-input {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.date-picker-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
color: #999;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 移动端适配搜索表单 */
|
||||
@media screen and (max-width: 768px) {
|
||||
.search-form-container {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.device-input,
|
||||
.date-picker-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.date-picker-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.ant-picker-panels) {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
:deep(.ant-picker-panel) {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
:deep(.ant-picker-dropdown) {
|
||||
max-width: calc(100vw - 32px) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-picker-panel-container) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user