feat: 新增重置密码页面

This commit is contained in:
landaiqing
2024-05-30 00:41:51 +08:00
parent 4ce065214a
commit fec48a8e74
11 changed files with 1897 additions and 1433 deletions

View File

@@ -0,0 +1,158 @@
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}
.login_content {
// margin-top: 40px;
// position: relative;
width: 790px;
background-color: rgb(255, 255, 255);
// height: 100%;
overflow: hidden;
box-shadow: rgb(0 0 0 / 15%) 0px 2px 15px;
//float: right;
height: 550px;
position: relative;
padding: 30px 0 30px 0;
border-radius: 15px;
}
.go_to_register {
cursor: pointer;
height: 70px;
width: 70px;
background-image: url('@/assets/icons/corner_markers.svg');
position: absolute;
top: 0px;
right: 0px;
text-align: right;
}
.go_to_register span {
font-size: 14px;
color: rgb(255, 255, 255);
font-weight: 500;
position: relative;
top: 10px;
right: 7px;
}
.lang {
width: 100%;
height: 40px;
line-height: 44px;
text-align: right;
:global(.ant-dropdown-trigger) {
margin-right: 24px;
}
}
.content {
//flex: 1;
//padding: 32px 0;
//box-sizing: border-box;
//margin: 60px auto;
//display: flex;
//align-items: center;
//justify-content: center;
//flex-direction: column;
//height: 100vh;
//overflow: auto;
}
@media (min-width: 768px) {
.container {
background-image: url('@/assets/images/background.png');
background-size: cover;
}
.content {
padding: 32px 0 24px;
}
.content img {
text-align: center;
}
}
.icon {
margin-left: 8px;
color: rgba(0, 0, 0, 0.2);
font-size: 24px;
vertical-align: middle;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: aquamarine;
}
}
.mp_code {
padding: 0px 60px;
//width: 361px;
height: 490px;
background-color: rgb(255, 255, 255);
border-right: 0.5px solid rgb(196, 203, 215);
}
.mp_code_title {
margin-top: 50px;
font-weight: 500;
font-size: 24px;
color: rgb(24, 24, 24)
}
.mp_code_img {
margin-top: 10px;
}
.alert {
margin-top: 25px;
width: 250px;
padding: 5px;
text-align: left;
vertical-align: middle;
}
.login_form {
// height: 100%;
// float: left;
margin: 0;
padding: 0 32px;
}
.mp_tips {
font-weight: bold;
color: rgb(7, 221, 7);
}
.logo span {
position: relative;
top: 2px;
color: rgba(0, 0, 0, .85);
font-weight: 600;
font-size: 33px;
}
.subTitle {
margin-top: 12px;
margin-bottom: 10px;
color: rgba(0, 0, 0, .45);
font-size: 14px;
}

View File

