vue-manage-system,一個基於 Vue.js 和 element-ui 的後臺管理系統模板,從2016年年末第一個commit,到如今差很少兩年了,GitHub上也有了 5k star,也是這些讓我有了持續更新的動力,其中也踩了不少坑,在這總結一下。css
github地址:vue-manage-systemhtml
線上地址:https://lin-xin.gitee.io/example/work/前端
element-ui 自帶的字體圖標比較少,並且許多比較常見的都沒有,所以須要本身引入本身想要的字體圖標。最受歡迎的圖標庫 Font Awesome,足足有 675 個圖標,但也所以致使字體文件比較大,而項目中又不須要用到這麼多圖標。那麼這時候,阿里圖標庫就是一個很是不錯的選擇。vue
首先在阿里圖標上建立一個項目,設置圖標前綴,好比 el-icon-lx,設置Font Family,好比 lx-iconfont,添加須要用到的圖標到項目中,我這邊選擇 Font class 生成在線連接,由於全部頁面都須要用到圖標,就直接在 index.html 中引入該css連接就好了git
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue-manage-system</title> <!-- 這裏引入阿里圖標樣式 --> <link rel="stylesheet" href="//at.alicdn.com/t/font_830376_qzecyukz0s.css"> </head> <body> <div id="app"></div> </body> </html>
而後須要設置前綴爲 el-icon-lx 的圖標類名使用 lx-iconfont 字體。github
[class*="el-icon-lx"], [class^=el-icon-lx] { font-family: lx-iconfont!important; }
可是這個樣式要放在哪裏才能夠呢?這可不是隨便放就行的。在 main.js 中,引入了 element-ui 的樣式,而樣式中有這樣的一段css:web
[class*=" el-icon-"], [class^=el-icon-]{ font-family: element-icons!important; speak: none; font-style: normal; font-weight: 400; font-variant: normal; text-transform: none; line-height: 1; vertical-align: baseline; display: inline-block; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
很明顯,若是這段 css 在咱們自定義樣式後面才執行,就會覆蓋了咱們的樣式,那自定義的圖標就顯示不了。而在 build 項目的時候,會把 APP.vue 中的的樣式打包進 app.css 中,而後再把 main.js 中引用到的樣式追加到後面。那麼咱們能夠把自定義樣式放到一個css文件中,而後在 main.js 引入 element-ui css 的後面引入,那就能夠覆蓋掉默認字體了,而後即可以在項目中經過 <i class="el-icon-lx-people"></i>
使用圖標了。element-ui
那機智的人就發現了,我自定義圖標的前綴不要含 el-icon- 就不會有這樣的問題了。是的,那麼爲了和原有字體保持同樣的樣式,須要複製它的整段csscanvas
/* 假設前綴爲 el-lx */ [class*="el-lx-"], [class^=el-lx-]{ font-family: lx-iconfont!important; speak: none; font-style: normal; font-weight: 400; font-variant: normal; text-transform: none; line-height: 1; vertical-align: baseline; display: inline-block; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
element-ui 關於導航菜單的文檔也是很是詳細了,可是仍是有人提 issue 或者加 QQ 問我:三級菜單怎麼弄等等。並且具體的菜單項多是服務器端根據權限而返回特定的數據項,所以不能寫死在模板中。數組
首先定好菜單數據的格式以下,即便服務器端返回的格式不是這樣,也須要前端處理成下面的格式:
export default { data() { return { items: [{ icon: 'el-icon-lx-home', index: 'dashboard', title: '系統首頁' },{ icon: 'el-icon-lx-calendar', index: '1', title: '表單相關', subs: [{ index: '1-1', title: '三級菜單', subs: [{ index: 'editor', title: '富文本編輯器' }] }] },{ icon: 'el-icon-lx-warn', index: '2', title: '錯誤處理', subs: [{ index: '404', title: '404頁面' }] }] } } }
icon 就是菜單圖標,就能夠用到咱們上面自定義的圖標了;index 就是路由地址;title 就是菜單名稱;subs 就是子菜單了。而模板則經過判斷菜單中是否包含 subs 從而顯示二級菜單和三級菜單。
<el-menu :default-active="onRoutes" :collapse="collapse" router> <template v-for="item in items"> <template v-if="item.subs"> <el-submenu :index="item.index" :key="item.index"> <template slot="title"> <i :class="item.icon"></i><span slot="title">{{ item.title }}</span> </template> <template v-for="subItem in item.subs"> <el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index"> <template slot="title">{{ subItem.title }}</template> <!-- 三級菜單 --> <el-menu-item v-for="(threeItem,i) in subItem.subs" :key="i" :index="threeItem.index"> {{ threeItem.title }} </el-menu-item> </el-submenu> <el-menu-item v-else :index="subItem.index" :key="subItem.index"> {{ subItem.title }} </el-menu-item> </template> </el-submenu> </template> <!-- 沒有二級菜單 --> <template v-else> <el-menu-item :index="item.index" :key="item.index"> <i :class="item.icon"></i><span slot="title">{{ item.title }}</span> </el-menu-item> </template> </template> </el-menu>
這樣就完成了一個動態的導航菜單。
經過 Header 組件中的一個按鈕來觸發 Sidebar 組件展開或收起,涉及到了組件之間傳遞數據,這裏經過 Vue.js 單獨的事件中心(Event Bus)管理組件間的通訊。
const bus = new Vue();
在 Header 組件中點擊按鈕時觸發 collapse 事件:
bus.$emit('collapse', true);
在 Sidebar 組件中監聽 collapse 事件:
bus.$on('collapse', msg => { this.collapse = msg; })
vue-manage-system 中用到的圖表插件是 vue-schart,是把一個基於 canvas 的圖表插件 schart.js 進行了封裝。要作到圖表可以自適應寬度,隨着 window 或者父元素的大小改變而從新渲染,若是圖表插件裏沒實現該功能,就須要本身手動實現。
vue-schart 中提供了 renderChart() 的方法能夠從新渲染圖表,Vue.js 中父組件調用子組件的方法,能夠經過 $refs 進行調用。
<schart ref="bar" canvasId="bar" :data="data" type="bar" :options="options"></schart>
而後監聽 window 的 resize 事件,調用 renderChart() 方法從新渲染圖表。
import Schart from 'vue-schart'; export default { components: { Schart }, mounted(){ window.addEventListener('resize', ()=>{ this.$refs.bar.renderChart(); }) } }
不過也要記得組件銷燬時移除監聽哦!監聽窗口大小改變完成了,那父元素大小改變呢?由於父元素寬度設爲百分比,當側邊欄摺疊的時候,父元素的寬度發生了變化。可是 div 並無 resize 事件,沒法監聽到它的寬度改變,可是觸發摺疊的時候,咱們是知道的。那麼是否能夠經過監聽到摺疊變化的時候,再調用渲染函數從新渲染圖表呢?那麼仍是經過 Event Bus 監聽側邊欄的改變,並在 300ms 後從新渲染,由於摺疊時候有 300ms 的動畫過程
bus.$on('collapse', msg => { setTimeout(() => { this.$refs.bar.renderChart(); }, 300); });
多標籤頁,也是提 issue 最多的一個功能。
當在 A 標籤頁輸入一些內容以後,打開 B 標籤再返回到 A,要保留離開前的狀態,所以須要使用 keep-alive 進行緩存,並且關閉以後的標籤頁就再也不緩存,避免關閉後再打開仍是以前的狀態。keep-alive 的屬性 include 的做用就是隻有匹配的組件會被緩存。include 匹配的不是路由名,而是組件名,那麼每一個組件都須要添加 name 屬性。
在 Tags 組件中,監聽路由變化,將打開的路由添加到標籤頁中:
export default { data() { return { tagsList: [] } }, methods: { setTags(route){ const isExist = this.tagsList.some(item => { return item.path === route.fullPath; }) if(!isExist){ this.tagsList.push({ title: route.meta.title, path: route.fullPath, name: route.matched[1].components.default.name }) } } }, watch:{ $route(newValue, oldValue){ this.setTags(newValue); } } }
在 setTags 方法中,將一個標籤對象存到標籤數組中,包括title(標籤顯示的title),path(標籤的路由地址),name(組件名,用於include匹配的)。路由地址須要用 fullPath 字段,若是使用 path 字段,那若是地址後面帶有參數,就都沒保存起來了。
在 Home 組件中,監聽到標籤的變化,緩存須要的組件。
<keep-alive :include="tagsList"> <router-view></router-view> </keep-alive>
export default { data(){ return { tagsList: [] } }, created(){ // 只有在標籤頁列表裏的頁面才使用keep-alive,即關閉標籤以後就不保存到內存中了。 bus.$on('tags', msg => { let arr = []; for(let i = 0, len = msg.length; i < len; i ++){ // 提取組件名存到tagsList中,經過include匹配 msg[i].name && arr.push(msg[i].name); } this.tagsList = arr; }) } }
因爲該項目中不包含任何業務代碼,因此仍是相對比較簡單的,不過從開發中仍是積累了一些經驗,在其它項目中能夠更加熟練地開發。功能雖然不算多,可是也勉強夠用,若是有什麼好的建議,能夠開 issue 一塊兒討論。