一个文笔一般,想到哪是哪的唯心论前端小白。
🧠 - 简介
常见的登录主要有这么些个:
- 账号密码登录:包含动态码、硬件设备、人脸识别...,主要是指站点或者平台自己实现的一整套登录注册流程
- 单点登录:由中间层 SSO 服务统一管理账号登录状态,并由专门用户管理平台去生产账号,多见于集团内部,一个工号可以登录集团内部所有的细平台
- 第三方登录:借用现有的平台的账号进行登录,并和本站点的数据进行互通,在现在互联网场景下非常常见
本文主要关注的就是第一个,账号密码登录中最常用的 注册 -> 登录 -> 忘记密码
这一套流程,并且简单记录一下开发过程。
本篇概览:
- 一个好看的登录+注册UI页面
- 全局scss变量的封装及使用
- 表单校验项统一代理及常见正则表达式的搜集机制
👁️ - 分析
首先,鉴于已经选好了 element-plus 作为UI组件库,并且已经有了复用性比较高的 layout 组件,可以说这里是为数不多的能够自己写一写 css 样式的模块了。
作为一个极简主义者,我一直不太喜欢把页面搞得太过于花里胡哨,只要能用就行哈!
登录
登录是很常见的那种登录,左边放个宣传小图,右边是主要功能区域,包含了扫码登录,三方登录,账号密码登录,忘记密码,记住密码,用户须知,注册这么些个功能。
其实登录页面还是很复杂的,很有搞头的!
注册
而注册页面就很简单了,为了简化用户的注册流程以及丰富用户的注册场景。支持三种注册方式:手机号注册(当前最常用),邮箱注册(国外友人的最爱),账号密码注册。
手机号注册和邮箱注册主要是验证码逻辑,需要后台配合发送邮件和短信验证码。
账号密码注册则主要是不法分子注意恶意注册,现在常用的方式主要前端人工校验,后台锁定IP之类的方案。
🫀 - 拆解
全局 scss 变量 和 mixins 的提取和安装
前文的目录结构中已经声明,src/assets/styles
中存放 css 样式文件。
基于这个约定,我又新建了 global-scss 文件,里面存放全局的 scss 文件,并由 index.scss 进行导出,只要引用到 index.scss 就能包含所有的模块了,这样主要是为了方便以后一旦 某个文件太大了,或者难以维护了,进行按需引入做准备。例如:对 global-mixins 进行拆分,然后由 index.scss 进行组装。
scss
// global-mixins.scss
// flex 布局:左右,上下,方向,换行
@mixin flex( $justify: flex-start, $aline: flex-start, $direction: row, $wrap: nowrap) {
display: flex;
flex-direction: $direction;
justify-content: $justify;
align-items: $aline;
flex-wrap: $wrap;
}
// 单行溢出 ...
@mixin single-ellipsis(){
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
// 多行文本溢出 ...
@mixin multi-ellipsis($line:2){
overflow : hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $line;
-webkit-box-orient: vertical;
}
// global-variable.scss
$headerHeight: 60px;
$footerHeight: 100px;
$mainWidth: 1336px;
$white: #eef7f2;
$blue: #3a89b0;
$black: #333;
// index.scss
@import './global-variable.scss';
@import './global-mixins.scss';
安装 globalSass,只需要在 vite.config.ts 中配上就好了:
ts
export default defineConfig({
// ...
// 配置全局 scss 变量文件
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/assets/styles/global-scss/index.scss";`,
charset: false,
},
},
},
// ...
})
如此这般,就可以在任何 vue 文件里面使用 被 /global-scss/index.scss 导出的scss变量了!
校验类的提取和使用
表单校验是最常见的一个场景了,而且 element-plus 已经实现了表单校验,为什么要再次提取呢?
因为我懒,不想去写如下的表单规则配置:
不能说这么写有什么问题,可维护性也没问题,但是我觉着这么用我更喜欢,仅此而已!
简单解释一下,我将 Validate 这个类,作为了常量来使用,在这个类中声明了五个方法:
- REQUIRED:必填项
- RANGE:长度限制
- TYPE:常见类型或者实际生活中固定结构的验证:手机号、邮箱、身份证号...
- REG:正则表达式校验
- CUSTOM:自定义校验,正则无法满足且不是常见类型
源码:
ts
type TriggerType = 'blur' | 'change'
export const NOUNMAP:{[key :string]: (str:string) => boolean} = {
USERNAME: (str: string) => /^[a-zA-Z0-9_-]{4,16}$/.test(str), // 用户名
CNNAME: (str:string) => /^[\u0391-\uFFE5]+$/gi.test(str), // 汉字 + 生僻字
NUM: (str: string) => /^-?\d*.?\d+$/.test(str), // 正负数 + 小数
EMAIL: (str: string) => /^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,4})$/.test(str),
PHONE: (str: string) => /^1[34578]\d{9}$/.test(str),
ID: (str: string) => /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(str),
URL: (str: string) => /^(https?|ftp|file):\/\/([\da-z.-]+)\.([a-z.]{2,6})([/\w.-]*)*\/?$/.test(str),
CARID: (str: string) => /^([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[a-zA-Z](([DF]((?![IO])[a-zA-Z0-9](?![IO]))[0-9]{4})|([0-9]{5}[DF]))|[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1})$/.test(str), // 车牌号
PASSWORD: (str: string) => /^[a-zA-Z0-9\s~`!@#$%^&*()_+-={}[\]|\\:;"'<,>.?/]+$/.test(str)
}
// 正则校验 - 汇总搜集
export const BASEREGMAP: {
[key :string]: RegExp
} = {
password: /^[a-zA-Z0-9\s~`!@#$%^&*()_+-={}[\]|\\:;"'<,>.?/]+$/
}
export default class VALIDATE {
/**
* 必填项校验
* @param label 中文备注
* @param trigger 触发方式,默认 blur
* @returns 校验范式
*/
static REQUIRED(label: string, trigger: TriggerType = 'blur') {
return { required: true, message: `${label}为必填项!`, trigger }
}
/**
* 长度范围校验
* @param min 最小长度
* @param max 最大长度
* @param label 中文备注
* @param trigger 触发方式
* @returns 校验范式
*/
static RANGE(min = 0, max = 16, label = '', trigger: TriggerType = 'blur') {
return { min, max, message: `${label}长度应该在${min}到${max}之间!`, trigger }
}
/**
* 内置类型正则校验
* @param type 内置类型 keyof typeof NOUNMAP
* @param label 中文备注
* @param trigger 触发方式
* @returns 校验范式
*/
static TYPE(type: keyof typeof NOUNMAP, label: string, trigger: TriggerType = 'blur'){
return {validator: (rule: any, value: any, callback: any) => {
if(!NOUNMAP[type]){
throw Error(`【入参错误】,${type} 在 NOUNMAP 中不存在,触发点 VALIDATE.TYPE!`)
}
if(!NOUNMAP[type](value)){
callback(new Error(`请输入正确的${label}`))
}else{
callback()
}
}, trigger}
}
/**
* 传入一个正则表达式进行校验
* @param reg 正则表达式
* @param desc 对应文案
* @param trigger 触发方式
* @returns 校验范式
*/
static REG(reg: RegExp, desc:string,trigger: TriggerType = 'blur'){
return {validator: (rule: any, value: any, callback: any) => {
if(!reg.test(value)){
callback(new Error(`${desc}`))
}else{
callback()
}
}, trigger
}
}
/**
* 自定义校验
* @param validator 校验方法
* @param trigger 触发方式
* @returns 校验范式
*/
static CUSTOM(validator: (rule: any, value: any, callback: any) => void, trigger: TriggerType = 'blur'){
return {validator, trigger}
}
}
如代码中所示,对外还暴露了一个BASEREGMAP的对象,用来搜集一些正则表达式,提高了这个小插件的扩展性。
页面小细节的处理
1. 实现三角形的二维码
三角形的二维码就是在二维码上盖了一个平行四边形,如果喜欢的话可以盖上任何东西。
2. 背景图片不失真
背景图片不失真的主要是用到了 css 的这个属性:
scss
background-size: auto 100%;
background-position: center;
background-size 值里面的100%给到值小的那个, 例如浏览器的 width>height ,那么 height 方向就是 100%。
3. 记住密码是怎么回事
正常情况下,浏览器(Chrome)会自动提醒你是否记录密码,但是有些特殊场景需要的话就增加一段逻辑了。
我是这么设计的,如果勾选的话,登录成功,就会存入localstorage中去,每次进入登录页面都会去 localstorage中读,如果读到了就自动填充。点击登录按钮时,如果没有勾选记住密码,则清空localstorage。
💪 - 落实
主要说明一下三方登录和扫码登录,这两个都需要后台紧密配合,而且需要走特定的申请流程。所以都是暂缺,遇到实际情况实际解决,而且跟本平台没有耦合性,只是放了个样子。
🛀 - 总结
看到这里,很容易被喷 过度封装。
面对这个模版项目我也思考过这个问题,之所以有人觉得过度封装,主要原因还是因为两点:
- 封装者没有提供完善的学习资料或者没有遵循 高内聚、低耦合、易拔插、易扩展 的理念,给后来者以无从下手的感官;
- 使用者有各自的开发习惯,不想受到封装者的约束,就冠之以 过度封装 的标签。
个人理解,每个封装的出发点都是好的,但是因为没有考虑周全或者其他原因肯定会存在这样或那样的不足,这就需要不停的优化和升级。像 vue/react 甚至 jQuery 这么优秀的框架(库)都在不停的迭代过程中。
所以我的结论就是,我就是这么封装的,喜欢你就用,不喜欢的话除非你能主导,不然你就受着。
推荐阅读
系列文章:
- 脚手架开发
- 模板项目初始化
- 模板项目开发规范与设计思路
- layout设计与开发
- login 设计与开发
- CURD页面的设计与开发
- 监控页面的设计与开发
- 富文本编辑器的使用与页面设开发设计
- 主题切换的设计与开发并页面
- 水印切换的设计与开发
- 全屏与取消全屏
- 开发提效之一键生成模块(页面)