feat: 管理后台

This commit is contained in:
mozzie 2023-03-06 16:41:00 +08:00
parent 3c1e8c2247
commit 031be2925f
16 changed files with 854 additions and 437 deletions

View File

@ -14,7 +14,8 @@
"less": "^4.1.3", "less": "^4.1.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "6.8.0" "react-router-dom": "6.8.0",
"@ant-design/icons": "5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.27", "@types/react": "^18.0.27",

View File

@ -1,52 +1,15 @@
import { Navigate, Route, Routes } from "react-router-dom";
import "./assets/less/common.less"; import "./assets/less/common.less";
import { Route, Routes, useNavigate } from "react-router-dom"; import Layout from "./layout";
import User from "./view/User"; import Login from "./view/Login";
import Home from "./view/Home";
import { Guard } from "./router/Guard";
function App() { function App() {
const navigate = useNavigate();
const routerList = [
{
path: "/",
element: <Home />,
name: "首页",
},
{
path: "user",
element: <User />,
name: "用户",
},
];
return ( return (
<>
<header>header</header>
<div>
<aside>
<ul>
{routerList.map((router) => (
<li key={router.path}>
<a onClick={() => navigate(router.path)}>{router.name}</a>
</li>
))}
</ul>
</aside>
<main>
<Routes> <Routes>
{routerList.map((router) => ( <Route key={"login"} path={"/login"} element={<Login />} />
<Route <Route key={"dash"} path={"/*"} element={<Layout />} />
key={router.path}
path={router.path}
element={<Guard>{router.element}</Guard>}
/>
))}
<Route path="*" element={<span>404</span>} /> <Route path="*" element={<span>404</span>} />
</Routes> </Routes>
</main>
</div>
</>
); );
} }

Binary file not shown.

View File

@ -3,6 +3,7 @@ html {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
height: 100%;
} }
* { * {
@ -14,3 +15,12 @@ ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#root {
height: 100%;
}
@font-face {
font-family: "bs";
src: url("./backset.woff");
}

View File

@ -0,0 +1,17 @@
.logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
display: flex;
align-items: center;
color: #fff;
svg {
width: 22px;
}
span {
padding-left: 10px;
font-family: "bs";
font-size: 16px;
}
}

View File

