二十四、案例:权限管理
大约 13 分钟约 3883 字
(一)创建案例框架
src/index.js
1. import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Router>
<App />
</Router>,
);
src/App.js
2. import React from "react";
import { Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
const App = () => {
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Layout>
);
};
export default App;
src/components/Home.jsx
3. import React from "react";
const Home = () => {
return (
<div>
<h2>这是主页</h2>
<p>此页面无需权限</p>
</div>
);
};
export default Home;
src/components/Layout.jsx
4. import React from "react";
import MainMenu from "./MainMenu";
const Layout = (props) => {
return (
<div>
<MainMenu />
<hr />
{props.children}
</div>
);
};
export default Layout;
src/components/MainMenu.jsx
5. import React from "react";
import { Link } from "react-router-dom";
const MainMenu = () => {
return (
<div>
<ul>
<li>
<Link to={"/"}>首页</Link>
</li>
<li>
<Link to={"/profile"}>用户信息</Link>
</li>
</ul>
</div>
);
};
export default MainMenu;
src/components/Profile.jsx
6. import React from "react";
const Profile = () => {
return (
<div>
<h2>用户信息</h2>
<p>此页面只有在登录后才能查看</p>
</div>
);
};
export default Profile;
src/pages/HomePage.jsx
7. import React from "react";
import Home from "../components/Home";
const HomePage = () => {
return (
<div>
<Home />
</div>
);
};
export default HomePage;
src/pages/ProfilePage.jsx
8. import React from "react";
import Profile from "../components/Profile";
const ProfilePage = () => {
return (
<div>
<Profile />
</div>
);
};
export default ProfilePage;
(二)创建表单
src/App.js
1. import React from "react";
import { Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import AuthFormPage from "./pages/AuthFormPage";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
const App = () => {
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/auth-form" element={<AuthFormPage />} />
</Routes>
</Layout>
);
};
export default App;
src/pages/AuthFormPage.jsx
2. import React from "react";
import AuthForm from "../components/AuthForm";
const AuthFormPage = () => {
return (
<div>
<AuthForm />
</div>
);
};
export default AuthFormPage;
src/components/AuthForm.jsx
3. import React, { useState } from "react";
import { useRef } from "react";
const AuthForm = () => {
const [isLoginForm, setIsLoginForm] = useState(true);
const usernameInp = useRef();
const passwordInp = useRef();
const emailInp = useRef();
const submitHandler = (e) => {
e.preventDefault();
const username = usernameInp.current.value;
const password = passwordInp.current.value;
if (!isLoginForm) {
const email = emailInp.current.value;
console.log("注册", username, password, email);
} else {
console.log("登录", username, password);
}
};
return (
<div>
<h2>{isLoginForm ? "登录" : "注册"}</h2>
<form onSubmit={submitHandler}>
<div>
<input type="text" placeholder="用户名" ref={usernameInp} />
</div>
{!isLoginForm && (
<div>
<input type="email" placeholder="邮箱" ref={emailInp} />
</div>
)}
<div>
<input type="text" placeholder="密码" ref={passwordInp} />
</div>
<div>
<button>{isLoginForm ? "登录" : "注册"}</button>
<div>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsLoginForm((pre) => !pre);
}}
>
{isLoginForm ? "没有账号?点击注册" : "已有账号?点击登录"}
</a>
</div>
</div>
</form>
</div>
);
};
export default AuthForm;
(三)完成注册功能
src/index.js
1. import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
);
src/components/AuthForm.jsx
2. import React, { useState } from "react";
import { useRef } from "react";
import { useRegisterMutation } from "../store/api/authApi";
const AuthForm = () => {
const [isLoginForm, setIsLoginForm] = useState(true);
const [regFn, { error: regError }] = useRegisterMutation();
const usernameInp = useRef();
const passwordInp = useRef();
const emailInp = useRef();
const submitHandler = (e) => {
e.preventDefault();
const username = usernameInp.current.value;
const password = passwordInp.current.value;
if (isLoginForm) {
console.log("登录", username, password);
} else {
const email = emailInp.current.value;
// console.log("注册", username, password, email);
regFn({
username,
password,
email,
}).then((res) => {
// console.log(res);
if (!res.error) setIsLoginForm(true);
});
}
};
return (
<div>
<p style={{ color: "red" }}>{regError && regError.data.error.message}</p>
<h2>{isLoginForm ? "登录" : "注册"}</h2>
<form onSubmit={submitHandler}>
<div>
<input type="text" placeholder="用户名" ref={usernameInp} />
</div>
{!isLoginForm && (
<div>
<input type="email" placeholder="邮箱" ref={emailInp} />
</div>
)}
<div>
<input type="text" placeholder="密码" ref={passwordInp} />
</div>
<div>
<button>{isLoginForm ? "登录" : "注册"}</button>
<div>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsLoginForm((pre) => !pre);
}}
>
{isLoginForm ? "没有账号?点击注册" : "已有账号?点击登录"}
</a>
</div>
</div>
</form>
</div>
);
};
export default AuthForm;
src/store/index.js
3. import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/dist/query/react";
import { authApi } from "./api/authApi";
const store = configureStore({
reducer: {
[authApi.reducerPath]: authApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authApi.middleware),
});
setupListeners(store.dispatch);
export default store;
src/store/api/authApi.js
4. import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
export const authApi = createApi({
reducerPath: "authApi",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api/",
}),
endpoints: (build) => {
return {
register: build.mutation({
query: (user) => {
return {
url: "/auth/local/register",
method: "post",
body: user,
};
},
}),
};
},
});
export const { useRegisterMutation } = authApi;
(四)完成登录注册
src/components/AuthForm.jsx
1. import React, { useState } from "react";
import { useRef } from "react";
import { useLoginMutation, useRegisterMutation } from "../store/api/authApi";
const AuthForm = () => {
const [isLoginForm, setIsLoginForm] = useState(true);
const [regFn, { error: regError }] = useRegisterMutation();
const [logFn, { error: logError }] = useLoginMutation();
const usernameInp = useRef();
const passwordInp = useRef();
const emailInp = useRef();
const submitHandler = (e) => {
e.preventDefault();
const username = usernameInp.current.value;
const password = passwordInp.current.value;
if (isLoginForm) {
// console.log("登录", username, password);
logFn({
identifier: username,
password,
}).then((res) => {
// console.log(res);
if (!res.error) {
// 登录成功后需要向系统中添加一个标识,标记用户的登录状态
// 登录状态(布尔值,token【jwt】,用户信息)
// 跳转到首页
}
});
} else {
const email = emailInp.current.value;
// console.log("注册", username, password, email);
regFn({
username,
password,
email,
}).then((res) => {
// console.log(res);
if (!res.error) setIsLoginForm(true);
});
}
};
return (
<div>
<p style={{ color: "red" }}>{regError && regError.data.error.message}</p>
<p style={{ color: "red" }}>{logError && logError.data.error.message}</p>
<h2>{isLoginForm ? "登录" : "注册"}</h2>
<form onSubmit={submitHandler}>
<div>
<input type="text" placeholder="用户名或邮箱" ref={usernameInp} />
</div>
{!isLoginForm && (
<div>
<input type="email" placeholder="邮箱" ref={emailInp} />
</div>
)}
<div>
<input type="text" placeholder="密码" ref={passwordInp} />
</div>
<div>
<button>{isLoginForm ? "登录" : "注册"}</button>
<div>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsLoginForm((pre) => !pre);
}}
>
{isLoginForm ? "没有账号?点击注册" : "已有账号?点击登录"}
</a>
</div>
</div>
</form>
</div>
);
};
export default AuthForm;
src/store/api/authApi.js
2. import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
export const authApi = createApi({
reducerPath: "authApi",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api/",
}),
endpoints: (build) => {
return {
register: build.mutation({
query: (user) => {
return {
url: "/auth/local/register",
method: "post",
body: user,
};
},
}),
login: build.mutation({
query: (user) => {
return {
url: "/auth/local",
method: "post",
body: user,
};
},
}),
};
},
});
export const { useRegisterMutation, useLoginMutation } = authApi;
(五)使用 redux 存储登录状态
src/components/AuthForm.jsx
1. import React, { useState } from "react";
import { useRef } from "react";
import { useLoginMutation, useRegisterMutation } from "../store/api/authApi";
import { useDispatch } from "react-redux";
import { login } from "../store/reducer/authSlice";
import { useNavigate } from "react-router-dom";
const AuthForm = () => {
const [isLoginForm, setIsLoginForm] = useState(true);
// 引入登录注册的API
const [regFn, { error: regError }] = useRegisterMutation();
const [logFn, { error: logError }] = useLoginMutation();
// 获取dispatch派发器
const dispatch = useDispatch();
// 获取Navigate
const nav = useNavigate();
const usernameInp = useRef();
const passwordInp = useRef();
const emailInp = useRef();
const submitHandler = (e) => {
e.preventDefault();
const username = usernameInp.current.value;
const password = passwordInp.current.value;
if (isLoginForm) {
// console.log("登录", username, password);
logFn({
identifier: username,
password,
}).then((res) => {
// console.log(res);
if (!res.error) {
// 登录成功后需要向系统中添加一个标识,标记用户的登录状态
// 登录状态(布尔值,token【jwt】,用户信息)
dispatch(
login({
token: res.data.jwt,
user: res.data.user,
}),
);
// 跳转到首页
nav("/", {
replace: true,
});
}
});
} else {
const email = emailInp.current.value;
// console.log("注册", username, password, email);
regFn({
username,
password,
email,
}).then((res) => {
// console.log(res);
if (!res.error) setIsLoginForm(true);
});
}
};
return (
<div>
<p style={{ color: "red" }}>{regError && regError.data.error.message}</p>
<p style={{ color: "red" }}>{logError && logError.data.error.message}</p>
<h2>{isLoginForm ? "登录" : "注册"}</h2>
<form onSubmit={submitHandler}>
<div>
<input type="text" placeholder="用户名或邮箱" ref={usernameInp} />
</div>
{!isLoginForm && (
<div>
<input type="email" placeholder="邮箱" ref={emailInp} />
</div>
)}
<div>
<input type="text" placeholder="密码" ref={passwordInp} />
</div>
<div>
<button>{isLoginForm ? "登录" : "注册"}</button>
<div>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsLoginForm((pre) => !pre);
}}
>
{isLoginForm ? "没有账号?点击注册" : "已有账号?点击登录"}
</a>
</div>
</div>
</form>
</div>
);
};
export default AuthForm;
src/components/MainMenu.jsx
2. import React from "react";
import { Link } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { logout } from "../store/reducer/authSlice";
const MainMenu = () => {
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
return (
<div>
<ul>
<li>
<Link to={"/"}>首页</Link>
</li>
{!auth.isLogged && (
<li>
<Link to={"/auth-form"}>登录/注册</Link>
</li>
)}
{auth.isLogged && (
<>
<li>
<Link to={"/profile"}>{auth.user.username}</Link>
</li>
<li>
<Link to={"/"} onClick={() => dispatch(logout())}>
登出
</Link>
</li>
</>
)}
</ul>
</div>
);
};
export default MainMenu;
src/store/index.js
3. import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/dist/query/react";
import { authApi } from "./api/authApi";
import { authSlice } from "./reducer/authSlice";
const store = configureStore({
reducer: {
[authApi.reducerPath]: authApi.reducer,
auth: authSlice.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authApi.middleware),
});
setupListeners(store.dispatch);
export default store;
src/store/reducer/authSlice.js
4. import { createSlice } from "@reduxjs/toolkit";
export const authSlice = createSlice({
name: "auth",
initialState: {
isLogged: false,
token: null,
user: null,
},
reducers: {
login(state, action) {
state.isLogged = true;
state.token = action.payload.token;
state.user = action.payload.user;
},
logout(state, action) {
state.isLogged = false;
state.token = null;
state.user = null;
},
},
});
export const { login, logout } = authSlice.actions;
(六)解决登录前后页面跳转问题
src/index.js
1. import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
);
src/App.js
2. import React from "react";
import { Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import NeedAuth from "./components/NeedAuth";
import AuthFormPage from "./pages/AuthFormPage";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
const App = () => {
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/profile"
element={
<NeedAuth>
<ProfilePage />
</NeedAuth>
}
/>
<Route path="/auth-form" element={<AuthFormPage />} />
</Routes>
</Layout>
);
};
export default App;
src/components/AuthForm.jsx
3. import React, { useState } from "react";
import { useRef } from "react";
import { useLoginMutation, useRegisterMutation } from "../store/api/authApi";
import { useDispatch } from "react-redux";
import { login } from "../store/reducer/authSlice";
import { useLocation, useNavigate } from "react-router-dom";
const AuthForm = () => {
const [isLoginForm, setIsLoginForm] = useState(true);
// 引入登录注册的API
const [regFn, { error: regError }] = useRegisterMutation();
const [logFn, { error: logError }] = useLoginMutation();
// 获取dispatch派发器
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const fromPage = location.state?.preLocation?.pathname || "/";
const usernameInp = useRef();
const passwordInp = useRef();
const emailInp = useRef();
const submitHandler = (e) => {
e.preventDefault();
const username = usernameInp.current.value;
const password = passwordInp.current.value;
if (isLoginForm) {
// console.log("登录", username, password);
logFn({
identifier: username,
password,
}).then((res) => {
// console.log(res);
if (!res.error) {
// 登录成功后需要向系统中添加一个标识,标记用户的登录状态
// 登录状态(布尔值,token【jwt】,用户信息)
dispatch(
login({
token: res.data.jwt,
user: res.data.user,
}),
);
// 跳转到之前访问过的页面
navigate(fromPage, {
replace: true,
});
}
});
} else {
const email = emailInp.current.value;
// console.log("注册", username, password, email);
regFn({
username,
password,
email,
}).then((res) => {
// console.log(res);
if (!res.error) setIsLoginForm(true);
});
}
};
return (
<div>
<p style={{ color: "red" }}>{regError && regError.data.error.message}</p>
<p style={{ color: "red" }}>{logError && logError.data.error.message}</p>
<h2>{isLoginForm ? "登录" : "注册"}</h2>
<form onSubmit={submitHandler}>
<div>
<input type="text" placeholder="用户名或邮箱" ref={usernameInp} />
</div>
{!isLoginForm && (
<div>
<input type="email" placeholder="邮箱" ref={emailInp} />
</div>
)}
<div>
<input type="text" placeholder="密码" ref={passwordInp} />
</div>
<div>
<button>{isLoginForm ? "登录" : "注册"}</button>
<div>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsLoginForm((pre) => !pre);
}}
>
{isLoginForm ? "没有账号?点击注册" : "已有账号?点击登录"}
</a>
</div>
</div>
</form>
</div>
);
};
export default AuthForm;
src/components/NeedAuth.jsx
4. import React from "react";
import { useSelector } from "react-redux";
import { Navigate, useLocation } from "react-router-dom";
const NeedAuth = (props) => {
const auth = useSelector((state) => state.auth);
const location = useLocation();
return auth.isLogged ? props.children : <Navigate state={{ preLocation: location }} to="/auth-form" replace />;
};
export default NeedAuth;
src/reducer/authSlice.js
5. import { createSlice } from "@reduxjs/toolkit";
export const authSlice = createSlice({
name: "auth",
initialState: {
isLogged: false,
token: null,
user: null,
},
reducers: {
login(state, action) {
state.isLogged = true;
state.token = action.payload.token;
state.user = action.payload.user;
},
logout(state, action) {
state.isLogged = false;
state.token = null;
state.user = null;
},
},
});
export const { login, logout } = authSlice.actions;
(七)使用本地存储实现登录持久化
src/reducer/authSlice.js
1. import { createSlice } from "@reduxjs/toolkit";
export const authSlice = createSlice({
name: "auth",
initialState: () => {
const token = localStorage.getItem("token");
if (!token)
return {
isLogged: false,
token: null,
user: null,
};
return {
isLogged: true,
token,
user: JSON.parse(localStorage.getItem("user")),
};
},
reducers: {
login(state, action) {
state.isLogged = true;
state.token = action.payload.token;
state.user = action.payload.user;
// 数据同时存储到本地
localStorage.setItem("token", state.token);
localStorage.setItem("user", JSON.stringify(state.user));
},
logout(state, action) {
state.isLogged = false;
state.token = null;
state.user = null;
// 情况本地存储
localStorage.removeItem("token");
localStorage.removeItem("user");
},
},
});
export const { login, logout } = authSlice.actions;
(八)token 失效自动登出
src/App.js
1. import React from "react";
import { Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import NeedAuth from "./components/NeedAuth";
import AuthFormPage from "./pages/AuthFormPage";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
import useAutoLogout from "./hooks/useAutoLogout";
const App = () => {
// token失效后自动登出
useAutoLogout();
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/profile"
element={
<NeedAuth>
<ProfilePage />
</NeedAuth>
}
/>
<Route path="/auth-form" element={<AuthFormPage />} />
</Routes>
</Layout>
);
};
export default App;
src/reducer/authSlice.js
2. import { createSlice } from "@reduxjs/toolkit";
export const authSlice = createSlice({
name: "auth",
initialState: () => {
const token = localStorage.getItem("token");
if (!token)
return {
isLogged: false,
token: null,
user: null,
expirationTime: 0, // token失效时间
};
return {
isLogged: true,
token,
user: JSON.parse(localStorage.getItem("user")),
expirationTime: +localStorage.getItem("expirationTime"),
};
},
reducers: {
login(state, action) {
state.isLogged = true;
state.token = action.payload.token;
state.user = action.payload.user;
// 获取当前时间戳
const currentTime = Date.now();
// 设置登录有效时间
const timeout = 1000 * 60 * 60 * 24 * 7; // 1 week
// const timeout = 1000 * 10; // 1 min
// 设置失效日期
state.expirationTime = currentTime + timeout;
// 数据同时存储到本地
localStorage.setItem("token", state.token);
localStorage.setItem("user", JSON.stringify(state.user));
localStorage.setItem("expirationTime", state.expirationTime + "");
},
logout(state, action) {
state.isLogged = false;
state.token = null;
state.user = null;
// 情况本地存储
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("expirationTime");
},
},
});
export const { login, logout } = authSlice.actions;
src/hooks/useAutoLogout.js
3. import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { logout } from "../store/reducer/authSlice";
const useAutoLogout = () => {
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
const timeout = auth.expirationTime - Date.now();
if (timeout < 1000 * 60) {
dispatch(logout());
return;
}
const timer = setTimeout(() => {
dispatch(logout());
}, timeout);
return () => {
clearTimeout(timer);
};
}, [auth, dispatch]);
};
export default useAutoLogout;
(九)引入学生列表
src/App.js
1. import React from "react";
import { Route, Routes } from "react-router-dom";
import useAutoLogout from "./hooks/useAutoLogout";
import Layout from "./components/Layout";
import NeedAuth from "./components/NeedAuth";
import AuthFormPage from "./pages/AuthFormPage";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
import StudentPage from "./pages/StudentPage";
const App = () => {
// token失效后自动登出
useAutoLogout();
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/profile"
element={
<NeedAuth>
<ProfilePage />
</NeedAuth>
}
/>
<Route path="/auth-form" element={<AuthFormPage />} />
<Route
path="/student"
element={
<NeedAuth>
<StudentPage />
</NeedAuth>
}
/>
</Routes>
</Layout>
);
};
export default App;
src/store/index.js
2. import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/dist/query/react";
import { authApi } from "./api/authApi";
import { studentApi } from "./api/studentApi";
import { authSlice } from "./reducer/authSlice";
const store = configureStore({
reducer: {
[authApi.reducerPath]: authApi.reducer,
auth: authSlice.reducer,
[studentApi.reducerPath]: studentApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authApi.middleware, studentApi.middleware),
});
setupListeners(store.dispatch);
export default store;
src/store/api/studentApi.js
3. import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
/*
https://www.lilichao.com/index.php/2022/05/27/rtk-query/
createApi() 创建Api对象
RTK Query的所有功能都需要通过该对象来进行
需要一个配置对象作为参数
*/
export const studentApi = createApi({
reducerPath: "studentApi", // Api的标识,不能喝其他Api或reducer重复
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api/",
}), // 指定查询的基础信息,发送请求使用的工具
tagTypes: ["student"], // 指定Api中的标签类型
endpoints: (build) => {
// build是请求的构建器,通过build来设置请求的相关信息
return {
getStudents: build.query({
query: () => {
return "students";
}, // 指定请求子路径,和baseUrl拼在一起
transformResponse: (baseQueryReturnValue) => {
return baseQueryReturnValue.data;
}, // 转换响应数据的格式
// providesTags: ["student"], // 为当前方法打标签,当标签失效时会重新执行当前方法
providesTags: [
{
type: "student",
id: "LIST",
},
],
}), // 查询
getStudentsById: build.query({
query: (id) => {
return `students/${id}`;
},
transformResponse: (baseQueryReturnValue) => {
return baseQueryReturnValue.data;
},
keepUnusedDataFor: 60, //设置数据缓存的时间,单位秒,默认60
providesTags: (result, error, id) => [
{
type: "student",
id,
},
], // 只有当前id变化时才打标签
}),
deleteStudent: build.mutation({
query: (id) => {
// 如果发送的不是get请求,需要返回一个对象来设置请求信息
return {
url: `students/${id}`,
method: "delete",
};
},
invalidatesTags: ["student"], // 令标签失效
}), // 修改
addStudents: build.mutation({
query: (stu) => {
return {
url: "students",
method: "post",
body: {
data: stu,
},
};
},
// invalidatesTags: ["student"], // 令标签失效【全部该标签都失效】
invalidatesTags: [
{
type: "student",
id: "LIST",
},
], // 只有id为LIST的student标签失效,重新执行带有id为LIST的student标签的方法
}),
updateStudent: build.mutation({
query: (stu) => {
return {
url: `students/${stu.id}`,
method: "put",
body: {
data: stu.attributes,
},
};
},
invalidatesTags: (result, error, stu) => [
{
type: "student",
id: stu.id,
},
{
type: "student",
id: "LIST",
},
], // 令标签失效
}),
};
}, // 指定Api中的各种功能,是一个方法,需要一个对象作为返回值
});
/*
Api对象创建后,会根据各种方法自动生成对应的钩子函数
钩子函数可用于向服务器发送请求
命名规则:getStudents --> useGetStudentsQuery()
*/
export const { useGetStudentsQuery, useGetStudentsByIdQuery, useDeleteStudentMutation, useAddStudentsMutation, useUpdateStudentMutation } =
studentApi;
src/pages/StudentPage.js
4. import React from "react";
import StudentList from "../components/Student/StudentList";
const StudentPage = () => {
return (
<div>
<StudentList />
</div>
);
};
export default StudentPage;
src/components/MainMenu.js
5. import React from "react";
import { Link } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { logout } from "../store/reducer/authSlice";
const MainMenu = () => {
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
return (
<div>
<ul>
<li>
<Link to={"/"}>首页</Link>
</li>
{!auth.isLogged && (
<li>
<Link to={"/auth-form"}>登录/注册</Link>
</li>
)}
{auth.isLogged && (
<>
<li>
<Link to={"/profile"}>{auth.user.username}</Link>
</li>
<li>
<Link to={"/student"}>学生</Link>
</li>
<li>
<Link to={"/"} onClick={() => dispatch(logout())}>
登出
</Link>
</li>
</>
)}
</ul>
</div>
);
};
export default MainMenu;
src/components/Student/Student.jsx
6. import React, { useState } from "react";
import { useDeleteStudentMutation } from "../../store/api/studentApi";
import StudentForm from "./StudentForm";
const Student = ({
stu: {
id,
attributes: { name, age, gender, address },
},
stu,
}) => {
const [editing, setEditing] = useState(false);
const [delStudent, { isSuccess }] = useDeleteStudentMutation();
const deleteStudent = () => {
delStudent(id);
};
const cancelEdit = () => {
setEditing(false);
};
return (
<>
{!editing && !isSuccess && (
<tr>
<td>{name}</td>
<td>{gender}</td>
<td>{age}</td>
<td>{address}</td>
<td>
<button onClick={deleteStudent}>删除</button>
<button onClick={() => setEditing(true)}>修改</button>
</td>
</tr>
)}
{isSuccess && (
<tr>
<td colSpan={5}>数据已删除!</td>
</tr>
)}
{editing && <StudentForm stu={stu} stuId={id} onCancelEdit={cancelEdit} />}
</>
);
};
export default Student;
src/components/Student/StudentForm.jsx
7. import React, { useEffect, useState } from "react";
import { useAddStudentsMutation, useGetStudentsByIdQuery, useUpdateStudentMutation } from "../../store/api/studentApi";
import "./StudentForm.css";
const StudentForm = (props) => {
// StudentForm一加载,自动获取最新的学生数据
const { data: students, isSuccess: isGetSuccess } = useGetStudentsByIdQuery(props.stuId, {
skip: !props.stuId,
refetchOnMountOrArgChange: true,
});
useEffect(() => {
if (isGetSuccess) setInputData(students.attributes);
}, [isGetSuccess]);
const [inputData, setInputData] = useState({
name: "",
gender: "男",
age: 0,
address: "",
});
const { name, gender, age, address } = inputData;
const nameChange = (e) => {
setInputData((pre) => ({ ...pre, name: e.target.value }));
};
const genderChange = (e) => {
setInputData((pre) => ({ ...pre, gender: e.target.value }));
};
const ageChange = (e) => {
setInputData((pre) => ({ ...pre, age: +e.target.value }));
};
const addressChange = (e) => {
setInputData((pre) => ({ ...pre, address: e.target.value }));
};
const [addStudent, { isSuccess: isAddSuccess }] = useAddStudentsMutation();
const onSubmitAdd = () => {
addStudent(inputData);
setInputData({
name: "",
gender: "男",
age: 0,
address: "",
});
};
const [editStudent, { isSuccess: isEditSuccess }] = useUpdateStudentMutation();
const onSubmitEdit = () => {
editStudent({
id: props.stuId,
attributes: inputData,
});
props.onCancelEdit();
};
return (
<>
<tr className="student-form">
<td>
<input type="text" value={name} onChange={nameChange} />
</td>
<td>
<select value={gender} onChange={genderChange}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</td>
<td>
<input type="text" value={age} onChange={ageChange} />
</td>
<td>
<input type="text" value={address} onChange={addressChange} />
</td>
<td>
{props.stu && (
<>
<button onClick={() => props.onCancelEdit()}>取消</button>
<button onClick={onSubmitEdit}>确认</button>
</>
)}
{!props.stu && <button onClick={onSubmitAdd}>添加</button>}
</td>
</tr>
</>
);
};
export default StudentForm;
src/components/Student/StudentForm.css
8. .student-form input {
width: 80px;
}
src/components/Student/StudentList.jsx
9. import React from "react";
import Student from "./Student";
import StudentForm from "./StudentForm";
import { useGetStudentsQuery } from "../../store/api/studentApi";
import "./StudentList.css";
const StudentList = () => {
// 调用Api钩子函数查询数据
const { data: stu, isSuccess, isLoading } = useGetStudentsQuery();
return (
<div className="box">
<table>
<caption>
<h2>学生列表</h2>
</caption>
<thead>
<tr>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>地址</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{isLoading && (
<tr>
<td>数据加载中...</td>
</tr>
)}
{isSuccess &&
stu.map((stu) => {
return <Student key={stu.id} stu={stu} />;
})}
</tbody>
<tfoot>
<StudentForm />
</tfoot>
</table>
</div>
);
};
export default StudentList;
src/components/Student/StudentList.css
10. .box {
margin: 10px auto;
background-color: lightcoral;
width: 90%;
padding: 0 20px 20px;
}
table {
border-collapse: collapse;
margin: 0 auto;
width: 100%;
}
tr {
border: 1px solid #eee;
}
td {
color: #fff;
border: 1px solid #eee;
text-align: center;
width: 200px;
}
button {
cursor: pointer;
}
(十)添加服务器验证 token
src/store/api/studentApi.js
1. import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
export const studentApi = createApi({
reducerPath: "studentApi",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api/",
prepareHeaders: (headers, { getState }) => {
// 获取用户token
const token = getState().auth.token;
if (token) headers.set("Authorization", `Bearer ${token}`);
return headers;
}, // 用于统一设置请求头
}),
tagTypes: ["student"],
endpoints: (build) => {
return {
getStudents: build.query({
query: () => {
return "students";
},
transformResponse: (baseQueryReturnValue) => {
return baseQueryReturnValue.data;
},
providesTags: [
{
type: "student",
id: "LIST",
},
],
}),
getStudentsById: build.query({
query: (id) => {
return `students/${id}`;
},
transformResponse: (baseQueryReturnValue) => {
return baseQueryReturnValue.data;
},
keepUnusedDataFor: 60,
providesTags: (result, error, id) => [
{
type: "student",
id,
},
],
}),
deleteStudent: build.mutation({
query: (id) => {
return {
url: `students/${id}`,
method: "delete",
};
},
invalidatesTags: ["student"],
}),
addStudents: build.mutation({
query: (stu) => {
return {
url: "students",
method: "post",
body: {
data: stu,
},
};
},
invalidatesTags: [
{
type: "student",
id: "LIST",
},
],
}),
updateStudent: build.mutation({
query: (stu) => {
return {
url: `students/${stu.id}`,
method: "put",
body: {
data: stu.attributes,
},
};
},
invalidatesTags: (result, error, stu) => [
{
type: "student",
id: stu.id,
},
{
type: "student",
id: "LIST",
},
],
}),
};
},
});
export const { useGetStudentsQuery, useGetStudentsByIdQuery, useDeleteStudentMutation, useAddStudentsMutation, useUpdateStudentMutation } =
studentApi;