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:
parent
dbba229c91
commit
79768079bf
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue