feat: 用户列表显示订阅分组及剩余天数
- User模型新增Subscriptions关联 - 用户列表批量加载订阅信息避免N+1查询 - GroupBadge组件支持显示剩余天数(过期红色、<=3天红色、<=7天橙色) - 用户管理页面新增订阅分组列
This commit is contained in:
@@ -84,6 +84,27 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-subscriptions="{ row }">
|
||||
<div v-if="row.subscriptions && row.subscriptions.length > 0" class="flex flex-wrap gap-1.5">
|
||||
<GroupBadge
|
||||
v-for="sub in row.subscriptions"
|
||||
:key="sub.id"
|
||||
:name="sub.group?.name || ''"
|
||||
:platform="sub.group?.platform"
|
||||
:subscription-type="sub.group?.subscription_type"
|
||||
:rate-multiplier="sub.group?.rate_multiplier"
|
||||
:days-remaining="sub.expires_at ? getDaysRemaining(sub.expires_at) : null"
|
||||
:title="sub.expires_at ? formatExpiresAt(sub.expires_at) : ''"
|
||||
/>
|
||||
</div>
|
||||
<span v-else class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs text-gray-400 dark:text-dark-500 bg-gray-50 dark:bg-dark-700/50">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
<span>{{ t('admin.users.noSubscription') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-balance="{ value }">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
</template>
|
||||
@@ -662,12 +683,14 @@ import Modal from '@/components/common/Modal.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import GroupBadge from '@/components/common/GroupBadge.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const columns = computed<Column[]>(() => [
|
||||
{ key: 'email', label: t('admin.users.columns.user'), sortable: true },
|
||||
{ key: 'role', label: t('admin.users.columns.role'), sortable: true },
|
||||
{ key: 'subscriptions', label: t('admin.users.columns.subscriptions'), sortable: false },
|
||||
{ key: 'balance', label: t('admin.users.columns.balance'), sortable: true },
|
||||
{ key: 'usage', label: t('admin.users.columns.usage'), sortable: false },
|
||||
{ key: 'concurrency', label: t('admin.users.columns.concurrency'), sortable: true },
|
||||
@@ -749,6 +772,20 @@ const formatDate = (dateString: string): string => {
|
||||
})
|
||||
}
|
||||
|
||||
// 计算剩余天数
|
||||
const getDaysRemaining = (expiresAt: string): number => {
|
||||
const now = new Date()
|
||||
const expires = new Date(expiresAt)
|
||||
const diffMs = expires.getTime() - now.getTime()
|
||||
return Math.ceil(diffMs / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
// 格式化过期时间(用于 tooltip)
|
||||
const formatExpiresAt = (expiresAt: string): string => {
|
||||
const date = new Date(expiresAt)
|
||||
return date.toLocaleString()
|
||||
}
|
||||
|
||||
const generateRandomPasswordStr = () => {
|
||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&*'
|
||||
let password = ''
|
||||
|
||||
Reference in New Issue
Block a user