Masques
一款基于 Vue3 的中后台前端解决方案,用于快速搭建中后台项目。由项目脚手架,cli 工具和一系列插件组成,可以用插件的形式引入一部分功能
Install / Use
/learn @SalengNotLittleMeng/MasquesREADME
masques
masques 是一款基于 Vue3 的中后台前端解决方案,用于快速搭建中后台项目。由项目脚手架,cli 工具和一系列插件组成,可以用插件的形式引入一部分功能,具体包括:
vue-cli 版本:4.5.0
目前模板还未对核心库进行整合,建议查看核心包
- 项目脚手架,内置项目的全部功能,并默认配置基于 githook 的校验机制和 github 的 CI 配置,文档生成等
- 项目 cli 工具,用于以命令行的方式拉取项目模版(cli 工具地址:https://github.com/SalengNotLittleMeng/masques-cli )
- 配置式请求层插件,可以通过插件的形式接入任何 Vue 项目,可以通过配置实现断线重连,取消重复请求,差异抹平,loading 动画等效果(插件化接入:https://github.com/SalengNotLittleMeng/masques-request )
- 自定义指令插件,可通过自定义指令的方式轻松实现图片懒加载,一键复制,无限滚动,自动聚焦,可拖动,备用图等功能(插件化接入:https://github.com/SalengNotLittleMeng/masques-directives )
- 增强式存储工具,实现 localstorage 的序列化,响应式存储数据,lru 对象,设置过期时间,批量存储,加密等功能(插件化接入:https://github.com/SalengNotLittleMeng/masques-storage )
- 开发工具库,包括 less 工具类和 JS 工具类,快速实现格式校验,省略号,日期校验等问题
- 支持注解(装饰器)开发,可以通过一些内置装饰器来完成请求,操作 Vuex 等效果
当前正在开发ing:权限管理插件
目录结构
-
.husky:处理 githook 的文件夹
-
public:ico,index.html 等网页配置相关文件的文件夹
-
src:核心代码文件夹
- animator:对动画的封装,使用时要手动引入这个文件
- api:axios 封装的文件夹,封装代码在 http.js,最后暴露出来的模块在 api.js,自己写对应的功能时要新建对应的 JS 文件并将接口函数暴露到 api.js 中
- components:通用组件文件夹,一些整个项目中需要用到的组件放在此处
- assets:静态文件文件夹,处理图片,iconfoot,svg 等文件,其中 icon 中的 JS 代码是下载的 iconfoot 的 symbol 文件
- config:统一配置文件夹,用来配置项目的 baseurl,token 名称等
- view:页面文件夹,在这个目录下建立自己对应页面的文件夹,在里面写 Vue 文件即可
- descriptor:注解(装饰器)文件夹,在此文件夹下添加项中可以使用的注解
- directive:自定义指令文件夹,注册各种自定义指令,这些自定义指令会作为插件安装到 Vue 上
- Mock:Mock 模拟数据的文件夹,管理接口模拟数据的部分
- plugins:通用插件文件夹,默认只有 element 的注册使用
- router:路由文件夹,管理页面的跳转
- store:vuex 文件夹,统一状态管理,其中 index.js 负责将所有模块暴露,使用时建立一个自己的模块,按 Vuex 的用法使用即可
- themes:less 通用配置文件夹,在此配置项目的主题色,通用字体大小,字体,通用类,混入等
- App.vue:Vue 的主组件,进入项目后见到的第一个组件
- main.js:Vue 的主文件,在此进行 Vue 的通用配置
-
babel.config.js:配置使用 babel 进行代码兼容
-
commitlint.config.js:配置 git commit 时对提交信息的检测
-
vue.config.js;vue 配置文件
axios 封装
插件化配置插件见仓库:https://github.com/SalengNotLittleMeng/masques-request
axios 封装的代码在 src/api/http.js 中,
axios 的封装做了以下几个方面的处理:
- 设置了默认配置并区分开发生成环境
- 添加了请求,响应拦截器,直接返回响应信息的 data
- 对各种类型的错误进行了统一处理
- 自动取消短时间内发出的连续请求
- 传输文件自动配置 formData 参数
- 根据参数配置请求头并决定是否序列化
- 配置了 element-ui 中的 loading,可以通过配置决定是否在请求期间开启 loading 动画
- 配置化添加 auth 凭证
- 支持重连机制
- 支持使用注解进行开发
- 支持并发的请求处理
axois 进行了模块化划分和自动装配,每个模块开发时新建一个以 Api.js 结尾的文件(比如 homeApi.js),先从 http.js 中引入封装好的 axios,然后将对应接口写成函数并暴露出来,在 api.js 中,会执行模块自动装配,将所有暴露出来的函数按模块放入一个对象并暴露出来
如果不想规定模块的名称,可以更改 api.js 中 context 函数的正则来修改
基本用法:
import myAxios from './http';
function getList(params) {
return myAxios({
url: '/toLogin',
method: 'post',
data: params,
});
}
export default {
//在这里导出所有函数
getList,
};
其中参数 url 是必选的,method 不填默认为 get,data 是请求参数,已在内部做了处理,不论 get 方法还是 post 方法都只需要传递 data 即可,如果需要进行文件上传,只需要添加配置 type:"formData",并将文件放在一个对象中传给 data 即可
// homeApi.js
function uploadImg(params) {
return myAxios({
url: '/upload',
method: 'post',
data: params,
type: 'formData',
});
// 调用方法
let img = this.$refs.files.files[0];
console.log(img);
this.$api.homeApi.getImg({ file: img, name: '小明' }).then((res) => {
console.log(res);
});
}
在 axios 的封装中,会根据参数type来添加请求头并作出响应的处理,具体包括:
* json(默认): "Content-Type": "application/json",
* "formData":"Content-Type": "multipart/form-data",
如果配置了type:"formData",那么参数仍然只需要传递一个params对象即可,封装的内部会将这个对象的所有参数注入到一个FormData对象中并将这个formData对象作为参数发送到后端.
* "urlencoded":"Content-Type": "application/x-www-form-urlencoded"
如果配置了form-urlencoded,那么封装的内部会将其序列化
也可以直接在参数内部添加其他请求头,但如果这样做,那么封装的内部不会进行其他处理
myAxios 的参数会在处理后直接传给 axios,因此你可以在里面添加任何 axios 合法的配置
return myAxios({
url: '/api/login',
method: 'post',
data: paramsList,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
transformRequest: [
(data) => {
// ...
},
],
});
对 axios 的封装默认会取消重复请求,当浏览器连续发出多个相同的请求时,axios 会将那些相同,且还没有到达服务器的请求取消掉,这一行为可以避免服务器收到过多连续的请求,但并不意味着会完全屏蔽短时间内的相同请求,如果用户网络状况良好的话依然会发出连续请求。因此必要时依然要进行节流和防抖处理
如果在一些场合(比如根据用户的输入不断向服务器发出请求,返回提示信息),你需要进行连续重复请求,可以在参数中添加 repeat_request_cancel:false 来阻止取消重复请求的行为。
return myAxios({
url: '/toLogin',
method: 'post',
data: params,
repeat_request_cancel: false,
});
axios 的封装中包含了对 element-ui 中 loading 的封装,可以通过添加 loading:true 来开启加载动画,在请求开始时渲染加载动画,在请求完成时结束动画
function getList(params) {
return myAxios({
url: '/toLogin',
method: 'post',
data: params,
loading: true,
});
}
如果想对加载动画进行个性化处理,可以对 myAxios 传入第二个参数,这个参数是一个对象,里面可以添加所有 elemnet-ui 中 loading 组件的合法配置,具体可以参考 element-plus 的配置文档
https://element-plus.org/zh-CN/component/loading.html#%E9%85%8D%E7%BD%AE%E9%A1%B9
return myAxios(
{
url: '/findByName',
method: 'get',
data: params,
},
{
text: 'hello',
}
);
api.js 这个文件会接收所有模块暴露出来的函数,并将他们统一暴露出去,在 main.js 中,会将 api.js 这个文件作为$api 这个变量引入并挂载到全局。在整个项目中的任意位置无需引入即可使用。
调用示例:
this.$api.homeApi.getmsg({ hi: 'vue' }).then(function (res) {
console.log(res);
});
基本格式:this.$api+模块名+接口函数名(传参)+.then 执行回调函数
添加 auth 凭证:只需在 src/myConfig.js 中配置好 auth 参数即可
auth: {
username: "testeradmin",
password: "testerpassword",
}
重连机制:重连机制默认不开启,可以通过在配置中添加 retryTimes 参数进行开启,这个参数会配置重连的次数,另外,两次重连的时间间隔可以通过 retryDelay 参数配置,不配置的话则默认间隔 0.5s.
另外,当重连机制开启后,取消重复请求的机制将会关闭
function getList(params) {
return myAxios({
url: '/toLogin',
method: 'post',
data: params,
retryTimes: 3,
retryDelay: 1000,
});
}
本脚手架中还为 Api 层添加了注解,在一些简单的业务中,我们可以用注解的方式写 Api 层,这种方式的好处是我们无需写一大串的链式调用,只需要写好配置后去写请求完成部分的逻辑即可,但其灵活性会有所降低,在面对复杂业务时更推荐链式调用的写法
使用注解依然要在模块中按格式去注册并暴露出函数(具体格式参考上面)
@Api({module:"homeApi",url:"/toLogin"})
doPost(params,res){
console.log(res)
}
其中两个必选参数:module,url 分别是函数所在的模块名和接口的 url,经过注解后的函数,第一个参数变成要传入的参数,第二个参数变成了请求完成后的回调参数 res(也就是请求到的数据),我们可以直接使用这个参数进行下一步处理
Vuex 的模块化处理
本脚手架对 Vuexj 进行了模块化封装,在 src/store/modules 下按照 modulesA.js 的格式建立 JS 文件即可,index.js 会自动解析这个目录下的所有文件并将他们作为模块挂载到 Vuex 上,模块名为 js 文件名。
export default {
state: {
num: 12,
},
mutations: {
newNumber(state, val) {
state.num = val;
},
},
actions: {},
namespaced: true,
};
使用时注意在变量前加上模块名,比如获取 moudlesA 中的 num 变量的值,可以使用:
this.$store.state.moudlesA.num;
如果使用 mapState, mapActions 的话,写法为:
computed: {
...mapState({
num:(state)=>state.moudlesA.num
})
},
修改值则需要在 commit 中函数名前加上/模块名,例如:
this.$store.commit('moudlesA/newNumber', 5);
另外,由于大部分情况下,commit 的作用都是去更改对应 state 中的值,因此在模块化之前,脚手架会自动给每个模块的每个 state 中的每个变量都自动添加一个叫做"_new+变量名"的 mutations 方法,因此我们可以在没有定义 mutations 的情况下通过这个变量名来修改它
// 即使没有定义_newbol这个方法,也可以用这个方法去修改bol这个变量
this.$store.commit('moudlesA/_newbol', false);
在一些情况下,你也可以通过注解的方式来使用 VueX,注解的好处是,在注解修饰的方法内部,可以直接使用或修改 Vuex 中的值,此时 Vuex 的值会响应式地被修改,而无需使用 commmit 方法来手动调用
@Store({module:"moudlesA"})
doStore(state){
console.log(state)
}
注解 Store 有一个必选参数 module,这个参数的值是要选择的模块的名称,被注解修饰的函数会有一个默认参数 state,这个参数是一个对象,代表了注解模块中 state 的对象,在被注解的函数中修改 state 这个对象的某个值,会响应式地修改保存在 Vuex 中的对应值
<div>{{$store.state.moudlesA.num}}</div>
@Store({module:"moudlesA"})
doStore(state){
setTimeout(()=>{
state.num=5
},1000)
}
// 1秒后页面显示的值从12变成5
这种响应式只能发生在state这个对象或这个对象地址的引用上,如果你将这个参数的值赋给某个变量,那么它不会触发响应式
doStore(state){
this.time=state
setTimeout(()=>{
this.time.num=5
},1000)
// 会触发响应式
}
doStore(state){
this.time=state.num
setTimeout(()=>{
this.time=5
},1000)
}
// 不会触发响应式
简而言之,只有如果需要赋值,要保证将 state 这个对象整体赋值,仅赋值某个变量是无法起到响应式的作用
Vue-Router 的全局守卫
vue-router 的封装主要是针对路由守卫的封装,如果需要在到达某一路由时要修改 title 的名称,那么可以在对应的路由配置上配置 meta 属性来实现
{
path: "/",
name: "Home",
component: Home,
meta: {
title: "主页",
},
children: [],
},
meta 属性还可以用来配置路由的权限,当配置了此权限后,只有登录的用户才能访问这一路由,否则会直接跳转到登录页
{
path: "/UserInfo",
name: "UserInfo",
component: () => import("../view/UserInfo/UserInfo.vue"),
// 利用元信息判别路由权限
meta: {
title: "用户信息",
requiresAuth: true,
},
children: [],
},
自定义指令的使用
脚手架中内置了一些实用的自定义指令(src/directive/*),这些自定义指令可以大大简化开发。同时,也建议尽量把与 DOM,BOM 相关的操作都用自定义指令的方式来实现。保证模块单一功能
调用方法:在 directive 模块下的每个文件的名称都是自定义指令的名称,dom 上 用 v-自定义指令名称 即可调用,具体调用方法和每个自定义指令的功能可以参考具体文件的注释
<div v-fix></div>
directive 文件夹下的 index.js 会自动收集模块中所有的自定义指令并作为插件挂载到 Vue 上,因此,如果要添加全局自定义指令,只需要在 src/directive 文件夹下创建自定义指令名命名的 JS 文件并暴露出去即可
iconfont 的使用
iconfont 已经被封装成组件并挂载到全局,在任意组件中无需引入即可使用
<vue-icon iconClass="bofang" color="red"></vue-icon>
iconClass 是一个必选项,要根据 iconfont 官网中项目的名字来写,名字前去掉 icon,其余可以传的属性还包括:
color,width,height,cursor
对于 icon 这个组件封装的思路是引入了 iconfont 上生成的 symbol 代码,因此只能使用项目中的 icon,如果要新增 icon,则要重新生成 symbol 代码并将生成的代码复制到 src/assets/icon/iconfont.js 中
全局组件注册
当涉及全局组件的注册时,要在 src/components/components.js 中引入组件并按照以下方法进行注册
install(app) {
app.component('VueIcon', icon);
},
动画的封装
src/animator/animator.js 这个文件对动画进行了 promise 封装,使其可以进行链式调用,并用 requestAnimationFrame 提升动画性能。封装后的动画可以用函数代替贝塞尔曲线来控制运动
这个函数在使用前需要手动引入文件
第一个参数是动画的持续时间,第二个参数是一个函数,表示每一帧执行的更新事件。
如果函数中的 p 没有使用,第一个参数会失效,动画事件将按照 CSS 的设置来执行,但仍可以进行链式调用
let func = new Animator(500, function (p) {
document.getElementsByClassName('func')[0].style.opacity = 1;
});
如果函数中的参数 p 被使用,那么第一个参数会生效,此时我们可以通过函数来控制动画的轨迹,比如下面这个例子,dom 宽度的变化速率会按照 400 _ p _ (2-p)这个函数进行变化(p 为时间进度比例),我们还可以同一函数设置常数来确定初始值
let line = new Animator(500, function (p) {
let ty = 400 * p * (2 - p);
document.getElementById('hrline').style.width = ty + 'px';
});
Mock 的封装
Mock 的封装在 Mock 文件夹中,其中封装部分在 MockServe.js 文件夹中,包括对参数的配置和暴露出的处理函数,这个函数会完成对 url 配置和对请求报文配置的简化工作,同时在最后执行 Mock.mock()函数,需要 Mock 操作的模块只需引入这个函数即可。
每个使用 Mock 的模块在 modules 文件夹下,每个模块引入 MockServe,并进行配置即可,配置项中第一项是 url,可以直接写接口名,无需写出完整请求路径;第二项是请求报文配置项,默认只需配置 data 中返回的内容即可,MockServe 会自动添加 code:200,message:"成功"的配置项,如果需要更改请求状态码或 message,那么在参数中添加这些配置可以覆盖默认配置;第三项是请求方式,默认是 post,如果需要改为其他请求方式在参数上写请求方式名即可
const tologin = MockServe(
'/toLogin',
{
dat
