feat(admin-web): 添加 redux-persist 实现登录状态持久化

- 安装 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-19 22:21:01 -08:00
parent dbba229c91
commit 79768079bf
5 changed files with 66 additions and 8 deletions

View File

@ -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",

View File

@ -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"
},

View File

@ -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 (
<Provider store={store}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<PersistGate loading={null} persistor={persistor}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</PersistGate>
</Provider>
);
}

View File

@ -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;

View File

@ -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<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;