11 KiB
交易中心 Tab 按需加载优化
问题描述
在后台管理的"交易中心"页面(/admin/distribution),存在性能问题:
原问题:
- 每次切换 tab 都会重新请求所有数据
- 包括概览数据、用户数据、订单数据、绑定数据、提现数据
- 即使某个 tab 的数据已经加载过,再次切换回来也会重新请求
- 导致不必要的网络请求和数据库查询
用户反馈:
每次切换tab不需要重新请求/api/admin/distribution/overview,每个tab的列表数据都不一样,切换tab的时候,请求tab内对应的内容即可
优化目标
- ✅ 概览数据只在初次加载时请求一次
- ✅ 用户数据只在初次加载时请求一次(多个 tab 需要用到)
- ✅ 各 tab 的数据按需加载,切换到该 tab 时才请求
- ✅ 已加载过的 tab 数据缓存,再次切换不重复请求
- ✅ 提供刷新功能,可强制重新加载当前 tab 数据
解决方案
1. 拆分加载逻辑
修改前(单一 loadData 函数):
const loadData = async () => {
setLoading(true)
// 加载概览数据
// 加载用户数据
// 加载订单数据
// 加载绑定数据
// 加载提现数据
setLoading(false)
}
useEffect(() => {
loadData() // ❌ 每次切换tab都执行
}, [activeTab])
修改后(分离初始化和按需加载):
// 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:
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() 函数,允许强制重新加载:
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
修改内容:
-
新增状态(第 114 行):
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set()) -
修改 useEffect(第 116-123 行):
// 修改前 useEffect(() => { loadData() }, [activeTab]) // 修改后 useEffect(() => { loadInitialData() }, []) useEffect(() => { loadTabData(activeTab) }, [activeTab]) -
新增
loadInitialData()函数(第 125-157 行):- 加载概览数据
- 加载用户数据
-
新增
loadTabData()函数(第 159-257 行):- 检查缓存
- 根据 tab 加载对应数据
- 标记已加载
-
新增
refreshCurrentTab()函数(第 259-270 行):- 清除当前 tab 缓存
- 重新加载数据
-
修改刷新按钮(第 375 行):
// 修改前 onClick={loadData} // 修改后 onClick={refreshCurrentTab} -
修改提现审批回调(第 285, 300 行):
// 修改前 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. 重启服务
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. 测试审批功能
- 切换到"提现审核" tab
- 批准或拒绝一条提现记录
- 应该只刷新提现数据(1 个请求)
- 不应该重新请求概览、用户、订单、绑定数据
注意事项
1. 用户数据全局共享
用户数据在初始化时加载一次,供所有 tab 使用:
- 订单管理需要:显示用户昵称、手机号
- 绑定管理需要:显示推荐人和被推荐人信息
- 提现审核需要:显示用户信息
2. 概览数据不自动更新
概览数据在初始化时加载,切换 tab 不会更新。如果需要最新的统计数据:
- 点击"刷新数据"按钮
- 或刷新整个页面(F5)
未来优化:可以考虑轮询或 WebSocket 实时更新概览数据。
3. 缓存策略
当前缓存策略:
- ✅ 同一页面会话内缓存(刷新页面会清空)
- ✅ 切换 tab 时保留缓存
- ✅ 点击"刷新数据"清空当前 tab 缓存
未来优化:
- 可以设置缓存过期时间(如 5 分钟)
- 可以使用 SWR 或 React Query 管理缓存
4. 概览 tab 的特殊性
overview tab 在第一次 loadTabData('overview') 时会命中缓存判断并跳过,因为:
- 初始化时会标记
overview为已加载(虽然实际是在loadInitialData中加载的) - 这是合理的,因为概览数据已经在初始化时加载了
实现建议:如果希望概览tab也能独立刷新,可以在 loadInitialData 中不标记 overview:
// 不推荐,因为会导致切换到overview tab时重复请求
setLoadedTabs(prev => new Set(prev).add('overview'))
扩展功能
1. 自动刷新
可以为特定 tab 添加自动刷新:
useEffect(() => {
if (activeTab === 'withdrawals') {
const interval = setInterval(() => {
refreshCurrentTab()
}, 30000) // 每 30 秒刷新一次提现数据
return () => clearInterval(interval)
}
}, [activeTab])
2. 缓存过期时间
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. 全局刷新
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
- 优化内容:
- 拆分
loadData为loadInitialData和loadTabData - 实现数据缓存机制(
loadedTabsSet) - 按需加载各 tab 数据
- 新增
refreshCurrentTab刷新功能 - 减少 80% 的 API 请求和数据库查询
- 拆分