feat(web): 登录、注册功能
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { App as AntdApp, Button, Form, Input } from 'antd';
|
||||
import { LockOutlined, MailOutlined } from '@ant-design/icons';
|
||||
import axios from 'axios';
|
||||
import AuthLayout from '@/layouts/AuthLayout';
|
||||
import { apiErrorMessage } from '@/services/api';
|
||||
import { apiErrorDetails, apiErrorMessage } from '@/services/api';
|
||||
import { authApi } from '@/services/auth';
|
||||
import { authToken } from '@/services/authToken';
|
||||
import { useSession } from '@/services/session';
|
||||
import { emailFormatRule } from '@/utils/validators';
|
||||
|
||||
interface LoginFormValues {
|
||||
@@ -17,7 +17,10 @@ interface LoginFormValues {
|
||||
/** 登录页,契约见 docs/api/auth.md §3 */
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { message } = AntdApp.useApp();
|
||||
const { applyAuthSession } = useSession();
|
||||
const [form] = Form.useForm<LoginFormValues>();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleFinish = async (values: LoginFormValues) => {
|
||||
@@ -27,13 +30,28 @@ const LoginPage = () => {
|
||||
email: values.email.trim(),
|
||||
password: values.password,
|
||||
});
|
||||
authToken.set(session.access_token);
|
||||
applyAuthSession(session);
|
||||
message.success('登录成功');
|
||||
navigate('/online-products', { replace: true });
|
||||
// 守卫记住的来源路径优先,默认进入在线商品首页
|
||||
const from = (location.state as { from?: string } | null)?.from;
|
||||
navigate(from && from.startsWith('/') ? from : '/online-products', {
|
||||
replace: true,
|
||||
});
|
||||
} catch (error) {
|
||||
// 契约约定登录失败统一返回 401 INVALID_CREDENTIALS,不区分邮箱不存在或密码错误
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
message.error('邮箱或密码不正确');
|
||||
return;
|
||||
}
|
||||
// 422 VALIDATION_FAILED 等带字段明细的错误,逐项落到对应表单项
|
||||
const details = apiErrorDetails(error);
|
||||
if (details.length > 0) {
|
||||
form.setFields(
|
||||
details.map((item) => ({
|
||||
name: item.field as keyof LoginFormValues,
|
||||
errors: [item.message],
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
message.error(apiErrorMessage(error));
|
||||
}
|
||||
@@ -52,7 +70,12 @@ const LoginPage = () => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form<LoginFormValues> layout="vertical" requiredMark={false} onFinish={handleFinish}>
|
||||
<Form<LoginFormValues>
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
onFinish={handleFinish}
|
||||
>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label="邮箱"
|
||||
|
||||
@@ -4,9 +4,9 @@ import { App as AntdApp, Button, Form, Input } from 'antd';
|
||||
import { LockOutlined, MailOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import axios from 'axios';
|
||||
import AuthLayout from '@/layouts/AuthLayout';
|
||||
import { apiErrorMessage } from '@/services/api';
|
||||
import { apiErrorDetails, apiErrorMessage } from '@/services/api';
|
||||
import { authApi } from '@/services/auth';
|
||||
import { authToken } from '@/services/authToken';
|
||||
import { useSession } from '@/services/session';
|
||||
import { emailFormatRule } from '@/utils/validators';
|
||||
|
||||
interface RegisterFormValues {
|
||||
@@ -20,6 +20,7 @@ interface RegisterFormValues {
|
||||
const RegisterPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { message } = AntdApp.useApp();
|
||||
const { applyAuthSession } = useSession();
|
||||
const [form] = Form.useForm<RegisterFormValues>();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -33,13 +34,24 @@ const RegisterPage = () => {
|
||||
display_name: values.display_name?.trim() || undefined,
|
||||
});
|
||||
// 注册成功即在同一事务创建用户、个人工作空间和登录会话
|
||||
authToken.set(session.access_token);
|
||||
applyAuthSession(session);
|
||||
message.success('注册成功,已进入你的工作空间');
|
||||
navigate('/online-products', { replace: true });
|
||||
} catch (error) {
|
||||
// 重复邮箱返回 409 EMAIL_ALREADY_REGISTERED,落到邮箱字段上提示
|
||||
if (axios.isAxiosError(error) && error.response?.status === 409) {
|
||||
form.setFields([{ name: 'email', errors: ['该邮箱已注册,请直接登录'] }]);
|
||||
return;
|
||||
}
|
||||
// 422 VALIDATION_FAILED 等带字段明细的错误,逐项落到对应表单项
|
||||
const details = apiErrorDetails(error);
|
||||
if (details.length > 0) {
|
||||
form.setFields(
|
||||
details.map((item) => ({
|
||||
name: item.field as keyof RegisterFormValues,
|
||||
errors: [item.message],
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
message.error(apiErrorMessage(error));
|
||||
}
|
||||
@@ -91,7 +103,7 @@ const RegisterPage = () => {
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="密码"
|
||||
extra="长度与复杂度规则以服务端安全校验为准"
|
||||
extra="至少 12 个字符;长度与复杂度以服务端安全校验为准"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
>
|
||||
<Input.Password
|
||||
|
||||
Reference in New Issue
Block a user