LoginForm 组件抽象与 SinglePage 布局
概述
登录页正式落地时,与其把所有表单结构硬写在页面里,不如先抽出一个独立的 LoginForm 组件,再由页面决定它摆在左、中、右哪个位置。配套地,认证页不应沿用后台三段式主布局,而是用 SinglePage 这类轻布局承载。本章把认证页的实现层次理顺:页面层管路由与布局,SinglePage 管整体承载与全局工具入口,LoginForm 管表单结构,i18n 与第三方登录作为可配置能力补充。
学习目标
- 理解接入认证页前先清理临时演示路由的必要性
- 掌握把
LoginForm抽成独立组件、用position解耦布局位置的思路 - 识别最值得先参数化的三类 props:
position、title、loginItems - 用
getIcon()图标工厂函数统一输入框前缀图标 - 用
SinglePage独立布局承载认证页,并用hideMenu排除后台菜单 - 在独立布局顶部挂语言切换与暗黑模式入口,复用已有全局组件
- 理解
title与hideMenu可同时存在,元信息照常工作但不进导航 - 认识分层职责:布局壳与表单组件的边界不可互相越界
- 掌握认证页与注册/找回页共享
LoginForm壳子的复用思路
一、接入认证页前先清理临时演示路由
登录注册页是系统正式入口,不应建立在一堆演示页面之上。先删掉为演示菜单折叠、顶部导航临时复制的页面,保留系统真正需要的页面结构,避免登录页一接入后路由和菜单仍残留无意义入口。这一步也有助于后续权限控制和菜单收口。
二、先抽 LoginForm 组件,再用 position 解耦布局位置
把"登录框"抽成独立组件后,页面层负责布局、表单组件负责输入结构,后续注册页、忘记密码页也更容易复用这套壳子。课程抽象出一个关键的布局变量 position(left / center / right),描述表单整体在页面中的位置——位置是布局属性而非表单业务属性,先做成 props 比后面拆布局逻辑更轻松。
interface LoginFormProps {
position?: "left" | "center" | "right"
title?: string
loginItems?: LoginItem[]
}三、最值得先参数化的是展示层差异大的部分
position、title、loginItems 三者带有明显的展示层差异:不同项目登录框位置不同、标题可有可无、第三方入口数量与图标不同。相比起来,账号、手机号、验证码等基础字段在这一节还不需要先做成动态表单模型。先抽项目差异大的部分收益最高,字段先写死、展示入口先抽象,是更务实的推进顺序。
四、基础表单模型先覆盖常见认证字段
先把最常见的认证字段收进一个 reactive 对象:username、password、phone、code、email、rePassword、remember。这一步的重点是"先建模",覆盖用户名 / 密码 / 手机验证码 / 邮箱注册 / 确认密码 / 记住密码等模式,后续再按登录态和注册态拆分。
const form = reactive({
username: "",
password: "",
phone: "",
code: "",
email: "",
rePassword: "",
remember: false
})五、getIcon() 统一输入框前缀图标
多个 el-input 需要用户、锁、手机、邮箱等前缀图标,若每个字段都重复写一段图标渲染逻辑,代码很快啰嗦。getIcon() 这类小型工厂函数返回渲染函数,统一风格并减少重复,后续若要统一图标尺寸、颜色也更有价值。
function getIcon(icon: string) {
return () => h(Iconify, { icon })
}六、用 SinglePage 独立布局承载认证页
登录页和后台主布局需求完全不同:它不需要 Header / Sidebar / Content 三段式,更需要整页居中、左右分栏或单卡片。通过 definePage({ meta: { layout: "single-page" } }) 让它走单独布局,比复用后台主布局清晰得多,后续加注册页、找回密码页也更方便。
七、position 本质是对齐策略而非绝对定位
控制表单位置时,更合理的是页面整体 flex 占满全屏,再通过 self-start / self-center / self-end 调整主轴位置,并把布局轴改成 flex-col,使"左 / 中 / 右"成为横向自对齐策略。这比写死 left: 0 / right: 0 更灵活、更适合响应式;为避免贴边,还应补横向 margin。位置是"布局壳"行为,不应散落在表单内部节点上。
八、i18n 优先提取高频入口文案
登录页属于入口页,国际化文案更要统一稳定。优先提取 placeholder、登录按钮、注册账号、记住我等高频率文案,收进同一命名空间(如 pages.login.username、pages.login.submit)。这类页面文案量小但结构完整,很适合作为 i18n 实践页。
九、SinglePage 顶部挂全局工具入口,并用 hideMenu 排除菜单
认证页接入 i18n 与暗黑模式后,若页面没有切换入口,能力实际不可用。由于认证页不走主后台 Header,最自然的做法是在 SinglePage 顶部 fixed 挂一组轻量工具按钮,复用已有的 DarkModeToggle 与 ChangeLocale,既不干扰中间表单又所有认证页共享。同时,登录页即便配置了 title,也应通过路由 meta 的 hideMenu: true 排除在后台菜单之外——title 与 hideMenu 可同时存在的,元信息和 i18n 继续工作但不进工作区导航。
definePage({
meta: {
layout: "single-page",
title: "pages.login.title",
hideMenu: true
}
})分层职责应保持清晰:SinglePage 管全屏居中、顶部工具区与路由承载,LoginForm 只管标题、输入框、按钮、第三方入口。不要把暗黑模式、语言切换再塞回 LoginForm;数据(如 locales)可先局部复用,后续再决定是否提到全局配置。
十、认证页与注册、找回页共享壳子
LoginForm 抽出后最大的收益是复用:注册页、找回密码页、甚至企业邀请激活页,都可以共用同一套表单壳,只替换标题、loginItems 与提交逻辑。共享壳子时要守住边界——表单组件只暴露 submit 事件和必要的 v-model 字段,页面层负责决定提交后跳哪、调哪个接口。这样认证相关页面越多,壳子的边际成本越低,也不会出现每个页面重写一遍输入框图标的浪费。
// 注册页复用 LoginForm,只改提交
<LoginForm
:title="$t('pages.register.title')"
:login-items="registerItems"
@submit="handleRegister"
/>常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 登录页一上来就写得很重 | 试图一次性做完登录、注册、短信、验证码和校验 | 先抽 LoginForm 基础壳子,只落地账号密码登录 |
| 登录页沿用后台主布局很奇怪 | Header、Sidebar 对认证页是冗余结构 | 单独做 SinglePage 布局 |
| 表单位置切换难控制 | 位置逻辑写死在页面节点里 | 抽 position props,在布局壳做对齐策略 |
| 前缀图标写得很重复 | 每个输入框都手写图标组件 | 用 getIcon() 统一生成 |
| 登录页已接 i18n 但用户没法切换语言 | 认证页没提供切换入口 | 在 SinglePage 顶部复用 ChangeLocale |
| 登录页配了 title 却进了后台菜单 | 菜单系统读取页面元信息生成导航 | 路由 meta 加 hideMenu: true |
| 暗黑/语言切换塞回 LoginForm | 职责越界 | 放 SinglePage 顶部全局工具区 |
| 注册页又重写一遍表单 | 没复用壳子 | 共用 LoginForm,只换 title/items/submit |
| title 和 hideMenu 冲突 | 误以为二者互斥 | 可同时设置,元信息工作但不进菜单 |