@ -0,0 +1,112 @@
import React, { Suspense } from "react";
import "./index.less";
import { VideoCameraAddOutlined, UserSwitchOutlined } from "@ant-design/icons";
import { Layout, Menu, MenuProps, Spin, theme } from "antd";
import { Route, Routes, useNavigate } from "react-router-dom";
import { Guard } from "../router/Guard";
import { navMenuList, navRoutes, sideMenuRoutes } from "../router";
const navMenus: MenuProps["items"] = navMenuList;
const sideMenus: MenuProps["items"] = [
{
key: "course",
icon: <VideoCameraAddOutlined />,
label: "课程",
children: [
{
key: "create",
label: "创建",
},
],
},
{
key: "user",
icon: <UserSwitchOutlined />,
label: "用户",
},
];
const { Header, Sider, Content } = Layout;
const Index: React.FC = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClickNavMenuItem = (p: any) => {
navigate(p.key);
};
const onClickSideMenuItem = (p: any) => {
const path = p.keyPath.reverse().join("/");
navigate(path.startsWith("/") ? path : "/" + path);
};
return (
<Layout style={{ height: "100%" }}>
<Header className="header">
<div className="logo">
<svg
fill="currentColor"
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M158.165333 499.498667A42.496 42.496 0 0 0 170.666667 469.333333V256a42.666667 42.666667 0 0 1 42.666666-42.666667 42.666667 42.666667 0 0 0 0-85.333333C142.762667 128 85.333333 185.429333 85.333333 256v195.669333l-30.165333 30.165334a42.666667 42.666667 0 0 0 0 60.330666l30.165333 30.165334V768c0 70.570667 57.429333 128 128 128a42.666667 42.666667 0 0 0 0-85.333333 42.666667 42.666667 0 0 1-42.666666-42.666667v-213.333333a42.496 42.496 0 0 0-12.501334-30.165334L145.664 512l12.501333-12.501333zM978.090667 495.658667a42.709333 42.709333 0 0 0-9.258667-13.824L938.666667 451.669333V256c0-70.570667-57.429333-128-128-128a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 1 42.666666 42.666667v213.333333a42.581333 42.581333 0 0 0 12.501334 30.165334l12.501333 12.501333-12.501333 12.501333A42.496 42.496 0 0 0 853.333333 554.666667v213.333333a42.666667 42.666667 0 0 1-42.666666 42.666667 42.666667 42.666667 0 1 0 0 85.333333c70.570667 0 128-57.429333 128-128v-195.669333l30.165333-30.165334a42.709333 42.709333 0 0 0 9.258667-46.506666zM669.738667 225.450667a42.752 42.752 0 0 0-69.546667 14.762666l-255.829333 512a42.624 42.624 0 0 0 23.893333 55.424 42.922667 42.922667 0 0 0 55.552-23.765333l255.786667-512a42.538667 42.538667 0 0 0-9.813334-46.421333z"></path>
</svg>
<span>Backset</span>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["/"]}
items={navMenus}
onClick={onClickNavMenuItem}
/>
</Header>
<Layout>
<Sider width={200} style={{ background: colorBgContainer }}>
<Menu
mode="inline"
defaultSelectedKeys={["1"]}
defaultOpenKeys={["course"]}
style={{ height: "100%", borderRight: 0 }}
items={sideMenus}
onClick={onClickSideMenuItem}
/>
</Sider>
<Layout className="site-layout">
<Content
style={{
margin: "24px 16px",
padding: 24,
minHeight: 280,
background: colorBgContainer,
}}
>
<Routes>
{[...navRoutes, ...sideMenuRoutes].map((router) => (
<Route
key={router.path}
path={router.path}
element={
<Suspense fallback={<Spin />}>
<Guard>{<router.element />}</Guard>
</Suspense>
}
/>
))}
<Route path="*" element={<span>404</span>} />
</Routes>
</Content>
</Layout>
</Layout>
</Layout>
);
};
export default Index;

View File

@ -0,0 +1,41 @@
import { lazy } from "react";
export interface IRouteMenuItem {
path: string;
name: string;
}
interface IRoute extends IRouteMenuItem {
element: React.LazyExoticComponent<() => JSX.Element>;
}
export const navRoutes: IRoute[] = [
{
path: "/",
element: lazy(() => import("../view/Overview")),
name: "总览",
},
{
path: "/payment",
element: lazy(() => import("../view/Payment")),
name: "支付",
},
];
export const sideMenuRoutes: IRoute[] = [
{
path: "/course/create",
element: lazy(() => import("../view/Course/Create")),
name: "课程创建",
},
{
path: "/user",
element: lazy(() => import("../view/User")),
name: "用户",
},
];
export const navMenuList = navRoutes.map((route) => {
const { path: key, name: label } = route;
return { key, label };
});

View File

@ -1,3 +0,0 @@
body {
background: grey;
}

View File

@ -0,0 +1,5 @@
const CourseCreate = () => {
return <div></div>;
};
export default CourseCreate;

View File

