Files
soul-yongping/开发文档/8、部署/交易中心Tab按需加载优化.md
2026-02-09 15:09:29 +08:00

420 lines
11 KiB
Markdown
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.

# 交易中心 Tab 按需加载优化
## 问题描述
在后台管理的"交易中心"页面(`/admin/distribution`),存在性能问题:
**原问题**
- 每次切换 tab 都会重新请求**所有数据**
- 包括概览数据、用户数据、订单数据、绑定数据、提现数据
- 即使某个 tab 的数据已经加载过,再次切换回来也会重新请求
- 导致不必要的网络请求和数据库查询
**用户反馈**
> 每次切换tab不需要重新请求/api/admin/distribution/overview每个tab的列表数据都不一样切换tab的时候请求tab内对应的内容即可
## 优化目标
1. ✅ 概览数据只在初次加载时请求一次
2. ✅ 用户数据只在初次加载时请求一次(多个 tab 需要用到)
3. ✅ 各 tab 的数据按需加载,切换到该 tab 时才请求
4. ✅ 已加载过的 tab 数据缓存,再次切换不重复请求
5. ✅ 提供刷新功能,可强制重新加载当前 tab 数据
## 解决方案
### 1. 拆分加载逻辑
**修改前**(单一 `loadData` 函数):
```typescript
const loadData = async () => {
setLoading(true)
// 加载概览数据
// 加载用户数据
// 加载订单数据
// 加载绑定数据
// 加载提现数据
setLoading(false)
}
useEffect(() => {
loadData() // ❌ 每次切换tab都执行
}, [activeTab])
```
**修改后**(分离初始化和按需加载):
```typescript
// 1. 初始化加载:概览 + 用户
const loadInitialData = async () => {
// 加载概览数据(/api/admin/distribution/overview
// 加载用户数据(/api/db/users
}
// 2. 按需加载tab数据
const loadTabData = async (tab: string) => {
if (loadedTabs.has(tab)) {
console.log(`${tab} 数据已缓存,跳过加载`)
return // ✅ 已加载过,跳过
}
switch (tab) {
case 'overview': break // 无需额外加载
case 'orders': // 加载订单
case 'bindings': // 加载绑定
case 'withdrawals': // 加载提现
}
setLoadedTabs(prev => new Set(prev).add(tab)) // ✅ 标记已加载
}
// 初次加载
useEffect(() => {
loadInitialData()
}, [])
// tab切换时按需加载
useEffect(() => {
loadTabData(activeTab)
}, [activeTab])
```
### 2. 数据缓存机制
使用 `Set` 记录已加载的 tab
```typescript
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set())
```
**加载流程**
```
用户访问页面
loadInitialData()
├─ 加载概览数据
└─ 加载用户数据
默认显示 overview tab
用户切换到 orders tab
loadTabData('orders')
├─ 检查 loadedTabs.has('orders')? 否
├─ 请求 /api/orders
├─ 更新 loadedTabs.add('orders')
└─ 完成
用户切换回 overview tab
loadTabData('overview')
└─ 检查 loadedTabs.has('overview')? 是,跳过加载
用户切换到 orders tab
loadTabData('orders')
└─ 检查 loadedTabs.has('orders')? 是,跳过加载
```
### 3. 刷新功能
新增 `refreshCurrentTab()` 函数,允许强制重新加载:
```typescript
const refreshCurrentTab = () => {
// 移除当前tab的缓存标记
setLoadedTabs(prev => {
const newSet = new Set(prev)
newSet.delete(activeTab)
return newSet
})
// 重新加载概览数据如果在概览tab
if (activeTab === 'overview') {
loadInitialData()
}
// 重新加载当前tab数据
loadTabData(activeTab)
}
```
**使用场景**
- 点击"刷新数据"按钮
- 审批提现后刷新提现列表
- 修改数据后刷新当前视图
## Tab 数据加载映射
| Tab 名称 | 加载内容 | API 接口 | 何时加载 |
|---------|---------|---------|---------|
| `overview` | 概览统计 | `/api/admin/distribution/overview` | 初始化 |
| `orders` | 订单列表 | `/api/orders` | 切换到订单tab时 |
| `bindings` | 绑定关系 | `/api/db/distribution` | 切换到绑定tab时 |
| `withdrawals` | 提现记录 | `/api/db/withdrawals` | 切换到提现tab时 |
| *全局* | 用户数据 | `/api/db/users` | 初始化多个tab需要 |
## 修改文件清单
**文件路径**`app/admin/distribution/page.tsx`
**修改内容**
1. **新增状态**(第 114 行):
```typescript
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set())
```
2. **修改 useEffect**(第 116-123 行):
```typescript
// 修改前
useEffect(() => {
loadData()
}, [activeTab])
// 修改后
useEffect(() => {
loadInitialData()
}, [])
useEffect(() => {
loadTabData(activeTab)
}, [activeTab])
```
3. **新增 `loadInitialData()` 函数**(第 125-157 行):
- 加载概览数据
- 加载用户数据
4. **新增 `loadTabData()` 函数**(第 159-257 行):
- 检查缓存
- 根据 tab 加载对应数据
- 标记已加载
5. **新增 `refreshCurrentTab()` 函数**(第 259-270 行):
- 清除当前 tab 缓存
- 重新加载数据
6. **修改刷新按钮**(第 375 行):
```typescript
// 修改前
onClick={loadData}
// 修改后
onClick={refreshCurrentTab}
```
7. **修改提现审批回调**(第 285, 300 行):
```typescript
// 修改前
loadData()
// 修改后
refreshCurrentTab()
```
## 性能对比
### 修改前
| 操作 | API 请求数 | 说明 |
|-----|-----------|------|
| 初次访问 | 5 个 | overview, users, orders, bindings, withdrawals |
| 切换到订单 tab | 5 个 | 重复请求所有数据 |
| 切换到绑定 tab | 5 个 | 重复请求所有数据 |
| 切换回概览 tab | 5 个 | 重复请求所有数据 |
| **总计** | **20 个** | 重复请求 4 次 |
### 修改后
| 操作 | API 请求数 | 说明 |
|-----|-----------|------|
| 初次访问概览tab | 2 个 | overview, users |
| 切换到订单 tab | 1 个 | orders首次 |
| 切换到绑定 tab | 1 个 | bindings首次 |
| 切换回概览 tab | 0 个 | ✅ 已缓存,跳过 |
| 切换回订单 tab | 0 个 | ✅ 已缓存,跳过 |
| **总计** | **4 个** | 减少 80% 请求 |
**性能提升**
- ✅ API 请求减少 **80%**
- ✅ 数据库查询减少 **80%**
- ✅ 页面加载速度提升
- ✅ 服务器负载降低
## 验证步骤
### 1. 重启服务
```powershell
pm2 restart mycontent
# 或
npm run dev
```
### 2. 打开开发者工具
访问 `http://localhost:3006/admin/distribution`
按 F12 打开 DevTools → Network 标签 → 勾选"Preserve log"
### 3. 测试加载流程
**步骤 1初次访问**
- 应该看到 2 个请求:
- `/api/admin/distribution/overview`
- `/api/db/users`
- 控制台输出:
```
[Admin] 加载初始数据...
[Admin] 概览数据加载成功
[Admin] 用户数据加载成功
[Admin] overview 数据已缓存,跳过加载
```
**步骤 2切换到"订单管理"tab**
- 应该看到 1 个新请求:
- `/api/orders`
- 控制台输出:
```
[Admin] 加载 orders 数据...
[Admin] 订单数据加载成功: X 条
```
**步骤 3切换到"绑定管理"tab**
- 应该看到 1 个新请求:
- `/api/db/distribution`
- 控制台输出:
```
[Admin] 加载 bindings 数据...
[Admin] 绑定数据加载成功: X 条
```
**步骤 4切换回"数据概览"tab**
- ✅ **不应该有任何新请求**
- 控制台输出:
```
[Admin] overview 数据已缓存,跳过加载
```
**步骤 5点击"刷新数据"按钮**
- 应该看到当前 tab 对应的 API 请求
- 控制台输出:
```
[Admin] 加载 overview 数据...
[Admin] 概览数据加载成功
```
### 4. 测试审批功能
1. 切换到"提现审核" tab
2. 批准或拒绝一条提现记录
3. 应该只刷新提现数据1 个请求)
4. 不应该重新请求概览、用户、订单、绑定数据
## 注意事项
### 1. 用户数据全局共享
用户数据在初始化时加载一次,供所有 tab 使用:
- 订单管理需要:显示用户昵称、手机号
- 绑定管理需要:显示推荐人和被推荐人信息
- 提现审核需要:显示用户信息
### 2. 概览数据不自动更新
概览数据在初始化时加载,切换 tab 不会更新。如果需要最新的统计数据:
- 点击"刷新数据"按钮
- 或刷新整个页面F5
**未来优化**:可以考虑轮询或 WebSocket 实时更新概览数据。
### 3. 缓存策略
当前缓存策略:
- ✅ 同一页面会话内缓存(刷新页面会清空)
- ✅ 切换 tab 时保留缓存
- ✅ 点击"刷新数据"清空当前 tab 缓存
**未来优化**
- 可以设置缓存过期时间(如 5 分钟)
- 可以使用 SWR 或 React Query 管理缓存
### 4. 概览 tab 的特殊性
`overview` tab 在第一次 `loadTabData('overview')` 时会命中缓存判断并跳过,因为:
1. 初始化时会标记 `overview` 为已加载(虽然实际是在 `loadInitialData` 中加载的)
2. 这是合理的,因为概览数据已经在初始化时加载了
**实现建议**如果希望概览tab也能独立刷新可以在 `loadInitialData` 中不标记 `overview`
```typescript
// 不推荐因为会导致切换到overview tab时重复请求
setLoadedTabs(prev => new Set(prev).add('overview'))
```
## 扩展功能
### 1. 自动刷新
可以为特定 tab 添加自动刷新:
```typescript
useEffect(() => {
if (activeTab === 'withdrawals') {
const interval = setInterval(() => {
refreshCurrentTab()
}, 30000) // 每 30 秒刷新一次提现数据
return () => clearInterval(interval)
}
}, [activeTab])
```
### 2. 缓存过期时间
```typescript
const [loadedTabs, setLoadedTabs] = useState<Map<string, number>>(new Map())
const loadTabData = async (tab: string) => {
const lastLoaded = loadedTabs.get(tab)
const now = Date.now()
// 5分钟内的缓存有效
if (lastLoaded && (now - lastLoaded) < 5 * 60 * 1000) {
console.log(`${tab} 缓存有效,跳过加载`)
return
}
// 加载数据...
setLoadedTabs(prev => new Map(prev).set(tab, now))
}
```
### 3. 全局刷新
```typescript
const refreshAll = () => {
setLoadedTabs(new Set()) // 清空所有缓存
loadInitialData() // 重新加载初始数据
loadTabData(activeTab) // 重新加载当前tab
}
```
## 相关文件
- **交易中心页面**`app/admin/distribution/page.tsx`
- **概览API**`app/api/admin/distribution/overview/route.ts`
- **用户API**`app/api/db/users/route.ts`
- **订单API**`app/api/orders/route.ts`
- **绑定API**`app/api/db/distribution/route.ts`
- **提现API**`app/api/db/withdrawals/route.ts`
## 版本信息
- **优化时间**2026-02-04
- **优化内容**
1. 拆分 `loadData` 为 `loadInitialData` 和 `loadTabData`
2. 实现数据缓存机制(`loadedTabs` Set
3. 按需加载各 tab 数据
4. 新增 `refreshCurrentTab` 刷新功能
5. 减少 80% 的 API 请求和数据库查询