feat(frontend): 前端界面优化与使用统计功能增强 (#46)

* feat(frontend): 前端界面优化与使用统计功能增强

主要改动:

1. 表格布局统一优化
   - 新增 TablePageLayout 通用布局组件
   - 统一所有管理页面的表格样式和交互
   - 优化 DataTable、Pagination、Select 等通用组件

2. 使用统计功能增强
   - 管理端: 添加完整的筛选和显示功能
   - 用户端: 完善 API Key 列显示
   - 后端: 优化使用统计数据结构和查询

3. 账户组件优化
   - 优化 AccountStatsModal、AccountUsageCell 等组件
   - 统一进度条和统计显示样式

4. 其他改进
   - 完善中英文国际化
   - 统一页面样式和交互体验
   - 优化各视图页面的响应式布局

* fix(test): 修复 stubUsageLogRepo.ListWithFilters 测试 stub

测试用例 GET /api/v1/usage 返回 500 是因为 stub 方法未实现,
现在正确返回基于 UserID 过滤的日志数据。

* feat(frontend): 统一日期时间显示格式

**主要改动**:
1. 增强 utils/format.ts:
   - 新增 formatDateOnly() - 格式: YYYY-MM-DD
   - 新增 formatDateTime() - 格式: YYYY-MM-DD HH:mm:ss

2. 全局替换视图中的格式化函数:
   - 移除各视图中的自定义 formatDate 函数
   - 统一导入使用 @/utils/format 中的函数
   - created_at/updated_at 使用 formatDateTime
   - expires_at 使用 formatDateOnly

3. 受影响的视图 (8个):
   - frontend/src/views/user/KeysView.vue
   - frontend/src/views/user/DashboardView.vue
   - frontend/src/views/user/UsageView.vue
   - frontend/src/views/user/RedeemView.vue
   - frontend/src/views/admin/UsersView.vue
   - frontend/src/views/admin/UsageView.vue
   - frontend/src/views/admin/RedeemView.vue
   - frontend/src/views/admin/SubscriptionsView.vue

**效果**:
- 日期统一显示为 YYYY-MM-DD
- 时间统一显示为 YYYY-MM-DD HH:mm:ss
- 提升可维护性,避免格式不一致

* fix(frontend): 补充遗漏的时间格式化统一

**补充修复**(基于 code review 发现的遗漏):

1. 增强 utils/format.ts:
   - 新增 formatTime() - 格式: HH:mm

2. 修复 4 个遗漏的文件:
   - src/views/admin/UsersView.vue
     * 删除 formatExpiresAt(),改用 formatDateTime()
     * 修复订阅过期时间 tooltip 显示格式不一致问题

   - src/views/user/ProfileView.vue
     * 删除 formatMemberSince(),改用 formatDate(date, 'YYYY-MM')
     * 统一会员起始时间显示格式

   - src/views/user/SubscriptionsView.vue
     * 修改 formatExpirationDate() 使用 formatDateOnly()
     * 保留天数计算逻辑

   - src/components/account/AccountStatusIndicator.vue
     * 删除本地 formatTime(),改用 utils/format 中的统一函数
     * 修复 rate limit 和 overload 重置时间显示

**验证**:
- TypeScript 类型检查通过 ✓
- 前端构建成功 ✓
- 所有剩余的 toLocaleString() 都是数字格式化,属于正确用法 ✓

**效果**:
- 订阅过期时间统一为 YYYY-MM-DD HH:mm:ss
- 会员起始时间统一为 YYYY-MM
- 重置时间统一为 HH:mm
- 消除所有不规范的原生 locale 方法调用
This commit is contained in:
IanShaw
2025-12-27 10:50:25 +08:00
committed by GitHub
parent cf8a64528c
commit 254f12543c
43 changed files with 1673 additions and 692 deletions

View File

@@ -30,13 +30,56 @@ export default {
title: 'Supported Providers',
description: 'Unified API interface for AI services',
supported: 'Supported',
soon: 'Soon'
soon: 'Soon',
claude: 'Claude',
gemini: 'Gemini',
more: 'More'
},
footer: {
allRightsReserved: 'All rights reserved.'
}
},
// Setup Wizard
setup: {
title: 'Sub2API Setup',
description: 'Configure your Sub2API instance',
database: {
title: 'Database Configuration',
host: 'Host',
port: 'Port',
username: 'Username',
password: 'Password',
databaseName: 'Database Name',
sslMode: 'SSL Mode',
ssl: {
disable: 'Disable',
require: 'Require',
verifyCa: 'Verify CA',
verifyFull: 'Verify Full'
}
},
redis: {
title: 'Redis Configuration',
host: 'Host',
port: 'Port',
password: 'Password (optional)',
database: 'Database'
},
admin: {
title: 'Admin Account',
email: 'Email',
password: 'Password',
confirmPassword: 'Confirm Password'
},
ready: {
title: 'Ready to Install',
database: 'Database',
redis: 'Redis',
adminEmail: 'Admin Email'
}
},
// Common
common: {
loading: 'Loading...',
@@ -142,7 +185,20 @@ export default {
accountCreatedSuccess: 'Account created successfully! Welcome to {siteName}.',
turnstileExpired: 'Verification expired, please try again',
turnstileFailed: 'Verification failed, please try again',
completeVerification: 'Please complete the verification'
completeVerification: 'Please complete the verification',
verifyYourEmail: 'Verify Your Email',
sessionExpired: 'Session expired',
sessionExpiredDesc: 'Please go back to the registration page and start again.',
verificationCode: 'Verification Code',
verificationCodeHint: 'Enter the 6-digit code sent to your email',
sendingCode: 'Sending...',
clickToResend: 'Click to resend code',
resendCode: 'Resend verification code',
oauth: {
code: 'Code',
state: 'State',
fullUrl: 'Full URL'
}
},
// Dashboard
@@ -377,6 +433,12 @@ export default {
noData: 'No data found'
},
// Table
table: {
expandActions: 'Expand More Actions',
collapseActions: 'Collapse Actions'
},
// Pagination
pagination: {
showing: 'Showing',
@@ -584,6 +646,7 @@ export default {
actions: 'Actions',
billingType: 'Billing Type'
},
rateAndAccounts: '{rate}x rate · {count} accounts',
accountsCount: '{count} accounts',
form: {
name: 'Name',
@@ -742,6 +805,13 @@ export default {
openai: 'OpenAI',
gemini: 'Gemini'
},
types: {
oauth: 'OAuth',
chatgptOauth: 'ChatGPT OAuth',
responsesApi: 'Responses API',
googleOauth: 'Google OAuth',
codeAssist: 'Code Assist'
},
columns: {
name: 'Name',
platformType: 'Platform/Type',
@@ -1022,6 +1092,7 @@ export default {
todayOverview: 'Today Overview',
cost: 'Cost',
requests: 'Requests',
tokens: 'Tokens',
highestCostDay: 'Highest Cost Day',
highestRequestDay: 'Highest Request Day',
date: 'Date',
@@ -1037,6 +1108,9 @@ export default {
todayCost: 'Today Cost',
usageTrend: '30-Day Cost & Request Trend',
noData: 'No usage data available for this account'
},
usageWindow: {
statsTitle: '5-Hour Window Usage Statistics'
}
},
@@ -1070,6 +1144,10 @@ export default {
enterProxyName: 'Enter proxy name',
leaveEmptyToKeep: 'Leave empty to keep current',
optionalAuth: 'Optional authentication',
form: {
hostPlaceholder: 'proxy.example.com',
portPlaceholder: '8080'
},
noProxiesYet: 'No proxies yet',
createFirstProxy: 'Create your first proxy to route traffic through it.',
// Batch import
@@ -1174,6 +1252,18 @@ export default {
searchUserPlaceholder: 'Search user by email...',
selectedUser: 'Selected',
user: 'User',
account: 'Account',
group: 'Group',
requestId: 'Request ID',
allModels: 'All Models',
allAccounts: 'All Accounts',
allGroups: 'All Groups',
allTypes: 'All Types',
allBillingTypes: 'All Billing',
inputCost: 'Input Cost',
outputCost: 'Output Cost',
cacheCreationCost: 'Cache Creation Cost',
cacheReadCost: 'Cache Read Cost',
failedToLoad: 'Failed to load usage records'
},
@@ -1211,16 +1301,20 @@ export default {
title: 'Site Settings',
description: 'Customize site branding',
siteName: 'Site Name',
siteNamePlaceholder: 'Sub2API',
siteNameHint: 'Displayed in emails and page titles',
siteSubtitle: 'Site Subtitle',
siteSubtitlePlaceholder: 'Subscription to API Conversion Platform',
siteSubtitleHint: 'Displayed on login and register pages',
apiBaseUrl: 'API Base URL',
apiBaseUrlPlaceholder: 'https://api.example.com',
apiBaseUrlHint:
'Used for "Use Key" and "Import to CC Switch" features. Leave empty to use current site URL.',
contactInfo: 'Contact Info',
contactInfoPlaceholder: 'e.g., QQ: 123456789',
contactInfoHint: 'Customer support contact info, displayed on redeem page, profile, etc.',
docUrl: 'Documentation URL',
docUrlPlaceholder: 'https://docs.example.com',
docUrlHint: 'Link to your documentation site. Leave empty to hide the documentation link.',
siteLogo: 'Site Logo',
uploadImage: 'Upload Image',
@@ -1236,12 +1330,18 @@ export default {
testConnection: 'Test Connection',
testing: 'Testing...',
host: 'SMTP Host',
hostPlaceholder: 'smtp.gmail.com',
port: 'SMTP Port',
portPlaceholder: '587',
username: 'SMTP Username',
usernamePlaceholder: 'your-email@gmail.com',
password: 'SMTP Password',
passwordPlaceholder: '********',
passwordHint: 'Leave empty to keep existing password',
fromEmail: 'From Email',
fromEmailPlaceholder: 'noreply@example.com',
fromName: 'From Name',
fromNamePlaceholder: 'Sub2API',
useTls: 'Use TLS',
useTlsHint: 'Enable TLS encryption for SMTP connection'
},
@@ -1249,6 +1349,7 @@ export default {
title: 'Send Test Email',
description: 'Send a test email to verify your SMTP configuration',
recipientEmail: 'Recipient Email',
recipientEmailPlaceholder: 'test@example.com',
sendTestEmail: 'Send Test Email',
sending: 'Sending...',
enterRecipientHint: 'Please enter a recipient email address'

View File

@@ -27,13 +27,56 @@ export default {
title: '支持的服务商',
description: 'AI 服务的统一 API 接口',
supported: '已支持',
soon: '即将推出'
soon: '即将推出',
claude: 'Claude',
gemini: 'Gemini',
more: '更多'
},
footer: {
allRightsReserved: '保留所有权利。'
}
},
// Setup Wizard
setup: {
title: 'Sub2API 安装向导',
description: '配置您的 Sub2API 实例',
database: {
title: '数据库配置',
host: '主机',
port: '端口',
username: '用户名',
password: '密码',
databaseName: '数据库名称',
sslMode: 'SSL 模式',
ssl: {
disable: '禁用',
require: '要求',
verifyCa: '验证 CA',
verifyFull: '完全验证'
}
},
redis: {
title: 'Redis 配置',
host: '主机',
port: '端口',
password: '密码(可选)',
database: '数据库'
},
admin: {
title: '管理员账户',
email: '邮箱',
password: '密码',
confirmPassword: '确认密码'
},
ready: {
title: '准备安装',
database: '数据库',
redis: 'Redis',
adminEmail: '管理员邮箱'
}
},
// Common
common: {
loading: '加载中...',
@@ -139,7 +182,20 @@ export default {
accountCreatedSuccess: '账户创建成功!欢迎使用 {siteName}。',
turnstileExpired: '验证已过期,请重试',
turnstileFailed: '验证失败,请重试',
completeVerification: '请完成验证'
completeVerification: '请完成验证',
verifyYourEmail: '验证您的邮箱',
sessionExpired: '会话已过期',
sessionExpiredDesc: '请返回注册页面重新开始。',
verificationCode: '验证码',
verificationCodeHint: '请输入发送到您邮箱的6位验证码',
sendingCode: '发送中...',
clickToResend: '点击重新发送验证码',
resendCode: '重新发送验证码',
oauth: {
code: '授权码',
state: '状态',
fullUrl: '完整URL'
}
},
// Dashboard
@@ -373,6 +429,12 @@ export default {
noData: '暂无数据'
},
// Table
table: {
expandActions: '展开更多操作',
collapseActions: '收起操作'
},
// Pagination
pagination: {
showing: '显示',
@@ -689,6 +751,7 @@ export default {
exclusiveFilter: '独占',
nonExclusive: '非独占',
public: '公开',
rateAndAccounts: '{rate}x 费率 · {count} 个账号',
accountsCount: '{count} 个账号',
enterGroupName: '请输入分组名称',
optionalDescription: '可选描述',
@@ -848,6 +911,10 @@ export default {
},
types: {
oauth: 'OAuth',
chatgptOauth: 'ChatGPT OAuth',
responsesApi: 'Responses API',
googleOauth: 'Google OAuth',
codeAssist: 'Code Assist',
api_key: 'API Key',
cookie: 'Cookie'
},
@@ -857,6 +924,9 @@ export default {
error: '错误',
cooldown: '冷却中'
},
usageWindow: {
statsTitle: '5小时窗口用量统计'
},
form: {
nameLabel: '账号名称',
namePlaceholder: '请输入账号名称',
@@ -1125,6 +1195,7 @@ export default {
todayOverview: '今日概览',
cost: '费用',
requests: '请求',
tokens: 'Token',
highestCostDay: '最高费用日',
highestRequestDay: '最高请求日',
date: '日期',
@@ -1364,6 +1435,18 @@ export default {
searchUserPlaceholder: '按邮箱搜索用户...',
selectedUser: '已选择',
user: '用户',
account: '账户',
group: '分组',
requestId: '请求ID',
allModels: '全部模型',
allAccounts: '全部账户',
allGroups: '全部分组',
allTypes: '全部类型',
allBillingTypes: '全部计费',
inputCost: '输入成本',
outputCost: '输出成本',
cacheCreationCost: '缓存创建成本',
cacheReadCost: '缓存读取成本',
failedToLoad: '加载使用记录失败'
},
@@ -1402,15 +1485,19 @@ export default {
description: '自定义站点品牌',
siteName: '站点名称',
siteNameHint: '显示在邮件和页面标题中',
siteNamePlaceholder: 'Sub2API',
siteSubtitle: '站点副标题',
siteSubtitleHint: '显示在登录和注册页面',
siteSubtitlePlaceholder: '订阅转 API 转换平台',
apiBaseUrl: 'API 端点地址',
apiBaseUrlHint: '用于"使用密钥"和"导入到 CC Switch"功能,留空则使用当前站点地址',
apiBaseUrlPlaceholder: 'https://api.example.com',
contactInfo: '客服联系方式',
contactInfoPlaceholder: '例如QQ: 123456789',
contactInfoHint: '填写客服联系方式,将展示在兑换页面、个人资料等位置',
docUrl: '文档链接',
docUrlHint: '文档网站的链接。留空则隐藏文档链接。',
docUrlPlaceholder: 'https://docs.example.com',
siteLogo: '站点Logo',
uploadImage: '上传图片',
remove: '移除',
@@ -1425,12 +1512,18 @@ export default {
testConnection: '测试连接',
testing: '测试中...',
host: 'SMTP 主机',
hostPlaceholder: 'smtp.gmail.com',
port: 'SMTP 端口',
portPlaceholder: '587',
username: 'SMTP 用户名',
usernamePlaceholder: 'your-email@gmail.com',
password: 'SMTP 密码',
passwordPlaceholder: '********',
passwordHint: '留空以保留现有密码',
fromEmail: '发件人邮箱',
fromEmailPlaceholder: 'noreply@example.com',
fromName: '发件人名称',
fromNamePlaceholder: 'Sub2API',
useTls: '使用 TLS',
useTlsHint: '为 SMTP 连接启用 TLS 加密'
},
@@ -1438,6 +1531,7 @@ export default {
title: '发送测试邮件',
description: '发送测试邮件以验证 SMTP 配置',
recipientEmail: '收件人邮箱',
recipientEmailPlaceholder: 'test@example.com',
sendTestEmail: '发送测试邮件',
sending: '发送中...',
enterRecipientHint: '请输入收件人邮箱地址'