最近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、插件的配置①配置vuexvuex4.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');
引入storeimport 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);});
最后导出routerexport 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);
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |