feat(usage): 使用记录增加计费模式字段 — 记录/展示/筛选 token/按次/图片
- DB: usage_logs 表新增 billing_mode VARCHAR(20) 列 - 后端: RecordUsage 写入时根据 image_count 判定计费模式 - 前端: 使用记录表格新增计费模式 badge 列 + 筛选下拉
This commit is contained in:
@@ -80,6 +80,7 @@ export interface CreateUsageCleanupTaskRequest {
|
||||
export interface AdminUsageQueryParams extends UsageQueryParams {
|
||||
user_id?: number
|
||||
exact_total?: boolean
|
||||
billing_mode?: string
|
||||
}
|
||||
|
||||
// ==================== API Functions ====================
|
||||
|
||||
@@ -133,6 +133,12 @@
|
||||
<Select v-model="filters.billing_type" :options="billingTypeOptions" @change="emitChange" />
|
||||
</div>
|
||||
|
||||
<!-- Billing Mode Filter -->
|
||||
<div class="w-full sm:w-auto sm:min-w-[200px]">
|
||||
<label class="input-label">{{ t('admin.usage.billingMode') }}</label>
|
||||
<Select v-model="filters.billing_mode" :options="billingModeOptions" @change="emitChange" />
|
||||
</div>
|
||||
|
||||
<!-- Group Filter -->
|
||||
<div class="w-full sm:w-auto sm:min-w-[200px]">
|
||||
<label class="input-label">{{ t('admin.usage.group') }}</label>
|
||||
@@ -232,6 +238,13 @@ const billingTypeOptions = ref<SelectOption[]>([
|
||||
{ value: 1, label: t('admin.usage.billingTypeSubscription') }
|
||||
])
|
||||
|
||||
const billingModeOptions = ref<SelectOption[]>([
|
||||
{ value: null, label: t('admin.usage.allBillingModes') },
|
||||
{ value: 'token', label: t('admin.usage.billingModeToken') },
|
||||
{ value: 'per_request', label: t('admin.usage.billingModePerRequest') },
|
||||
{ value: 'image', label: t('admin.usage.billingModeImage') }
|
||||
])
|
||||
|
||||
const emitChange = () => emit('change')
|
||||
|
||||
const debounceUserSearch = () => {
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-billing_mode="{ row }">
|
||||
<span class="inline-flex items-center rounded px-2 py-0.5 text-xs font-medium" :class="getBillingModeBadgeClass(row.billing_mode)">
|
||||
{{ getBillingModeLabel(row.billing_mode) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-tokens="{ row }">
|
||||
<!-- 图片生成请求 -->
|
||||
<div v-if="row.image_count > 0" class="flex items-center gap-1.5">
|
||||
@@ -350,6 +356,18 @@ const getRequestTypeBadgeClass = (row: AdminUsageLog): string => {
|
||||
return 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200'
|
||||
}
|
||||
|
||||
const getBillingModeLabel = (mode: string | null | undefined): string => {
|
||||
if (mode === 'per_request') return t('admin.usage.billingModePerRequest')
|
||||
if (mode === 'image') return t('admin.usage.billingModeImage')
|
||||
return t('admin.usage.billingModeToken')
|
||||
}
|
||||
|
||||
const getBillingModeBadgeClass = (mode: string | null | undefined): string => {
|
||||
if (mode === 'per_request') return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
|
||||
if (mode === 'image') return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'
|
||||
}
|
||||
|
||||
const formatCacheTokens = (tokens: number): string => {
|
||||
if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(1)}M`
|
||||
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`
|
||||
|
||||
@@ -3356,6 +3356,11 @@ export default {
|
||||
allBillingTypes: 'All Billing Types',
|
||||
billingTypeBalance: 'Balance',
|
||||
billingTypeSubscription: 'Subscription',
|
||||
billingMode: 'Billing Mode',
|
||||
billingModeToken: 'Token',
|
||||
billingModePerRequest: 'Per Request',
|
||||
billingModeImage: 'Image',
|
||||
allBillingModes: 'All Billing Modes',
|
||||
ipAddress: 'IP',
|
||||
clickToViewBalance: 'Click to view balance history',
|
||||
failedToLoadUser: 'Failed to load user info',
|
||||
|
||||
@@ -3515,6 +3515,11 @@ export default {
|
||||
allBillingTypes: '全部计费类型',
|
||||
billingTypeBalance: '钱包余额',
|
||||
billingTypeSubscription: '订阅套餐',
|
||||
billingMode: '计费模式',
|
||||
billingModeToken: '按量',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按次(图片)',
|
||||
allBillingModes: '全部计费模式',
|
||||
ipAddress: 'IP',
|
||||
clickToViewBalance: '点击查看充值记录',
|
||||
failedToLoadUser: '加载用户信息失败',
|
||||
|
||||
@@ -1036,6 +1036,9 @@ export interface UsageLog {
|
||||
// Cache TTL Override
|
||||
cache_ttl_overridden: boolean
|
||||
|
||||
// 计费模式
|
||||
billing_mode?: string | null
|
||||
|
||||
created_at: string
|
||||
|
||||
user?: User
|
||||
|
||||
@@ -392,7 +392,7 @@ const resetFilters = () => {
|
||||
const range = getLast24HoursRangeDates()
|
||||
startDate.value = range.start
|
||||
endDate.value = range.end
|
||||
filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null }
|
||||
filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null, billing_mode: undefined }
|
||||
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
||||
applyFilters()
|
||||
}
|
||||
@@ -477,6 +477,7 @@ const allColumns = computed(() => [
|
||||
{ key: 'endpoint', label: t('usage.endpoint'), sortable: false },
|
||||
{ key: 'group', label: t('admin.usage.group'), sortable: false },
|
||||
{ key: 'stream', label: t('usage.type'), sortable: false },
|
||||
{ key: 'billing_mode', label: t('admin.usage.billingMode'), sortable: false },
|
||||
{ key: 'tokens', label: t('usage.tokens'), sortable: false },
|
||||
{ key: 'cost', label: t('usage.cost'), sortable: false },
|
||||
{ key: 'first_token', label: t('usage.firstToken'), sortable: false },
|
||||
|
||||
Reference in New Issue
Block a user