Browse Source

登录 注册 忘记密码的UI完成

main
lk 2 months ago
parent
commit
e268f6eed1
  1. 145
      src/pages/login/register.tsx
  2. 160
      src/pages/use/login/index.tsx
  3. 0
      src/pages/use/login/style.ts
  4. 115
      src/pages/use/password/retrieve.tsx
  5. 145
      src/pages/use/register/index.tsx
  6. 6
      src/request.ts
  7. 334
      src/routes.tsx
  8. 8
      src/service/system.ts
  9. 22
      src/store/system/user.ts

145
src/pages/login/register.tsx

@ -1,145 +0,0 @@
import { Layout, Input, Button, Typography, Row, Col, Form } from "antd";
import { LockOutlined, MailOutlined } from "@ant-design/icons";
import { createFileRoute } from "@tanstack/react-router";
import { useAtomValue } from "jotai";
import React, { memo, useEffect, useState } from "react";
import {emailCodeAtom} from "@/store/system/user.ts";
const { Title, Text, Link } = Typography;
const Register = memo(() => {
const [registerForm] = Form.useForm();
const { mutate: emailCodeMutate } = useAtomValue(emailCodeAtom);
const handleRegisterSubmit = (values: any) => {
console.log(values);
// 调用注册API
};
const handleGetCode = () => {
const email = registerForm.getFieldValue("email");
setCountdown(10);
setIsButtonDisabled(true);
emailCodeMutate({ email });
};
const [countdown, setCountdown] = useState<number>(0);
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);
useEffect(() => {
let timer: number;
if (countdown > 0) {
timer = setTimeout(() => setCountdown(countdown - 1), 1000);
} else {
setIsButtonDisabled(false);
}
return () => clearTimeout(timer);
}, [countdown]);
useEffect(() => {
document.body.className = "register";
return () => {
document.body.className = document.body.className.replace("register", "");
};
}, []);
return (
<Layout
style={{
height: "100vh",
background: "url('https://placehold.co/1920x1080') no-repeat center center",
backgroundSize: "cover",
}}
>
<Row justify="center" align="middle" style={{ height: "100%" }}>
<Col>
<Row>
<Col span={12} style={{ padding: "20px" }}>
<Title level={3}></Title>
<Text></Text>
<br />
<Link href="#"> &gt;</Link>
</Col>
<Col
span={12}
style={{
padding: "20px",
background: "#fff",
borderRadius: "8px",
position: "relative",
width: "500px",
height: "500px",
}}
>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: "80px solid transparent",
borderTop: "80px solid #ff9800",
textAlign: "center",
cursor: "pointer", // 设置手型光标
}}
>
<text
style={{
cursor: "pointer", // 设置手型光标
position: "absolute",
top: "-70px",
right: "0px",
color: "#fff",
fontSize: "16px",
transform: "rotate(0deg)",
textAlign: "center",
width: "60px",
display: "block",
}}
>
</text>
</div>
<Form form={registerForm} onFinish={handleRegisterSubmit}>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<MailOutlined />}
addonAfter={
<Button onClick={handleGetCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : "获得验证码"}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>
<Input size="large" placeholder="请输入验证码" prefix={<LockOutlined />} />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入密码" }]}>
<Input.Password size="large" placeholder="请输入密码" prefix={<LockOutlined />} />
</Form.Item>
<Form.Item name="confirmPassword" rules={[{ required: true, message: "请确认密码" }]}>
<Input.Password size="large" placeholder="请确认密码" prefix={<LockOutlined />} />
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
</Button>
</Form>
<Row justify="space-between" style={{ marginTop: "10px" }}>
<Link href="#"></Link>
<Link href="#"></Link>
</Row>
</Col>
</Row>
</Col>
</Row>
</Layout>
);
});
export const Route = createFileRoute("/register")({
component: Register,
});
export default Register;

160
src/pages/login/index.tsx → src/pages/use/login/index.tsx

