Vue企业级优雅实战05-框架开发01-登录界面
预览本文的实现效果:
# gitee
git clone git@gitee.com:cloudyly/dscloudy-admin-single.git
# github
git clone git@github.com:cloudyly/dscloudy-admin-single.git
git checkout 03_LoginPage
经过前面的一系列准备工作,现在终于进入到框架基础功能及组件的正式开发阶段!我会从登录界面到主界面、业务功能一步步迭代开发,在迭代开发过程中逐步实现通用组件及基础功能框架、第三方框架的整合。首先是登录,依次从界面、表单校验,到网络请求、mock 数据、状态管理等展开描述。本文主要内容:开发登录界面,实现登录界面的国际化及登录表单校验。后续文章将围绕登录,从网络请求 axios 的封装、Mock JS、状态管理等方面进行实现。登录界面效果图如下:

Git 本地仓库切换新分支:
git checkout -b 03_LoginPage
确认分支:
git branch
1 准备工作
1.1 定义插座
通过 vue-cli 创建的工程,App.vue 文件中默认会生成很多内容,将里面的模板、JS、样式都删除,只保留路由的插座即可:
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="scss">
</style>
1.2 创建页面
登录功能属于核心模块,在 core module 中创建登录页面 login.vue,该文件暂时保留最简代码。
src/modules/core/pages/login.vue:
<template>
<div>登录页面</div>
</template>
<script>
export default {
name: 'login'
}
</script>
<style scoped>
</style>
1.3 配置路由
修改路由配置,删除脚手架默认创建的路由,定义登录界面的路由。
src/router/index.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: "about" */ '../modules/core/pages/login')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
1.4 安装模块
我们采用模块化的架构,需要将模块安装到主工程中才能加载该模块。首先在 main.js 中安装 core 模块:
import moduleCore from '@/modules/core'
...
Vue.use(moduleCore, store)
...
1.5 测试
经过上面的几个步骤,已经添加并完成登录界面、核心模块的配置。先测试上述操作是否正确,再进行后续的编码实现工作。在浏览器中访问 http://localhost:8080/login, 如果能否访问刚才创建的登录页面(如下图所示)、控制台不报错,则说明上面的改造没有问题。

1.6 特别说明
这里需要特别说明:上面的路由设置只是临时性的,后面会对路由进行改造,包括动态路由加载、路由配置规则、路由权限控制等内容。不要幻想着一步登天、一口气吃成一个大胖子。实战驱动,需要明确目标、专注。循序渐进、目标分解。跟着文章的节奏,逐个功能点开发实现,最终会潜移默化的得到想得到的。
2 国际化改造
由于系统模块较多,需要国际化的文本也多,如果按照之前定义国家化语言文件的方式,会导致 src/i18n 中的 en.js、zh.js 文件太过庞大,故上面两个文件只保留框架级的语言,其他语言放在各个模块中、并按功能模块进行拆分配置。
2.1 创建目录及文件
src/modules/core/ 按照如下结构创建目录及文件:
modules/
|- core/
|- i18n/ 存放 core module 的语言文件
|- login/ 存放登录功能的语言文件
|- en.js 登录功能的英文
|- zh.js 登录功能的中文
|- index.js 导入并导出 core module 所有功能涉及的英文和中文
|- ...
|- index.js
每个 module 中的 i18n 目录,都用于存放语言文件。在该目录下可以创建多个子目录,每个子目录对应一个功能,里面分别包含 en.js 和 zh.js 文件。 在与子目录平级(i18n目录下)创建 index.js,该文件引入各个子目录功能的语言配置。 最后只需要在 src/i18n 中引入各个模块的语言文件即可。 这样便将语言文件分散到各个 module 中,便于各个 module 自行维护。
2.2 配置 core module 语言文件
src/modules/core/i18n/login/en.js:
export default {
loginTitle: 'User Sign In',
usernamePlaceHolder: 'Please input username',
passwordPlaceHolder: 'Please input password',
validCodePlaceHolder: 'Please input valid code',
login: 'SIGN IN',
username: 'Username',
password: 'Password',
validCode: 'Valid Code',
loginError: 'Sign Error',
userInfoError: 'Get User Info Error',
noFunctionPermission: 'the account has no function permission'
}
src/modules/core/i18n/login/zh.js:
export default {
loginTitle: '用户登录',
usernamePlaceHolder: '请输入用户名',
passwordPlaceHolder: '请输入密码',
validCodePlaceHolder: '请输入验证码',
login: '登录',
username: '用户名',
password: '密码',
validCode: '验证码',
loginError: '登录失败',
userInfoError: '获取用户信息失败',
noFunctionPermission: '该用户没有菜单权限'
}
在 src/modules/core/i18n/index.js 中将其进行聚合并导出:
import loginZh from './login/zh'
export default {
en: {
login: loginEn
},
zh: {
login: loginZh
}
}
2.3 全局引入
完成 core 中登录功能的语言配置后,需要将其引入到全局的语言配置(src/i18n/)中
src/i18n/en.js
export default {
app: {
appName: 'Micro Service Micro Front Platform'
},
valid: {
notNull: ' Not Blank',
minLength: ' min length is ',
maxLength: ' max length is ',
lengthIs: ' length must '
},
core: core.en
}
src/i18n/zh.js
import core from '@/modules/core/i18n/index'
export default {
app: {
appName: '微服务微前端基础框架'
},
valid: {
notNull: ' 不能为空',
minLength: ' 最小长度是 ',
maxLength: ' 最大长度是 ',
lengthIs: ' 长度必须是 '
},
core: core.zh
}
3 登录界面开发
3.1 布局结构
界面布局结构如下:

整个界面的布局采用弹性盒子模型(flex),在《全局设置》中,创建了 mixin.scss,里面定义了一些样式,通过 @import 引入该文件后,便可以方便的使用 @include 混入里面的样式类,这里使用到了 flex() 和 flex-col()。
3.2 代码实现
src/modules/core/pages/login.vue:
<div class="site">
<div class="login-container">
<div class="title">{{ $t('app.appName') }}</div>
<div class="login-wrap">
<div class="login-title">{{ $t('core.login.loginTitle') }}</div>
<div class="login-form">
<el-form ref="form" label-position="left" :model="loginForm">
<el-form-item prop="username">
<el-input v-model="loginForm.username" :placeholder="$t('core.login.usernamePlaceHolder')">
<ds-svg-icon slot="prefix" class-name="login-form-icon" icon="user"></ds-svg-icon>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" :type="passwordType" :placeholder="$t('core.login.passwordPlaceHolder')">
<ds-svg-icon slot="prefix" class-name="login-form-icon" icon="pwd"></ds-svg-icon>
<span class="icon-eye" slot="suffix" @click="onEyeClick">
<ds-svg-icon class-name="login-form-icon" :icon="passwordType === 'password' ? 'eye-close' : 'eye-open'"></ds-svg-icon>
</span>
</el-input>
</el-form-item>
<el-form-item prop="validCode">
<el-input v-model="loginForm.validCode" :placeholder="$t('core.login.validCodePlaceHolder')">
<ds-svg-icon slot="prefix" class-name="login-form-icon" icon="valid-code"></ds-svg-icon>
<span slot="suffix">
<img v-if="this.key" :src="validCodeUrl" @click="onCheckCodeClick" class="valid-code-img">
</span>
</el-input>
</el-form-item>
<el-button type="primary" class="submit-btn" @click="onLoginBtnClick">{{ $t('core.login.login') }}</el-button>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
passwordType: 'password',
key: new Date().getTime(),
validCodeUrl: require('@/assets/demo/valid-code.png'),
loginForm: {
username: '',
password: '',
validCode: ''
}
}
},
methods: {
onEyeClick () {
this.passwordType = this.passwordType === 'password' ? 'text' : 'password'
},
onCheckCodeClick () {
},
onLoginBtnClick () {
}
}
}
</script>
<style scoped lang="scss">
@import "~@/assets/scss/config.scss";
@import "~@/assets/scss/mixin.scss";
.site {
background: url("~@/assets/img/login-bg.jpg") no-repeat;
background-size: 100% 100%;
@include flex-col(center, center);
color: $color6;
.login-container {
width: 100%;
height: 120px;
background-color: rgba(37, 88, 132, 0.4);
padding: 0 20px;
@include flex();
position: relative;
.title {
font-size: 48px;
margin-left: 10%;
}
.login-wrap {
width: 400px;
height: 290px;
background: rgba(255, 255, 255, 0.7);
position: absolute;
right: 10%;
border-radius: 3px;
box-shadow: 1px 2px 2px 1px #FBFBFB;
.login-title {
font-size: 18px;
font-weight: bold;
color: $colorM4;
line-height: 60px;
border-bottom: 1px solid $colorM4;
margin: 0 20px;
}
.login-form {
margin: 20px 20px;
::v-deep .el-input__inner {
border-radius: 0;
}
.login-form-icon {
font-size: 14px;
margin-bottom: -1px;
margin-left: 2px;
color: $color3;
}
.icon-eye {
margin-right: 5px;
cursor: pointer;
}
.valid-code-img {
width: 75px;
height: 24px;
margin-top: 2px;
cursor: pointer;
}
.submit-btn {
margin-top: 20px;
width: 100%;
}
.el-form-item.is-error .login-form-icon {
color: red;
}
}
}
}
}
</style>
登录表单设计了三个字段:用户名 username,密码 password,验证码 validCode。实现了密码的明文、暗文切换。
验证码采用本地图片模拟,后面会专门用一篇文章记录登录的逻辑(前端使用公钥对密码进行处理、后端解析后加密存储、生成 jwt token等,整个过程使用 RSA、Spring Cloud OAuth 2、 JWT、非对称加密等知识)。
4 表单验证
4.1 通用规则封装
在中台后台管理系统中,表单验证是个必备的功能,很多规则可以复用,于是我定义了 rules.js 文件。该文件用于存放通用的规则。现在只使用到非空校验、长度校验规则,后续有新的规则(如时间校验、数字校验、手机号校验等),我会把规则添加到这个文件中,不断迭代完善。
src/common/rules.js
import i18n from '@/i18n'
const validNotNullAndLengthRange = (rule, value, callback, displayName, minLength, maxLength) => {
if (!value) {
callback(new Error(`${displayName}${i18n.t('valid.notNull')}`))
} else {
if ((minLength > -1) && (value.length < minLength)) {
callback(new Error(`${displayName}${i18n.t('valid.minLength')}${minLength}`))
} else if ((maxLength > -1) && (value.length > maxLength)) {
callback(new Error(`${displayName}${i18n.t('valid.maxLength')}${maxLength}`))
} else {
callback()
}
}
}
const validNotNullAndLength = (rule, value, callback, displayName, length) => {
if (!value) {
callback(new Error(`${displayName}${i18n.t('valid.notNull')}`))
} else {
if (value.length !== length) {
callback(new Error(`${displayName}${i18n.t('valid.lengthIs')}${length}`))
} else {
callback()
}
}
}
export default {
/**
* 校验字符串: 非空
* @param displayName 错误提示展示的字段名
*/
notNull: (displayName) => {
return { required: true, message: `${displayName}${i18n.t('valid.notNull')}` }
},
/**
* 校验字符串: 非空 且 长度区间
* @param displayName 错误提示展示的字段名
* @param minLength 最小长度(-1表示不限制)
* @param maxLength 最大长度(-1表示不限制)
*/
notNullAndLengthRange: (displayName, minLength, maxLength) => {
return {
validator: (rule, value, callback) => validNotNullAndLengthRange(rule, value, callback, displayName, minLength, maxLength)
}
},
/**
* 校验字符串: 非空 且 长度等于
* @param displayName 错误提示展示的字段名
* @param length 长度值
*/
notNullAndLength: (displayName, length) => {
return {
validator: (rule, value, callback) => validNotNullAndLength(rule, value, callback, displayName, length)
}
}
}
4.2 全局挂载
由于系统中很多地方都要使用校验规则,将上面的校验规则挂到 Vue 原型的 commonRules 属性上,这样在 Vue 文件便可以方便的通过 this.commonRules.xxx 进行使用。
main.js:
import rules from '@/common/rules'
...
Vue.prototype.commonRules = rules
...
4.3 登录表单
改造登录表单,使其支持验证。首先修改 login.vue 的表单,为其添加校验规则 :rule="loginRules",
然后在 data 中定义规则 loginRules:
return {
// ...
loginRules: {
username: [
this.commonRules.notNullAndLengthRange(this.$t('core.login.username'), 4, 18)
],
password: [
this.commonRules.notNullAndLengthRange(this.$t('core.login.password'), 6, 18)
],
validCode: [
this.commonRules.notNullAndLength(this.$t('core.login.validCode'), 4)
]
}
}
},
上面的规则分表表示:
用户名:不能为空,长度 4 - 18 位;
密码:不能为空,长度 6 - 18 位;
验证码:不能为空,长度 4 位。
刷新页面,现在便可以在输入内容或失去焦点时进行该字段的校验了。但点击按钮,还不会校验表单。
4.4 提交时校验
实现点击 ‘登录’ 按钮时,对登录表单进行校验,这里使用 es6 中的 async await 方式进行异步操作:
async onLoginBtnClick () {
const valid = await this.$refs.form.validate()
if (!valid) {
return
}
console.log('表单校验通过,提交数据')
}
此时,点击按钮会对表单进行校验,校验通过才会执行后面的逻辑。
下一篇将会整合 axios 到工程中。
提交代码:
git cz
[框架开发] 登录界面
合并到 master 分支:
git merge 03_LoginPage
将本地分支分别全部推送到 Gitee 和 GitHub
git push --all github_origin
更多内容请关注我的个人公众号,留言可加我个人微信或交流问题

