diff --git a/5.react/8.ecommerce/ecommerce-front/package.json b/5.react/8.ecommerce/ecommerce-front/package.json index 381722a..60da907 100644 --- a/5.react/8.ecommerce/ecommerce-front/package.json +++ b/5.react/8.ecommerce/ecommerce-front/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@ant-design/icons": "^5.3.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/5.react/8.ecommerce/ecommerce-front/pnpm-lock.yaml b/5.react/8.ecommerce/ecommerce-front/pnpm-lock.yaml index a33e855..61faef4 100644 --- a/5.react/8.ecommerce/ecommerce-front/pnpm-lock.yaml +++ b/5.react/8.ecommerce/ecommerce-front/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@ant-design/icons': + specifier: ^5.3.6 + version: 5.3.6(react-dom@18.2.0)(react@18.2.0) '@testing-library/jest-dom': specifier: ^5.17.0 version: 5.17.0 diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddCategory.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddCategory.tsx new file mode 100644 index 0000000..360fafe --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddCategory.tsx @@ -0,0 +1,55 @@ +import { Button, Form, Input, message } from "antd"; +import React, { useState } from "react"; +import Layout from "../core/Layout"; +import axios from "axios"; +import { API } from "../../config"; +import { isAuth } from "../../helpers/auth"; +import { Link } from "react-router-dom"; +import { Jwt } from "../../types/auth"; + +const AddCategory = () => { + const { user, token } = isAuth() as Jwt; + + async function addCategory(name: string) { + try { + let response = await axios.post<{ name: string }>( + `${API}/category/create/${user._id}`, + { + name: name, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + message.success(`[${response.data.name}] 分类添加成功`); + } catch (error: any) { + message.error(error.response.data.error); + } + } + + const onFinish = (value: { name: string }) => { + const { name } = value; + if (name) addCategory(name); + }; + return ( + +
+ + + + + + +
+ +
+ ); +}; + +export default AddCategory; diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddProduct.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddProduct.tsx new file mode 100644 index 0000000..8076eae --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AddProduct.tsx @@ -0,0 +1,113 @@ +import { Button, Form, Input, message, Select, Upload } from "antd"; +import { UploadOutlined } from "@ant-design/icons"; +import React, { useEffect, useState } from "react"; +import { useShallow } from "zustand/react/shallow"; +import Layout from "../core/Layout"; +// import { useDispatch, useSelector } from "react-redux" +// import { getCategory } from "../../store/actions/category.actions" +// import { AppState } from "../../store/reducers/index" +// import { CategoryState } from "../../store/reducers/category.reducer" +import { RcFile } from "antd/lib/upload"; +import axios from "axios"; +import { API } from "../../config"; +import { isAuth } from "../../helpers/auth"; +import { Jwt } from "../../types/auth"; +import { useCategoryStore } from "../../store/category"; + +const AddProduct = () => { + const [file, setFile] = useState(); + + const getCategories = useCategoryStore((state) => state.getCategories); + const categories = useCategoryStore((state) => state.result); + + useEffect(() => { + getCategories(); + }, [getCategories]); + + const { user, token } = isAuth() as Jwt; + + const onFinish = (product: any) => { + const formData = new FormData(); + for (let attr in product) { + formData.set(attr, product[attr]); + } + if (typeof file !== "undefined") { + formData.set("photo", file); + } + + axios + .post(`${API}/product/create/${user._id}`, formData, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then( + () => { + message.success("商品添加成功"); + }, + () => { + message.error("商品添加失败"); + } + ); + }; + + const addProductForm = () => { + const props = { + accept: "image/*", + beforeUpload: function (file: RcFile) { + setFile(file); + return false; + }, + }; + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); + }; + + return ( + + {addProductForm()} + + ); +}; + +export default AddProduct; diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminDashboard.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminDashboard.tsx new file mode 100644 index 0000000..267d927 --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminDashboard.tsx @@ -0,0 +1,69 @@ +import React, { useState } from "react"; +import Layout from "../core/Layout"; +import { Jwt } from "../../types/auth"; +import { isAuth } from "../../helpers/auth"; +import { Col, Descriptions, Menu, MenuProps, Row, Typography } from "antd"; +import { Link, useNavigate } from "react-router-dom"; +import { + ShoppingCartOutlined, + UserOutlined, + OrderedListOutlined, +} from "@ant-design/icons"; + +const items: MenuProps["items"] = [ + { + label: "添加分类", + key: "/create/category", + icon: , + }, + { + label: "添加产品", + key: "/create/product", + icon: , + }, + { + label: "订单列表", + key: "/admin/orders", + icon: , + }, +]; +const AdminDashboard = () => { + const { + user: { name, email }, + } = isAuth() as Jwt; + + const navigate = useNavigate(); + + const [current, setCurrent] = useState(""); + const onClick: MenuProps["onClick"] = (e) => { + setCurrent(e.key); + navigate(e.key); + }; + + const adminLinks = () => ( + <> + 管理员链接 + + + ); + + const adminInfo = () => ( + + {name} + {email} + 管理员 + + ); + + return ( + + + {adminLinks()} + + {adminInfo()} + + + ); +}; + +export default AdminDashboard; diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminRoute.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminRoute.tsx new file mode 100644 index 0000000..7c6c1c4 --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/AdminRoute.tsx @@ -0,0 +1,18 @@ +import React, { FC } from "react"; +import { Navigate } from "react-router-dom"; +import { isAuth } from "../../helpers/auth"; +import { Jwt } from "../../types/auth"; + +const AdminRoute: FC = ({ component: Component, ...rest }) => { + const auth = isAuth(); + if (auth) { + const { + user: { role }, + } = auth as Jwt; + if (role === 1) return ; + } + + return ; +}; + +export default AdminRoute; diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/Dashboard.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/Dashboard.tsx new file mode 100644 index 0000000..41ed0b8 --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/Dashboard.tsx @@ -0,0 +1,12 @@ +import React from "react" +import Layout from "../core/Layout" + +const Dashboard = () => { + return ( + + Dashboard + + ) +} + +export default Dashboard diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/admin/PrivateRoute.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/admin/PrivateRoute.tsx new file mode 100644 index 0000000..b540109 --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/components/admin/PrivateRoute.tsx @@ -0,0 +1,14 @@ +import React, { FC } from "react"; +import { Navigate } from "react-router-dom"; +import { isAuth } from "../../helpers/auth"; + +const PrivateRoute: FC = ({ component: Component, ...rest }) => { + const auth = isAuth(); + if (auth) { + return ; + } + + return ; +}; + +export default PrivateRoute; diff --git a/5.react/8.ecommerce/ecommerce-front/src/components/core/SignIn.tsx b/5.react/8.ecommerce/ecommerce-front/src/components/core/SignIn.tsx index c13c29c..6e7967c 100644 --- a/5.react/8.ecommerce/ecommerce-front/src/components/core/SignIn.tsx +++ b/5.react/8.ecommerce/ecommerce-front/src/components/core/SignIn.tsx @@ -1,15 +1,13 @@ -import React, { useEffect } from "react"; +import React from "react"; import Layout from "./Layout"; import { Button, Form, Input, Result } from "antd"; import { SigninPayload, useSigninStore } from "../../store/auth"; import { isAuth } from "../../helpers/auth"; import { Jwt } from "../../types/auth"; -import { redirect, useNavigate } from "react-router-dom"; +import { Navigate } from "react-router-dom"; const SignIn = () => { const signinState = useSigninStore(); - const navigate = useNavigate(); - const auth = isAuth(); // 2. 登录失败 显示错误信息 const showError = () => { if (signinState.loaded && !signinState.success) { @@ -23,21 +21,23 @@ const SignIn = () => { } }; - useEffect(() => { - // 3. 登录成功 根据角色跳转到对应的管理页面 + // 3. 登录成功 根据角色跳转到对应的管理页面 + const redirectToDashboard = () => { + const auth = isAuth(); if (auth) { const { user: { role }, } = auth as Jwt; + if (role === 0) { // 注册用户 - navigate("/user/dashboard"); + return ; } else { // 管理员 - navigate("/admin/dashboard"); + return ; } } - }); + }; const onFinish = async (value: SigninPayload) => { // 发送登录请求 await signinState.signin(value); @@ -58,9 +58,10 @@ const SignIn = () => { ); - return auth ? null : ( + return ( {showError()} + {redirectToDashboard()} {signinForm()} ); diff --git a/5.react/8.ecommerce/ecommerce-front/src/router/index.tsx b/5.react/8.ecommerce/ecommerce-front/src/router/index.tsx index 0f05c17..5b18f88 100644 --- a/5.react/8.ecommerce/ecommerce-front/src/router/index.tsx +++ b/5.react/8.ecommerce/ecommerce-front/src/router/index.tsx @@ -3,6 +3,12 @@ import Home from "../components/core/Home"; import Shop from "../components/core/Shop"; import SignIn from "../components/core/SignIn"; import SignUp from "../components/core/SignUp"; +import Dashboard from "../components/admin/Dashboard"; +import PrivateRoute from "../components/admin/PrivateRoute"; +import AdminRoute from "../components/admin/AdminRoute"; +import AdminDashboard from "../components/admin/AdminDashboard"; +import AddCategory from "../components/admin/AddCategory"; +import AddProduct from "../components/admin/AddProduct"; const router = createBrowserRouter([ { @@ -21,6 +27,22 @@ const router = createBrowserRouter([ path: "signup", element: , }, + { + path: "user/dashboard", + element: , + }, + { + path: "admin/dashboard", + element: , + }, + { + path: "/create/category", + element: , + }, + { + path: "/create/product", + element: , + } ]); export default router; diff --git a/5.react/8.ecommerce/ecommerce-front/src/store/category.ts b/5.react/8.ecommerce/ecommerce-front/src/store/category.ts new file mode 100644 index 0000000..65beb88 --- /dev/null +++ b/5.react/8.ecommerce/ecommerce-front/src/store/category.ts @@ -0,0 +1,37 @@ +import { create } from "zustand"; +import { API } from "../config"; +import axios from "axios"; + +export interface Category { + _id: string; + name: string; +} +export interface CategoryState { + loaded: boolean; + success: boolean; + result: Category[]; + getCategories: () => void; +} + +const useCategoryStore = create((set, get) => ({ + loaded: false, + success: false, + result: [], + getCategories: async () => { + set({ loaded: false, success: false, result: [] }); + let isSuccess = false; + // 请求接口 + + try { + let response = await axios.get(`${API}/categories`); + set({ loaded: true, success: true, result: response.data }); + + isSuccess = true; + } catch (error: any) { + isSuccess = false; + } + return isSuccess; + }, +})); + +export { useCategoryStore };