@ -1,39 +1,73 @@
import { Layout, Tabs, Input, Button, Typography, Row, Col, Form } from "antd"; import { Layout, Tabs, Input, Button, Typography, Row, Col, Form } from "antd";
import { QrcodeOutlined, UserOutlined, LockOutlined } from "@ant-design/icons"; import { QrcodeOutlined, UserOutlined, LockOutlined } from "@ant-design/icons";
import SelectLang from "@/components/select-lang"; import SelectLang from "@/components/select-lang";
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { useTranslation } from "@/i18n.ts"; import { useTranslation } from "@/i18n.ts";
import { emailCodeAtom, emailLoginAtom, upLoginAtom, upLoginFormAtom } from "@/store/system/user.ts";
import {
emailCodeAtom,
emailLoginAtom,
telegramCodeAtom,
telegramLoginAtom,
upLoginAtom,
} from "@/store/system/user.ts";
import React, { memo, useEffect, useLayoutEffect, useState } from "react"; import React, { memo, useEffect, useLayoutEffect, useState } from "react";
import { useStyles } from "./style.ts";
const { Title, Text, Link } = Typography; const { Title, Text, Link } = Typography;
const { TabPane } = Tabs; const { TabPane } = Tabs;
const Login = memo(() => { const Login = memo(() => {
const navigate = useNavigate();
const [upform] = Form.useForm(); const [upform] = Form.useForm();
const [emailform] = Form.useForm(); const [emailform] = Form.useForm();
const [telegramform] = Form.useForm();
const { mutate: upLoginFun } = useAtomValue(upLoginAtom); const { mutate: upLoginFun } = useAtomValue(upLoginAtom);
const { mutate: emailCodeMutate } = useAtomValue(emailCodeAtom); const { mutate: emailCodeMutate } = useAtomValue(emailCodeAtom);
const { mutate: emailLoginMutate } = useAtomValue(emailLoginAtom); const { mutate: emailLoginMutate } = useAtomValue(emailLoginAtom);
const { mutate: telegramCodeMutate } = useAtomValue(telegramCodeAtom);
const { mutate: telegramLoginMutate } = useAtomValue(telegramLoginAtom);
const uphandleSubmit = (values: any) => { const uphandleSubmit = (values: any) => {
console.log(values); console.log(values);
upLoginFun(values); upLoginFun(values);
}; };
const getEmailCode = () => {
const email = emailform.getFieldValue("email");
setCountdown(10);
setIsButtonDisabled(true);
emailCodeMutate({ email });
};
const emailhandleSubmit = (values: any) => { const emailhandleSubmit = (values: any) => {
console.log(values);
emailLoginMutate(values); emailLoginMutate(values);
}; };
const handleGetCode = () => {
const email = emailform.getFieldValue("email");
const getTelegramCode = () => {
const telegram = telegramform.getFieldValue("telegram");
setCountdown(10); setCountdown(10);
setIsButtonDisabled(true); setIsButtonDisabled(true);
emailCodeMutate({ email });
telegramCodeMutate({ telegram });
};
const telegramhandleSubmit = (values: any) => {
telegramLoginMutate(values);
}; };
const [countdown, setCountdown] = useState<number>(0);
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);
const [countdown, setCountdown] = useState<number>(() => {
const savedCountdown = localStorage.getItem("countdown");
return savedCountdown !== null ? Number(savedCountdown) : 0;
});
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(() => {
const savedIsButtonDisabled = localStorage.getItem("isButtonDisabled");
return savedIsButtonDisabled !== null ? JSON.parse(savedIsButtonDisabled) : false;
});
useEffect(() => {
localStorage.setItem("countdown", String(countdown));
}, [countdown]);
useEffect(() => {
localStorage.setItem("isButtonDisabled", JSON.stringify(isButtonDisabled));
}, [isButtonDisabled]);
useEffect(() => { useEffect(() => {
let timer: number; let timer: number;
@ -45,12 +79,6 @@ const Login = memo(() => {
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [countdown]); }, [countdown]);
const [loginMod] = useState([
{ value: "single", info: "帐号密码登录" },
{ value: "multiple-email", info: "邮箱登录" },
{ value: "multiple-plane", info: "飞机登录" },
]);
useLayoutEffect(() => { useLayoutEffect(() => {
document.body.className = "login"; document.body.className = "login";
return () => { return () => {
@ -86,41 +114,11 @@ const Login = memo(() => {
height: "400px", height: "400px",
}} }}
> >
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: "80px solid transparent",
borderTop: "80px solid #ff9800",
textAlign: "center",
cursor: "pointer", // 设置手型光标
}}
>
<text
style={{
cursor: "pointer", // 设置手型光标
position: "absolute",
top: "-70px",
right: "0px",
color: "#fff",
fontSize: "16px",
transform: "rotate(0deg)",
textAlign: "center",
width: "60px",
display: "block",
}}
>
</text>
</div>
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab="账号密码登录" key="1">
<TabPane tab="邮箱密码登录" key="1">
<Form form={upform} onFinish={uphandleSubmit}> <Form form={upform} onFinish={uphandleSubmit}>
<Form.Item name="username" rules={[{ required: true, message: "请输入账号" }]}>
<Input size="large" placeholder="请输入账号" prefix={<UserOutlined />} />
<Form.Item name="username" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input size="large" placeholder="请输入邮箱" prefix={<UserOutlined />} />
</Form.Item> </Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入登录密码" }]}> <Form.Item name="password" rules={[{ required: true, message: "请输入登录密码" }]}>
<Input.Password size="large" placeholder="请输入登录密码" prefix={<LockOutlined />} /> <Input.Password size="large" placeholder="请输入登录密码" prefix={<LockOutlined />} />
@ -133,7 +131,7 @@ const Login = memo(() => {
</Button> </Button>
</Form> </Form>
</TabPane> </TabPane>
<TabPane tab="邮箱登录" key="2">
<TabPane tab="邮箱验证登录" key="2">
<Form form={emailform} onFinish={emailhandleSubmit}> <Form form={emailform} onFinish={emailhandleSubmit}>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }]}> <Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input <Input
@ -141,7 +139,7 @@ const Login = memo(() => {
placeholder="请输入邮箱" placeholder="请输入邮箱"
prefix={<UserOutlined />} prefix={<UserOutlined />}
addonAfter={ addonAfter={
<Button onClick={handleGetCode} disabled={isButtonDisabled}>
<Button onClick={getEmailCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : "获得验证码"} {isButtonDisabled ? `${countdown}秒后重试` : "获得验证码"}
</Button> </Button>
} }
@ -155,17 +153,61 @@ const Login = memo(() => {
</Button> </Button>
</Form> </Form>
</TabPane> </TabPane>
{/*<TabPane tab="飞机登录" key="3">*/}
{/* <Form form={form} onFinish={handleSubmit}>*/}
{/* <Form.Item name="planeCode" rules={[{ required: true, message: "请输入飞机代码" }]}>*/}
{/* <Input size="large" placeholder="请输入飞机代码" prefix={<UserOutlined />} />*/}
{/* </Form.Item>*/}
{/* </Form>*/}
{/*</TabPane>*/}
<TabPane tab="飞机验证登录" key="3">
<Form form={telegramform} onFinish={telegramhandleSubmit}>
<Form.Item name="telegram" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<UserOutlined />}
addonAfter={
<Button onClick={getTelegramCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : "获得飞机验证码"}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入飞机验证码" }]}>
<Input.Password size="large" placeholder="请输入飞机验证码" prefix={<LockOutlined />} />
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
</Button>
</Form>
</TabPane>
</Tabs> </Tabs>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: "80px solid transparent",
borderTop: "80px solid #ff9800",
textAlign: "center",
}}
>
<span
style={{
position: "absolute",
top: "-70px",
right: "0px",
color: "#fff",
fontSize: "16px",
transform: "rotate(0deg)",
textAlign: "center",
width: "60px",
display: "block",
cursor: "pointer", // 设置手型光标
}}
onClick={() => navigate({ to: "/register" })}
>
</span>
</div>
<Row justify="space-between" style={{ marginTop: "10px" }}> <Row justify="space-between" style={{ marginTop: "10px" }}>
<Link href="#"></Link>
<Link href="#"></Link>
<Link onClick={() => navigate({ to: "/pwdRetrieve" })}></Link>
</Row> </Row>
</Col> </Col>
</Row> </Row>

0
src/pages/login/style.ts → src/pages/use/login/style.ts

115
src/pages/use/password/retrieve.tsx

@ -0,0 +1,115 @@
import { Layout, Input, Button, Typography, Row, Col, Form } from "antd";
import { LockOutlined, MailOutlined, SecurityScanOutlined } from "@ant-design/icons";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useAtomValue } from "jotai";
import React, { memo, useEffect, useState } from "react";
import { emailCodeAtom } from "@/store/system/user.ts";
const { Title, Text, Link } = Typography;
const PwdRetrieve = memo(() => {
const navigate = useNavigate();
const [retrieveForm] = Form.useForm();
const { mutate: emailCodeMutate } = useAtomValue(emailCodeAtom);
const handleRetrieveSubmit = (values: any) => {
console.log(values);
// 调用找回密码API
};
const handleGetCode = () => {
const email = retrieveForm.getFieldValue("email");
setCountdown(10);
setIsButtonDisabled(true);
emailCodeMutate({ email });
};
const [countdown, setCountdown] = useState<number>(0);
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);
useEffect(() => {
let timer: number;
if (countdown > 0) {
timer = setTimeout(() => setCountdown(countdown - 1), 1000);
} else {
setIsButtonDisabled(false);
}
return () => clearTimeout(timer);
}, [countdown]);
useEffect(() => {
document.body.className = "retrieve";
return () => {
document.body.className = document.body.className.replace("retrieve", "");
};
}, []);
return (
<Layout
style={{
height: "100vh",
background: "url('https://placehold.co/1920x1080') no-repeat center center",
backgroundSize: "cover",
}}
>
<Row justify="center" align="middle" style={{ height: "100%" }}>
<Col>
<Row>
<Col span={12} style={{ padding: "20px" }}>
<Title level={3}></Title>
<Text></Text>
<br />
<Link href="#"> &gt;</Link>
</Col>
<Col
span={12}
style={{
padding: "20px",
background: "#fff",
borderRadius: "8px",
position: "relative",
width: "500px",
height: "500px",
}}
>
<Form form={retrieveForm} onFinish={handleRetrieveSubmit} style={{ marginTop: "50px" }}>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<MailOutlined />}
addonAfter={
<Button onClick={handleGetCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : "获得验证码"}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>
<Input size="large" placeholder="请输入验证码" prefix={<SecurityScanOutlined />} />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入新密码" }]}>
<Input.Password size="large" placeholder="请输入新密码" prefix={<LockOutlined />} />
</Form.Item>
<Form.Item name="confirmPassword" rules={[{ required: true, message: "请确认新密码" }]}>
<Input.Password size="large" placeholder="请确认新密码" prefix={<LockOutlined />} />
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
</Button>
</Form>
<Row justify="space-between" style={{ marginTop: "10px" }}>
<Link onClick={() => navigate({ to: "/login" })}></Link>
</Row>
</Col>
</Row>
</Col>
</Row>
</Layout>
);
});
export const Route = createFileRoute("/pwdRetrieve")({
component: PwdRetrieve,
});
export default PwdRetrieve;

145
src/pages/use/register/index.tsx

@ -0,0 +1,145 @@
import { Layout, Input, Button, Typography, Row, Col, Form } from "antd";
import { LockOutlined, MailOutlined, SecurityScanOutlined } from "@ant-design/icons";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useAtomValue } from "jotai";
import React, { memo, useEffect, useState } from "react";
import { emailCodeAtom } from "@/store/system/user.ts";
const { Title, Text, Link } = Typography;
const Register = memo(() => {
const navigate = useNavigate();
const [registerForm] = Form.useForm();
const { mutate: emailCodeMutate } = useAtomValue(emailCodeAtom);
const handleRegisterSubmit = (values: any) => {
console.log(values);
// 调用注册API
};
const handleGetCode = () => {
const email = registerForm.getFieldValue("email");
setCountdown(10);
setIsButtonDisabled(true);
emailCodeMutate({ email });
};
const [countdown, setCountdown] = useState<number>(0);
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);
useEffect(() => {
let timer: number;
if (countdown > 0) {
timer = setTimeout(() => setCountdown(countdown - 1), 1000);
} else {
setIsButtonDisabled(false);
}
return () => clearTimeout(timer);
}, [countdown]);
useEffect(() => {
document.body.className = "register";
return () => {
document.body.className = document.body.className.replace("register", "");
};
}, []);
return (
<Layout
style={{
height: "100vh",
background: "url('https://placehold.co/1920x1080') no-repeat center center",
backgroundSize: "cover",
}}
>
<Row justify="center" align="middle" style={{ height: "100%" }}>
<Col>
<Row>
<Col span={12} style={{ padding: "20px" }}>
<Title level={3}></Title>
<Text></Text>
<br />
<Link href="#"> &gt;</Link>
</Col>
<Col
span={12}
style={{
padding: "20px",
background: "#fff",
borderRadius: "8px",
position: "relative",
width: "500px",
height: "500px",
}}
>
<Form form={registerForm} onFinish={handleRegisterSubmit} style={{ marginTop: "50px" }}>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<MailOutlined />}
addonAfter={
<Button onClick={handleGetCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : "获得验证码"}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>
<Input size="large" placeholder="请输入验证码" prefix={<SecurityScanOutlined />} />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入密码" }]}>
<Input.Password size="large" placeholder="请输入密码" prefix={<LockOutlined />} />
</Form.Item>
<Form.Item name="confirmPassword" rules={[{ required: true, message: "请确认密码" }]}>
<Input.Password size="large" placeholder="请确认密码" prefix={<LockOutlined />} />
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
</Button>
</Form>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: "80px solid transparent",
borderTop: "80px solid #ff9800",
textAlign: "center",
}}
>
<span
style={{
cursor: "pointer", // 设置手型光标
position: "absolute",
top: "-70px",
right: "0px",
color: "#fff",
fontSize: "16px",
transform: "rotate(0deg)",
textAlign: "center",
width: "60px",
display: "block",
}}
onClick={() => navigate({ to: "/login" })}
>
</span>
</div>
<Row justify="space-between" style={{ marginTop: "10px" }}>
<Link onClick={() => navigate({ to: "/login" })}></Link>
</Row>
</Col>
</Row>
</Col>
</Row>
</Layout>
);
});
export const Route = createFileRoute("/register")({
component: Register,
});
export default Register;

6
src/request.ts

@ -71,9 +71,6 @@ axiosInstance.interceptors.response.use(
if (window.location.pathname === "/login") { if (window.location.pathname === "/login") {
return Promise.reject(new Error("to login")); return Promise.reject(new Error("to login"));
} }
if (window.location.pathname === "/register") {
return Promise.reject(new Error("register"));
}
// 401: 未登录 // 401: 未登录
message.error("登录失败,跳转重新登录"); message.error("登录失败,跳转重新登录");
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
@ -100,9 +97,6 @@ axiosInstance.interceptors.response.use(
if (window.location.pathname === "/login") { if (window.location.pathname === "/login") {
return; return;
} }
if (window.location.pathname === "/register") {
return;
}
setToken(""); setToken("");
// 401: 未登录 // 401: 未登录
message.error("登录失败,跳转重新登录"); message.error("登录失败,跳转重新登录");

334
src/routes.tsx

@ -1,57 +1,61 @@
import NotPermission from '@/components/error/403.tsx'
import NotFound from '@/components/error/404.tsx'
import ErrorPage from '@/components/error/error.tsx'
import Loading from '@/components/loading'
import FetchLoading from '@/components/loading/FetchLoading.tsx'
import PageLoading from '@/components/page-loading'
import { PageStoreProvider } from '@/store'
import { AuthenticatedRoute as AuthenticatedImport } from './_authenticatedRoute.tsx'
import EmptyLayout from '@/layout/EmptyLayout.tsx'
import NotPermission from "@/components/error/403.tsx";
import NotFound from "@/components/error/404.tsx";
import ErrorPage from "@/components/error/error.tsx";
import Loading from "@/components/loading";
import FetchLoading from "@/components/loading/FetchLoading.tsx";
import PageLoading from "@/components/page-loading";
import { PageStoreProvider } from "@/store";
import { AuthenticatedRoute as AuthenticatedImport } from "./_authenticatedRoute.tsx";
import EmptyLayout from "@/layout/EmptyLayout.tsx";
// import ListPageLayout from '@/layout/ListPageLayout.tsx' // import ListPageLayout from '@/layout/ListPageLayout.tsx'
// import { Route as DashboardImport } from '@/pages/dashboard' // import { Route as DashboardImport } from '@/pages/dashboard'
import { Route as LoginRouteImport } from '@/pages/login'
import { Route as RegisterRouteImport } from '@/pages/login/register'
import { generateUUID } from '@/utils/uuid.ts'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import Login2 from '@/pages/login2/index.tsx'
import { Route as LoginRouteImport } from "@/pages/use/login";
import { Route as RegisterRouteImport } from "@/pages/use/register";
import { Route as PwdRetrieveImport } from "@/pages/use/password/retrieve";
import { generateUUID } from "@/utils/uuid.ts";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Login2 from "@/pages/login2/index.tsx";
import { import {
AnyRoute, AnyRoute,
createRootRouteWithContext, createRootRouteWithContext,
createRoute, createRoute,
createRouter, lazyRouteComponent,
createRouter,
lazyRouteComponent,
Outlet, Outlet,
redirect, redirect,
RouterProvider, useRouter, useRouterState,
RouterProvider,
useRouter,
useRouterState,
// createHashHistory, // createHashHistory,
} from '@tanstack/react-router'
} from "@tanstack/react-router";
// import { TanStackRouterDevtools } from '@tanstack/router-devtools' // import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { createElement, memo, useEffect, useRef } from 'react'
import RootLayout from './layout/RootLayout'
import { IRootContext, MenuItem } from './global'
import { createElement, memo, useEffect, useRef } from "react";
import RootLayout from "./layout/RootLayout";
import { IRootContext, MenuItem } from "./global";
// import { DevTools } from 'jotai-devtools' // import { DevTools } from 'jotai-devtools'
import { useAtomValue } from 'jotai'
import { userMenuDataAtom } from '@/store/system/user.ts'
import { ApiContextProvider } from '@/context.ts'
import Register from "@/pages/login/register.tsx";
import { useAtomValue } from "jotai";
import { userMenuDataAtom } from "@/store/system/user.ts";
import { ApiContextProvider } from "@/context.ts";
const PageRootLayout = () => { const PageRootLayout = () => {
return <PageStoreProvider>
return (
<PageStoreProvider>
<RootLayout /> <RootLayout />
</PageStoreProvider> </PageStoreProvider>
}
);
};
export const queryClient = new QueryClient({ export const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: false, retry: false,
}
}
})
},
},
});
const rootRoute = createRootRouteWithContext<IRootContext>()({ const rootRoute = createRootRouteWithContext<IRootContext>()({
component: () => ( component: () => (
<> <>
<FetchLoading /> <FetchLoading />
<Outlet /> <Outlet />
@ -60,46 +64,44 @@ const rootRoute = createRootRouteWithContext<IRootContext>()({
</> </>
), ),
beforeLoad: ({ location }) => { beforeLoad: ({ location }) => {
if (location.pathname === '/') {
return redirect({ to: '/dashboard' })
if (location.pathname === "/") {
return redirect({ to: "/dashboard" });
} }
}, },
loader: () => {
},
loader: () => {},
notFoundComponent: NotFound, notFoundComponent: NotFound,
pendingComponent: PageLoading, pendingComponent: PageLoading,
errorComponent: ({ error }) => <ErrorPage error={error} />, errorComponent: ({ error }) => <ErrorPage error={error} />,
})
});
const emptyRoute = createRoute({ const emptyRoute = createRoute({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
id: '/_empty',
id: "/_empty",
component: EmptyLayout, component: EmptyLayout,
})
});
const authRoute = AuthenticatedImport.update({ const authRoute = AuthenticatedImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
id: '/_authenticated',
} as any)
id: "/_authenticated",
} as any);
const layoutNormalRoute = createRoute({ const layoutNormalRoute = createRoute({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
id: '/_normal_layout',
id: "/_normal_layout",
component: PageRootLayout, component: PageRootLayout,
})
});
const layoutAuthRoute = createRoute({ const layoutAuthRoute = createRoute({
getParentRoute: () => authRoute, getParentRoute: () => authRoute,
id: '/_auth_layout',
id: "/_auth_layout",
component: PageRootLayout, component: PageRootLayout,
})
});
const notAuthRoute = createRoute({ const notAuthRoute = createRoute({
getParentRoute: () => layoutNormalRoute, getParentRoute: () => layoutNormalRoute,
path: '/not-auth',
component: NotPermission
})
path: "/not-auth",
component: NotPermission,
});
// const dashboardRoute = DashboardImport.update({ // const dashboardRoute = DashboardImport.update({
// path: '/dashboard', // path: '/dashboard',
@ -107,20 +109,26 @@ const notAuthRoute = createRoute({
// } as any) // } as any)
const loginRoute = LoginRouteImport.update({ const loginRoute = LoginRouteImport.update({
path: '/login',
path: "/login",
getParentRoute: () => emptyRoute, getParentRoute: () => emptyRoute,
} as any)
} as any);
const registerRoute = RegisterRouteImport.update({ const registerRoute = RegisterRouteImport.update({
path: '/register',
path: "/register",
getParentRoute: () => emptyRoute, getParentRoute: () => emptyRoute,
} as any)
} as any);
const pwdRetrieveRoute = PwdRetrieveImport.update({
path: "/pwdRetrieve",
getParentRoute: () => emptyRoute,
} as any);
// const login2Route = LoginRouteImport.update({ // const login2Route = LoginRouteImport.update({
// path: '/login2', // path: '/login2',
// getParentRoute: () => emptyRoute, // getParentRoute: () => emptyRoute,
// } as any) // } as any)
// //
// const menusRoute = createRoute({ // const menusRoute = createRoute({
// getParentRoute: () => layoutAuthRoute, // getParentRoute: () => layoutAuthRoute,
@ -142,21 +150,20 @@ const registerRoute = RegisterRouteImport.update({
// path: '/system/roles', // path: '/system/roles',
// }).lazy(async () => await import('@/pages/system/roles').then(d => d.Route)) // }).lazy(async () => await import('@/pages/system/roles').then(d => d.Route))
declare module '@tanstack/react-router' {
declare module "@tanstack/react-router" {
interface FileRoutesByPath { interface FileRoutesByPath {
'/_authenticated': {
preLoaderRoute: typeof AuthenticatedImport
parentRoute: typeof rootRoute
},
'/_normal_layout': {
preLoaderRoute: typeof layoutNormalRoute
parentRoute: typeof rootRoute
},
'/_layout': {
preLoaderRoute: typeof layoutAuthRoute
parentRoute: typeof rootRoute
},
"/_authenticated": {
preLoaderRoute: typeof AuthenticatedImport;
parentRoute: typeof rootRoute;
};
"/_normal_layout": {
preLoaderRoute: typeof layoutNormalRoute;
parentRoute: typeof rootRoute;
};
"/_layout": {
preLoaderRoute: typeof layoutAuthRoute;
parentRoute: typeof rootRoute;
};
// '/': { // '/': {
// preLoaderRoute: typeof DashboardImport // preLoaderRoute: typeof DashboardImport
// parentRoute: typeof layoutAuthRoute // parentRoute: typeof layoutAuthRoute
@ -165,15 +172,20 @@ declare module '@tanstack/react-router' {
// preLoaderRoute: typeof DashboardImport // preLoaderRoute: typeof DashboardImport
// parentRoute: typeof layoutAuthRoute // parentRoute: typeof layoutAuthRoute
// }, // },
'/login': {
preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRoute
},
'/register': {
preLoaderRoute: typeof RegisterRouteImport
parentRoute: typeof rootRoute
},
"/login": {
preLoaderRoute: typeof LoginRouteImport;
parentRoute: typeof rootRoute;
};
"/register": {
preLoaderRoute: typeof RegisterRouteImport;
parentRoute: typeof rootRoute;
};
"/pwdRetrieve": {
preLoaderRoute: typeof PwdRetrieveImport;
parentRoute: typeof rootRoute;
};
// '/system/menus': { // '/system/menus': {
// preLoaderRoute: typeof menusRoute // preLoaderRoute: typeof menusRoute
// parentRoute: typeof layoutAuthRoute // parentRoute: typeof layoutAuthRoute
@ -190,26 +202,26 @@ declare module '@tanstack/react-router' {
// preLoaderRoute: typeof rolesRoute // preLoaderRoute: typeof rolesRoute
// parentRoute: typeof layoutAuthRoute // parentRoute: typeof layoutAuthRoute
// }, // },
'/welcome': {
preLoaderRoute: typeof rootRoute
parentRoute: typeof layoutAuthRoute
},
"/welcome": {
preLoaderRoute: typeof rootRoute;
parentRoute: typeof layoutAuthRoute;
};
} }
} }
const modules = import.meta.glob('./pages/**/*.{jsx,tsx}')
const modules = import.meta.glob("./pages/**/*.{jsx,tsx}");
console.log(modules)
console.log(modules);
const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => { const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => {
// 递归生成路由,如果有routes则递归生成子路由 // 递归生成路由,如果有routes则递归生成子路由
const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => {
const path = menu.path?.replace(parentRoute.options?.path, '')
const isLayout = menu.children && menu.children.length > 0 && menu.type === 'menu'
const path = menu.path?.replace(parentRoute.options?.path, "");
const isLayout = menu.children && menu.children.length > 0 && menu.type === "menu";
const isApi = menu.type === 'api'
const api = menu.properties?.startsWith('{') ? JSON.parse(menu.properties)?.api : undefined
const isApi = menu.type === "api";
const api = menu.properties?.startsWith("{") ? JSON.parse(menu.properties)?.api : undefined;
if (isLayout && (!menu.path || !menu.component)) { if (isLayout && (!menu.path || !menu.component)) {
//没有component的layout,直接返回 //没有component的layout,直接返回
@ -217,7 +229,7 @@ const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => {
getParentRoute: () => layoutAuthRoute, getParentRoute: () => layoutAuthRoute,
id: `/layout-no-path-${generateUUID()}`, id: `/layout-no-path-${generateUUID()}`,
component: EmptyLayout, component: EmptyLayout,
})
});
} }
// @ts-ignore 添加menu属性,方便后面获取 // @ts-ignore 添加menu属性,方便后面获取
@ -226,114 +238,110 @@ const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => {
menu, menu,
isApi, isApi,
api, api,
} as any
} as any;
if (isLayout) { if (isLayout) {
options.id = path ?? `/layout-${generateUUID()}`
options.id = path ?? `/layout-${generateUUID()}`;
} else { } else {
if (!path) { if (!path) {
console.log(`${menu.name}没有设置视图`)
options.id = menu.meta.name
console.log(`${menu.name}没有设置视图`);
options.id = menu.meta.name;
} else { } else {
options.path = path
options.path = path;
} }
} }
let component = menu.component
let component = menu.component;
// menu.type // menu.type
// 1,组件(页面),2,IFrame,3,外链接,4,按钮 // 1,组件(页面),2,IFrame,3,外链接,4,按钮
if (menu.type === 'iframe') {
component = '@/components/Iframe'
if (menu.type === "iframe") {
component = "@/components/Iframe";
} }
//处理component路径 //处理component路径
component = component.replace(/^\/pages/, '')
component = component.replace(/^\//, '')
component = component.replace(/^\/pages/, "");
component = component.replace(/^\//, "");
return createRoute({ return createRoute({
...options, ...options,
// @ts-ignore fix import // @ts-ignore fix import
component: lazyRouteComponent(() => { component: lazyRouteComponent(() => {
//处理最后可能包含index || index.tsx || index.jsx //处理最后可能包含index || index.tsx || index.jsx
if (component.endsWith('.tsx')) {
component = component.replace(/\.tsx$/, '')
if (component.endsWith(".tsx")) {
component = component.replace(/\.tsx$/, "");
} }
if (component.endsWith('.jsx')) {
component = component.replace(/\.jsx$/, '')
if (component.endsWith(".jsx")) {
component = component.replace(/\.jsx$/, "");
} }
if (component.endsWith('/index')) {
component = component.replace(/\/index$/, '')
if (component.endsWith("/index")) {
component = component.replace(/\/index$/, "");
} }
let module: () => Promise<any>
let module: () => Promise<any>;
//优先匹配无index的情况 //优先匹配无index的情况
if (modules[`./pages/${component}.tsx`] || modules[`./pages/${component}.jsx`]) { if (modules[`./pages/${component}.tsx`] || modules[`./pages/${component}.jsx`]) {
module = modules[`./pages/${component}.tsx`] || modules[`./pages/${component}.jsx`]
module = modules[`./pages/${component}.tsx`] || modules[`./pages/${component}.jsx`];
} else { } else {
module = modules[`./pages/${component}/index.tsx`] || modules[`./pages/${component}/index.jsx`]
module = modules[`./pages/${component}/index.tsx`] || modules[`./pages/${component}/index.jsx`];
} }
if (!module) { if (!module) {
return NotFound
return NotFound;
} }
return module().then((d: any) => { return module().then((d: any) => {
if (d.Route) { if (d.Route) {
d.Route.update({ d.Route.update({
path: menu.path, path: menu.path,
})
});
} }
return d
})
return d;
});
}), }),
notFoundComponent: NotFound, notFoundComponent: NotFound,
})
}
});
};
// 对menuData递归生成路由,只处理type =1 的菜单 // 对menuData递归生成路由,只处理type =1 的菜单
const did = (menus: MenuItem[], parentRoute: AnyRoute) => { const did = (menus: MenuItem[], parentRoute: AnyRoute) => {
return menus.filter((item) => [ 'menu', 'iframe', 'api' ].includes(item.type)).map((item, index) => {
return menus
.filter((item) => ["menu", "iframe", "api"].includes(item.type))
.map((item, index) => {
// 如果有children则递归生成子路由,同样只处理type =1 的菜单 // 如果有children则递归生成子路由,同样只处理type =1 的菜单
const route = generateRoutes(item, parentRoute)
const route = generateRoutes(item, parentRoute);
// console.log(route) // console.log(route)
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
const children = did(item.children, route)
const children = did(item.children, route);
if (children.length > 0) { if (children.length > 0) {
route.addChildren(children)
}
route.addChildren(children);
} }
route.init({ originalIndex: index })
return route
})
} }
route.init({ originalIndex: index });
return route;
});
};
const routes = did(menuData, parentRoute)
const routes = did(menuData, parentRoute);
parentRoute.addChildren(routes)
}
const routeTree = rootRoute.addChildren(
[
parentRoute.addChildren(routes);
};
const routeTree = rootRoute.addChildren([
//非Layout //非Layout
loginRoute, loginRoute,
emptyRoute, emptyRoute,
registerRoute, registerRoute,
pwdRetrieveRoute,
//login2Route, //login2Route,
// 添加新的自定义路由 // 添加新的自定义路由
//不带权限Layout //不带权限Layout
layoutNormalRoute.addChildren([
notAuthRoute,
]),
layoutNormalRoute.addChildren([notAuthRoute]),
//带权限Layout //带权限Layout
// dashboardRoute, // dashboardRoute,
authRoute.addChildren(
[
layoutAuthRoute
authRoute.addChildren([
layoutAuthRoute,
/*.addChildren( /*.addChildren(
[ [
menusRoute, menusRoute,
@ -343,31 +351,26 @@ const routeTree = rootRoute.addChildren(
] ]
),*/ ),*/
]), ]),
]
)
]);
export const RootProvider = memo((props: { context: Partial<IRootContext> }) => { export const RootProvider = memo((props: { context: Partial<IRootContext> }) => {
const { data: menuData, isLoading, refetch } = useAtomValue(userMenuDataAtom);
const { data: menuData, isLoading, refetch } = useAtomValue(userMenuDataAtom)
const isFetchRef = useRef(false)
const isFetchRef = useRef(false);
useEffect(() => { useEffect(() => {
if (isFetchRef.current) { if (isFetchRef.current) {
return
return;
} }
isFetchRef.current = true
refetch()
}, [])
isFetchRef.current = true;
refetch();
}, []);
if (isLoading) { if (isLoading) {
return <PageLoading/>
return <PageLoading />;
} }
generateDynamicRoutes(menuData ?? [], layoutAuthRoute)
generateDynamicRoutes(menuData ?? [], layoutAuthRoute);
// const hashHistory = createHashHistory() // const hashHistory = createHashHistory()
@ -375,34 +378,39 @@ export const RootProvider = memo((props: { context: Partial<IRootContext> }) =>
routeTree, routeTree,
// history: hashHistory, // history: hashHistory,
context: { queryClient, menuData: [] }, context: { queryClient, menuData: [] },
defaultPreload: 'intent',
defaultPreload: "intent",
defaultPendingComponent: () => <Loading loading={true} delay={100} />, defaultPendingComponent: () => <Loading loading={true} delay={100} />,
InnerWrap: ({ children }) => { InnerWrap: ({ children }) => {
const { matches } = useRouterState()
const { routesById } = useRouter()
const { matches } = useRouterState();
const { routesById } = useRouter();
//取最后一个match的route //取最后一个match的route
const route = matches[matches.length - 1]
const route = matches[matches.length - 1];
// 排除rootRoute // 排除rootRoute
if (!route || route.id === '__root__') {
return <>{children}</>
if (!route || route.id === "__root__") {
return <>{children}</>;
} }
const options = routesById[route.routeId].options
const options = routesById[route.routeId].options;
return <ApiContextProvider value={{
return (
<ApiContextProvider
value={{
api: options?.api, api: options?.api,
menu: options?.menu, menu: options?.menu,
isApi: options?.isApi, isApi: options?.isApi,
path: options?.path, path: options?.path,
}}>{children}</ApiContextProvider>
}
})
}}
>
{children}
</ApiContextProvider>
);
},
});
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ ...props.context, menuData, queryClient }} /> <RouterProvider router={router} context={{ ...props.context, menuData, queryClient }} />
</QueryClientProvider> </QueryClientProvider>
)
})
);
});
export default RootProvider
export default RootProvider;