@@ -0,0 +1,256 @@
/** @format */
import { LockOutlined, MobileOutlined, SafetyOutlined, WechatOutlined } from "@ant-design/icons";
import { CaptFieldRef, ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
import { Alert, Button, ConfigProvider, Form, Image, message, Space, Tabs } from "antd";
import { useRef, useState } from "react";
import { TinyColor } from "@ctrl/tinycolor";
import logo from "@/assets/icons/schisandra.svg";
// import background from '@/assets/images/background.png'
import qrCode from "@/assets/images/login_qrcode-landaiqing.jpg";
import styles from "./index.module.less";
import { observer } from "mobx-react";
import FooterComponent from "@/components/Footer";
import { getSms, register } from "@/api/user";
// import useStore from '@/utils/store/useStore.tsx'
type LoginType = "phone";
export default observer(() => {
const [form] = Form.useForm();
const captchaRef = useRef<CaptFieldRef | null | undefined>();
const [loginType, setLoginType] = useState<LoginType>("phone");
const colors = ['#fc6076', '#ff9a44', '#ef9d43', '#e75516'];
const getHoverColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).lighten(5).toString());
const getActiveColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).darken(5).toString());
const items = [
{
key: "phone",
label: (
<span>
<SafetyOutlined />
</span>
),
},
];
const onSubmit = async (formData: object) => {
const res: any = await register(formData);
if (res && res.success) {
message.success(res.data);
} else {
message.error(res.data);
}
};
return (
<div className={styles.container}>
<div className={styles.content}>
<Space>
<Space className={styles.login_content}>
<Space align="center" className={styles.mp_code}>
<Space direction="vertical" align="center">
<span className={styles.mp_code_title}></span>
<Image
preview={false}
height={210}
width={200}
className={styles.mp_code_img}
// src={generateMpRegCodeData.data?.qrCodeUrl}
src={qrCode}
/>
<Alert
// message={(<span>微信扫码<span>关注公众号</span></span>)}
description={
<div>
<span>
<span className={styles.mp_tips}></span>
</span>
<br />
</div>
}
// type="success"
showIcon={true}
className={styles.alert}
icon={<WechatOutlined />}
/>
</Space>
</Space>
<Form
form={form}
className={styles.login_form}
initialValues={{
autoLogin: true,
}}>
<Space direction="vertical" align="center">
<Space className={styles.logo}>
<img
alt="logo"
src={logo}
style={{ width: "44px", height: "44px" }}
/>
<span></span>
</Space>
<div className={styles.subTitle}></div>
</Space>
<Tabs
centered={true}
items={items}
activeKey={loginType}
onChange={(activeKey) =>
setLoginType(activeKey as LoginType)
}></Tabs>
<>
<ProFormText
fieldProps={{
size: "large",
prefix: <MobileOutlined className={"prefixIcon"} />,
autoComplete: "off",
}}
name="phone"
placeholder="请输入手机号"
rules={[
{
required: true,
message: "请输入手机号!",
},
{
pattern: /^1\d{10}$/,
message: "手机号格式错误!",
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: "large",
prefix: <SafetyOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: "large",
}}
placeholder={"请输入验证码"}
captchaTextRender={(timing: boolean) => {
if (timing) {
return `${"获取验证码"}`;
}
return "获取验证码";
}}
name="activeCode"
phoneName={"phone"}
countDown={300}
rules={[
{
required: true,
message: "请输入验证码!",
},
]}
fieldRef={captchaRef}
onGetCaptcha={async (phone: string) => {
const res: any = await getSms(phone);
if (res && res.success) {
message.success(res.data, 3);
} else {
message.warning(res.data, 3);
}
}}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder="请输入新密码"
rules={[
{
required: true,
message: "请输入新密码!",
},
{
pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message:
"密码长度需在6~18位字符且必须包含字母和数字",
},
]}
/>
<ProFormText.Password
name="confirmPassword"
dependencies={["password"]}
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder="请再次确认密码"
rules={[
{
required: true,
message: "请再次确认密码!",
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
new Error("两次输入的密码不一致!"),
);
},
}),
]}
/>
</>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: `linear-gradient(135deg, ${colors.join(", ")})`,
colorPrimaryHover: `linear-gradient(135deg, ${getHoverColors(colors).join(", ")})`,
colorPrimaryActive: `linear-gradient(135deg, ${getActiveColors(colors).join(", ")})`,
lineWidth: 0,
},
},
}}>
<Button
type="primary"
block
size="large"
onClick={async () => {
const validateFields = [
"phone",
"password",
"activeCode",
"confirmPassword",
];
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest);
})
.catch((error) => {
console.error(error);
});
}}>
</Button>
</ConfigProvider>
</Form>
<a href="/login" className={styles.go_to_register}>
<span></span>
</a>
</Space>
</Space>
</div>
<FooterComponent />
</div>
);
});

View File