Vue企业级优雅实战05-框架开发01-登录界面的更多相关文章
- Vue企业级优雅实战04-组件开发01-SVG图标组件
(后续的文章 公众号会提前一周更新,欢迎关注文末的微信公众号:程序员搞艺术) 预览本文的实现效果: # gitee git clone git@gitee.com:cloudyly/dscloudy- ...
- Vue企业级优雅实战-00-开篇
从2018.1.开始参与了多个企业的中台建设,这些中台的技术选型几乎都是基于 Spring Cloud 微服务架构 + 基于 Vue 全家桶的前端.我前后端架构及开发我几乎各占一半的精力,在企业级前端 ...
- Vue企业级优雅实战03-准备工作04-全局设置
本文包括如下几个部分: 初始化环境变量文件 JS 配置文件初始化:如是否开启 Mock 数据.加载本地菜单.URL 请求路径等: 国际化文件初始化:初始化国际化文件的结构: 整合 Element UI ...
- Vue企业级优雅实战02-准备工作03-提交 GIT 平台
代码管理.版本管理是件老大难的事情,尤其多人开发中的代码冲突.突击功能时面临的 hotfix 等.本文只是简单说说如何将一套代码提交到两个 Git 平台(GitHub.GitEE)上.其他的 Git ...
- Vue3 企业级优雅实战 - 组件库框架 - 1 搭建 pnpm monorepo
前两篇文章分享了基于 vite3 vue3 的组件库基础工程 vue3-component-library-archetype 和用于快速创建该工程的工具 yyg-cli,但在中大型的企业级项目中,通 ...
- vue.js2.0实战(1):搭建开发环境及构建项目
Vue.js学习系列: vue.js2.0实战(1):搭建开发环境及构建项目 https://my.oschina.net/brillantzhao/blog/1541638 vue.js2.0实战( ...
- eclipse Tomcat和 MYSQL JAVA web新手开发示例--登录界面连接数据库
登录界面login.jsp 1 <%@ page language="java" import="java.util.*" contentType=&qu ...
- Vue3 企业级优雅实战 - 组件库框架 - 3 搭建组件库开发环境
前文已经初始化了 workspace-root,从本文开始就需要依次搭建组件库.example.文档.cli.本文内容是搭建 组件库的开发环境. 1 packages 目录 前面在项目根目录下创建了 ...
- Vue3 企业级优雅实战 - 组件库框架 - 2 初始化 workspace-root
上文已经搭建了 pnpm + monorepo 的基础环境,本文对 workspace-root 进行初始化配置,包括:通用配置文件.公共依赖.ESLint. 1 通用配置文件 在项目 根目录 下添加 ...
随机推荐
- [LeetCode]11. 盛最多水的容器(双指针)
题目 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...
- [程序员代码面试指南]数组和矩阵-数组的partition调整
题目 补充问题:数组只含0,1,2,对数组排序,要求时间复杂度O(n),额外空间复杂度O(1) 题解 维护三个变量,l,idx,r.左区间[0,l],中间区间[l+1,idx],右区间[idx+1,r ...
- [Binder深入学习一]Binder驱动——基础数据结构
具体代码路径: kernel/drivers/staging/android/binder.c kernel/drivers/staging/android/binder.h /* * binder_ ...
- 在CentOS 7服务器中使用Jexus发布.net core webapi
环境: 服务器:CentOS 7 64位 .net core 2.1 Jexus独立版 官网:https://www.jexus.org/ 按照官网安装独立版命令:curl https://jexus ...
- P1295 [TJOI2011]书架 线段树优化dp,单调栈
P1295 [TJOI2011]书架 本题思路比较好想(对我来说不是),但代码细节很多,奈何洛谷的题解只有思路,然后就是 没有丝毫解释的代码,让人看起来很头疼(~~ 尤其是像我这样的蒟蒻~~),所以便 ...
- 我的Python自学之路-001 列表的知识
#_date_:2020/9/11 '''列表和字典是python中用的最多的数据类型 假如要存储一个班级的人名,需要怎么做?有这么几种方法:1.定义很多个变量: name0 = 'wucaho' n ...
- JDK8在windows系统下安装
一.下载 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK8 目前大部分公司内部使用的还是jdk ...
- stringstream使用
stringstream的头文件是<sstream>,stringstream可以作为中间介质,实现字符串和数字之间的转换. 数字转string double a=213; string ...
- 新版 C# 高效率编程指南
前言 C# 从 7 版本开始一直到如今的 9 版本,加入了非常多的特性,其中不乏改善性能.增加程序健壮性和代码简洁性.可读性的改进,这里我整理一些使用新版 C# 的时候个人推荐的写法,可能不适用于所有 ...
- 数据类型-字符串(str)
1.只要是被单引号,双引号,三引号括起来的,都是字符串类型 2.字符串里面元素:单个字母,单个符号,都称之为一个元素 例如:s='hello!' (6个元素) len(数据)统计数据的长度pri ...