"backend/internal/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "6dcb27632e6176c3b5155afa81634a09109a3b3f"
Commit a51e0047 authored by erio's avatar erio
Browse files

feat(usage): 使用记录增加计费模式字段 — 记录/展示/筛选 token/按次/图片

- DB: usage_logs 表新增 billing_mode VARCHAR(20) 列
- 后端: RecordUsage 写入时根据 image_count 判定计费模式
- 前端: 使用记录表格新增计费模式 badge 列 + 筛选下拉
parent 726730bb
...@@ -69,6 +69,12 @@ ...@@ -69,6 +69,12 @@
</span> </span>
</template> </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 }"> <template #cell-tokens="{ row }">
<!-- 图片生成请求 --> <!-- 图片生成请求 -->
<div v-if="row.image_count > 0" class="flex items-center gap-1.5"> <div v-if="row.image_count > 0" class="flex items-center gap-1.5">
...@@ -350,6 +356,18 @@ const getRequestTypeBadgeClass = (row: AdminUsageLog): string => { ...@@ -350,6 +356,18 @@ const getRequestTypeBadgeClass = (row: AdminUsageLog): string => {
return 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200' 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 => { const formatCacheTokens = (tokens: number): string => {
if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(1)}M` if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(1)}M`
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K` if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`
......
...@@ -3356,6 +3356,11 @@ export default { ...@@ -3356,6 +3356,11 @@ export default {
allBillingTypes: 'All Billing Types', allBillingTypes: 'All Billing Types',
billingTypeBalance: 'Balance', billingTypeBalance: 'Balance',
billingTypeSubscription: 'Subscription', billingTypeSubscription: 'Subscription',
billingMode: 'Billing Mode',
billingModeToken: 'Token',
billingModePerRequest: 'Per Request',
billingModeImage: 'Image',
allBillingModes: 'All Billing Modes',
ipAddress: 'IP', ipAddress: 'IP',
clickToViewBalance: 'Click to view balance history', clickToViewBalance: 'Click to view balance history',
failedToLoadUser: 'Failed to load user info', failedToLoadUser: 'Failed to load user info',
......
...@@ -3515,6 +3515,11 @@ export default { ...@@ -3515,6 +3515,11 @@ export default {
allBillingTypes: '全部计费类型', allBillingTypes: '全部计费类型',
billingTypeBalance: '钱包余额', billingTypeBalance: '钱包余额',
billingTypeSubscription: '订阅套餐', billingTypeSubscription: '订阅套餐',
billingMode: '计费模式',
billingModeToken: '按量',
billingModePerRequest: '按次',
billingModeImage: '按次(图片)',
allBillingModes: '全部计费模式',
ipAddress: 'IP', ipAddress: 'IP',
clickToViewBalance: '点击查看充值记录', clickToViewBalance: '点击查看充值记录',
failedToLoadUser: '加载用户信息失败', failedToLoadUser: '加载用户信息失败',
......
...@@ -1036,6 +1036,9 @@ export interface UsageLog { ...@@ -1036,6 +1036,9 @@ export interface UsageLog {
// Cache TTL Override // Cache TTL Override
cache_ttl_overridden: boolean cache_ttl_overridden: boolean
// 计费模式
billing_mode?: string | null
created_at: string created_at: string
user?: User user?: User
......
...@@ -392,7 +392,7 @@ const resetFilters = () => { ...@@ -392,7 +392,7 @@ const resetFilters = () => {
const range = getLast24HoursRangeDates() const range = getLast24HoursRangeDates()
startDate.value = range.start startDate.value = range.start
endDate.value = range.end 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) granularity.value = getGranularityForRange(startDate.value, endDate.value)
applyFilters() applyFilters()
} }
...@@ -477,6 +477,7 @@ const allColumns = computed(() => [ ...@@ -477,6 +477,7 @@ const allColumns = computed(() => [
{ key: 'endpoint', label: t('usage.endpoint'), sortable: false }, { key: 'endpoint', label: t('usage.endpoint'), sortable: false },
{ key: 'group', label: t('admin.usage.group'), sortable: false }, { key: 'group', label: t('admin.usage.group'), sortable: false },
{ key: 'stream', label: t('usage.type'), 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: 'tokens', label: t('usage.tokens'), sortable: false },
{ key: 'cost', label: t('usage.cost'), sortable: false }, { key: 'cost', label: t('usage.cost'), sortable: false },
{ key: 'first_token', label: t('usage.firstToken'), sortable: false }, { key: 'first_token', label: t('usage.firstToken'), sortable: false },
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment