一、动机?
有时候我们想快速的拥有一个登录页面的需求,而不是自己重新写一个新的登录页面。
如果是一个管理系统,对 UI 的要求不高,需要快速的集成登录相关的内容。自己写一个登录页可能也花费不了多长时间。但是今天分享的是 pro-components 的登录页给出的方案,快速的制作登录的逻辑。
二、登录常见的需求
登录类型
- 密码登录
- 手机登录
- 扫码登录
- ...
@ant-design/icons 集成了两种方案,一种是登录表单 LoginForm
和登录页面 LoginFormPage
两种不同的组件。提供的登录 UI 有:
- 账号密码登录
- 手机号登录
其实简单的情况我们不需要手机号登录。这两种方式实现的方式是 Tabs/Tabs.TabPane
切换:
ts
<Tabs
centered
activeKey={loginType}
onChange={(activeKey) => setLoginType(activeKey as LoginType)}
>
<Tabs.TabPane key={'account'} tab={'账号密码登录'} />
<Tabs.TabPane key={'phone'} tab={'手机号登录'} />
</Tabs>
这里的 Tabs 分为两个,一个密码登录的,一个是手机登录的。当然你也可以根据自己的需求,自己自定义,实现二维码登录。
登录表单
登录表单中常用中的常用内容:
- 用户名(或邮箱)
- 密码(hash with salt 加密方法或者根据自己需求使用加密算法)
- 验证码 (短信验证码,图片验证码等方式)
- 二维码 (二维码登录)
关于加密的类库:
- WebCryptoAPI 是 web api 使用时,更具兼容性考虑
- CryptoJS 提供了各种加密算法
- bcrypt.js
表单验证
antd 表单中对前端表单提供了验证的机制:
- 必须项目
- 错误提示
- 校验规则:使用
rules
属性
当然 antd 中支持自定义校验规则。
自动登录与忘记密码
- checkbox 标记记住密码
- a 标签,跳转忘记密码页面
三、集成 pro-components
在 React 项目中集成 pro-components 非常简单,如果还需用到图标相关的内容,需要 @ant-design/icons
和 antd
。
sh
@ant-design/pro-components @ant-design/icons antd
组件
ts
import {
LoginForm,
ProConfigProvider,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
setAlpha,
} from '@ant-design/pro-components';
LoginForm
这里 LoginForm 就是登录组件的框架,登录的标题以及一些非表单的额外信息,可以在这里处理:
- logo 地址位置
- title 主标题
- subTitle 副标题
- actions 额外的登录信息
- message 顶部配置信息
ProFormCaptcha
tsx
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
captchaProps={{
size: 'large',
}}
placeholder={'请输入验证码'}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${'获取验证码'}`;
}
return '获取验证码';
}}
name="captcha"
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
onGetCaptcha={async () => {
message.success('获取验证码成功!验证码为:1234');
}}
/>
由验证码按钮和验证码输入框组成。同时 captchaTextRender 根据倒计时开情况进行或者, onGetCaptcha 可以发送请求到后端获取验证码。
四、LoginFormPage
在 Login 基础上实现了左右布局,增加一个广告位置,同时在组件也添加了一些额外的属性:
- backgroundImageUrl 整个区域的背景图片
- activityConfig 活动配置* *
五、Remix 中实践
创建应用并安装依赖
sh
npx create-remix@latest app
cd app
pnpm add @ant-design/icons @ant-design/pro-components antd remix-utils crypto-js
pnpm add @types/crypto-js -D
添加 ClientOnly 到 _Admin.tsx
中:
tsx
import { Outlet } from "@remix-run/react";
import { ClientOnly } from "remix-utils/client-only";
export default function Admin() {
return <ClientOnly>{() => <Outlet />}</ClientOnly>;
}
ClientOnly 组件原因是组件有些是客户端渲染的内容,不需要服务端渲染,或者有组件,不支持服务端渲染。
工具函数处理 password (示例)
ts
import * as cj from 'crypto-js'
export function hashedPassword(password: string) {
const salted = /* your hash*/;
return cj.SHA256(password + salted).toString()
}
这里使用 crypto-js 提供的 SHA256 方法, 在密码与加盐的拼接之后,进行加密,不会直接将数据传递给后端。
登录实现
tsx
import type { ActionFunction } from '@remix-run/node'
import { useEffect } from "react";
import { json } from "@remix-run/node";
import { useActionData, useNavigate, useSubmit } from "@remix-run/react";
import { message, theme } from "antd";
import * as _icons from "@ant-design/icons";
import { LoginForm, ProFormCheckbox, ProFormText } from "@ant-design/pro-components";
// utils
import { hashedPassword } from "~/utils/hash";
const { UserOutlined, LockOutlined } = _icons;
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData()
return json({
name: formData.get('name'),
password: formData.get("password")
})
}
export default function AdminLogin() {
const { token } = theme.useToken();
const actionData = useActionData<typeof action>()
const submit = useSubmit()
const navigate = useNavigate()
const onFinish = async (values: any) => {
values.password = hashedPassword(values.password)
submit(values, { method: 'POST'})
}
useEffect(() => {
if(actionData) {
message.info('action sucess')
navigate('/')
}
}, [actionData, navigate])
return (
<div>
<LoginForm logo="/favicon.ico" title="快速开始" subTitle="副标题" onFinish={onFinish}>
<ProFormText
name="name"
fieldProps={{
size: "large",
prefix: <UserOutlined className={"prefixIcon"} />,
}}
placeholder={"用户名: admin or user"}
rules={[
{
required: true,
message: "请输入用户名!",
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
strengthText:
"Password should contain numbers, letters and special characters, at least 8 characters long.",
statusRender: (value) => {
const getStatus = () => {
if (value && value.length > 12) {
return "ok";
}
if (value && value.length > 6) {
return "pass";
}
return "poor";
};
const status = getStatus();
if (status === "pass") {
return (
<div style={{ color: token.colorWarning }}>强度:中</div>
);
}
if (status === "ok") {
return (
<div style={{ color: token.colorSuccess }}>强度:强</div>
);
}
return <div style={{ color: token.colorError }}>强度:弱</div>;
},
}}
placeholder={"密码: ant.design"}
rules={[
{
required: true,
message: "请输入密码!",
},
]}
/>
<div
style={{
marginBlockEnd: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
自动登录
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
href=""
>
忘记密码
</a>
</div>
</LoginForm>
</div>
);
}
在 onFinish 中使用 submit 函数进行提交 post 请求,在 action 函数中直接接受 post 方法,并且获取 formData 中的数据,直接返回给前端,这里并没有区分请求方法,直接获取数据。前端通过 useActionData 获取 action 中返回值,然后 message 弹出,并路由跳转到主页的整个流程。
六、小结
本文主要讲解登录中的基本需求,以及 antd pro-component 中的 LoginForm 和 LoginFormPage 方便快捷的接入 React 相关的框架中。其中包含登录前端基本组件的使用以及密码的安全管理。本文篇前端部分,为了方便在 Remix 中有一个简单实现,如果你要需要一个简单快速的实现一个登录页面,值得尝试。