參考:
https://github.com/PanJiaChen/vue-element-admin/blob/master/README.zh-CN.md
https://juejin.im/post/59097cd7a22b9d0065fb61d2
1、當項目逐漸變大之后,文件與文件直接的引用關系會很復雜,這時候就需要使用alias了。
//webpack.base.config.js alias: { 'src': path.resolve(__dirname, '../src'), 'components': path.resolve(__dirname, '../src/components'), 'api': path.resolve(__dirname, '../src/api'), 'utils': path.resolve(__dirname, '../src/utils'), 'store': path.resolve(__dirname, '../src/store'), 'router': path.resolve(__dirname, '../src/router') } //使用xxx.js import stickTop from 'components/stickTop' import getArticle from 'api/article'
2、ESLint
不管是多人合作還是個人項目,代碼規范是很重要的。這樣做不僅可以很大程度地避免基本語法錯誤,也保證了代碼的可讀性。推薦 eslint+vscode 來寫 vue。
每次保存,vscode就能標紅不符合eslint規則的地方,同時還會做一些簡單的自我修正。安裝步驟如下:
1)首先在vscode里安裝eslint插件
2)點擊 文件 > 首選項 > 設置 打開 VSCode 配置文件,添加如下配置。這樣每次保存的時候就可以根據根目錄下.eslintrc.js你配置的eslint規則來檢查和做一些簡單的fix。
{ "files.autoSave":"off", "eslint.validate": [ "javascript", "javascriptreact", "html", { "language": "vue", "autoFix": true } ], "eslint.options": { "plugins": ["html"] }, "eslint.autoFixOnSave": true }
3、js環境變量
process對象是 Node 的一個全局對象,提供當前 Node 進程的信息。它可以在腳本的任意位置使用,不必通過require命令加載。process.env屬性返回一個對象,包含了當前Shell的所有環境變量。通常的做法是,新建一個環境變量NODE_ENV,用它確定當前所處的開發階段,生產階段設為production,開發階段設為develop或staging,然后在腳本中讀取process.env.NODE_ENV即可。
DefinePlugin:允許你創建一個在編譯時可以配置的全局常量。這可能會對開發模式和發布模式的構建允許不同的行為非常有用。
//webpack.prod.conf.js var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv new webpack.DefinePlugin({ 'process.env': env })
//package.json "scripts": { "dev": "node build/dev-server.js", "build:prod": "cross-env NODE_ENV=production node build/build.js", "build:sit": "cross-env NODE_ENV=sit node build/build.js" }
cross-env這個npm包使得在使用或設置環境變量時能用同一種方式適配不同的平台(以unix方式設置環境變量,然后在windows上也能兼容運行)
3、前后端交互
1)封裝 axios
//request.js import axios from 'axios' import { Indicator, MessageBox } from 'mint-ui' // 創建axios實例 const service = axios.create({ baseURL: process.env.BASE_API, // api的base_url timeout: 60000 // 請求超時時間 }) // request攔截器 service.interceptors.request.use(config => { // Do something before request is sent Indicator.open('加載中...') return config }, error => { // Do something with request error Promise.reject(error) }) // respone攔截器 service.interceptors.response.use( response => { Indicator.close() const res = response.data if (res.ReturnCode !== '000000') { if (res.ReturnMsg) { MessageBox.alert(res.ReturnMsg) } else { MessageBox.alert('系統未知錯誤') } return Promise.reject(res) } else { return res } }, error => { Indicator.close() MessageBox.alert('太火爆了吧,稍安勿躁,親,再試試') return Promise.reject(error) } ) export default service
//api/xxx.js import request from '@/utils/request' // 登錄 export function userLogin(data) { return request({ url: 'login.do', method: 'post', data }) }
2)跨域問題
常用的方式就是 cors
全稱為 Cross Origin Resource Sharing(跨域資源共享)。這種方案對於前端來說和平時發請求寫法上沒有任何區別,工作量基本都在后端這里。前端也是有解決方案的,dev環境也可以通過webpack-dev-server
的proxy
來解決。【webpack的dev-server 使用了非常強大的 http-proxy-middleware 包】
a、直接使用http-proxy-middleware包
//配置js1 var proxyMiddleware = require('http-proxy-middleware'); var express = require('express'); var app = express(); Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = {target: options} } app.use(proxyMiddleware(options.filter || context, options)) }); //配置js2 proxyTable: { "/tt": { target: "http://localhost:8090", changeOrigin: true } }
b、使用webpack-dev-server此npm包(webpack的devServer配置
)
//webpack.dev.config.js devServer: { proxy: config.dev.proxyTable }, //配置js proxyTable: { "/portal": { target: "http://localhost:8080" } }
3)Mock 數據
mockjs:攔截請求並代理到本地模擬數據
4、權限控制
詳見https://juejin.im/post/591aa14f570c35006961acac
做后台項目區別於做其它的項目,權限驗證與安全性是非常重要的,可以說是一個后台項目一開始就必須考慮和搭建的基礎核心功能。我們所要做到的是:不同的權限對應着不同的路由,同時側邊欄也需根據不同的權限,異步生成(動態根據用戶的 role 算出其對應有權限的路由,通過 router.addRoutes 動態掛載這些路由)。
//permission.js const whiteList = ['/login']// 不重定向白名單 router.beforeEach((to, from, next) => { NProgress.start() // 開啟Progress const token = store.state.user.token if (token) { // 判斷是否有token if (to.path === '/login') { next({ path: '/' }) } else { store.dispatch('GenerateRoutes').then(() => { // 生成可訪問的路由表 router.addRoutes(store.getters.addRouters) // 動態添加可訪問路由表 next() }) } } else { if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入 next() } else { next('/login') // 否則全部重定向到登錄頁! } } })
5、樣式
常見的工作流程是,全局樣式都寫在 src/styles
目錄下,每個頁面自己對應的樣式都寫在自己的 .vue
文件之中
1)使用scoped解決樣式沖突問題(基於PostCss的,加了一個作用局的概念)
//編譯前 .example { color: red; } //編譯后 .example[_v-f3f3eg9] { color: red; }
2)自定義 element-ui 樣式
由於element-ui的樣式我們是在全局引入的,所以你想在某個view里面覆蓋它的樣式就不能加scoped,但你又想只覆蓋這個頁面的element樣式,你就可在它的父級加一個class,以用命名空間來解決問題。
.aritle-page{ //你的命名空間 .el-tag { //element-ui 元素 margin-right: 0px; } }
6、ECharts
管理后台圖表也是常見的需求。ECharts支持webpack引入,圖省事可以將ECharts整個引入;
不過ECharts還是不小的,我們大部分情況只是用到很少一部分功能,因此可以按需引入的。
// 引入 ECharts 主模塊 var echarts = require('echarts/lib/echarts'); // 引入柱狀圖 require('echarts/lib/chart/bar'); // 引入提示框和標題組件 require('echarts/lib/component/tooltip'); require('echarts/lib/component/title');
7、使用icon圖標
雪碧圖,就是將多個圖片合成一個圖片,然后利用 css 的 background-position 定位顯示不同的 icon 圖標。但這個也有一個很大的痛點,維護困難。每新增一個圖標,都需要改動原始圖片,還可能不小心出錯影響到前面定位好的圖片,而且一修改雪碧圖,圖片緩存就失效了,久而久之你不知道該怎么維護了。
Iconfont-阿里巴巴矢量圖標庫—symbol引用:這是一種全新的使用方式,應該說這才是未來的主流,也是平台目前推薦的用法。
- 支持多色圖標了,不再受單色限制。
- 通過一些技巧,支持像字體那樣,通過
font-size
,color
來調整樣式。 - 兼容性較差,支持 ie9+,及現代瀏覽器。
- 瀏覽器渲染svg的性能一般,還不如png。
第一步:拷貝項目下面生成的symbol代碼:
引入 ./iconfont.js
第二步:加入通用css代碼(引入一次就行):
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
第三步:挑選相應圖標並獲取類名,應用於頁面:
<svg class="icon" aria-hidden="true"> <use xlink:href="#icon-xxx"></use> </svg>
1)創建
icon-svg組件
<!--components/Icon-svg--> <template> <svg class="svg-icon" aria-hidden="true"> <use :xlink:href="iconName"></use> </svg> </template> <script> export default { name: 'icon-svg', props: { iconClass: { type: String, required: true } }, computed: { iconName() { return `#icon-${this.iconClass}` } } } </script>
//引入svg組件 import IconSvg from '@/components/IconSvg' //全局注冊icon-svg Vue.component('icon-svg', IconSvg) //在代碼中使用 <icon-svg icon-class="password" />
2)使用 svg-sprite優化
a、優化原因:現在所有的 svg-sprite
都是通過 iconfont 的 iconfont.js
生成的
- 首先它是一段用js來生成svg的代碼,所有圖標 icon 都很不直觀。你完全不知道哪個圖標名對應什么圖標, 每次增刪改圖標只能整體js文件一起替換。