@ -0,0 +1,134 @@
#root {
background-image: linear-gradient(
to right,
#e95659,
#e15084,
#c55aaa,
#976bc4,
#5678ce
);
height: 100vh;
}
.login {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.frosted {
display: flex;
align-items: center;
height: 290px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 1);
.bird-one {
width: 250px;
height: 290px;
position: relative;
background-color: #b4dfff;
overflow: hidden;
.beak {
width: 90px;
height: 90px;
position: absolute;
top: 65px;
left: 85px;
transform: rotate(55deg);
background-color: #ff4658;
}
.neck {
width: 200px;
height: 260px;
border-top-left-radius: 15% 10%;
position: absolute;
bottom: 0;
right: -30px;
transform: skewX(-20deg);
background: linear-gradient(to top, #4c77d3 86%, #ff4658 16%);
overflow: hidden;
}
.neck::before {
content: "";
width: 200px;
height: 20px;
border-top-left-radius: 18% 80%;
position: absolute;
top: 17px;
background: linear-gradient(to top, #102f97, #194fe6);
box-shadow: 0px -1px 0px 0.8px #417ef8, 0px -5px 8px rgba(0, 0, 0, 0.3);
}
.cir-blue {
width: 200px;
height: 200px;
border: 20px solid #2d4899;
border-top-left-radius: 65% 50%;
position: absolute;
top: 120px;
left: -20px;
background-color: #ff4658;
}
.feathers {
width: 200px;
height: 200px;
border-radius: 50%;
position: absolute;
top: 170px;
left: 135px;
background: linear-gradient(to left, #ccedff 75%, #4dabfd);
box-shadow: -6px 5px 8px rgba(0, 0, 0, 0.5);
}
.feather {
width: 30px;
height: 150px;
position: absolute;
border-top-left-radius: 100% 50%;
border-bottom-left-radius: 100% 50%;
}
.fe1 {
top: 12px;
left: 25px;
background-color: #84c7fc;
box-shadow: -8px 12px 8px rgba(0, 0, 0, 0.5), -1.5px 0px 0 #76c1ff;
}
.fe2 {
top: 5px;
left: 45px;
background-color: #99d7fe;
box-shadow: -5px 8px 8px rgba(0, 0, 0, 0.5), -1.5px 0 0 #52bafa;
}
.fe3 {
left: 65px;
background-color: #c4e9fb;
box-shadow: -5px 5px 8px rgba(0, 0, 0, 0.5), -0.8px 0 0 #60a2fd;
}
.eye {
width: 55px;
height: 55px;
border-radius: 50%;
position: absolute;
top: 80px;
right: 75px;
background-color: white;
}
.eye::before {
content: "";
width: 16px;
height: 25px;
border-radius: 50%;
position: absolute;
top: 15px;
left: 20px;
background: linear-gradient(to left, #2f4ba1 50%, #4b71d6 50%);
box-shadow: -0.6px 0px 0px 0.7px #364e9e, -4px 0px 4px #737e92,
2px 0px 3px #96a6bd;
}
}
.content {
padding: 30px;
}
}

View File

@ -0,0 +1,66 @@
import { Button, Checkbox, Form, Input } from "antd";
import "./index.less";
const onFinish = (values: any) => {
console.log("Success:", values);
};
const Login = () => {
return (
<div className="login">
<div className="frosted">
<div className="bird-one">
<div className="beak"></div>
<div className="neck">
<div className="cir-blue"></div>
</div>
<div className="feathers">
<div className="feather fe1"></div>
<div className="feather fe2"></div>
<div className="feather fe3"></div>
</div>
<div className="eye"></div>
</div>
<div className="content">
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
initialValues={{ remember: true }}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
name="username"
wrapperCol={{ span: 24 }}
rules={[{ required: true, message: "请输入用户名" }]}
>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: "请输入密码" }]}
wrapperCol={{ span: 24 }}
>
<Input.Password placeholder="密码" />
</Form.Item>
<Form.Item name="remember" valuePropName="checked">
<Checkbox></Checkbox>
</Form.Item>
<Form.Item wrapperCol={{ span: 24 }}>
<Button type="primary" htmlType="submit" block>
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
);
};
export default Login;

View File

@ -1,11 +1,8 @@
import { Button, message } from "antd"; import { Button, message } from "antd";
import { useArticle } from "@backset/ui";
import { useMount } from "../../hooks"; import { useMount } from "../../hooks";
export default function Index() { export default function Index() {
useMount(() => { useMount(() => {});
console.log(useArticle());
});
const onClick = () => { const onClick = () => {
message.info(`hi, 很惆怅啊, vite 哪里又有坑哦`); message.info(`hi, 很惆怅啊, vite 哪里又有坑哦`);

View File

@ -0,0 +1,5 @@
import React from "react";
export default function Index() {
return <div>UserIndex</div>;
}

View File

@ -1,5 +1,5 @@
import React from "react"; const User = () => {
return <div></div>;
};
export default function Index() { export default User;
return <div>UserIndex</div>;
}

View File

@ -5,4 +5,7 @@ import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tsconfigPaths()], plugins: [react(), tsconfigPaths()],
server: {
port: 5174,
},
}); });

File diff suppressed because it is too large Load Diff