美文网首页奇思妙想Vue
使用vite搭建基于vue3.0+element-plus的后台

使用vite搭建基于vue3.0+element-plus的后台

作者: Zick | 来源:发表于2021-09-16 23:33 被阅读0次
    最近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);
    

    相关文章

      网友评论

        本文标题:使用vite搭建基于vue3.0+element-plus的后台

        本文链接:https://www.haomeiwen.com/subject/cobdgltx.html