- 其次它也做不到按需加載,不能根據我們使用了那些 svg 動態的生成
svg-sprite
。 - 自定義性差,通常導出的svg包含大量的無用信息,例如編輯器源信息、注釋等。通常包含其它一些不會影響渲染結果或可以移除的內容。
- 添加不友善,如果我有一些自定義的svg圖標,該如何和原有的
iconfont
整合到一起呢?目前只能將其也上傳到iconfont
和原有的圖標放在一個項目庫中,之后再重新下載,很繁瑣。
b、在 vue-cli
的基礎上進行改造,加入 svg-sprite-loader(可以將多個 svg 打包成
svg-sprite
)
vue-cli
默認情況下會使用
url-loader
對svg進行處理,會將它放在
/img
目錄下,所以這時候我們引入
svg-sprite-loader
會引發一些沖突(解決方案有兩種,最簡單的就是你可以將 test 的 svg 去掉,這樣就不會對svg做處理了,當然這樣做是很不友善的:你不能保證你所有的 svg 都是用來當做 icon的,有些真的可能只是用來當做圖片資源的;不能確保你使用的一些第三方類庫會使用到 svg)。最安全合理的做法是使用 webpack 的 exclude 和 include ,讓
svg-sprite-loader
只處理你指定文件夾下面的 svg,
url-loaer
只處理除此文件夾之外的所有svg。
//webpack.base.config.js { test: /\.svg$/, loader: 'svg-sprite-loader', include: [resolve('src/icons')], options: { symbolId: 'icon-[name]' } }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', exclude: [resolve('src/icons')], options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }
c、自動導入
我們創建一個專門放置圖標 icon 的文件夾如:@/src/icons,將所有 icon 放在這個文件夾下。之后我們就要使用到 webpack 的 require.context。require.context有三個參數(directory:說明需要檢索的目錄;useSubdirectories:是否檢索子目錄;regExp: 匹配文件的正則表達式)
//src/icons/index.js const requireAll = requireContext => requireContext.keys().map(requireContext) const svgs = require.context('./svgs', false, /\.svg$/)
//去svgs文件夾(不包含子目錄)下面的找所有文件名以.svg
結尾的文件
requireAll(svgs)
3)進一步優化自己的svg-移除無用信息
iconfont 網站導出的 svg 內容已經算蠻精簡的了,但你會發現其實還是與很多無用的信息,造成了不必要的冗余。好在 svg-sprite-loader也考慮到了這點,它目前只會獲取 svg 中 path 的內容,而其它的信息一概不會獲取。但任何你在 path 中產生的冗余信息它就不會做處理了,如注釋什么的。這時候我們就要使用另一個很好用的東西了-- svgo:因為SVG文件,尤其從各種變假期導出的SVG,通常包含大量的無用信息,例如編輯器源信息,注釋,因此默認或者非最優值以及其他一些不會影響渲染結果的元素可以移除或轉換。