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.production
的 VUE_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' ), },