feat(inbounds): bulk-select clients + UX polish

- ClientBulkModal: add `comment` and VLESS `reverseTag` fields so the
  bulk-add modal can set them on every generated client (matching the
  single-client form)
- ClientRowTable: add multi-select checkboxes (desktop + mobile) with a
  tri-state select-all and a sticky bulk-action bar; emits a new
  `delete-clients` event so the parent can wipe the picked clients in
  one go. Hidden entirely when the inbound has only one client (the
  last one must stay)
- ClientRowTable: new "Remained" column shows live remaining quota
  per client (∞ for unlimited, red when depleted)
- InboundInfoModal: Remained cell now shows the ∞ tag when the client
  has no totalGB limit, matching how Total Usage already renders it
- InboundsPage: add Online tag (+ per-bucket popovers listing client
  emails) to the summary card so it mirrors the per-inbound row, and
  wire an `onDeleteClients` handler that loops the existing single-
  delete endpoint then refreshes once
- InboundList: forward the `delete-clients` event; hide empty remarks
  on both the desktop table (custom #bodyCell) and the mobile card
- useInbounds: aggregate an `online` email list across all inbounds
  so the summary popover has data to render
This commit is contained in:
MHSanaei
2026-05-11 03:50:28 +02:00
parent e4900f1bd4
commit 6d732d8d32
6 changed files with 231 additions and 14 deletions
+15 -4
View File
@@ -62,6 +62,7 @@ const emit = defineEmits([
'info-client',
'reset-traffic-client',
'delete-client',
'delete-clients',
'toggle-enable-client',
]);
@@ -404,6 +405,7 @@ function showQrCodeMenu(dbInbound) {
@info-client="(p) => emit('info-client', p)"
@reset-traffic-client="(p) => emit('reset-traffic-client', p)"
@delete-client="(p) => emit('delete-client', p)"
@delete-clients="(p) => emit('delete-clients', p)"
@toggle-enable-client="(p) => emit('toggle-enable-client', p)" />
</div>
</div>
@@ -423,6 +425,7 @@ function showQrCodeMenu(dbInbound) {
@qrcode-client="(p) => emit('qrcode-client', p)" @info-client="(p) => emit('info-client', p)"
@reset-traffic-client="(p) => emit('reset-traffic-client', p)"
@delete-client="(p) => emit('delete-client', p)"
@delete-clients="(p) => emit('delete-clients', p)"
@toggle-enable-client="(p) => emit('toggle-enable-client', p)" />
</template>
@@ -523,27 +526,35 @@ function showQrCodeMenu(dbInbound) {
<a-tag color="green" style="margin: 0">{{ clientCount[record.id].clients }}</a-tag>
<a-popover v-if="clientCount[record.id].deactive.length" :title="t('disabled')">
<template #content>
<div v-for="email in clientCount[record.id].deactive" :key="email">{{ email }}</div>
<div class="client-email-list">
<div v-for="email in clientCount[record.id].deactive" :key="email">{{ email }}</div>
</div>
</template>
<a-tag style="margin: 0; padding: 0 2px">{{ clientCount[record.id].deactive.length }}</a-tag>
</a-popover>
<a-popover v-if="clientCount[record.id].depleted.length" :title="t('depleted')">
<template #content>
<div v-for="email in clientCount[record.id].depleted" :key="email">{{ email }}</div>
<div class="client-email-list">
<div v-for="email in clientCount[record.id].depleted" :key="email">{{ email }}</div>
</div>
</template>
<a-tag color="red" style="margin: 0; padding: 0 2px">{{ clientCount[record.id].depleted.length
}}</a-tag>
</a-popover>
<a-popover v-if="clientCount[record.id].expiring.length" :title="t('depletingSoon')">
<template #content>
<div v-for="email in clientCount[record.id].expiring" :key="email">{{ email }}</div>
<div class="client-email-list">
<div v-for="email in clientCount[record.id].expiring" :key="email">{{ email }}</div>
</div>
</template>
<a-tag color="orange" style="margin: 0; padding: 0 2px">{{ clientCount[record.id].expiring.length
}}</a-tag>
</a-popover>
<a-popover v-if="clientCount[record.id].online.length" :title="t('online')">
<template #content>
<div v-for="email in clientCount[record.id].online" :key="email">{{ email }}</div>
<div class="client-email-list">
<div v-for="email in clientCount[record.id].online" :key="email">{{ email }}</div>
</div>
</template>
<a-tag color="blue" style="margin: 0; padding: 0 2px">{{ clientCount[record.id].online.length }}</a-tag>
</a-popover>