Files
yinghuoapi/frontend/src/components/common
IanShaw 254f12543c 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 方法调用
2025-12-27 10:50:25 +08:00
..
2025-12-18 14:26:55 +08:00
2025-12-18 13:50:39 +08:00
2025-12-18 14:26:55 +08:00

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 formatter
  • data: any[] - Array of data objects to display
  • loading?: boolean - Show loading skeleton

Slots:

  • empty - Custom empty state content
  • cell-{key} - Custom cell renderer for specific column (receives row and value)

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 items
  • page: number - Current page (1-indexed)
  • pageSize: number - Items per page
  • pageSizeOptions?: number[] - Available page size options (default: [10, 20, 50, 100])

Events:

  • update:page - Emitted when page changes
  • update: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 visibility
  • title: string - Modal title
  • size?: '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 content
  • footer - 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 visibility
  • title: string - Dialog title
  • message: string - Confirmation message
  • confirmText?: 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 confirms
  • cancel - 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 title
  • value: number | string - Main value to display
  • icon?: Component - Icon component
  • change?: number - Percentage change value
  • changeType?: '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 component
  • title: string - Empty state title
  • description: string - Empty state description
  • actionText?: string - Action button text
  • actionTo?: string | object - Router link destination
  • actionIcon?: boolean - Show plus icon in button (default: true)

Slots:

  • icon - Custom icon content
  • action - 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