項目地址 vue-admin-webapp 歡迎star,forkcss
相信許多人和我同樣剛接觸 vue 時看文檔都很枯燥,看完 vue,還有 vueRouter 、vuex 、vue-cli、es6 (學不動了。。。 ) 對於看完教程以後又遲遲不能上手實際項目,只能寫一些簡單的小demo,這確定和實際生產工做是有出入的,因而乎我就打算本身從零開始使用最新的技術棧搭建一個vue後臺管理系統,依此加深對理論知識的學習,並加強本身的項目能力,因此但願本系列教程對你開發vue項目有所幫助。html
vue-admin-webapp 是一個後臺管理 spa 頁面,它基於 vue 和 element-ui 採用了最新的前端技術棧,實現了登陸權限驗證,動態路由生成,並使用 easy-mock 來模擬請求數據,實現了典型的業務模型案例,它能夠幫你快速搭建後臺管理系統模板,並根據實際的業務需求添加路由來實現企業級管理頁面,相信本項目必定能幫助到你。前端
- 在線預覽-githubvue
- 在線預覽-gitee (推薦國內用戶)node
目前版本基於 webpack 4.0+
和 vue-cli 3.x
版本構建,須要 Node.js 8.9或更高版本(推薦8.11.0+),相關知識能夠自行進官網進行了解android
- 登陸 / 註銷
- 登陸仿GeeTest-極驗安全策略
- 頁面
- 初次進入引導用戶
- sideBar收縮和展開
- 全屏控制
- 側邊欄
- 根據不一樣用戶權限展現相應的動態左側菜單
- 權限驗證
- 管理員頁面
- 權限設置
- 表格操做
- 涉及日常業務遇到的相關表格操做(參考)
- Excel
- Excel導出
- Excel導入
- 多級表頭導出
- Echarts
- 滑動顯示更多數據
- 動態切換charts
- map地圖使用
- Icons
- element-icon
- 阿里iconfont
複製代碼
在開始以前,請確保在本地安裝 node 和 webpack 及 git。 本項目涉及的技術棧主要有 ES6 、vue 、vuex 、vue-router 、vue-cli 、axios 、webpack 、element-ui 、easyMock ,因此你最好提早熟悉瞭解這些知識,這將對你認識學習該項目有很大幫助webpack
下面是整個項目的目錄結構ios
├── public # 靜態資源 │ ├── favicon.ico # favicon圖標 │ └── index.html # html模板 ├── src # 源代碼 │ ├── api # 全部請求 │ ├── assets # 圖片、字體等靜態資源 │ ├── components # 全局公用組件 │ ├── layout # 頁面總體佈局盒子 │ ├── mixins # 全局混入模塊 │ ├── plugins # 全局插件部分 │ ├── router # 路由 │ ├── store # 全局store管理 │ ├── style # 全局樣式 │ ├── utils # 全局公用方法 │ ├── vendor # 公用vendor(excel導入導出) │ ├── views # views全部頁面 │ ├── App.vue # 入口頁面 │ ├── main.js # 入口文件 加載組件 初始化等 ├── .borwserslistrc # 瀏覽器兼容相關 ├── .env.xxx # 環境變量配置  ├── .eslintrc.js # eslint 配置項 ├── .gitignore.js # git忽略文件設置 ├── .babelrc.config.js # babel-loader 配置 ├── package.json # package.json ├── postcss.config.js # postcss 配置 └── vue.config.js # vue-cli 配置 複製代碼
# 克隆項目 git clone git@github.com:gcddblue/vue-admin-webapp.git # 進入項目目錄 cd vue-admin-webapp # 安裝依賴 npm install # 啓動服務 npm run serve 複製代碼
啓動完成後將打開瀏覽器訪問 http://localhost:8080
,接下來你就能夠根據本身的實際需求,能夠添加或修改路由,編寫本身的業務代碼。git
除去登陸頁外,整個頁面架構由三個部分組成 頭部
側邊欄
右側內容頁
在項目@/layout/index.js文件中對對這三個組件進行封裝,經過點擊左側菜單切換右側router-view
的路由更替,對應的項目文件以下es6
在vue項目中,和後臺進行請求交互這塊,咱們一般都會選擇axios庫,它是基於promise的http庫,可運行在瀏覽器端和node.js中。在本項目中主要實現了請求和響應攔截,get,post請求封裝。
經過在項目中建立不一樣環境的文件,我這裏只建立了開發和生產環境的,固然,你也能夠建立基於測試的.env.test
等文件,以.env.production
爲例:
ENV = 'production' # base api VUE_APP_BASE_API = 'https://www.easy-mock.com/mock/5cee951f11690b5261b75566/admin' 複製代碼
只要以 VUE_APP_
開頭的變量都會被 webpack.DefinePlugin
靜態嵌入到客戶端的包中。你能夠在應用的代碼中這樣訪問它們,例如我在@/api/index.js中初始化axios:
const $axios = axios.create({ timeout: 30000, // 基礎url,會在請求url中自動添加前置連接 baseURL: process.env.VUE_APP_BASE_API }) 複製代碼
經過建立api文件夾將全部接口都集中在這個文件夾中,根據不一樣的業務建立不一樣js文件,來更好的劃分接口的功能,其中index.js中代碼以下:
import axios from 'axios' import Qs from 'qs' // 處理post請求數據格式 import store from '@/store' import router from '@/router' import Vue from 'vue' import { Loading, Message } from 'element-ui' // 引用element-ui的加載和消息提示組件 const $axios = axios.create({ // 設置超時時間 timeout: 30000, // 基礎url,會在請求url中自動添加前置連接 baseURL: process.env.VUE_APP_BASE_API }) Vue.prototype.$http = axios // 這裏併發請求以便在組件使用this.$http.all(),具體看dashborad頁面 // 在全局請求和響應攔截器中添加請求狀態 let loading = null /** * 請求攔截器 * 用於處理請求前添加loading、判斷是否已保存token,並在每次請求頭部添加token */ $axios.interceptors.request.use( config => { loading = Loading.service({ text: '拼命加載中' }) const token = store.getters.token if (token) { config.headers.Authorization = token // 請求頭部添加token } return config }, error => { return Promise.reject(error) } ) /** * 響應攔截器 * 用於處理loading狀態關閉、請求成功回調、響應錯誤處理 */ $axios.interceptors.response.use( response => { if (loading) { loading.close() } const code = response.status // 請求成功返回response.data if ((code >= 200 && code < 300) || code === 304) { return Promise.resolve(response.data) } else { return Promise.reject(response) } }, error => { if (loading) { loading.close() } console.log(error) if (error.response) { switch (error.response.status) { case 401: // 返回401 清除token信息並跳轉到登錄頁面 store.commit('DEL_TOKEN') router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }) break case 404: Message.error('網絡請求不存在') break default: Message.error(error.response.data.message) } } else { // 請求超時或者網絡有問題 if (error.message.includes('timeout')) { Message.error('請求超時!請檢查網絡是否正常') } else { Message.error('請求失敗,請檢查網絡是否已鏈接') } } return Promise.reject(error) } ) // get,post請求方法 export default { post(url, data) { return $axios({ method: 'post', url, data: Qs.stringify(data), headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }) }, get(url, params) { return $axios({ method: 'get', url, params }) } } 複製代碼
如上,你們能夠看個人註釋說明,axios配置的封裝是整個項目中很重要的模塊,其實在不一樣的項目中,axios封裝都大同小異,因此,只要掌握了一種技巧,下次開發新項目也就很容易完成封裝這塊。
路由是組織一個vue項目的關鍵,在對項目原型分析後,接下來的第一步就是編寫路由,本項目中,主要分爲兩種路由,currencyRoutes
和 asyncRoutes
currencyRoutes
:表明通用路由,意思就是不須要權限判斷,不一樣角色用戶都顯示的頁面,如:登錄頁、404等
asyncRoutes
: 表明動態路由,須要經過判斷權限動態分配的頁面,有關的權限判斷的方法接下來會介紹。
路由相關配置說明:
/** * 路由相關屬性說明 * hidden: 當設置hidden爲true時,意思不在sideBars側邊欄中顯示 * mete{ * title: xxx, 設置sideBars側邊欄名稱 * icon: xxx, 設置ideBars側邊欄圖標 * noCache: true 當設置爲true時不緩存該路由頁面 * } */ 複製代碼
本項目經過路由聯動更新側邊欄,全部側邊欄配置都是在前端完成的,經過訪問接口,後端會返回一個權限相關的list數組,其中數組值爲路由的name屬性值,前端經過遞歸遍歷asyncRoutes
判斷權限list中是否包含有對應的name路由,最終會返回包含該用戶角色全部權限路由頁面的addRoutes的數組對象。
具體實現是在路由index.js中設置一個全局前置導航守衛,具體判斷流程以下:
// 導航守衛 router.beforeEach(async (to, from, next) => { document.title = getTitle(to.meta.title) if (to.path === '/login') { next() } else { if (store.getters.token) { const hasRoles = store.getters.roles.length > 0 if (hasRoles) { next() } else { try { const { roles } = await store.dispatch('user/_getInfo') const addRoutes = await store.dispatch( 'permission/getAsyncRoutes', roles ) router.addRoutes(addRoutes) // hack method to ensure that addRoutes is complete // set the replace: true, so the navigation will not leave a history record next({ ...to, replace: true }) } catch (error) { Message.error(error) } } } else { next({ path: '/login', query: { redirect: to.fullPath } }) } } }) 複製代碼
這裏我在經過addRoutes添加路由時,遇到一個bug,當切換角色時,並不能刪除以前添加動態路由,因此這邊從新初始化router.matcher
的屬性方式實現:
const creatRouter = () => { return new Router({ routes: currencyRoutes, scrollBehavior() { return { x: 0, y: 0 } } }) } const router = creatRouter() // 解決addRoute不能刪除動態路由問題 export function resetRouter() { const reset = creatRouter() router.matcher = reset.matcher } 複製代碼
當我每次退出登陸的時候執行resetRouter
方法來初始化router對象,實現刪除以前動態添加的路由。
最後經過element-ui的el-menu組件來遞歸遍歷路由對象加載側邊欄。
身爲前端開發人員,相信你們都知道Mock數據吧,它的做用主要就是僞造假數據使團隊能夠並行開發,本項目使用了 easy-mock 來實現接口數據的請求,你們能夠去官網看下簡單教程,easy-mock
它的好處就是不用像傳統mock數據那樣須要在項目中建立mock文件夾並攔截ajax來實現假數據請求,它是真真實實的api請求,並容許任何跨域請求,下面是本項目全部接口
其中全部接口經過建立 _res
字段來判斷請求是否含有Authorzation頭部字段是否含有token來判斷用戶是不是登錄狀態,以下 getCardsData接口的配置:
{ code: 0, data: { vistors: '@integer(10000, 100000)', message: '@integer(100, 1000)', order: '@integer(0, 1000)', profit: '@integer(1000, 100000)' }, _res: function({ _req, Mock }) { if (!_req.header.authorization) { return { status: 401, data: { msg: '未受權' } } } else { return { status: 200 } } } } 複製代碼
mock數據在項目開發中可以起到推動項目進度的功效,你們能夠預先和後端人員商量好,並先拿到假數據字段,而後mock本身的假數據,這樣你就能夠不用等後端人員開發接口而使項目卡住。通常在項目中,建立.env.development
和.env.production
文件,表明了開發和生產環境,在文件裏能夠定義不一樣環境接口的請求url
# base api VUE_APP_BASE_API = 'https://www.easy-mock.com/mock/5cee951f11690b5261b75566/admin' 複製代碼
在封裝axios這樣初始化
const $axios = axios.create({ // 設置超時時間 timeout: 30000, // 基礎url,會在請求url中自動添加前置連接 baseURL: process.env.VUE_APP_BASE_API }) 複製代碼
這樣就能夠自動根據不一樣的環境切換請求地址,不用咱們一個一個的修改每個請求接口
經過將登陸函數封裝在store中,當點擊登錄時,調用this.$store.dispatch('user/_login', this.ruleForm)
這個action方法,當後臺接口驗證成功時,會返回 token
字段,前端會調用 localStroage
接口將這個 token
保存在本地,之後每次請求前經過攔截器將這個token保存在 Authorization
這個頭部字段中,後臺只要驗證這個token就知道這個用戶的信息了。還不僅token的同窗,能夠 瘋狂點擊token說明 裏面對http爲何要添加toekn及token介紹的都很詳細。
這裏我還採用了仿 geetest 行爲驗證,經過滑動圖片來驗證真人操做,其中原理利用 h5 canves繪製功能,繪製底部圖片和滑塊圖片,而後監聽mouseMove事件,當滑動block摳出的圖片和初始化圖片的y座標差小於10時觸發驗證成功函數。
若是你的多個組件都用到一個或多個方法,咱們能夠不用每次都粘貼複製,這樣豈不是很low,咱們能夠將這些方法封裝在一個js文件中,當個人某個組件須要調用這個方法時
import aMixin from '@/mixins/a-mixin' export default { name: 'page1', mixins: [newsMixin] //調用mixins屬性,將aMixin這個模塊的數據及方法等都添加進這個組建吧 } 複製代碼
這個方法有什麼用呢,它主要是能夠將一個對象凍結,防止對象被修改,那這個對vue項目有什麼優化做用呢,你們都知道vue採用了數據劫持的方式遍歷數據對象,把這些屬性轉爲getter、settter方法來監聽並通知數據的變化,因此當你遇到一個巨大的數組或者對象,而且肯定數據不會修改,這時就可使用 Object.freeze()
方法來組織vue對這個巨大數據的轉化,,這可讓性能獲得很大的提高,舉個例子:
new Vue({ data: { // vue不會對list裏的object作getter、setter綁定 list: Object.freeze([ { value: 1 }, { value: 2 } ]) }, mounted () { // 界面不會有響應 this.list[0].value = 100; // 下面兩種作法,界面都會響應 this.list = [ { value: 100 }, { value: 200 } ]; this.list = Object.freeze([ { value: 100 }, { value: 200 } ]); } }) 複製代碼
當咱們某個組件或js文件須要引入多個模塊時,通常作法就是,import每一個模塊,這樣顯然是至關繁瑣的,這時 require.context
函數將派上用場,那個這個函數到底怎麼用呢,這裏官法介紹是 主要用來實現自動化導入模塊,在前端工程中,若是遇到從一個文件夾引入不少模塊的狀況,可使用這個api,它會遍歷文件夾中的指定文件,而後自動導入,使得不須要每次顯式的調用import導入模塊
require.context
函數接受三個參數:
如
require.context('./test', false, /.test.js$/) #上面的代碼遍歷當前目錄下的test文件夾的全部.test.js結尾的文件,不遍歷子目錄 複製代碼
require.context
函數執行後返回一個函數,而且這個函數包含了三個屬性:
咱們常會遍歷keys返回的數組來對路徑進行處理,這是至關方便的,最後 require.context
返回的函數接受keys放回數組中的路徑成員做爲參數,並返回這個路徑文件的模塊
下面是我使用 require.context
函數動態生成moudles對象
import Vue from 'vue' import Vuex from 'vuex' import getters from './getters' const path = require('path') Vue.use(Vuex) const files = require.context('./modules', false, /\.js$/) let modules = {} files.keys().forEach(key => { let name = path.basename(key, '.js') modules[name] = files(key).default || files(key) }) const store = new Vuex.Store({ modules, getters }) export default store 複製代碼
對於一些不常改動的模塊庫,例如: vue
vueRouter
vuex
echarts
element-ui
等, 咱們讓 webpack
不將他們進行打包,而是經過 cdn
引入,這樣就能夠減小代碼大小,減小服務器帶寬,並經過cdn將它們緩存起來,提升網站性能 。
具體實現就是修改 vue.config.js
,爲對象模塊添加 externals
完整配置以下:
const cdn = { css: [ // element-ui css 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' ], js: [ // vue 'https://unpkg.com/vue/2.5.22/vue.min.js', // element-ui 'https://unpkg.com/element-ui/lib/index.js', // vue 'https://unpkg.com/vuex/3.1.0/vuex.min.js' ] } # 不打包vue、element-ui、vuex module.exports = { externals: { vue: 'Vue', 'element-ui':'ELEMENT', vuex: 'Vuex' }, chainWebpack: config => { config.plugin('html') .tap(args => { args[0].cdn = cdn return args }) } } 複製代碼
接下來修改 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <% if (process.env.NODE_ENV === 'production') { %> <!-- 引入樣式 --> <% for(var css of htmlWebpackPlugin.options.cdn.css) { %> <link rel="stylesheet" href="<%=css%>"> <% } %> <!-- 引入js --> <% for(var js of htmlWebpackPlugin.options.cdn.js) { %> <script src="<%=js%>"></script> <% } %> <% } %> <title>vue-admin-webapp</title> </head> <body> <noscript> <strong>We're sorry but vue-admin-webapp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> 複製代碼
好了,大公告成
能夠關注個人另外一篇文章 正確姿式使用vue cli3配置多頁項目
能夠關注個人另外一篇文章 正確姿式使用vue cli3建立項目
這個項目是我在上班之餘斷斷續續開發的,沒太寫過技術貼,文筆和邏輯組織能力仍是至關差的,你們見諒。起初在沒開始作以前以爲應該至關的順利的,沒想到真正一步一步實現時,仍是和本身最初設想是有出入的,期間遇到了很多的bug,大多都是由於細節不注意,也讓我更加體會 好記性不如爛筆頭 這句話,實踐纔是真理啊,多動手,多探索。最後我會考慮使用 uni-app 這個框架來開發多平臺(小程序、android、ios、h5)移動版vue後臺管理系統,期待吧...