環境搭建事先準備
本文只注重前端的搭建,後端只提及部分
本框架是基於vue-cli3搭建的,首先你需要安裝vue-cli腳手架。
新手上路,有什麼寫錯的請指點
- 查看你的版本,cmd命令
vue -V
- 創建你的項目(admin爲項目名稱)
vue create admin
然後安裝你的各種負載,這裏不進行詳細說明
我的package.json可以進行參考
"dependencies": {
"axios": "^0.19.0",
"babel-plugin-import": "^1.12.2",
"core-js": "^3.3.2",
"enquire.js": "^2.1.6",
"less": "^3.10.3",
"vue": "^2.6.10",
"vue-apexcharts": "^1.5.1",
"vue-router": "^3.1.3",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.3",
"babel-plugin-component": "^1.1.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"less-loader": "^5.0.0",
"lint-staged": "^9.4.2",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"svg-sprite-loader": "^4.1.6",
"vue-template-compiler": "^2.6.10"
},
- 配置vue.config.js
在vue-cli3以後,目錄結構十分精簡,那麼我們的配置寫在哪裏?
官方文檔有說明,在項目根目錄下面新建vue.config.js文件
以下我的配置,可以結合官網文檔參考一下
const path = require('path') // 引入path模塊
function resolve (dir) {
return path.join(__dirname, dir) // path.join(__dirname)設置絕對路徑
}
module.exports = {
publicPath: '/', // 基本路徑
outputDir: 'dist', // 輸出文件目錄
lintOnSave: true, // eslint-loader 是否在保存的時候檢查
productionSourceMap: true, // 生產環境是否生成 sourceMap 文件
// css相關配置
css: {
extract: true, // 是否使用css分離插件 ExtractTextPlugin
sourceMap: false, // 開啓 CSS source maps
modules: false,
loaderOptions: {
less: {
modifyVars: {
'primary-color': '#1DA57A',
'link-color': '#1DA57A',
'border-radius-base': '2px'
},
javascriptEnabled: true
}
}
},
chainWebpack: config => {
// 配置路徑別名
config.resolve.alias
.set('@', resolve('src'))
.set('_c', resolve('src/components'))
.set('_v', resolve('src/views'))
.set('_u', resolve('src/utils'))
.set('_m', resolve('src/common/mixin'))
.set('_api', resolve('src/api'))
config.module.rules.delete('svg') // 配置svg-sprite-loader,沒有此需求請註釋
config.module
.rule('svg-smart')
.test(/\.svg$/)
.include
.add(resolve('src/assets/svgs'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
},
// webpack-dev-server 相關配置
devServer: {
open: false, // 是否在構建完成後打開默認瀏覽器
host: '127.0.0.1', // 監聽地址
port: 8999, // 端口
https: false, // 是否有必須通過https的服務
hotOnly: false,
proxy: {
'/api': {
target: 'url', //代理
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
},
'/foo': {
target: '<other_url>'
}
},
before: app => {}
},
// 第三方插件配置
pluginOptions: {}
}
以上便是當前目錄結構
- 路由配置
路由使用的是動態路由,分爲兩部分:不需要鑑權的和需要鑑權的
以下便是基礎路由(不需要鑑權的):
import Vue from 'vue'
import Router from 'vue-router'
// 全局Router異常處理
const originalPush = Router.prototype.push
Router.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => { if (typeof err !== 'undefined')console.log(err) })
}
Vue.use(Router)
let constRouter = [
{
path: '/login',
name: '登錄頁',
component: LoginView // 需要自行引入
},
{ // 主界面,基礎路由中先不指定組件
path: '/',
name: '主界面',
redirect: '/home'
},
{
path: '*',
name: '404',
component: 404// 需要自行引入
},
]
let router = new Router({
// mode: 'history',
routes: constRouter
})
export default router
需要鑑權的路由,在登錄時候,後臺拼接返回路由
我們需要用到router.addRoutes來添加動態路由。
在登陸系統的時候,如果登錄成功,然後返回一個SESSIONID或者JWT作爲身份鑑權標識或者沒有???(取決於你的後臺,這裏不過多敘述),然後重定向路由
login({ // 登錄請求,後面會講到
username: name,
password: password
}).then((r) => {
this.$router.push('/') //重定向路由
}).catch((e) => {
console.error(e)
})
- 路由攔截配置
這部分借鑑github上面一個大佬的,找半天地址沒找到
import router from './../router'
import localStore from '_u/localstorage' // 存到瀏覽器
import request from '_u/request' // axios請求
const whiteList = ['/login'] // 白名單
let asyncRouter // 新增路由
// 導航守衛,渲染動態路由
router.beforeEach((to, from, next) => {
if (whiteList.indexOf(to.path) !== -1) {
next()
}
let token = localStore.get('USER_TOKEN')
let user = localStore.get('USER')
let userRouter = get('USER_ROUTER')
if (token.length && user) {
if (!asyncRouter) {
if (!userRouter) {
request.get(`menu/${user.username}`).then((res) => {
asyncRouter = res.data
save('USER_ROUTER', asyncRouter)
go(to, next)
}).catch(err => { console.error(err) })
} else {
asyncRouter = userRouter
go(to, next)
}
} else {
next()
}
} else {
next('/login')
}
})
function go (to, next) {
asyncRouter = filterAsyncRouter(asyncRouter)
console.log(asyncRouter)
router.addRoutes(asyncRouter)
next({ ...to, replace: true })
}
function save (name, data) {
localStorage.setItem(name, JSON.stringify(data))
}
function get (name) {
return JSON.parse(localStorage.getItem(name))
}
function filterAsyncRouter (routes) {
return routes.filter((route) => {
let component = route.component
if (component) {
switch (route.component) {
case 'MenuView':
route.component = MenuView // 表示菜單頁面
break
case 'PageView':
route.component = PageView // 表示頁面
break
case 'EmptyPageView':
route.component = EmptyPageView //表示空白頁面
break
case 'HomePageView':
route.component = HomePageView //表示主界面
break
default:
route.component = view(component) // 其他均爲組件
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
}
})
}
function view (path) {
return function (resolve) {
import(`@/views/${path}.vue`).then(mod => {
resolve(mod)
})
}
}
剛剛登錄請求重定向會被路由攔截器給攔截,判斷用戶是否有登錄,沒有登錄就會請求後臺獲取權限(動態路由),然後存儲在本地。接下來動態添加路由,這就完成了路由的動態添加。
router.addRoutes(asyncRouter) //上面有
- axios的配置
在上面其實已經用到了請求,下面講解如何配置axios。
import axios from 'axios'
import { message, Modal, notification } from 'ant-design-vue' // 前端框架
import moment from 'moment'
import store from '../store' // vuex
import localstore from '_u/localstorage' // 本地存儲
moment.locale('zh-cn') // 語言
// 統一配置
let REQUEST= axios.create({
baseURL: 'http://127.0.0.1:9527/', // 後臺地址,如果啓用api代理,在vue.config.js要進行修改
responseType: 'json',
validateStatus (status) {
// 200 外的狀態碼都認定爲失敗
return status === 200
}
})
// 攔截請求
REQUEST.interceptors.request.use((config) => {
if (config.url !== 'login') {
let expireTime = store.state.account.expireTime
let now = moment().format('YYYYMMDDHHmmss')
if (now - expireTime >= -10) {
Modal.error({
title: '登錄已過期',
content: '很抱歉,登錄已過期,請重新登錄',
okText: '重新登錄',
mask: false,
onOk: () => {
return new Promise((resolve, reject) => {
localstore.clear()
location.reload()
}).catch(function (reason) {
console.log('catch:', reason)
})
}
})
}
}
if (store.state.account.token) {
config.headers.Authentication = store.state.account.token
}
return config
}, (error) => {
return Promise.reject(error)
})
// 攔截響應
REQUEST.interceptors.response.use((config) => {
return config
}, (error) => {
if (error.response) {
let errorMessage = error.response.data === null ? '系統內部異常,請聯繫網站管理員' : error.response.data.message
switch (error.response.status) {
case 404:
notification.error({
message: '系統提示',
description: '很抱歉,資源未找到',
duration: 4
})
break
case 403:
case 401:
notification.warn({
message: '系統提示',
description: '沒有相應權限或者登錄已失效',
duration: 4
})
localstore.clear()
break
default:
notification.error({
message: '系統提示',
description: errorMessage,
duration: 4
})
break
}
}
return Promise.resolve(error)
})
export default request
然後請求格式:
import request from '_u/request' // 上面哪個axios實例
export function login (data) {
return request({
method: 'post',
url: '/login',
data: data
})
}
再補充一下目錄結構
其他(按鈕權限)
- 按鈕權限列表
如果你想給不同用戶展示不同的按鈕的時候(數據權限),你會用到這個。
哪個你後臺不光需要返回動態路由,還需要返回按鈕權限數組列表
這樣的一個東西,然後存儲再vuex(刷新會丟失),還要存儲在本地
-
自定義指令
官方文檔
install.js 全局註冊,注意要在main.js裏面引用
import Vue from 'vue'
import { hasPermission, hasNoPermission} from '_u/permissionDirect'
const Plugins = [
hasPermission,
hasNoPermission,
hasAnyPermission,
hasRole,
hasAnyRole
]
Plugins.map((plugin) => {
Vue.use(plugin)
})
export default Vue
// 必須包含列出的所有權限,元素才顯示
export const hasPermission = {
install (Vue) {
Vue.directive('hasPermission', {
bind (el, binding, vnode) {
let permissions = vnode.context.$store.state.account.permissions
let value = binding.value
let flag = true
for (let v of value) {
if (!permissions.includes(v)) {
flag = false
}
}
if (!flag) {
if (!el.parentNode) {
el.style.display = 'none'
} else {
el.parentNode.removeChild(el)
}
}
}
})
}
}
// 當不包含列出的權限時,渲染該元素
export const hasNoPermission = {
install (Vue) {
Vue.directive('hasNoPermission', {
bind (el, binding, vnode) {
let permissions = vnode.context.$store.state.account.permissions
let value = binding.value
let flag = true
for (let v of value) {
if (permissions.includes(v)) {
flag = false
}
}
if (!flag) {
if (!el.parentNode) {
el.style.display = 'none'
} else {
el.parentNode.removeChild(el)
}
}
}
})
}
}
看着很多,就是一句話,判斷有沒有這個權限,沒有就不渲染這個按鈕
使用就是下面這個樣子
<a-button @click="batchDelete" v-hasPermission="['user:delete']">刪除</a-button>