From 79768079bf122ae46fef030aa3adc203ca745036 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 19 Dec 2025 22:21:01 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-web):=20=E6=B7=BB=E5=8A=A0=20redux-p?= =?UTF-8?q?ersist=20=E5=AE=9E=E7=8E=B0=E7=99=BB=E5=BD=95=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 安装 redux-persist 依赖 - 配置 persistReducer 持久化 auth slice 到 localStorage - 添加 PersistGate 确保 rehydration 完成后再渲染 - 处理 REHYDRATE action 恢复认证状态 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/admin-web/package-lock.json | 10 +++++ frontend/admin-web/package.json | 1 + frontend/admin-web/src/app/providers.tsx | 7 +++- .../src/store/redux/slices/authSlice.ts | 14 +++++++ frontend/admin-web/src/store/redux/store.ts | 42 ++++++++++++++++--- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/frontend/admin-web/package-lock.json b/frontend/admin-web/package-lock.json index 04f75e2b..8493706a 100644 --- a/frontend/admin-web/package-lock.json +++ b/frontend/admin-web/package-lock.json @@ -18,6 +18,7 @@ "react-dom": "^18.3.1", "react-redux": "^9.2.0", "recharts": "^2.15.0", + "redux-persist": "^6.0.0", "xlsx": "^0.18.5", "zustand": "^5.0.3" }, @@ -5346,6 +5347,15 @@ "license": "MIT", "peer": true }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", diff --git a/frontend/admin-web/package.json b/frontend/admin-web/package.json index 396f7dbc..33a11502 100644 --- a/frontend/admin-web/package.json +++ b/frontend/admin-web/package.json @@ -24,6 +24,7 @@ "react-dom": "^18.3.1", "react-redux": "^9.2.0", "recharts": "^2.15.0", + "redux-persist": "^6.0.0", "xlsx": "^0.18.5", "zustand": "^5.0.3" }, diff --git a/frontend/admin-web/src/app/providers.tsx b/frontend/admin-web/src/app/providers.tsx index 1e802eca..e47f00d6 100644 --- a/frontend/admin-web/src/app/providers.tsx +++ b/frontend/admin-web/src/app/providers.tsx @@ -1,9 +1,10 @@ 'use client'; import { Provider } from 'react-redux'; +import { PersistGate } from 'redux-persist/integration/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useState } from 'react'; -import { store } from '@/store/redux/store'; +import { store, persistor } from '@/store/redux/store'; export function Providers({ children }: { children: React.ReactNode }) { const [queryClient] = useState( @@ -20,7 +21,9 @@ export function Providers({ children }: { children: React.ReactNode }) { return ( - {children} + + {children} + ); } diff --git a/frontend/admin-web/src/store/redux/slices/authSlice.ts b/frontend/admin-web/src/store/redux/slices/authSlice.ts index 4ab72395..8d4c615f 100644 --- a/frontend/admin-web/src/store/redux/slices/authSlice.ts +++ b/frontend/admin-web/src/store/redux/slices/authSlice.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { REHYDRATE } from 'redux-persist'; import type { User } from '@/types/user.types'; interface AuthState { @@ -51,6 +52,19 @@ const authSlice = createSlice({ } }, }, + extraReducers: (builder) => { + builder.addCase(REHYDRATE, (state, action: PayloadAction<{ auth?: AuthState } | undefined>) => { + if (action.payload?.auth) { + const rehydratedAuth = action.payload.auth; + state.user = rehydratedAuth.user; + state.token = rehydratedAuth.token; + state.refreshToken = rehydratedAuth.refreshToken; + state.permissions = rehydratedAuth.permissions; + state.isAuthenticated = rehydratedAuth.isAuthenticated; + } + state.loading = false; + }); + }, }); export const { setCredentials, logout, setLoading, updateUser } = authSlice.actions; diff --git a/frontend/admin-web/src/store/redux/store.ts b/frontend/admin-web/src/store/redux/store.ts index 9a6350dd..9659a673 100644 --- a/frontend/admin-web/src/store/redux/store.ts +++ b/frontend/admin-web/src/store/redux/store.ts @@ -1,16 +1,46 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, combineReducers } from '@reduxjs/toolkit'; +import { + persistStore, + persistReducer, + FLUSH, + REHYDRATE, + PAUSE, + PERSIST, + PURGE, + REGISTER, +} from 'redux-persist'; +import storage from 'redux-persist/lib/storage'; import authReducer from './slices/authSlice'; import settingsReducer from './slices/settingsSlice'; import notificationReducer from './slices/notificationSlice'; +const rootReducer = combineReducers({ + auth: authReducer, + settings: settingsReducer, + notification: notificationReducer, +}); + +const persistConfig = { + key: 'rwadurian-admin', + version: 1, + storage, + whitelist: ['auth'], // 只持久化 auth slice +}; + +const persistedReducer = persistReducer(persistConfig, rootReducer); + export const store = configureStore({ - reducer: { - auth: authReducer, - settings: settingsReducer, - notification: notificationReducer, - }, + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], + }, + }), devTools: process.env.NODE_ENV !== 'production', }); +export const persistor = persistStore(store); + export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;