你是这个时代的探索者,你的每一个代码都照亮了未知的领域。无论前方有多少挑战,你都不会孤单,因为你手中的键盘是你的朋友,你的伙伴。💼
登录页面

- 适配手机端

api 目录结构

interface/index.ts
ts
// 基础响应类型
export interface IBaseResult {
code: number;
message: string;
timestamp: number;
}
// 分页请求参数
export interface IReqPage {
pageNum: number;
pageSize: number;
}
interface/user.ts
ts
import { IBaseResult } from ".";
// 登录请求参数
export interface ISignin {
username: string;
password: string;
remember?: boolean;
}
// token
export interface IToken {
access_token?: string;
refresh_token?: string;
}
// 登录响应结果
export interface IResultLogin extends IBaseResult {
data?: IToken;
}
userApi.ts
ts
import { IResultLogin, ISignin } from "@/apis/interface/user"
import { http } from "@/http"
// 登录接口
export const useSigninApi = (params: ISignin) => {
return http.post<IResultLogin>('/api/v1/auth/signin', params)
}
services/UserService.ts
ts
/*
* @Author: vhen
* @Date: 2024-01-18 19:27:12
* @LastEditTime: 2024-01-30 08:01:58
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\services\UserService.ts
*
*/
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { ISignin } from "@/apis/interface/user";
import { useSigninApi } from '@/apis/userApi';
// 设置token
const tokenData = atomWithStorage<any>('userToken', {
access_token: '',
refresh_token: ''
});
const tokenInfo = atom(
get => get(tokenData),
async (_get, set, userParams: ISignin) => {
const res = await useSigninApi(userParams);
set(tokenData, res.data);
return res;
},
)
// 记住登录用户
const loginData = atomWithStorage<any | null>('loginInfo', null)
const loginInfo = atom(
get => get(loginData),
async (_get, set, info: ISignin | null) => {
set(loginData, info);
return info;
},
)
export {
loginInfo, tokenInfo
};
登录调接口
LoginForm.tsx组件
tsx
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input, notification } from 'antd';
import { useAtom } from 'jotai';
import { useEffect, useState } from 'react';
import { useNavigate } from "react-router-dom";
import { ISignin } from "@/apis/interface/user";
import { loginInfo, tokenInfo } from '@/services/UserService';
import { Encrypt } from '@/utils/crypto';
const LoginForm: React.FC = () => {
const [form] = Form.useForm();
const navigate = useNavigate();
const [loading, setLoading] = useState<boolean>(false);
const [, setUserToken] = useAtom(tokenInfo)
const [userInfo, setLoginInfo] = useAtom(loginInfo)
const initLoginInfo = () => {
if (userInfo) {
form.setFieldsValue({
username: userInfo.username,
password: userInfo.password,
remember: userInfo.remember
})
}
}
// 登录
const handlerLogin = async (loginForm: ISignin) => {
try {
setLoading(true)
if (loginForm.remember) {
setLoginInfo(loginForm)
} else {
setLoginInfo(null)
}
loginForm.password = Encrypt(loginForm.password)
const { code } = await setUserToken(loginForm)
if (code === 1) {
notification.success({
message: '温馨提示',
description: '登录成功',
})
navigate('/home')
}
} finally {
setLoading(false);
}
}
useEffect(() => {
initLoginInfo()
})
return (
<Form form={form} className='w-full' initialValues={{ remember: false }}
autoComplete="off" onFinish={handlerLogin}>
<Form.Item name="username" rules={[{ required: true, message: "请输入用户名" }]}>
<Input prefix={<UserOutlined />} placeholder="请输入用户名" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入密码" }]}>
<Input.Password prefix={<LockOutlined />} placeholder="请输入密码" />
</Form.Item>
<Form.Item >
<div className='flex justify-between'>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>记住我</Checkbox>
</Form.Item>
<a className="login-form-forgot" href="#!">
忘记密码?
</a>
</div>
</Form.Item>
<Form.Item>
<Button type="primary" loading={loading} htmlType="submit" className="w-full">
登录
</Button>
</Form.Item>
</Form>
)
}
export default LoginForm;
index.module.scss
scss
.login{
display: flex;
align-items: center;
justify-content: flex-end;
background-image: url(../../assets/images/login_bg.jpg);
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
padding-right: 260px;
&-title{
padding-bottom: 30px;
font-size: 18px;
text-align: center;
font-weight: bold;
color: #000;
}
&-form{
display: flex;
flex-direction: column;
width: 400px;
padding: 50px 20px 20px;
background: #fff;
border-radius: 20px;
box-shadow: 0 5px 15px #0000000d;
box-sizing: border-box;
min-height: 300px;
}
}
@media screen and (max-width:992px) {
.login{
padding: 0 20px;
justify-content:center;
}
}
@media screen and (min-width:993px) and (max-width:1500px) {
.login{
padding-right: 120px;
}
}
index.tsx
tsx
import LoginForm from "./components/LoginForm";
import style from './index.module.scss';
const Login: React.FC = () => {
return (
<div className={style.login}>
<div className={style['login-form']}>
<div className={style['login-title']} >欢迎来到 React-Vhen-Blog-Admin</div>
<LoginForm />
{/* <Divider plain>其他登录方式</Divider> */}
</div>
</div>
)
}
export default Login;
- 测试正常登录

- 账号密码错误

结束语
如有不足之处,望海涵,欢迎关注一起学习,一起成长