@@ -6,6 +6,7 @@ import {
LockOutlined,
MobileOutlined,
QqOutlined,
SafetyOutlined,
UserOutlined,
WechatOutlined,
} from "@ant-design/icons";
@@ -15,7 +16,18 @@ import {
ProFormCheckbox,
ProFormText,
} from "@ant-design/pro-components";
import { Alert, Button, Divider, Form, Image, message, Space, Tabs, Tooltip } from "antd";
import {
Alert,
Button,
ConfigProvider,
Divider,
Form,
Image,
message,
Space,
Tabs,
Tooltip,
} from "antd";
import { CSSProperties, useEffect, useRef, useState } from "react";
import logo from "@/assets/icons/schisandra.svg";
import qrCode from "@/assets/images/login_qrcode-landaiqing.jpg";
@@ -26,6 +38,7 @@ import RotateCaptcha, { CaptchaInstance, type TicketInfoType } from "react-rotat
import { get, load } from "@/api/captcha/index.ts";
import { getSms, oauthLogin } from "@/api/user";
import { VerfiyCaptcha } from "@/api/captcha/api.ts";
import { TinyColor } from "@ctrl/tinycolor";
// import useStore from '@/utils/store/useStore.tsx'
type LoginType = "account" | "phone";
@@ -40,6 +53,11 @@ export default observer(() => {
const [form] = Form.useForm();
const captcha = useRef<CaptchaInstance>(null);
const captchaRef = useRef<CaptFieldRef | null | undefined>();
const colors = ["#40e495", "#30dd8a", "#2bb673"];
const getHoverColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).lighten(5).toString());
const getActiveColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).darken(5).toString());
async function verify(token: string, deg: number): Promise<TicketInfoType> {
const data: any = {
@@ -214,7 +232,7 @@ export default observer(() => {
<ProFormCaptcha
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
prefix: <SafetyOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: "large",
@@ -252,32 +270,45 @@ export default observer(() => {
<ProFormCheckbox noStyle name="autoLogin">
</ProFormCheckbox>
<a style={{ float: "right" }}></a>
<a href={"/forget"} style={{ float: "right" }}>
</a>
</div>
<Button
type="primary"
block
size="large"
onClick={async () => {
let validateFields;
if (loginType === "account") {
validateFields = ["username", "password"];
} else {
validateFields = ["mobile", "captcha"];
}
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values);
})
.catch((error) => {
console.error(error);
});
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: `linear-gradient(116deg, ${colors.join(", ")})`,
colorPrimaryHover: `linear-gradient(116deg, ${getHoverColors(colors).join(", ")})`,
colorPrimaryActive: `linear-gradient(116deg, ${getActiveColors(colors).join(", ")})`,
lineWidth: 0,
},
},
}}>
</Button>
<Button
type="primary"
block
size="large"
onClick={async () => {
let validateFields;
if (loginType === "account") {
validateFields = ["username", "password"];
} else {
validateFields = ["mobile", "captcha"];
}
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values);
})
.catch((error) => {
console.error(error);
});
}}>
</Button>
</ConfigProvider>
<div
style={{

View File

@@ -1,9 +1,15 @@
/** @format */
import { LockOutlined, MobileOutlined, WechatOutlined } from "@ant-design/icons";
import { ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
import { Alert, Button, Form, Image, message, Space, Tabs } from "antd";
import { useState } from "react";
import {
LockOutlined,
MobileOutlined,
SafetyOutlined,
UserOutlined,
WechatOutlined,
} from "@ant-design/icons";
import { CaptFieldRef, ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
import { Alert, Button, ConfigProvider, Form, Image, message, Space, Tabs } from "antd";
import { useRef, useState } from "react";
import logo from "@/assets/icons/schisandra.svg";
// import background from '@/assets/images/background.png'
import qrCode from "@/assets/images/login_qrcode-landaiqing.jpg";
@@ -11,11 +17,19 @@ import styles from "./index.module.less";
import { observer } from "mobx-react";
import FooterComponent from "@/components/Footer";
import { getSms, register } from "@/api/user";
import { TinyColor } from "@ctrl/tinycolor";
// import useStore from '@/utils/store/useStore.tsx'
type LoginType = "phone";
export default observer(() => {
const [form] = Form.useForm();
const captchaRef = useRef<CaptFieldRef | null | undefined>();
const colors = ["#6253E1", "#04BEFE"];
const getHoverColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).lighten(5).toString());
const getActiveColors = (colors: string[]) =>
colors.map((color) => new TinyColor(color).darken(5).toString());
const items = [
{
key: "phone",
@@ -99,6 +113,27 @@ export default observer(() => {
}></Tabs>
<>
<ProFormText
fieldProps={{
size: "large",
prefix: <UserOutlined className={"prefixIcon"} />,
autoComplete: "off",
}}
name="userName"
placeholder="请输入用户名"
rules={[
{
required: true,
message: "请输入用户名!",
},
{
pattern: /^[a-zA-Z0-9_-]{3,16}$/,
message:
"用户名只能是4到16位字母数字下划线减号",
},
]}
/>
<ProFormText
fieldProps={{
size: "large",
@@ -106,7 +141,7 @@ export default observer(() => {
autoComplete: "off",
}}
name="phone"
placeholder="请输入手机号"
placeholder="请输入手机号"
rules={[
{
required: true,
@@ -119,6 +154,41 @@ export default observer(() => {
]}
/>
<ProFormCaptcha
fieldProps={{
size: "large",
prefix: <SafetyOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: "large",
}}
placeholder={"请输入验证码"}
captchaTextRender={(timing: boolean) => {
if (timing) {
return `${"获取验证码"}`;
}
return "获取验证码";
}}
name="activeCode"
phoneName={"phone"}
countDown={300}
rules={[
{
required: true,
message: "请输入验证码!",
},
]}
fieldRef={captchaRef}
onGetCaptcha={async (phone: string) => {
const res: any = await getSms(phone);
if (res && res.success) {
message.success(res.data, 3);
} else {
message.warning(res.data, 3);
}
}}
/>
<ProFormText.Password
name="password"
fieldProps={{
@@ -165,62 +235,42 @@ export default observer(() => {
}),
]}
/>
<ProFormCaptcha
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: "large",
}}
placeholder={"请输入验证码"}
captchaTextRender={(timing: boolean) => {
if (timing) {
return `${"获取验证码"}`;
}
return "获取验证码";
}}
name="activeCode"
phoneName={"phone"}
countDown={300}
rules={[
{
required: true,
message: "请输入验证码!",
},
]}
onGetCaptcha={async (phone: string) => {
const res: any = await getSms(phone);
if (res && res.success) {
message.success(res.data, 3);
} else {
message.warning(res.data, 3);
}
}}
/>
</>
<Button
type="primary"
block
size="large"
onClick={async () => {
const validateFields = [
"phone",
"password",
"activeCode",
"confirmPassword",
];
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest);
})
.catch((error) => {
console.error(error);
});
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: `linear-gradient(90deg, ${colors.join(", ")})`,
colorPrimaryHover: `linear-gradient(90deg, ${getHoverColors(colors).join(", ")})`,
colorPrimaryActive: `linear-gradient(90deg, ${getActiveColors(colors).join(", ")})`,
lineWidth: 0,
},
},
}}>
</Button>
<Button
type="primary"
block
size="large"
onClick={async () => {
const validateFields = [
"phone",
"userName",
"password",
"activeCode",
"confirmPassword",
];
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest);
})
.catch((error) => {
console.error(error);
});
}}>
</Button>
</ConfigProvider>
</Form>
<a href="/login" className={styles.go_to_register}>
<span></span>