Files
yinghuoapi/frontend/src/components/layout/TablePageLayout.vue
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

115 lines
3.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="table-page-layout" :class="{ 'mobile-mode': isMobile }">
<!-- 固定区域操作按钮 -->
<div v-if="$slots.actions" class="layout-section-fixed">
<slot name="actions" />
</div>
<!-- 固定区域搜索和过滤器 -->
<div v-if="$slots.filters" class="layout-section-fixed">
<slot name="filters" />
</div>
<!-- 滚动区域表格 -->
<div class="layout-section-scrollable">
<div class="card table-scroll-container">
<slot name="table" />
</div>
</div>
<!-- 固定区域分页器 -->
<div v-if="$slots.pagination" class="layout-section-fixed">
<slot name="pagination" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const isMobile = ref(false)
const checkMobile = () => {
isMobile.value = window.innerWidth < 1024
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
</script>
<style scoped>
/* 桌面端Flexbox 布局 */
.table-page-layout {
@apply flex flex-col gap-6;
height: calc(100vh - 64px - 4rem); /* 减去 header + lg:p-8 的上下padding */
}
.layout-section-fixed {
@apply flex-shrink-0;
}
.layout-section-scrollable {
@apply flex-1 min-h-0 flex flex-col;
}
/* 表格滚动容器 - 增强版表体滚动方案 */
.table-scroll-container {
@apply flex flex-col overflow-hidden h-full bg-white dark:bg-dark-800 rounded-2xl border border-gray-200 dark:border-dark-700 shadow-sm;
}
.table-scroll-container :deep(.table-wrapper) {
@apply flex-1 overflow-x-auto overflow-y-auto;
/* 确保横向滚动条显示在最底部 */
scrollbar-gutter: stable;
}
.table-scroll-container :deep(table) {
@apply w-full;
min-width: max-content; /* 关键:确保表格宽度根据内容撑开,从而触发横向滚动 */
display: table; /* 使用标准 table 布局以支持 sticky 列 */
}
.table-scroll-container :deep(thead) {
@apply bg-gray-50/80 dark:bg-dark-800/80 backdrop-blur-sm;
}
.table-scroll-container :deep(tbody) {
/* 保持默认 table-row-group 显示,不使用 block */
}
.table-scroll-container :deep(th) {
/* 表头高度和文字加粗优化 */
@apply px-5 py-4 text-left text-sm font-bold text-gray-900 dark:text-white border-b border-gray-200 dark:border-dark-700;
@apply uppercase tracking-wider; /* 让表头更有设计感 */
}
.table-scroll-container :deep(td) {
@apply px-5 py-4 text-sm text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-dark-800;
}
/* 移动端:恢复正常滚动 */
.table-page-layout.mobile-mode .table-scroll-container {
@apply h-auto overflow-visible border-none shadow-none bg-transparent;
}
.table-page-layout.mobile-mode .layout-section-scrollable {
@apply flex-none min-h-fit;
}
.table-page-layout.mobile-mode .table-scroll-container :deep(.table-wrapper) {
@apply overflow-visible;
}
.table-page-layout.mobile-mode .table-scroll-container :deep(table) {
@apply flex-none;
display: table;
min-width: 100%;
}
</style>