写在前面
接上一篇,本次增加了以下功能(第三项用前端思维难以理解,所以放到最后,方便忽略)
- 路由守卫
- 加入
element 表单验证
- 基于
oop
思想封装业务层ts
实现,提供默认实现与自定义实现
一、路由守卫
作用:当用户没有登录时,将其定向到登录页
,不能做其他页面的操作。
在router/index.ts
中增加:
// 先加入相关引用
import {AUTH} from "@/ts/common/auth"
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue'
//配置相关的页面 requireAuth属性,为true表示需要被守卫验证
const routes: Array<RouteRecordRaw> = [
{
path: AUTH.LOGIN_PATH,
name: 'login',
component: LoginView,
meta: {
title: "登录页",
requireAuth: false // 标识该路由是否需要登录
}
},
{
path: '/home',
name: 'home',
component: HomeView,
meta: {
title: "首页",
requireAuth: true // 标识该路由是否需要登录
}
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
//在这个函数中增加守卫验证代码
router.beforeEach(
(to, from, next) => {
/**
* 未登录则跳转到登录页
* 未登录跳转到登录页,也可以通过axios的响应拦截器去实现,但是会先在当前页面渲染一下,再跳转到登录页,会有个闪动的现象
* 这里通过路由守卫的方式,不会在当前页闪现一下,但是需要在每个路由组件添加一个是否需要登录的标识位,如本项目中的requireAuth字段
*/
if (to.matched.some((auth) => auth.meta.requireAuth)) {
if (AUTH.isXtoken()) {
next()
} else {
console.log("未登录")
next({
path: AUTH.LOGIN_PATH
});
}
} else {
next()
}
})
二、加入element 表单验证
作用:验证准备提交的参数是否合法,并且给出错误提示
下面都是LoginView.vue
的相关代码(代码太多,我简化了)
- 在
data方法
中增加一个rules块
来描述字段的规则及提示信息,字段要与formData块
的字段一致 - 在
data方法的return块
中增加一个formData块
来定义表单字段及默认值 - 在
data方法的return块
中增加validate字段
来方便控制表单提交的前进或停止动作
export default defineComponent({
name: "LoginView",
data() {
return {
isLoading: false,
validate: false,
formData: {
name: "",
pwd: ""
},
rules: {
name: [
{required: true, message: '请输入账户名', trigger: 'blur'},
{min: 1, max: 5, message: '长度在 1 到 5 个字符', trigger: 'blur'}
],
pwd: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 1, max: 5, message: '长度在 1 到 5 个字符', trigger: 'blur'}
],
}
}
}
}
- 在页面上的
el-form
表单上加上:model属性、:rules属性
,属性值就是上面data块
中定义的 - 在页面上的
el-form
表单上加上ref属性
,属性值可以随意写,建议最好是rule
字符串开头,后面跟上model属性
,好区分一点 - 在
el-form-item
上增加prop属性
,属性值是rules块
中定义的 - 另外
el-button
上我做了一个:loading
,这个太简单就不说了
<el-form :inline="true" :model="formData" :rules="rules" ref="ruleForm" class="login_form">
<el-form-item label="账号" prop="name">
<el-input v-model="formData.name" placeholder="请输入账号"></el-input>
</el-form-item>
<el-button type="primary" class="ht_btn login_btn" :loading="isLoading" @click="login">login
</el-button>
</el-form>
- 在
defineComponent函数methods块
中增加validateForm函数
methods: {
//需要验证这个表单只需要调用就行
validateForm() {
//这里必须要重新申明对象为any,网上说因为form不是响应式的
let ruleForm: any = this.$refs.ruleForm
return ruleForm.validate((valid: boolean) => {
this.validate = valid
});
},
login() {
//验证表单
this.validateForm()
if (!this.validate) return
// 开始登录
}
}
- 附上一张效果图
三、基于oop
思想封装业务层ts
实现,提供默认实现与自定义实现
- 为什么要这么做:
LoginView.vue
离业务最近,其次是对应的logIn.ts
- 我想让这两者更关注业务实现(向哪里以什么方式提交什么数据,成功或失败的处理函数)
- 最大的一点就是减少代码量,少写代码,默认实现,同时保留自定义扩展
基于以上的想法,我从login.ts
中做了一层OOP
抽象,出现了一个serviceRequest.ts
,它可以帮你完成绝大部分工作,并且具有默认实现,以自定义实现,有需要你还可以重写它的方法来达到你的预期。
我很贴心的照顾了习惯使用promise
的开发者,哈哈哈
- 以下是
serviceRequest.ts
代码
import {ElMessage} from "element-plus";
/**
* 定义一个公共业务接口
*/
export interface BaseServiceInterface {
/**
* 设置即将执行的渲染回调
* @param successCallback 成功回调
* @param failedCallback 失败回调
*/
nextCallback(successCallback?: Function, failedCallback?: Function): void;
/**
* promise 处理
* 当 nativeCallback 不为 null 时具有最高优先级
* @param promise 基于http请求的promise对象
* @param nativeCallback 用promise原本的方式回调处理
*/
promiseHandle(promise: Promise<any>, nativeCallback?: Function): void;
}
/**
* 默认实现公共业务接口
*/
export class BaseService implements BaseServiceInterface {
constructor(
public successCallback?: Function,
public failedCallback?: Function
) {
}
/**
* 将回调函数设置值
* @param successCallback 成功回调
* @param failedCallback 失败回调
*/
nextCallback(successCallback?: Function, failedCallback?: Function) {
this.successCallback = successCallback
this.failedCallback = failedCallback
}
/**
* promise 默认处理
* 当 nativeCallback 不为 null 时覆盖 promiseHandle 默认处理,直接执行 nativeCallback
* @param promise 基于 http 请求的 promise 对象
* @param nativeCallback 用 promise 原本的方式回调处理
*/
promiseHandle(promise: Promise<any>, nativeCallback?: Function) {
if (nativeCallback != null) {
nativeCallback(promise)
} else {
promise.then(res => {
//成功回调
if (this.successCallback != null) {
this.successCallback(res)
this.successCallback = () => {
}
}
}).catch(err => {
//失败回调
if (this.failedCallback != null) {
this.failedCallback(err)
this.failedCallback = () => {
}
}
ElMessage.error(err.message)
})
}
}
}
基于这个serviceRequest.ts
,重写了login.ts
代码,效果就在这里了,请看现在的login.ts
有多简单
- 以下是
login.ts
代码
import service from "@/ts/common/http"
import {BaseService} from "@/ts/common/serviceRequest";
/**
* 定义了当前 ts 登录模块的业务方法
*
*/
interface LoginInterface {
/**
* 登录
* @param login 登录的参数
* @param nativeCallback 可选,用 promise 原本的方式回调处理的函数
*/
login(login: LoginBO, nativeCallback?: Function): void;
/**
* 退出登录
* @param nativeCallback 可选,用 promise 原本的方式回调处理的函数
*/
out(nativeCallback?: Function): void;
/**
* 验证登录是否有效
* @param nativeCallback 可选,用 promise 原本的方式回调处理的函数
*/
checkToken(nativeCallback?: Function): void;
}
/**
* 定义 login 请求的入参
*/
export class LoginBO {
constructor(public name: string, //账号
public pwd: string, //密码
public platform?: number //平台(pc:1)
) {
}
}
/**
* 定义 login 请求的业务出参,相当于 AResponse.data;http.ts 会自动转义的
*/
export class UserVO {
constructor(public code?: string, //状态码
public userNo?: string, //用户编号
public mobile?: string, //手机号
public newUser?: boolean, //是新用户(是:true,否:false)
public deletedUser?: boolean, //是已禁用用户(是:true,否:false)
public nick?: string, //用户昵称
public photo?: string, //用户头像
public xtoken?: string, //X-TOKEN
public successful?: boolean //是否登录成功
) {
}
}
/**
* 实现 LoginInterface
* 继承 BaseService 获得基础实现
*/
class LoginService extends BaseService implements LoginInterface {
/**
* 注意:这个方法可以删除,不是必须的,这里写上只是演示方法重写
* 若 base 类不满足需求,可以重写父类实现
* 子类重写 nextCallback 方法
*/
override nextCallback(successCallback?: Function, failedCallback?: Function) {
console.log("LoginService nextCallback")
super.nextCallback(successCallback, failedCallback)
}
/**
* 注意:这个方法可以删除,不是必须的,这里写上只是演示方法重写
* 若 base 类不满足需求,可以重写父类实现
* 子类重写 promiseHandle 方法
*/
override promiseHandle(promise: Promise<any>, nativeCallback?: Function) {
console.log("LoginService promiseHandle")
super.promiseHandle(promise, nativeCallback)
}
/**
* 通过调用 promiseHandle 实现 login 请求
* @param loginBO 登录参数
* @param nativeCallback 若不为空则覆盖 promiseHandle 默认处理,直接执行 nativeCallback
*/
login(loginBO: LoginBO, nativeCallback?: Function) {
this.promiseHandle(service.post('/login/', loginBO), nativeCallback)
}
/**
* 通过调用 promiseHandle 实现 checkToken 请求
* @param nativeCallback 若不为空则覆盖 promiseHandle 默认处理,直接执行 nativeCallback
*/
checkToken(nativeCallback?: Function) {
this.promiseHandle(service.get('/login/check_token'), nativeCallback)
}
/**
* 通过调用 promiseHandle 实现 out 请求
* @param nativeCallback 若不为空则覆盖 promiseHandle 默认处理,直接执行 nativeCallback
*/
out(nativeCallback?: Function) {
this.promiseHandle(service.get('/login/out'), nativeCallback)
}
}
export let loginService = new LoginService()
代码重点在 class LoginService
最后的3
个业务函数,只需要一行代码即可。基于login.ts
在LoginView.vue
的使用方式有很多玩法了
- 以下是
LoginView.vue
的代码
methods: {
validateForm() {
let ruleForm: any = this.$refs.ruleForm
return ruleForm.validate((valid: boolean) => {
this.validate = valid
});
},
login() {
// 表单验证
this.validateForm()
if (!this.validate) return
// 按钮加上 loading 属性
this.isLoading = true
// 用最原始的方式使用 promise 来处理 Callback
// 并且自动转义 promise<T> 对应的返回数据模型
// 此 Callback 会覆盖 nextCallback 的执行
loginService.login(new LoginBO(this.formData.name, this.formData.pwd),
(promise: Promise<AResponse<UserVO>>) => {
promise.then(aResponse => {
// 成功的处理函数
this.isLoading = false
AUTH.setXtoken(aResponse.data!!.xtoken!!)
console.log(aResponse)
this.$router.push({name: "home"})
}).catch(aResponse => {
// 失败的处理函数
this.isLoading = false
console.log(aResponse)
})
})
},
login2() {
// 表单验证
this.validateForm()
if (!this.validate) return
// 按钮加上 loading 属性
this.isLoading = true
// 先调用 nextCallback 设置函数
// 使用 loginService 的默认实现 来处理 Callback ,这里我传递了成功和失败的2个 Callback
// 并且自动转义 promise<T> 对应的返回数据模型
loginService.nextCallback((aResponse: AResponse<UserVO>) => {
// 成功的处理函数
this.isLoading = false
AUTH.setXtoken(aResponse.data!!.xtoken!!)
console.log(aResponse)
this.$router.push({name: "home"})
},
(aResponse: AResponse<UserVO>) => {
// 失败的处理函数
this.isLoading = false
console.log(aResponse)
})
// 调用登录接口
loginService.login(new LoginBO(this.formData.name, this.formData.pwd))
},
out() {
// 先调用 nextCallback 设置函数
// 使用 loginService 的默认实现 来处理 Callback ,这里我只传递了成功的 Callback ,
// 并且自动转义 promise<T> 对应的返回数据模型
loginService.nextCallback((aResponse: AResponse<void>) => {
// 成功的处理函数
AUTH.removeXtoken()
console.log(aResponse)
})
// 调用退出登录接口
loginService.out()
},
checkToken() {
// 使用 loginService的默认实现 来处理 Callback ,
// 这里我只传递了一个 Callback 不管成不成功都可以执行,
// 同时会给这个箭头函数一个 promise ,想干啥自己写就行,
// 并且自动转义 promise<T> 对应的返回数据模型
loginService.checkToken((aResponse: AResponse<Boolean>) => {
// 通用处理函数
console.log(aResponse)
})
}
}
上面展示了4
种方式使用login.ts
提供的接口完成请求和返回函数的执行。但是可以看到LoginView.vue
的代码不管采用哪种写法,都没有明显的变化,原因是重业务,业务本身是高度定制化,没办法再抽象的,所以优化LoginView.vue
代码量很困难,只能去优化它对应的ts
。
额外说明:
如果要新写一个模块,我只需要新写一个ts
文件和一个对应的页面就行,重点是ts
文件中代码量很少,几乎就是粘贴复制。
这里可能有人要杠,为什么还要新写一个ts
,直接一个ts
写完整个项目所有模块不行么?答案是行,但是不建议这么做。理由如下:
- 代码量的减少目的是提升开发效率、代码维护成本变少
- 基于
oop
抽离业务模块是为了清晰业务,明确每个ts
的职责和功能,方便维护和代码审查,你不想别人看你的代码说跟翔一样吧? - 后端改动对前端影响更小,发起请求和数据模型都被封装自动转换了
四、关于大家评论说我用java的思想写前端
首先感谢大家对我学前端这事的关注,并且没有对我拙劣的前端技术各种抨击。我不是要卷各位前端大佬,只是想在更大的范围内做到技术闭环,仅此而已。
我觉得思想是程序员的最终经验,代码只是一个逻辑表达。oop
的思想是不分技术和方向的。
评论区