最近vite越来火爆了,2.0发布以后也趋于稳定,写了一个demo发现运行速度确实非常快,和使用webpack的vue-cli相比完全是降维打击,想着搭个架子,方便以后使用。小菜鸡的初次尝试,希望各位大神不吝赐教。
1、使用yarn初始化vite模板
yarn create @vitejs/app
之后会让你输入项目名称,选择框架等。这里我们输入名称为jianshu,框架选择vue,回车
然后进入项目中,输入yarn回车,安装依赖
cd jianshu && yarn
安装完成之后,使用yarn dev
命令启动开发服务。
可以看到只花了477毫秒就启动完成了,对比vue-cli来说快了很多。我们看到vite默认端口是3000,而不是8080,这个可以在项目里配置。打开localhost:3000
地址,可以看到vite默认的欢迎页面。
2、安装我们需要用到的插件
网络请求使用axios,css预处理使用sass,还有登录验证会用到的jd-md5加密,当然还有我们的element-plus,最后就是vue全家桶:vuex和vue-router。
yarn add axios sass js-md5 element-plus
由于默认安装vuex和vue-router还是3.x,和vue3.0搭配不上,这里我们单独安装这两个插件的最新的4.x版本
yarn add vuex@lastest
yarn add vue-router@lastest
全部安装完成后,来进行配置
3、插件的配置
①配置vuex
vuex4.x的语法和3.x差距比较大,有能力的同学可以自行翻看官方文档。在src目录下新建store文件夹,然后新建index.js。
这里我们只配置最基础的token,userInfo,storeInfo三个数据,全部存入localstorage中做持久化。
然后配置了set方法来设置/清空数据。集成了loginIn和loginOut的方法,方便调用。
import { createStore } from 'vuex';
export default createStore({
state() {
return {
userInfo: JSON.parse(localStorage.getItem('userInfo')),
storeInfo: JSON.parse(localStorage.getItem('storeInfo')),
token: localStorage.getItem('token')
}
},
mutations: {
SetToken(state, data) {
state.token = data.token;
if(data){
localStorage.setItem('token', data.token);
localStorage.setItem('expire', data.expire)
}else{
localStorage.removeItem('token');
localStorage.removeItem('expire');
}
},
SetUser(state, data) {
state.userInfo = data;
if(data){
localStorage.setItem('userInfo', JSON.stringify(data));
}else{
localStorage.removeItem('userInfo');
}
},
SetStore(state, data){
state.storeInfo = data;
if(data){
localStorage.setItem('storeInfo', JSON.stringify(data));
}else{
localStorage.removeItem('storeInfo');
}
},
LoginIn(state, data) {
this.commit('SetToken', data);
this.commit('SetUser', data.user);
},
LoginOut() {
this.commit('SetToken', '');
this.commit('SetUser', '');
this.commit('SetStore', '');
location.href = '/';
},
}
});
然后在main.js里进行配置。默认的main.js是这样的
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
不太方便我们配置插件,这里需要改造一下
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App);
app.mount('#app');
引入store
import store from './store';
...
app.use(store);
②配置网络请求axios
在src目录下新建plugins文件夹,然后新建axios.js。
引入axios和element-plus的loading组件,然后引入store。
import axios from 'axios';
import {ElLoading} from 'element-plus';
import store from '../store';
import {uiMsg, apiHost} from '../utils';
这里的uiMsg和apiHost是单独封装到utils里面,方便调用的
在src目录下新建utils文件夹,然后新建index.js,封装一些常用的工具函数。
import { ElMessage } from 'element-plus';
// api
// const apiHost = 'https://product.com/api'; //生产
const apiHost = 'http://dev.com/api'; //开发
// 消息提示
function uiMsg(msg, type = 'error', onClose) {
ElMessage.closeAll();
ElMessage({
message: msg,
type,
duration: 1500,
customClass: 'ui-msg-zindex',
onClose: () => {
onClose && onClose();
}
});
}
// 复制剪贴板
function uiCopy(str) {
let copyDom = document.createElement('div');
copyDom.innerText = str;
copyDom.style.position = 'absolute';
copyDom.style.top = '0px';
copyDom.style.right = '-9999px';
document.body.appendChild(copyDom);
//创建选中范围
let range = document.createRange();
range.selectNode(copyDom);
//移除剪切板中内容
window.getSelection().removeAllRanges();
//添加新的内容到剪切板
window.getSelection().addRange(range);
//复制
let successful = document.execCommand('copy');
copyDom.parentNode.removeChild(copyDom);
try {
uiMsg({
msg: successful ? "复制成功!" : "复制失败,请手动复制内容",
type: successful ? 'success' : 'error'
})
} catch (err) {}
}
// 深拷贝
function uiDeepCopy(obj, cache = []) {
function find(list, f) {
return list.filter(f)[0];
}
if (obj === null || typeof obj !== 'object') {
return obj;
}
const hit = find(cache, (c) => c.original === obj);
if (hit) {
return hit.copy;
}
const copy = Array.isArray(obj) ? [] : {};
cache.push({original: obj, copy});
Object.keys(obj).forEach((key) => {
copy[key] = uiDeepCopy(obj[key], cache);
});
return copy;
}
export { apiHost, uiMsg, uiCopy, uiDeepCopy }
初始化axios,使用requestCount来优化多个请求同步进行时,loading闪烁的问题。
let http = axios.create({
baseURL: apiHost,
timeout: 6000,
headers: {
'Content-Type': 'application/json;charset=UTF-8;'
}
});
let loading,
requestCount = 0;
const ShowLoading = ()=>{
if(requestCount === 0 && !loading){
loading = ElLoading.service({
background: 'rgba(0,0,0,.7)'
});
}
requestCount++;
}
const HideLoading = ()=>{
requestCount--;
if(requestCount === 0){
loading.close();
}
}
因为我们后台接口大部分都是使用jwt认证,所以需要在request拦截器中给请求加上token。
然后在response拦截器中加入token过期失效的处理,这里可以根据实际情况修改判断条件。
http.interceptors.request.use(config=>{
ShowLoading();
if (store.state.token) {
config.headers['Authorization'] = 'Bearer ' + store.state.token;
}
config.headers.post['Content-Type'] = 'application/json';
return config;
});
http.interceptors.response.use(response=>{
HideLoading();
return response.data;
}, error=>{
if(error.response.status === 401){
// 401 token过期,退出登录
uiMsg('登录已过期,请重新登录', null, ()=>{
store.dispatch('LoginOut');
});
}else{
return Promise.reject(error)
}
});
封装get和post请求。加入函数节流处理,添加接口状态判断和错误消息提示。
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
function get(url, params) {
return new Promise((resolve) => {
let _timestamp = new Date().getTime();
http.get(url, { params }).then(res => {
if(new Date().getTime() - _timestamp < 200){
setTimeout(() => {
if (res.code === 200) {
resolve(res);
} else {
res.msg && uiMsg(res.msg);
}
}, 200);
}else{
if (res.code === 200) {
resolve(res);
} else {
res.msg && uiMsg(res.msg);
}
}
}).catch(error => {});
})
}
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
function post(url, params) {
return new Promise((resolve) => {
let _timestamp = new Date().getTime();
http.post(url, params).then(res => {
if(new Date().getTime() - _timestamp < 200){
setTimeout(() => {
if (res.code === 200) {
resolve(res);
} else {
res.msg && uiMsg(res.msg);
}
}, 200);
}else{
if (res.code === 200) {
resolve(res);
} else {
res.msg && uiMsg(res.msg);
}
}
})
.catch(error => {});
})
}
export {get, post};
完成后在main.js中进行配置,同时引入utils里面的工具函数,挂载到vue全局上。
import {get, post} from './plugins/axios';
import {uiMsg, uiCopy, uiDeepCopy} from './utils';
...
app.config.globalProperties.$uiMsg = uiMsg;
app.config.globalProperties.$uiCopy = uiCopy;
app.config.globalProperties.$uiDeepCopy = uiDeepCopy;
app.config.globalProperties.$get = get;
app.config.globalProperties.$post = post;
③配置vue-router
在scr目录下新建router目录,然后新建index.js。
引入vue-router和vuex。
import {createRouter, createWebHistory} from 'vue-router';
import store from '../store';
首先配置不需要权限控制的页面。在src目录下新建views目录,然后分别新建登录:/login/index.vue,404:/error/notFound.vue,无权限:/error/noPermission.vue页面。这里我们使用异步加载引入。
// 不需要权限的页面
const constantRoutes = [
{
// 登录
path: '/login',
name: 'login',
component: ()=>import('../views/login/index.vue')
},
{
// 404
path: '/:pathMatch(.*)',
name: 'notFound',
component: ()=>import('../views/error/notFound.vue')
},
{
// 无权限
path: '/noPermission',
name: 'noPermission',
component: ()=>import('../views/error/noPermission.vue')
}
];
然后来配置普通页面。随意配置三个示例页面。
const asyncRoutes = {
path: '/',
name: 'main',
component: ()=>import('../views/main.vue'),
children: [
{
// 首页
path: '/',
name: 'home',
component: ()=>import('../views/home/index.vue')
},
{
// 用户列表
path: '/userList',
name: 'userList',
component: ()=>import('../views/setting/userList.vue')
},
{
// 权限设置
path: '/permission',
name: 'permission',
component: ()=>import('../views/setting/permission.vue')
}
]
}
然后来初始化router。
const router = createRouter({
history: createWebHistory('/'),
routes: constantRoutes
});
router.addRoute(asyncRoutes);
我们在router跳转中,加入token判断
router.beforeEach((to, from, next)=>{
// 登录判断
if(store.state.token){
if(to.path === '/login'){
next({path: '/'});
}else{
// 权限判断
next();
}
}else{
if(to.path === '/login'){
next();
}else{
next({path: '/login'})
}
}
});
跳转完成后,将滚动条位置重置。
router.afterEach(to => {
window.scrollTo(0, 0);
});
最后导出router
export default router;
然后到main.js中配置
import router from './router';
...
app.use(router);
④配置elemet-plus。
在plugins文件夹下新建element.js。
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
export default app=>{
app.use(ElementPlus);
}
然后在main.js中进行配置。
import installElementPlus from './plugins/element';
...
installElementPlus(app);