1. 程式人生 > 實用技巧 >基於vue的前端架構

基於vue的前端架構

1、區域性樣式與全域性樣式

區域性樣式:一般都是使用scoped方案:

<style lang="scss" scoped>
  ...
</style>

全域性樣式:variable.scss 全域性變數管理;mixins.scss 全域性Mixins管理;global.scss 全域性樣式

其中variable.scss和mixins.scss會優先於global.scss載入,並且可以不通過import的方式在專案中任何位置使用這些變數和mixins

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      sass: {
        prependData: `
        @import '@/styles/variable.scss'
; @import '@/styles/mixins.scss'; `, }, }, }, }

2、體驗優化

頁面載入進度條

使用nprogress對路由跳轉時做一個偽進度條,這樣做在網路不好的情況下可以讓使用者知道頁面已經在載入了:

import NProgress from 'nprogress';

router.beforeEach(() => {
  NProgress.start();
});

router.afterEach(() => {
  NProgress.done();
});

美化滾動條

::-webkit-scrollbar 
{ width: 6px; height: 6px; } ::-webkit-scrollbar-track { width: 6px; background: rgba(#101F1C, 0.1); -webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; } ::-webkit-scrollbar-thumb { background-color: rgba(#101F1C, 0.5); background-clip: padding-box; min-height
: 28px; -webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; } ::-webkit-scrollbar-thumb:hover { background-color: rgba(#101F1C, 1); }

3、移動端100vh問題

在移動端使用100vh時,發現在Chrome、Safari瀏覽器中,因為瀏覽器欄和一些導航欄、連線欄導致不一樣的呈現:

你以為的100vh===視口高度

實際上100vh===視口高度 + 瀏覽器工具欄(位址列等等)的高度

解決方案:

安裝 vh-check (npm install vh-check --save)

import vhCheck from 'vh-check';
vhCheck('browser-address-bar');

定義一個css Mixin

@mixin vh($height: 100vh) {
  height: $height;
  height: calc(#{$height} - var(--browser-address-bar, 0px));
}

4、靜態資源與圖示

靜態資源

所有的靜態資原始檔都會上傳到 阿里雲 OSS 上,所以在環境變數上加以區分。

.env.development.env.productionVUE_APP_STATIC_URL屬性分別配置了本地的靜態資源伺服器地址和線上 OSS 的地址。

本地的靜態資源伺服器是通過 pm2 + http-server 建立的,設計師切完直接扔進來就好了。

自動註冊Svg圖示

直接name等於檔名即可使用

<template>
    <svg name="logo" />
</template>

首先需要對@/assets/icons 資料夾下的svg圖示進行自動註冊,需要對webpack 和 svg-sprite-loader 進行了相關設定,檔案全部打包成 svg-sprite

module.exports = {
  chainWebpack: (config) => {
    config.module
      .rule('svg')
      .exclude.add(resolve('src/assets/icons'))
      .end();

    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/assets/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader');
  },
}

寫一個全域性用的 Vue 元件<m-svg />:

@/components/m-svg/index.js

const requireAll = (requireContext) => requireContext.keys().map(requireContext);
const req = require.context('@/assets/icons', false, /\.svg$/);
requireAll(req);

@/components/m-svg/index.vue

<template>
  <svg class="mw-svg" aria-hidden="true">
    <use :xlink:href="iconName"></use>
  </svg>
</template>
<script>
export default {
  name: 'm-svg',
  props: {
    name: { type: String, default: '' },
  },
  computed: {
    iconName() {
      return `#${this.name}`;
    },
  },
};
</script>
<style lang="scss" scoped>
.mw-svg {
  width: 1.4em;
  height: 1.4em;
  fill: currentColor;
  overflow: hidden;
  line-height: 1em;
  display: inline-block;
}
</style>

放置在 @/assets/icons 資料夾下的檔名

5、Axios封裝

import axios from 'axios';
import get from 'lodash/get';
import storage from 'store';
// 建立 axios 例項
const request = axios.create({
 // API 請求的預設字首
 baseURL: process.env.VUE_APP_BASE_URL,
 timeout: 10000, // 請求超時時間
});

// 異常攔截處理器
const errorHandler = (error) => {
 const status = get(error, 'response.status');
 switch (status) {
   /* eslint-disable no-param-reassign */
   case 400: error.message = '請求錯誤'; break;
   case 401: error.message = '未授權,請登入'; break;
   case 403: error.message = '拒絕訪問'; break;
   case 404: error.message = `請求地址出錯: ${error.response.config.url}`; break;
   case 408: error.message = '請求超時'; break;
   case 500: error.message = '伺服器內部錯誤'; break;
   case 501: error.message = '服務未實現'; break;
   case 502: error.message = '閘道器錯誤'; break;
   case 503: error.message = '服務不可用'; break;
   case 504: error.message = '閘道器超時'; break;
   case 505: error.message = 'HTTP版本不受支援'; break;
   default: break;
   /* eslint-disabled */
 }
 return Promise.reject(error);
};

// request interceptor
request.interceptors.request.use((config) => {
 // 如果 token 存在
 // 讓每個請求攜帶自定義 token 請根據實際情況自行修改
 // eslint-disable-next-line no-param-reassign
 config.headers.Authorization = `bearer ${storage.get('ACCESS_TOKEN')}`;
 return config;
}, errorHandler);

// response interceptor
request.interceptors.response.use((response) => {
 const dataAxios = response.data;
 // 這個狀態碼是和後端約定的
 const { code } = dataAxios;
 // 根據 code 進行判斷
 if (code === undefined) {
   // 如果沒有 code 代表這不是專案後端開發的介面
   return dataAxios;
 // eslint-disable-next-line no-else-return
 } else {
   // 有 code 代表這是一個後端介面 可以進行進一步的判斷
   switch (code) {
     case 200:
       // [ 示例 ] code === 200 代表沒有錯誤
       return dataAxios.data;
     case 'xxx':
       // [ 示例 ] 其它和後臺約定的 code
       return 'xxx';
     default:
       // 不是正確的 code
       return '不是正確的code';
   }
 }
}, errorHandler);

export default request;

6、跨域問題

可以用devServer提供的proxy代理:

// vue.config.js
devServer: {
  proxy: {
    '/api': {
      target: 'http://47.100.186.132/your-path/api',
      ws: true,
      changeOrigin: true,
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}

7、路由

Layout

佈局暫時分為三大類:

frameln:基於BasicLayout,通常需要登陸或許可權認證的路由

frameOut:不需要動態判斷許可權的路由,如登入頁或通用頁面

errorPage:例如404

許可權驗證

通過獲取當前使用者的許可權去比對路由表,生成當前使用者的許可權可訪問的路由表,通過router.addRoutes動態掛載到router上

  判斷頁面是否需要登陸狀態,需要則跳轉到/user/login

  本地儲存中不存在token則跳轉到/user/login

  如果存在token,使用者資訊不存在,自動呼叫vuex、‘/system/user/getInfo’

在路由中,集成了許可權驗證的功能,需要為頁面增加許可權時,在meta下新增相應的key

auth:當auth為true時,此頁面需要進行登入許可權驗證,只針對frameIn路由有效

permissions:permissions每一個key對應許可權功能的驗證,當key的值為true時,代表具有許可權,若key為false,配合v-permission指令,可以隱藏相應的DOM。

import router from '@/router';
import store from '@/store';
import storage from 'store';
import util from '@/libs/utils';

// 進度條
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';

const loginRoutePath = '/user/login';
const defaultRoutePath = '/home';

/**
 * 路由攔截
 * 許可權驗證
 */
router.beforeEach(async (to, from, next) => {
  // 進度條
  NProgress.start();
  // 驗證當前路由所有的匹配中是否需要有登入驗證的
  if (to.matched.some((r) => r.meta.auth)) {
    // 是否存有token作為驗證是否登入的條件
    const token = storage.get('ACCESS_TOKEN');
    if (token && token !== 'undefined') {
      // 是否處於登入頁面
      if (to.path === loginRoutePath) {
        next({ path: defaultRoutePath });
        // 查詢是否儲存使用者資訊
      } else if (Object.keys(store.state.system.user.info).length === 0) {
        store.dispatch('system/user/getInfo').then(() => {
          next();
        });
      } else {
        next();
      }
    } else {
      // 沒有登入的時候跳轉到登入介面
      // 攜帶上登陸成功之後需要跳轉的頁面完整路徑
      next({
        name: 'Login',
        query: {
          redirect: to.fullPath,
        },
      });
      NProgress.done();
    }
  } else {
    // 不需要身份校驗 直接通過
    next();
  }
});

router.afterEach((to) => {
  // 進度條
  NProgress.done();
  util.title(to.meta.title);
});

8、構建優化

包分析工具

const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');

module.exports = {
  chainWebpack: (config) => {
    if (process.env.use_analyzer) {
      config
        .plugin('webpack-bundle-analyzer')
        .use(WebpackBundleAnalyzer.BundleAnalyzerPlugin);
    }
  },
};

開啟Gzip

chainWebpack: (config) => {
  config
    .plugin('CompressionPlugin')
    .use(CompressionPlugin, []);
},

路由懶載入

{
  path: 'home',
  name: 'Home',
  component: () => import(
    /* webpackChunkName: "home" */ '@/views/home/index.vue'
  ),
},