* 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 方法调用
Common Components
This directory contains reusable Vue 3 components built with Composition API, TypeScript, and TailwindCSS.
Components
DataTable.vue
A generic data table component with sorting, loading states, and custom cell rendering.
Props:
columns: Column[]- Array of column definitions with key, label, sortable, and formatterdata: any[]- Array of data objects to displayloading?: boolean- Show loading skeleton
Slots:
empty- Custom empty state contentcell-{key}- Custom cell renderer for specific column (receivesrowandvalue)
Usage:
<DataTable
:columns="[
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email' },
{ key: 'status', label: 'Status', formatter: (val) => val.toUpperCase() }
]"
:data="users"
:loading="isLoading"
>
<template #cell-actions="{ row }">
<button @click="editUser(row)">Edit</button>
</template>
</DataTable>
Pagination.vue
Pagination component with page numbers, navigation, and page size selector.
Props:
total: number- Total number of itemspage: number- Current page (1-indexed)pageSize: number- Items per pagepageSizeOptions?: number[]- Available page size options (default: [10, 20, 50, 100])
Events:
update:page- Emitted when page changesupdate:pageSize- Emitted when page size changes
Usage:
<Pagination
:total="totalUsers"
:page="currentPage"
:pageSize="pageSize"
@update:page="currentPage = $event"
@update:pageSize="pageSize = $event"
/>
Modal.vue
Modal dialog with customizable size and close behavior.
Props:
show: boolean- Control modal visibilitytitle: string- Modal titlesize?: 'sm' | 'md' | 'lg' | 'xl' | 'full'- Modal size (default: 'md')closeOnEscape?: boolean- Close on Escape key (default: true)closeOnClickOutside?: boolean- Close on backdrop click (default: true)
Events:
close- Emitted when modal should close
Slots:
default- Modal body contentfooter- Modal footer content
Usage:
<Modal :show="showModal" title="Edit User" size="lg" @close="showModal = false">
<form @submit.prevent="saveUser">
<!-- Form content -->
</form>
<template #footer>
<button @click="showModal = false">Cancel</button>
<button @click="saveUser">Save</button>
</template>
</Modal>
ConfirmDialog.vue
Confirmation dialog built on top of Modal component.
Props:
show: boolean- Control dialog visibilitytitle: string- Dialog titlemessage: string- Confirmation messageconfirmText?: string- Confirm button text (default: 'Confirm')cancelText?: string- Cancel button text (default: 'Cancel')danger?: boolean- Use danger/red styling (default: false)
Events:
confirm- Emitted when user confirmscancel- Emitted when user cancels
Usage:
<ConfirmDialog
:show="showDeleteConfirm"
title="Delete User"
message="Are you sure you want to delete this user? This action cannot be undone."
confirm-text="Delete"
cancel-text="Cancel"
danger
@confirm="deleteUser"
@cancel="showDeleteConfirm = false"
/>
StatCard.vue
Statistics card component for displaying metrics with optional change indicators.
Props:
title: string- Card titlevalue: number | string- Main value to displayicon?: Component- Icon componentchange?: number- Percentage change valuechangeType?: 'up' | 'down' | 'neutral'- Change direction (default: 'neutral')formatValue?: (value) => string- Custom value formatter
Usage:
<StatCard title="Total Users" :value="1234" :icon="UserIcon" :change="12.5" change-type="up" />
Toast.vue
Toast notification component that automatically displays toasts from the app store.
Usage:
<!-- Add once in App.vue or layout -->
<Toast />
// Trigger toasts from anywhere using the app store
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
appStore.addToast({
type: 'success',
title: 'Success!',
message: 'User created successfully',
duration: 3000
})
appStore.addToast({
type: 'error',
message: 'Failed to delete user'
})
LoadingSpinner.vue
Simple animated loading spinner.
Props:
size?: 'sm' | 'md' | 'lg' | 'xl'- Spinner size (default: 'md')color?: 'primary' | 'secondary' | 'white' | 'gray'- Spinner color (default: 'primary')
Usage:
<LoadingSpinner size="lg" color="primary" />
EmptyState.vue
Empty state placeholder with icon, message, and optional action button.
Props:
icon?: Component- Icon componenttitle: string- Empty state titledescription: string- Empty state descriptionactionText?: string- Action button textactionTo?: string | object- Router link destinationactionIcon?: boolean- Show plus icon in button (default: true)
Slots:
icon- Custom icon contentaction- Custom action button/link
Usage:
<EmptyState
title="No users found"
description="Get started by creating your first user account."
action-text="Add User"
:action-to="{ name: 'users-create' }"
/>
Import
You can import components individually:
import { DataTable, Pagination, Modal } from '@/components/common'
Or import specific components:
import DataTable from '@/components/common/DataTable.vue'
Features
All components include:
- TypeScript support with proper type definitions
- Accessibility with ARIA attributes and keyboard navigation
- Responsive design with mobile-friendly layouts
- TailwindCSS styling for consistent design
- Vue 3 Composition API with
<script setup> - Slot support for customization