420 lines
11 KiB
Markdown
420 lines
11 KiB
Markdown
# 交易中心 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 请求和数据库查询
|