項目地址 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後臺管理系統,期待吧...