From d051be3f65354ab71af9a1e3cc8332a18a4aacc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Thu, 22 Jan 2026 21:30:21 +0800 Subject: [PATCH] a --- frontend/REDUX_SETUP.md | 97 +++++++++++++ frontend/src/App.jsx | 103 ++++++-------- frontend/src/components/AccountSelector.jsx | 107 ++++++-------- frontend/src/components/ConfigPanel.jsx | 64 +++------ frontend/src/components/GlobalConfig.jsx | 10 +- frontend/src/components/Recommendations.jsx | 25 ++-- frontend/src/components/StatsDashboard.jsx | 13 +- frontend/src/components/TradeList.jsx | 23 +-- frontend/src/main.jsx | 6 +- frontend/src/store/appSlice.js | 147 ++++++++++++++++++++ frontend/src/store/index.js | 10 ++ 11 files changed, 388 insertions(+), 217 deletions(-) create mode 100644 frontend/REDUX_SETUP.md create mode 100644 frontend/src/store/appSlice.js create mode 100644 frontend/src/store/index.js diff --git a/frontend/REDUX_SETUP.md b/frontend/REDUX_SETUP.md new file mode 100644 index 0000000..fcdc5bc --- /dev/null +++ b/frontend/REDUX_SETUP.md @@ -0,0 +1,97 @@ +# Redux 全局状态管理设置说明 + +## 📦 安装依赖 + +请手动运行以下命令安装 Redux 相关依赖: + +```bash +cd frontend +npm install @reduxjs/toolkit react-redux +``` + +## ✅ 已完成的实现 + +### 1. Redux Store 结构 + +**文件**: `frontend/src/store/index.js` +- 配置了 Redux store +- 使用 `@reduxjs/toolkit` 的 `configureStore` + +**文件**: `frontend/src/store/appSlice.js` +- 管理全局状态: + - `currentUser`: 当前登录用户 + - `viewingUserId`: 管理员查看的用户ID + - `accountId`: 当前选中的账号ID + - `accounts`: 当前用户的账号列表 + - `users`: 用户列表(管理员可见) + +### 2. 已更新的组件 + +#### App.jsx +- ✅ 使用 `Provider` 包裹整个应用 +- ✅ 使用 Redux hooks (`useSelector`, `useDispatch`) +- ✅ 用户切换逻辑使用 Redux actions + +#### AccountSelector.jsx +- ✅ 完全使用 Redux 管理账号选择 +- ✅ 用户切换时自动选择第一个 active 账号 +- ✅ 账号切换时自动同步到 Redux store + +#### StatsDashboard.jsx +- ✅ 使用 `useSelector(selectAccountId)` 获取当前账号ID +- ✅ 当 `accountId` 变化时自动重新加载数据 + +#### TradeList.jsx +- ✅ 使用 `useSelector(selectAccountId)` 获取当前账号ID +- ✅ 当 `accountId` 变化时自动重新加载数据 + +#### Recommendations.jsx +- ✅ 使用 `useSelector(selectAccountId)` 获取当前账号ID +- ✅ 当 `accountId` 变化时自动更新默认下单量 + +#### ConfigPanel.jsx +- ✅ 使用 Redux 获取 `accountId`, `currentUser`, `isAdmin` 等 +- ✅ 移除了 localStorage 轮询和事件监听 +- ✅ 当 `accountId` 变化时自动重新加载配置 + +#### GlobalConfig.jsx +- ✅ 使用 Redux 获取 `currentUser`, `isAdmin` +- ✅ 移除了 props 传递 + +## 🔄 工作流程 + +### 用户切换流程 +1. 管理员点击切换用户 +2. `App.jsx` 调用 `dispatch(switchUser(userId))` +3. Redux store 更新 `viewingUserId` +4. `AccountSelector` 监听到 `viewingUserId` 变化 +5. 自动加载新用户的账号列表 +6. 自动选择第一个 active 账号 +7. Redux store 更新 `accountId` +8. 所有使用 `useSelector(selectAccountId)` 的组件自动重新渲染 +9. 各组件在 `useEffect` 中检测到 `accountId` 变化,重新加载数据 + +### 账号切换流程 +1. 用户在 `AccountSelector` 中选择账号 +2. `AccountSelector` 调用 `dispatch(setAccountId(accountId))` +3. Redux store 更新 `accountId` +4. 所有使用 `useSelector(selectAccountId)` 的组件自动重新渲染 +5. 各组件在 `useEffect` 中检测到 `accountId` 变化,重新加载数据 + +## 🎯 优势 + +1. **统一状态管理**: 所有用户和账号状态都在 Redux store 中 +2. **自动同步**: 切换用户/账号后,所有页面自动更新 +3. **响应式**: 使用 React hooks,状态变化自动触发重新渲染 +4. **易于调试**: Redux DevTools 可以查看所有状态变化 +5. **向后兼容**: 保留了自定义事件 (`ats:account:changed`),确保旧代码仍能工作 + +## 📝 注意事项 + +1. **需要安装依赖**: 请运行 `npm install @reduxjs/toolkit react-redux` +2. **localStorage 同步**: Redux store 会自动同步到 localStorage,确保刷新后状态不丢失 +3. **事件兼容**: 保留了 `ats:account:changed` 事件,但主要使用 Redux 状态管理 + +## 🚀 下一步 + +安装依赖后,系统将完全使用 Redux 管理用户和账号状态,切换用户/账号时所有页面会自动同步更新。 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 19b0ea6..700b32f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react' import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' import ConfigPanel from './components/ConfigPanel' import ConfigGuide from './components/ConfigGuide' import TradeList from './components/TradeList' @@ -9,77 +10,56 @@ import LogMonitor from './components/LogMonitor' import AccountSelector from './components/AccountSelector' import GlobalConfig from './components/GlobalConfig' import Login from './components/Login' -import { api, clearAuthToken, clearCurrentAccountId, setCurrentAccountId, getCurrentAccountId } from './services/api' - -const VIEWING_USER_ID_KEY = 'ats_viewing_user_id' +import { api, clearAuthToken } from './services/api' +import { + setCurrentUser, + setViewingUserId, + setUsers, + switchUser, + selectCurrentUser, + selectViewingUserId, + selectUsers, + selectIsAdmin, + selectEffectiveUserId, +} from './store/appSlice' import './App.css' function App() { - const [me, setMe] = useState(null) + const dispatch = useDispatch() + const currentUser = useSelector(selectCurrentUser) + const viewingUserId = useSelector(selectViewingUserId) + const users = useSelector(selectUsers) + const isAdmin = useSelector(selectIsAdmin) + const effectiveUserId = useSelector(selectEffectiveUserId) + const [checking, setChecking] = useState(true) - const [users, setUsers] = useState([]) - const [viewingUserId, setViewingUserId] = useState(() => { - // 管理员:从localStorage读取当前查看的用户ID,默认是当前登录用户 - const stored = localStorage.getItem(VIEWING_USER_ID_KEY) - if (stored) { - const parsed = parseInt(stored, 10) - if (Number.isFinite(parsed) && parsed > 0) return parsed - } - return null - }) const [showUserPopover, setShowUserPopover] = useState(false) const userPopoverRef = React.useRef(null) const refreshMe = async () => { try { const u = await api.me() - setMe(u) + dispatch(setCurrentUser(u)) - const isAdmin = (u?.role || '') === 'admin' + const isAdminValue = (u?.role || '') === 'admin' // 管理员:加载用户列表 - if (isAdmin) { + if (isAdminValue) { try { const userList = await api.getUsers() - setUsers(Array.isArray(userList) ? userList : []) + const usersArray = Array.isArray(userList) ? userList : [] + dispatch(setUsers(usersArray)) // 如果viewingUserId未设置或不在列表中,设置为当前登录用户 const currentUserId = parseInt(String(u?.id || ''), 10) - if (!viewingUserId || !userList.some((user) => parseInt(String(user?.id || ''), 10) === viewingUserId)) { - setViewingUserId(currentUserId) - localStorage.setItem(VIEWING_USER_ID_KEY, String(currentUserId)) + if (!viewingUserId || !usersArray.some((user) => parseInt(String(user?.id || ''), 10) === viewingUserId)) { + dispatch(setViewingUserId(currentUserId)) } } catch (e) { console.error('加载用户列表失败:', e) } } - - // 登录后默认选择账号(避免 admin/用户切换时沿用旧 accountId) - try { - const list = await api.getAccounts() - const accounts = Array.isArray(list) ? list : [] - const active = accounts.filter((a) => String(a?.status || 'active') === 'active') - - let target = null - if (isAdmin) { - // 管理员:默认选第一个 active 账号(避免沿用旧值导致“看起来还是上个账号”) - target = active[0]?.id || accounts[0]?.id - } else { - // 普通用户:优先选“自己的账号”(且必须 active),否则选第一个 active - const uid = parseInt(String(u?.id || ''), 10) - const match = active.find((a) => parseInt(String(a?.id || ''), 10) === uid) - target = match?.id || active[0]?.id || accounts[0]?.id - } - - if (target) { - const cur = getCurrentAccountId() - const next = parseInt(String(target), 10) - if (Number.isFinite(next) && next > 0 && cur !== next) setCurrentAccountId(next) - } - } catch (e) { - // ignore - } } catch (e) { - setMe(null) + dispatch(setCurrentUser(null)) } finally { setChecking(false) } @@ -98,20 +78,15 @@ function App() { } }, [showUserPopover]) - // 必须在定义 effectiveUserId 之前定义 isAdmin - const isAdmin = (me?.role || '') === 'admin' - // 当前查看的用户信息 const currentViewingUser = users.find((u) => parseInt(String(u?.id || ''), 10) === viewingUserId) - const effectiveUserId = isAdmin && viewingUserId ? viewingUserId : parseInt(String(me?.id || ''), 10) const handleSwitchUser = (userId) => { const nextUserId = parseInt(String(userId || ''), 10) if (Number.isFinite(nextUserId) && nextUserId > 0) { - setViewingUserId(nextUserId) - localStorage.setItem(VIEWING_USER_ID_KEY, String(nextUserId)) + dispatch(switchUser(nextUserId)) setShowUserPopover(false) - // 切换用户后,触发account变化事件,让AccountSelector重新加载该用户的账号列表 + // 触发自定义事件,保持向后兼容 window.dispatchEvent(new CustomEvent('ats:user:switched', { detail: { userId: nextUserId } })) } } @@ -131,7 +106,7 @@ function App() { ) } - if (!me) { + if (!currentUser) { return } @@ -142,7 +117,7 @@ function App() {

自动交易系统

- +
仪表板 @@ -176,7 +151,7 @@ function App() { gap: '4px' }} > - 👤 {currentViewingUser?.username || me?.username || '选择用户'} + 👤 {currentViewingUser?.username || currentUser?.username || '选择用户'} {currentViewingUser?.role === 'admin' ? '(管理员)' : ''} @@ -229,7 +204,7 @@ function App() {
) : ( - {me?.username ? me.username : 'user'} + {currentUser?.username ? currentUser.username : 'user'} )}