diff --git a/frontend/src/pages/inbounds/ClientRowTable.vue b/frontend/src/pages/inbounds/ClientRowTable.vue index 6ed33119..c5cc949b 100644 --- a/frontend/src/pages/inbounds/ClientRowTable.vue +++ b/frontend/src/pages/inbounds/ClientRowTable.vue @@ -8,6 +8,8 @@ import { RetweetOutlined, DeleteOutlined, EllipsisOutlined, + CaretUpOutlined, + CaretDownOutlined, } from '@ant-design/icons-vue'; import { Modal } from 'ant-design-vue'; @@ -50,14 +52,55 @@ const inbound = computed(() => props.dbInbound.toInbound()); const clients = computed(() => inbound.value?.clients || []); const currentPage = ref(1); -const paginatedClients = computed(() => { - if (!props.pageSize || props.pageSize <= 0) return clients.value; - const start = (currentPage.value - 1) * props.pageSize; - return clients.value.slice(start, start + props.pageSize); +const sortState = ref({ column: null, order: null }); + +const sortFns = { + client: (a, b) => (a.email || '').localeCompare(b.email || ''), + enable: (a, b) => Number(a.enable) - Number(b.enable), + online: (a, b) => { + const aOn = !!a.email && props.onlineClients.includes(a.email); + const bOn = !!b.email && props.onlineClients.includes(b.email); + return Number(aOn) - Number(bOn); + }, + traffic: (a, b) => (getSum(a.email) - getSum(b.email)), + remained: (a, b) => (getRem(a.email) - getRem(b.email)), + alltime: (a, b) => (getAllTime(a.email) - getAllTime(b.email)), + expiry: (a, b) => (a.expiryTime || Infinity) - (b.expiryTime || Infinity), +}; + +const sortedClients = computed(() => { + const { column, order } = sortState.value; + if (!column || !order) return clients.value; + const fn = sortFns[column]; + if (!fn) return clients.value; + const sorted = [...clients.value].sort(fn); + return order === 'descend' ? sorted.reverse() : sorted; }); +const paginatedClients = computed(() => { + if (!props.pageSize || props.pageSize <= 0) return sortedClients.value; + const start = (currentPage.value - 1) * props.pageSize; + return sortedClients.value.slice(start, start + props.pageSize); +}); + +function toggleSort(col) { + const cur = sortState.value; + if (cur.column !== col) { + sortState.value = { column: col, order: 'ascend' }; + } else if (cur.order === 'ascend') { + sortState.value = { column: col, order: 'descend' }; + } else { + sortState.value = { column: null, order: null }; + } +} + +function sortIndicator(col) { + if (sortState.value.column !== col) return null; + return sortState.value.order === 'ascend' ? 'ascend' : 'descend'; +} + watch([clients, () => props.pageSize], () => { - const total = clients.value.length; + const total = sortedClients.value.length; const size = props.pageSize > 0 ? props.pageSize : (total || 1); const maxPage = Math.max(1, Math.ceil(total / size)); if (currentPage.value > maxPage) currentPage.value = maxPage; @@ -282,13 +325,55 @@ function confirmBulkDelete() { @change="(e) => selectAll(e.target.checked)" />
{{ t('pages.settings.actions') }}
-
{{ t('enable') }}
-
{{ t('online') }}
-
{{ t('pages.inbounds.client') }}
-
{{ t('pages.inbounds.traffic') }}
-
{{ t('remained') }}
-
{{ t('pages.inbounds.allTimeTraffic') }}
-
{{ t('pages.inbounds.expireDate') }}
+
+ {{ t('enable') }} + + + + +
+
+ {{ t('online') }} + + + + +
+
+ {{ t('pages.inbounds.client') }} + + + + +
+
+ {{ t('pages.inbounds.traffic') }} + + + + +
+
+ {{ t('remained') }} + + + + +
+
+ {{ t('pages.inbounds.allTimeTraffic') }} + + + + +
+
+ {{ t('pages.inbounds.expireDate') }} + + + + +