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:
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user