8
src/service/system.ts

@ -24,6 +24,14 @@ const systemServ = {
return request.post<System.LoginResponse>("/sys/login/email", data); return request.post<System.LoginResponse>("/sys/login/email", data);
}, },
telegramCode: (data: any) => {
return request.post<System.LoginResponse>("/sys/telegram", data);
},
telegramLogin: (data: any) => {
return request.post<System.LoginResponse>("/sys/login/telegram", data);
},
logout: () => { logout: () => {
// //
}, },

22
src/store/system/user.ts

@ -73,6 +73,28 @@ export const emailLoginAtom = atomWithMutation<any, any>((get) => ({
retry: false, retry: false,
})); }));
export const telegramCodeAtom = atomWithMutation<any, any>((get) => ({
mutationKey: ["telegramCode"],
mutationFn: async (params) => {
return await systemServ.telegramCode(params);
},
onSuccess: (res) => {
return res.data;
},
retry: false,
}));
export const telegramLoginAtom = atomWithMutation<any, any>((get) => ({
mutationKey: ["telegramLogin"],
mutationFn: async (params) => {
return await systemServ.telegramLogin(params);
},
onSuccess: (res) => {
message.success(t("login.success"));
return res.data;
},
retry: false,
}));
export const logoutAtom = atomWithMutation(() => ({ export const logoutAtom = atomWithMutation(() => ({
mutationKey: ["logout"], mutationKey: ["logout"],
mutationFn: async () => { mutationFn: async () => {

Loading…
Cancel
Save