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-dom": "^18.3.1",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.0",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
|
@ -5346,6 +5347,15 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"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": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.0",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { store } from '@/store/redux/store';
|
import { store, persistor } from '@/store/redux/store';
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
|
|
@ -20,7 +21,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { REHYDRATE } from 'redux-persist';
|
||||||
import type { User } from '@/types/user.types';
|
import type { User } from '@/types/user.types';
|
||||||
|
|
||||||
interface AuthState {
|
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;
|
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 authReducer from './slices/authSlice';
|
||||||
import settingsReducer from './slices/settingsSlice';
|
import settingsReducer from './slices/settingsSlice';
|
||||||
import notificationReducer from './slices/notificationSlice';
|
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({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: persistedReducer,
|
||||||
auth: authReducer,
|
middleware: (getDefaultMiddleware) =>
|
||||||
settings: settingsReducer,
|
getDefaultMiddleware({
|
||||||
notification: notificationReducer,
|
serializableCheck: {
|
||||||
},
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||||
|
},
|
||||||
|
}),
|
||||||
devTools: process.env.NODE_ENV !== 'production',
|
devTools: process.env.NODE_ENV !== 'production',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const persistor = persistStore(store);
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue