完整項目地址:vue-element-adminjavascript
系列文章:php
在前面兩篇文章中已經把基礎工做環境構建完成,也已經把後臺核心的登陸和權限問題完成了,如今手摸手,一塊兒進入實操。css
去年十月份開始用 vue 作管理後臺的時候絕不猶豫的就選擇了 element-ui,那時候 vue2.0 剛發佈也沒多久,市面上也沒有不少其它的 vue2.0 的 ui 框架可供選擇。雖然 element-ui
也有不少的不足,前期的bug也很多,但我仍是選擇了它,簡單說一下我選擇element-ui
的緣由吧:html
element-ui
發版至今release了四十多個版本,以前平均都是一週一個小版本更新(是否是不當心暴露了它bug多的問題/(ㄒoㄒ)/~~)(ps: 至2017.12.4 已經迭代了74個版本,還保持着較高更新頻率)。element-ui
的拓展組件,也有不少相關的 qq 討論羣或者 gitter。說了這麼多優勢,做爲一個資深element-ui
用戶仍是有些要抱怨的~和react老大哥 Ant Design 相比仍是有必定的差距的,無論是組件的豐富性,參數的可配性仍是文檔的完整性,亦或是UI的交互和美觀度。不過 ant 也是通過了近9k次commit的不斷打磨,纔有了今天。我也相信 element-ui
也會愈來愈好的。前端
這裏還有一些其它的框架(只討論pc端的框架)你們能夠自行選擇:vue
Framework7-Vue
感受還不是很完善,還須要觀望一段時間。並且它有本身的路由規則,因此不能使用 vue-router
,這點仍是很不方便的。簡單列舉了一些主流的框架,不得不感慨如今vue的生態圈真是太繁榮了,上述框架樓主並無深刻使用過,很差發表太多建議,你們自行甄別適合本身業務的框架吧。java
這裏開始咱們會開始介紹一些結合Element的開發經驗。node
有些產品就是這麼殘忍,能完成需求就不錯了,還要讓咱們作動態換膚。Element官網上也提供了自定義主題的方案 同時也提供了一個在線自定義主題的demoreact
是否是很酷,做者也說明了實現的方案 地址,大概思路:webpack
我看完以爲真的仍是有點複雜的。有沒有簡單的方案呢? 讓咱們思考一下,讓咱們本身寫動態換膚該怎麼寫呢?最多見的方法就是寫兩套主題,一套叫day theme
,一套叫night theme
,night theme
主題 都在一個.night-theme
的命名空間下,咱們動態的在body
上add .night-theme
, remove .night-theme
。這就是最簡單的動態換膚。因此咱們也能不能順着這個思路,基於 element-ui 實現動態換膚呢?
首先咱們下載官方經過的 Theme generator ,一個專門用來生成Element主題的工具。按照文檔,咱們生成了須要的主題。
gulp-css-wrap
這個神器,輕輕鬆鬆就完成了咱們想要的結果
var path = require('path')
var gulp = require('gulp')
var cleanCSS = require('gulp-clean-css');
var cssWrap = require('gulp-css-wrap');
var customThemeName='.custom-theme'
gulp.task('css-wrap', function() {
return gulp.src( path.resolve('./theme/index.css'))
.pipe(cssWrap({selector:customThemeName}))
.pipe(cleanCSS())
.pipe(gulp.dest('dist'));
});
gulp.task('move-font', function() {
return gulp.src(['./theme/fonts/**']).pipe(gulp.dest('dist/fonts'));
});
gulp.task('default',['css-wrap','move-font']);
複製代碼
這樣就獲得了一個以.custom-theme爲命名空間的自定義主題了,以後咱們在項目中引入主題
//main.js
import 'assets/custom-theme/index.css'
複製代碼
咱們在換膚的地方toggleClass(document.body, 'custom-theme')
一直toggle body 的 class就能夠了。咱們就簡單實現了動態換膚效果。
var head = document.getElementsByTagName('HEAD').item(0);
var style = document.createElement('link');
style.href = 'style.css';
style.rel = 'stylesheet';
style.type = 'text/css';
head.appendChild(style);
複製代碼
更新(2017.12)
element-ui
官方更新了2.0版本,同時也提供了一個新的換膚思路。 文檔
這裏又有談一下導航欄的問題,本項目裏的側邊欄是根據 router.js 配置的路由而且根據權限動態生成的,這樣就省去了寫一遍路由還要手動再寫一次側邊欄這種麻煩事,但也遇到了一個問題,路由可能會有多層嵌套,不少人反饋本身的側邊欄會有三級,甚至還有五級的。因此重構了一下側邊欄,使用了遞歸組件,這樣無論你多少級,都能愉快的顯示了。代碼
:default-active="$route.path"
複製代碼
將default-active
一直指向當前路由就能夠了,就是這麼簡單。
點擊側邊欄 刷新當前路由
在用 spa(單頁面開發) 這種開發模式以前,大部分都是多頁面後臺,用戶每次點擊側邊欄都會從新請求這個頁面,用戶漸漸養成了點擊側邊欄當前路由來刷新頁面的習慣。但如今 spa 就不同了,用戶點擊當前高亮的路由並不會刷新view,由於vue-router
會攔截你的路由,它判斷你的url
並無任何變化,因此它不會觸發任何鉤子或者是view
的變化。issue地址,社區也對該問題展開了激烈討論。
current URL
就不會觸發任何東西,那我可不能夠強行觸發東西你?上有政策, 下有對策咱們變着花來hack。方法也很簡單,經過不斷改變
url
的
query
來觸發view的變化。咱們監聽側邊欄每一個link 的 click事件,每次點擊都給router push 一個不同的query 來確保會從新刷新view。
clickLink(path) {
this.$router.push({
path,
query: {
t: +new Date() //保證每次點擊路由的query項都是不同的,確保會從新刷新view
}
})
}
複製代碼
但這也有一個弊端就是 url 後面有一個很難看的 query 後綴如 xxx.com/article/list?t=1496832345025
,但我司用戶們表示能接受。。。只能暫時這樣hack了,不知道你們有沒有更好的方法,學習學習。
通過好幾個版本的迭代,element-ui 的table組件已經能知足大部分業務需求了。不過rowSpan colSpan表格行/列合併如今並非支持(element-ui2.0版本以後開始支持)。官方對此功能的更新狀況能夠關注這個issue。
這裏我着重講一下table表格幾個經常使用的業務形態。
import Sortable from 'sortablejs'
let el = document.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
let sortable = Sortable.create(el)
複製代碼
在table mounted以後申明Sortable.create(el)
table的每行tr就能夠隨意拖拽了,麻煩的目前咱們的排序都是基於dom的,咱們的數據層list並無隨之改變。因此咱們就要手動的來管理咱們的列表。
this.sortable = Sortable.create(el, {
onEnd: evt => { //監聽end事件 手動維護列表
const tempIndex = this.newList.splice(evt.oldIndex, 1)[0];
this.newList.splice(evt.newIndex, 0, tempIndex);
}
});
複製代碼
這樣咱們就簡單的完成了 table 拖拽排序。這裏若是不是基於 dom 的排序推薦使用Vue.Draggable。完整代碼
table內聯編輯也是一個常見的需求。
<el-table-column min-width="300px" label="標題">
<template scope="scope">
<el-input v-show="scope.row.edit" size="small" v-model="scope.row.title"></el-input>
<span v-show="!scope.row.edit">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="編輯" width="120">
<template scope="scope">
<el-button v-show='!scope.row.edit' type="primary" @click='scope.row.edit=true' size="small" icon="edit">編輯</el-button>
<el-button v-show='scope.row.edit' type="success" @click='scope.row.edit=false' size="small" icon="check">完成</el-button>
</template>
</el-table-column>
複製代碼
經過dialog來編輯,新建,刪除table的元素這種業務場景相對於前面說的兩種更加的常見。並且也有很多的小坑。 首先咱們要明確一個點 vue 是一個MVVM框架,咱們傳統寫代碼是命令式編程,拿到table這個dom以後就是命令式對dom增刪改。而咱們如今用聲明式編程,只用關注data的變化就行了,因此咱們這裏的增刪改都是基於list這個數組來的。這裏咱們還要明確一點vue 列表渲染注意事項
因爲 JavaScript 的限制, Vue 不能檢測如下變更的數組: * 當你利用索引直接設置一個項時,例如: vm.items[indexOfItem] = newValue
因此咱們想改變table中第一條數據的值,經過this.list[0]=newValue
這樣是不會生效的。
解決方案:
// Array.prototype.splice`
example1.items.splice(indexOfItem, 1, newValue)
複製代碼
因此咱們能夠經過
//添加數據
this.list.unshift(this.temp);
//刪除數據
const index = this.list.indexOf(row); //找到要刪除數據在list中的位置
this.list.splice(index, 1); //經過splice 刪除數據
//修改數據
const index = this.list.indexOf(row); //找到修改的數據在list中的位置
this.list.splice(index, 1,this.updatedData); //經過splice 替換數據 觸發視圖更新
複製代碼
這樣咱們就完成了對table的增刪改操做,列表view也自動響應發生了變化。這裏在修改數據的時候還有一個小坑須要主要。 當咱們拿到須要修改行的數據時候不能直接將它直接賦值給dialog,否則會發生下面的問題。
//賦值對象是一個obj
this.objData=Object.assign({}, row) //這樣就不會共用同一個對象
//數組咱們也有一個巧妙的防範
newArray = oldArray.slice(); //slice會clone返回一個新數組
複製代碼
tab在後臺項目中也比較經常使用的。假設咱們有四個tab選項,每一個tab都會向後端請求數據,但咱們但願一開始只會請求當前的tab數據,並且tab來回切換的時候不會重複請求,只會實例化一次。首先咱們想到的就是用v-if
這樣的確能作到一開始不會掛載後面的tab,但有一個問題,每次點擊這個tab組件都會從新掛載一次,這是咱們不想看到的,這時候咱們就能夠用到<keep-alive>
了。
keep-alive 包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們。 它是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出如今父組件鏈中。
因此咱們就能夠這樣寫tabs了
<el-tabs v-model="activeTab">
<el-tab-pane label="簡介及公告" name="announcement">
<announcement />
</el-tab-pane>
<el-tab-pane label="資訊" name="information">
<keep-alive>
<information v-if="activeTab=='information'" />
</keep-alive>
</el-tab-pane>
<el-tab-pane label="直播流配置" name="stream">
<keep-alive>
<stream v-if="activeTab=='stream'" />
</keep-alive>
</el-tab-pane>
</el-tabs>
複製代碼
Select 選擇器直接使用沒有什麼太多問題,但不少時候咱們須要經過Select來回顯一些數據,當咱們<el-select v-model="objValue">
select 綁定一個obj value回顯就會很蛋疼了,它要求必須保持同一個引用issue。這就意味着,咱們回顯數據的時候想先要找到該數據在arr中的位置,再回塞:demo。這還不是在遠程搜索的狀況下,若是是遠程搜索的狀況還要當疼。 這裏推薦一下vue-multiselect 它能完美的解決前面Element select的問題。目前也是vue component 中比較好用的一個,ui也很是的好看,建議你們能夠嘗試性用一下,真的很是的不錯。
Upload自己沒什麼好說的,文檔寫的蠻清楚了。這裏主要說一下怎麼將Upload組件和七牛直傳結合在一塊兒。
這裏咱們選擇api直傳的方式,就是咱們首先要經過後端(go,node,php均可以)文檔生成七牛上傳必要的token(上傳憑證)和key(資源的最終名稱)。 因此如今只要想辦法講token和key塞進post請求裏面就能夠了,好在官方也提供了這個方法。
<template>
<el-upload
action="https://upload.qbox.me"
:data="dataObj"
drag
:multiple="true"
:before-upload="beforeUpload">
<i class="el-icon-upload"></i>
<div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div>
</el-upload>
</template>
<script>
import { getToken } from 'api/qiniu'; // 獲取七牛token 後端經過Access Key,Secret Key,bucket等生成token
// 七牛官方sdk https://developer.qiniu.com/sdk#official-sdk
export default{
data() {
return {
dataObj: { token: '', key: '' },
image_uri: [],
fileList: []
}
},
methods: {
beforeUpload() {
const _self = this;
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key;
const token = response.data.qiniu_token;
_self._data.dataObj.token = token;
_self._data.dataObj.key = key;
resolve(true);
}).catch(err => {
console.log(err)
reject(false)
});
});
}
}
}
</script>
複製代碼
在使用Element的時候,官方提供了不少能夠本身寫render function的地方,但因爲Element內部都是用jsx 寫render function的,因此demo也都是jsx,但不少人本身項目中實際上是沒有安裝的,致使報錯。但說真的用createElement裸寫render 函數仍是有些蛋疼。咱們要用jsx,首先要安裝 babel-plugin-transform-vue-jsx 安裝方法以下:
npm install\
babel-plugin-syntax-jsx\
babel-plugin-transform-vue-jsx\
babel-helper-vue-jsx-merge-props\
babel-preset-es2015\
--save-dev
複製代碼
.babelrc:文件
{
"presets": ["es2015"],
"plugins": ["transform-vue-jsx"]
}
複製代碼
這樣咱們就能夠愉快的使用 jsx 寫render function了。
**click事件不觸發問題:**一直有人在羣裏問<el-input @click="handlenClick">Click Me</el-input>
怎麼不觸發click事件,雖然element文檔還有完善的空間但這種問題你們還真要本身好好認真看一下官方的FAQ了。
官方說明了全部的原生事件必須添加 .native 修飾符。
修改element樣式問題: 用ui組件總免不了須要對它作一些個性化定製的需求,因此咱們就要覆蓋element的一些樣式。 首先咱們要了解一下vue scoped是什麼,不少人很是喜歡用scoped,媽媽不再用擔憂樣式衝突問題了,其實scoped也沒有很神祕的,它就是基於PostCss的,加了一個做用局的概念。
//編譯前
.example {
color: red;
}
//編譯後
.example[_v-f3f3eg9] {
color: red;
}
複製代碼
它和咱們傳統的命名空間的方法避免css衝突沒有什麼本質性的區別。 如今咱們來講說怎麼覆蓋element-ui樣式。因爲element-ui的樣式咱們是在全局引入的,因此你想在某個view裏面覆蓋它的樣式就不能加scoped,但你又想只覆蓋這個頁面的element樣式,你就可在它的父級加一個class,以用命名空間來解決問題。
.aritle-page{ //你的命名空間
.el-tag { //element-ui 元素
margin-right: 0px;
}
}
複製代碼
建議向樓主同樣專門建一個scss文件裏專門自定義element-ui的各類樣式。線上代碼
其它關於element相關的東西真的沒有什麼好說的了,人家文檔和源碼就放在那裏,有問題就去看文檔,再去issue裏找找,再去看看源碼,大部分問題都能解決了。給一個訣竅其實大部分詭異的問題均可以經過加一個key或者 Vue.nextTick來解決。。
管理後臺富文本也是一個很是重要的功能,樓主在這裏也踩了很多的坑。樓主在項目裏最終選擇了 tinymce
這裏在簡述一下推薦使用tinymce的緣由:tinymce 是一家老牌作富文本的公司(這裏也推薦 ckeditor,也是一家一直作富文本的公司,新版本很不錯),它的產品經受了市場的承認,無論是文檔仍是配置的自由度都很好。在使用富文本的時候有一點也很關鍵就是複製格式化,以前在用一款韓國人作的富文本summernote被它的格式化坑的死去活來,但 tinymce 的去格式化至關的好,它還有一個增值項目就是powerpaste,那是無比的強大,支持從word裏面複製各類東西,都不會有問題。富文本還有一點也很關鍵,就是拓展性。樓主用tinymce寫了好幾個插件,學習成本和容易度都不錯,很方便拓展。最後一點就是文檔很完善,基本你想獲得的配置項,它都有。tinymce也支持按需加載,你能夠經過它官方的build頁定製本身須要的plugins。 我再來分析一下市面上其它的一些富文本:
樓主列舉了不少富文本但並無列舉任何 vue 相關的富文本,主要是由於富文本真的比想象中複雜,在前面的文章裏也說過了,其實用 vue 封裝組件很方便的,不必去用人家封裝的東西什麼vue-quill vue-editor這種都只是簡單包了一層,沒什麼難度的。還不如本身來封裝,靈活性可控性更強一點。還有一點基於 vue 真沒什麼好的富文本,不像 react 有 facebook 出的 draft-js,ory 出的 editor,這種大廠出的產品。
固然你也能夠選擇一些付費的富文本編輯器,做者本身公司裏面有一個項目就使用了 froala-editor 這款編輯器。無論是美觀和易用性都是不錯的,公司買的是專業版,一年也就 $349
,價格也是很合理的,但其實省去的程序員開發陳本可能遠不止這個價錢。
這裏來簡單講一下在本身項目中使用 Tinymce
的方法。
因爲目前使用 npm 安裝
Tinymce
方法比較負責複雜並且還有一些問題(往後可能會採用該模式)。:space_invader:
目前採用全局引用的方式。代碼地址:static/tinymce
static目錄下的文件不會被打包, 在 index.html 中引入。
使用 因爲富文本不適合雙向數據流,因此只會 watch 傳入富文本的內容一次變化,只會就不會再監聽了,若是以後還有改變富文本內容的需求。 能夠經過 this.refs.xxx.setContent()
來設置
源碼也很簡單,有任何別的需求均可以在 @/components/Tinymce/index.vue
中自行修改。
markdown 咱們這裏選用了 simplemde-markdown-editor ,簡單的用vue封裝了一下地址,若是需求方能接受 markdown 就必定要用 markdown,坑真心會比富文本少不少。這裏咱們用markdown作了編輯器,還須要一個能解析的的東西。能夠你傳給後端讓後端幫你轉化,也能夠前端本身來,這裏推薦一個轉化庫showdown。使用方法:
import('showdown').then(showdown => { //用了 Dynamic import
const converter = new showdown.Converter();//初始化
this.html = converter.makeHtml(this.content)//轉化
})
複製代碼
用法也很簡單兩行代碼就完成了markdown to html,固然它還有不少個性畫的配置,你們有需求自行研究吧。
這裏先明確一點,若是你的業務需求對導出文件的格式沒有什麼要求,不建議導出成xlsx格式的,直接導出成csv的就行了,真的會簡單不少。建立一個a標籤,寫上data:text/csv;charset=utf-8
頭,再把數據塞進去,encodeURI(csvContent)
一下就行了,詳情就不展開了,你們能夠借鑑這個stackoverflow回答。 咱們重點說一下轉xlsx,咱們這裏用到了js-xlsx,一個功能很強大excel處理庫,只是下載各類格式excel,還支持讀取excel,但上手難度也很是大,至關的複雜,其中涉及很多二進制相關的東西。不過好在官方給了咱們一個demo例子,咱們寫不來還抄不來麼,因而咱們就借鑑官方的例子來改造了一下,具體原理就不詳細說了,真的很複雜。。。 重點是咱們怎麼使用!首先咱們封裝一個Export2Excel.js, 它又依賴三個庫
require('script-loader!file-saver'); //保存文件用
require('script-loader!vendor/Blob'); //轉二進制用
require('script-loader!xlsx/dist/xlsx.core.min'); //xlsx核心
因爲這幾個文件不支持import引入,因此咱們須要`script-loader`來將他們掛載到全局環境下。
複製代碼
它暴露了兩個接口export_table_to_excel
和export_json_to_excel
,咱們經常使用export_json_to_excel
由於更加的可控一點,咱們能夠自由的洗數據。
handleDownload() {
require.ensure([], () => { // 用 webpack Code Splitting xlsl仍是很大的
const { export_json_to_excel } = require('vendor/Export2Excel');
const tHeader = ['序號', '文章標題', '做者', '閱讀數', '發佈時間']; // excel 表格頭
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'];
const list = this.list;
const data = this.formatJson(filterVal, list); // 自行洗數據 按序排序的一個array數組
export_json_to_excel(tHeader, data, '列表excel');
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
}
複製代碼
管理後臺圖表也是常見得需求。這裏圖表就只推薦ECharts,功能齊全,社區demo也豐富gallery。我仍是那個觀點,大部分插件建議你們仍是本身用vue來包裝就行了,真的很簡單。ECharts支持webpack引入,圖省事能夠將ECharts整個引入var echarts = require('echarts');
不過ECharts仍是不小的,咱們大部分狀況只是用到不多一部分功能,我平時習慣於按需引入的。
// 引入 ECharts 主模塊
var echarts = require('echarts/lib/echarts');
// 引入柱狀圖
require('echarts/lib/chart/bar');
// 引入提示框和標題組件
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
複製代碼
webpack中使用ECharts文檔 ECharts按需引入模塊文檔 接下來咱們就要在vue中聲明初始化ECharts了。由於ECharts初始化必須綁定dom,因此咱們只能在vue的mounted生命週期裏初始化。
mounted() {
this.initCharts();
},
methods: {
this.initCharts() {
this.chart = echarts.init(this.$el);
this.setOptions();
},
setOptions() {
this.chart.setOption({
title: {
text: 'ECharts 入門示例'
},
tooltip: {},
xAxis: {
data: ["襯衫", "羊毛衫", "雪紡衫", "褲子", "高跟鞋", "襪子"]
},
yAxis: {},
series: [{
name: '銷量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
})
}
}
複製代碼
就這樣簡單,ECharts就配置完成了,這時候你想說個人data是遠程獲取的,或者說我動態改變ECharts的配置該怎麼辦呢?咱們能夠經過watch來觸發setOptions方法
//第一種 watch options變化 利用vue的深度 watcher,options一有變化就從新setOption
watch: {
options: {
handler(options) {
this.chart.setOption(this.options)
},
deep: true
},
}
//第二種 只watch 數據的變化 只有數據變化時觸發ECharts
watch: {
seriesData(val) {
this.setOptions({series:val})
}
}
複製代碼
其實都差很少,仍是要結合本身業務來封裝。後面就和平時使用ECharts沒有什麼區別了。題外話ECharts的可配置項真心多,你們使用的時候可能要花一點時間瞭解它的api的。知乎有個問題:百度還有什麼比較良心的產品?答案:ECharts,可見ECharts的強大與好用。
建立與編輯 其實後臺建立與編輯功能是最多見的了,它區別去前臺項目多了改的需求,但大部分建立頁面與編輯頁面字段和ui幾乎是同樣的,因此咱們準備公用一個component來對應不一樣的頁面。有兩種常見的方法,來區別建立與編輯。
computed: {
isEdit() {
return this.$route.meta.isEdit // 根據meta判斷
// return this.$route.path.indexOf('edit') !== -1 // 根據路由判斷
}
},
created() {
if (this.isEdit) {
this.fetchData();
}
},
複製代碼
就這樣簡單的實現了多路由複用了一個component,其實不僅是建立和編輯能夠這樣用,如兩個列表的如出一轍,只是一個是內部文章另外一個是調取外部文章都能複用組件,經過meta的方式來判斷調取不一樣的接口。
常規佔坑,這裏是手摸手,帶你用vue擼後臺系列。
完整項目地址:vue-element-admin
樓主我的免費圈子