feat(admin): add user balance/concurrency history modal
- Add new API endpoint GET /admin/users/:id/balance-history with pagination and type filter - Add SumPositiveBalanceByUser for calculating total recharged amount - Create UserBalanceHistoryModal component with: - User info header (email, username, created_at, current balance, notes, total recharged) - Type filter dropdown (all/balance/admin_balance/concurrency/admin_concurrency/subscription) - Quick deposit/withdraw buttons - Paginated history list with icons and colored values - Add instant tooltip on balance column for better UX - Add z-index prop to BaseDialog for modal stacking control - Update i18n translations (zh/en)
This commit is contained in:
@@ -300,8 +300,29 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-balance="{ value }">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<template #cell-balance="{ value, row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="group relative">
|
||||
<button
|
||||
class="font-medium text-gray-900 underline decoration-dashed decoration-gray-300 underline-offset-4 transition-colors hover:text-primary-600 dark:text-white dark:decoration-dark-500 dark:hover:text-primary-400"
|
||||
@click="handleBalanceHistory(row)"
|
||||
>
|
||||
${{ value.toFixed(2) }}
|
||||
</button>
|
||||
<!-- Instant tooltip -->
|
||||
<div class="pointer-events-none absolute bottom-full left-1/2 z-50 mb-1.5 -translate-x-1/2 whitespace-nowrap rounded bg-gray-900 px-2 py-1 text-xs text-white opacity-0 shadow-lg transition-opacity duration-75 group-hover:opacity-100 dark:bg-dark-600">
|
||||
{{ t('admin.users.balanceHistoryTip') }}
|
||||
<div class="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-dark-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click.stop="handleDeposit(row)"
|
||||
class="rounded px-2 py-0.5 text-xs font-medium text-emerald-600 transition-colors hover:bg-emerald-50 dark:text-emerald-400 dark:hover:bg-emerald-900/20"
|
||||
:title="t('admin.users.deposit')"
|
||||
>
|
||||
{{ t('admin.users.deposit') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-usage="{ row }">
|
||||
@@ -456,6 +477,15 @@
|
||||
{{ t('admin.users.withdraw') }}
|
||||
</button>
|
||||
|
||||
<!-- Balance History -->
|
||||
<button
|
||||
@click="handleBalanceHistory(user); closeActionMenu()"
|
||||
class="flex w-full items-center gap-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-dark-700"
|
||||
>
|
||||
<Icon name="dollar" size="sm" class="text-gray-400" :stroke-width="2" />
|
||||
{{ t('admin.users.balanceHistory') }}
|
||||
</button>
|
||||
|
||||
<div class="my-1 border-t border-gray-100 dark:border-dark-700"></div>
|
||||
|
||||
<!-- Delete (not for admin) -->
|
||||
@@ -479,6 +509,7 @@
|
||||
<UserApiKeysModal :show="showApiKeysModal" :user="viewingUser" @close="closeApiKeysModal" />
|
||||
<UserAllowedGroupsModal :show="showAllowedGroupsModal" :user="allowedGroupsUser" @close="closeAllowedGroupsModal" @success="loadUsers" />
|
||||
<UserBalanceModal :show="showBalanceModal" :user="balanceUser" :operation="balanceOperation" @close="closeBalanceModal" @success="loadUsers" />
|
||||
<UserBalanceHistoryModal :show="showBalanceHistoryModal" :user="balanceHistoryUser" @close="closeBalanceHistoryModal" @deposit="handleDepositFromHistory" @withdraw="handleWithdrawFromHistory" />
|
||||
<UserAttributesConfigModal :show="showAttributesModal" @close="handleAttributesModalClose" />
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -509,6 +540,7 @@ import UserEditModal from '@/components/admin/user/UserEditModal.vue'
|
||||
import UserApiKeysModal from '@/components/admin/user/UserApiKeysModal.vue'
|
||||
import UserAllowedGroupsModal from '@/components/admin/user/UserAllowedGroupsModal.vue'
|
||||
import UserBalanceModal from '@/components/admin/user/UserBalanceModal.vue'
|
||||
import UserBalanceHistoryModal from '@/components/admin/user/UserBalanceHistoryModal.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
@@ -828,6 +860,10 @@ const showBalanceModal = ref(false)
|
||||
const balanceUser = ref<AdminUser | null>(null)
|
||||
const balanceOperation = ref<'add' | 'subtract'>('add')
|
||||
|
||||
// Balance History modal state
|
||||
const showBalanceHistoryModal = ref(false)
|
||||
const balanceHistoryUser = ref<AdminUser | null>(null)
|
||||
|
||||
// 计算剩余天数
|
||||
const getDaysRemaining = (expiresAt: string): number => {
|
||||
const now = new Date()
|
||||
@@ -1078,6 +1114,30 @@ const closeBalanceModal = () => {
|
||||
balanceUser.value = null
|
||||
}
|
||||
|
||||
const handleBalanceHistory = (user: AdminUser) => {
|
||||
balanceHistoryUser.value = user
|
||||
showBalanceHistoryModal.value = true
|
||||
}
|
||||
|
||||
const closeBalanceHistoryModal = () => {
|
||||
showBalanceHistoryModal.value = false
|
||||
balanceHistoryUser.value = null
|
||||
}
|
||||
|
||||
// Handle deposit from balance history modal
|
||||
const handleDepositFromHistory = () => {
|
||||
if (balanceHistoryUser.value) {
|
||||
handleDeposit(balanceHistoryUser.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle withdraw from balance history modal
|
||||
const handleWithdrawFromHistory = () => {
|
||||
if (balanceHistoryUser.value) {
|
||||
handleWithdraw(balanceHistoryUser.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动时关闭菜单
|
||||
const handleScroll = () => {
|
||||
closeActionMenu()
|
||||
|
||||
Reference in New Issue
Block a user