- 暂时移除未使用的变量
- 配置项目依赖
- 组织项目基础结构
- 完成auth页面书写
Change-Id: I132c32f131111121619eb69240ece0a39e295c36
diff --git a/src/feature/auth/AuthLayout.tsx b/src/feature/auth/AuthLayout.tsx
new file mode 100644
index 0000000..cceaf2d
--- /dev/null
+++ b/src/feature/auth/AuthLayout.tsx
@@ -0,0 +1,44 @@
+import { Card, Flex } from "antd";
+import { Outlet } from "react-router";
+import auth_background from "../../assets/auth_background.png"
+import slogan from "../../assets/slogan.png"
+function AuthLayout() {
+
+ return (
+ <Flex gap="20px" justify="center">
+ <div
+ style={{
+ width: '45%',
+ height: '80vh',
+ backgroundImage: `url(${auth_background})`,
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ borderRadius: '12px',
+ position: 'relative',
+ }}
+ >
+ {/* <h1>
+ 登录创驿
+ </h1>
+ <p>
+ 与众多用户和创作者一起交流
+ </p> */}
+ </div>
+ <Flex
+ style={{ width: 400, height: '80vh' }}
+ vertical
+ gap="20px"
+ justify="space-between"
+ >
+ <Card>
+ <Outlet></Outlet>
+ </Card>
+ <Card style={{ padding: 0, margin: 0 }}>
+ <img src={slogan} width="100%" />
+ </Card>
+ </Flex>
+ </Flex>
+ );
+}
+
+export default AuthLayout;
\ No newline at end of file
diff --git a/src/feature/auth/Forget.tsx b/src/feature/auth/Forget.tsx
new file mode 100644
index 0000000..25bf6dc
--- /dev/null
+++ b/src/feature/auth/Forget.tsx
@@ -0,0 +1,118 @@
+import { MailOutlined, LockOutlined } from '@ant-design/icons';
+import { Button, Form, Input, message, Row, Col } from 'antd';
+import { NavLink } from 'react-router';
+import { useState, useEffect } from 'react';
+
+function Forget() {
+ const [countdown, setCountdown] = useState(0);
+ const [emailSent,] = useState(false);
+
+ const onFinish = async () => {
+
+ };
+
+ useEffect(() => {
+ let countdownTimer = null;
+
+ if (countdown > 0) {
+ countdownTimer = setTimeout(() => {
+ setCountdown(prev => prev - 1);
+ }, 1000);
+ }
+
+ return () => {
+ if (countdownTimer) {
+ clearTimeout(countdownTimer);
+ }
+ };
+ }, [countdown]);
+
+ const resendCode = () => {
+ if (countdown > 0) return;
+ setCountdown(60);
+ message.info('验证码已重新发送');
+ };
+
+ return (
+ <Form
+ name="forget"
+ initialValues={{ remember: true }}
+ style={{ maxWidth: 360 }}
+ onFinish={onFinish}
+ >
+ <h2>重置密码</h2>
+ <p>请输入您注册时使用的邮箱地址</p>
+
+ <Form.Item
+ name="email"
+ rules={[
+ { required: true, message: '请输入您的邮箱!' },
+ { type: 'email', message: '请输入正确的邮箱格式' }
+ ]}
+ >
+ <Input prefix={<MailOutlined />} placeholder="注册邮箱" />
+ </Form.Item>
+
+ {emailSent && (
+ <>
+ <Form.Item
+ name="code"
+ rules={[{ required: true, message: '请输入验证码!' }]}
+ >
+ <Row gutter={8}>
+ <Col span={16}>
+ <Input placeholder="验证码" />
+ </Col>
+ <Col span={8}>
+ <Button
+ disabled={countdown > 0}
+ onClick={resendCode}
+ style={{ width: '100%' }}
+ >
+ {countdown > 0 ? `${countdown}s后重试` : '重新发送'}
+ </Button>
+ </Col>
+ </Row>
+ </Form.Item>
+
+ <Form.Item
+ name="password"
+ rules={[
+ { required: true, message: '请设置新密码!' },
+ { min: 6, message: '密码长度至少为6位' }
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="新密码" />
+ </Form.Item>
+
+ <Form.Item
+ name="confirmPassword"
+ dependencies={['password']}
+ rules={[
+ { required: true, message: '请确认新密码!' },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (!value || getFieldValue('password') === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次输入的密码不一致!'));
+ },
+ }),
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="确认新密码" />
+ </Form.Item>
+ </>
+ )}
+
+ <Form.Item>
+ <Button block type="primary" htmlType="submit">
+ {emailSent ? '确认重置' : '获取验证码'}
+ </Button>
+ 或 <NavLink to='/login'>返回登录</NavLink>
+ </Form.Item>
+ </Form>
+ );
+}
+
+export default Forget;
\ No newline at end of file
diff --git a/src/feature/auth/Login.tsx b/src/feature/auth/Login.tsx
new file mode 100644
index 0000000..14bf6c8
--- /dev/null
+++ b/src/feature/auth/Login.tsx
@@ -0,0 +1,50 @@
+
+import { LockOutlined, MailOutlined } from '@ant-design/icons';
+import { Button, Checkbox, Form, Input, Flex } from 'antd';
+import { NavLink } from 'react-router';
+function Login() {
+ const onFinish = (values: unknown) => {
+ console.log('Received values of form: ', values);
+ };
+
+ return (
+ <Form
+ name="login"
+ initialValues={{ remember: true }}
+ style={{ maxWidth: 360 }}
+ onFinish={onFinish}
+ >
+ <h2>登录</h2>
+ <Form.Item
+ name="email"
+ rules={[{ required: true, message: '请输入你的账号(注册邮箱)!' }, { type: 'email', message: '请输入正确的邮箱' }]}
+ >
+ <Input prefix={<MailOutlined />} placeholder="账号(注册邮箱)" />
+ </Form.Item>
+ <Form.Item
+ name="password"
+ rules={[{ required: true, message: '请输入你的密码!' }]}
+ >
+ <Input prefix={<LockOutlined />} type="password" placeholder="密码" />
+ </Form.Item>
+ <Form.Item>
+ <Flex justify="space-between" align="center">
+ <Form.Item name="remember" valuePropName="checked" noStyle>
+ <Checkbox>自动登录</Checkbox>
+ </Form.Item>
+ <NavLink to='/forget'> 忘记密码 </NavLink>
+
+ </Flex>
+ </Form.Item>
+
+ <Form.Item>
+ <Button block type="primary" htmlType="submit">
+ 登录
+ </Button>
+ 或 <NavLink to='/register'>注册</NavLink>
+ </Form.Item>
+ </Form>
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/src/feature/auth/Register.tsx b/src/feature/auth/Register.tsx
new file mode 100644
index 0000000..5b90c59
--- /dev/null
+++ b/src/feature/auth/Register.tsx
@@ -0,0 +1,149 @@
+import { useEffect, useState } from 'react';
+import { LockOutlined, MailOutlined, NumberOutlined, UserOutlined } from '@ant-design/icons';
+import { Button, Checkbox, Form, Input, Space } from 'antd';
+import { NavLink } from 'react-router';
+
+interface FormValues {
+ name: string;
+ email: string;
+ verifyCode: string;
+ password: string;
+ confirmPassword: string;
+ agreement: boolean;
+}
+
+function Register() {
+ const [countdown, setCountdown] = useState(0);
+ const [form] = Form.useForm<FormValues>();
+ const [emailStatues] = useState(false);
+
+ const sendVerificationCode = () => {
+ setCountdown(60)
+ }
+
+ // 发送表单倒计时
+ useEffect(() => {
+ if (countdown > 0) {
+ const timer = setTimeout(() => {
+ setCountdown(prev => prev - 1);
+ }, 1000);
+
+ return () => clearTimeout(timer);
+ }
+ }, [countdown]);
+
+
+ // 表单提交
+ const onFinish = (values: FormValues) => {
+ console.log('注册成功:', values);
+ };
+
+ return (
+ <Form
+ form={form}
+ style={{ maxWidth: 360 }}
+ onFinish={onFinish}
+ scrollToFirstError
+ >
+ <h2>注册</h2>
+
+ <Form.Item
+ name="name"
+ rules={[{ required: true, message: '请输入用户名' }]}
+ >
+ <Input prefix={<UserOutlined />} placeholder="请输入用户名" />
+ </Form.Item>
+
+ <Form.Item
+ name="email"
+
+ rules={[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '邮箱格式错误' }]}
+ >
+ <Input
+ prefix={<MailOutlined />}
+ placeholder="请输入邮箱"
+ />
+ </Form.Item>
+
+ <Form.Item
+ name="verifyCode"
+ rules={[{ required: true, message: '请填写验证码' }]}
+ >
+ <Space direction="horizontal" size="small">
+ <Input
+ prefix={<NumberOutlined />}
+ placeholder="请输入验证码"
+ style={{ flex: 1 }}
+ />
+ <Button
+ disabled={countdown > 0 || !emailStatues}
+ onClick={sendVerificationCode}
+ style={{ width: 120 }}
+ >
+ {countdown > 0 ? `${countdown}s后重试` : '发送验证码'}
+ </Button>
+ </Space>
+ </Form.Item>
+
+ <Form.Item
+ name="password"
+ rules={[
+ { required: true, message: '请输入密码' },
+ { min: 6, message: '密码长度至少为6位' },
+ ]}
+ >
+ <Input.Password
+ prefix={<LockOutlined />}
+ placeholder="请输入密码"
+ />
+ </Form.Item>
+
+ <Form.Item
+ name="confirmPassword"
+ dependencies={['password']}
+ rules={[
+ { required: true, message: '请确认密码' },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (!value || getFieldValue('password') === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject('两次输入的密码不一致');
+ },
+ }),
+ ]}
+ >
+ <Input.Password
+ prefix={<LockOutlined />}
+ placeholder="请确认密码"
+ />
+ </Form.Item>
+
+ <Form.Item
+ name="agreement"
+ valuePropName="checked"
+ rules={[
+ {
+ validator: (_, value) =>
+ value ? Promise.resolve() : Promise.reject('请阅读并同意用户协议'),
+ },
+ ]}
+ >
+ <Checkbox>我已阅读并同意用户协议:<a>《创驿平台用户协议》</a></Checkbox>
+ </Form.Item>
+
+ <Form.Item>
+ <Button
+ type="primary"
+ htmlType="submit"
+ style={{ width: '100%' }}
+ >
+ 注册
+ </Button>
+ 或 <NavLink to='/login'>返回登录</NavLink>
+ </Form.Item>
+ </Form>
+ );
+}
+
+export default Register;
\ No newline at end of file
diff --git a/src/feature/auth/authSlice.ts b/src/feature/auth/authSlice.ts
new file mode 100644
index 0000000..01cd4e5
--- /dev/null
+++ b/src/feature/auth/authSlice.ts
@@ -0,0 +1,17 @@
+import { createSlice } from "@reduxjs/toolkit";
+import type { AuthState } from "../../store/types";
+
+
+const initialState: AuthState = {
+ token: '',
+ loading: false,
+ isAuth: false,
+}
+
+const authSlice = createSlice({
+ name: 'auth',
+ initialState,
+ reducers: {},
+});
+
+export default authSlice.reducer;
\ No newline at end of file