# 交易中心 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>(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>(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>(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 请求和数据库查询