vue-element-admin4.0国内节点访问地址:https://panjiachen.gitee.io/vue-element-admin-site/zh/
本此使用的是https://github.com/PanJiaChen/vue-element-admin/tree/i18n 国际化分支的版本。说是除了国际化其他都一样。
本文主要介绍前台动态的使用资源权限。
- 后台使用springboot2搭建项目就不说了,百度一下一大堆。
- 前台开发前需要安装一下nodejs,这里注意一下nodejs和npm的版本要相互匹配,否则在编译的时候会报错。
打开项目后需要安装一下依赖的模块。
npm install
- 1、全局请求配置的修改。
src/utils/request.js 当然也有用src/utils/request-axios.js的 修改方式大同小异zhe'li
import axios from 'axios'// import { MessageBox, Message } from 'element-ui'import { MessageBox, Message } from 'element-ui'import store from '@/store'import { getToken, removeToken } from '@/utils/auth' const qs = require('querystring') create an axios instanceconst service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, url = base url + request url withCredentials: true, send cookies when cross-domain requests timeout: 5000, request timeout jsonData: false}) request interceptorservice.interceptors.request.use( config => { if (store.getters.token) { let each request carry token ['X-Token'] is a custom headers key please modify it according to the actual situation config.headers['X-Token'] = getToken() } if (config.method.toLowerCase() === 'get') { config.params = config.data } else if (config.method.toLowerCase() === 'post') { if (config.jsonData) { config.headers['Content-Type'] = 'application/json;charset=UTF-8' config.data = JSON.stringify(config.data) } else { config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8' config.data = qs.stringify(config.data) } } console.log(config) // for debug return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) }) // response interceptorservice.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ r esponse => { const res = response.data // code==2000是业务逻辑成功,其他的返回code都是业务逻辑错误 if (res.code === 5002) { // to re-login // token过期或者密码被修改了都需要重新获取token MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) } else { return res } }, error => { // console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) removeToken() if (error.response && error.response.status === 403) { this.$router.push(`/login?redirect=${this.$route.fullPath}`) } return Promise.reject(error) }) export default service
这个文件修改的主要是get、post请求传递参数的设置。
get使用params传递参数。post就data
还有就是form数据需要转化,使用qs.stringify来转换。转化成A=1&B=2&c=3这样的形式。
- 2、后台接口路径的配置。
api请求都放在一个文件下面,这样以后维护比较方便
至于请求后台方法每个界面一个方法,例如这样
页面调用方法需要引用,类似这种
export default { name: 'user-list', components: { Pagination }, directives: { waves }, filters: { statusFilter(status) { const statusMap = { published: 'success', draft: 'info', deleted: 'danger' } return statusMap[status] }, typeFilter(type) { return calendarTypeKeyValue[type] } }, data() { return { tableKey: 0, list: null, total: 0, listLoading: true, listQuery: { page: 1, limit: 20, importance: undefined, title: undefined, type: undefined, username: undefined, deptname: undefined, rolename: undefined }, calendarTypeOptions, showReviewer: false, temp: { id: undefined, remark: '', username: '', rolename: '', deptid: '', roleid: '', password: '' }, dialogFormVisible: false, dialogCzmmVisible: false, dialogStatus: '', textMap: { update: '修改用户', create: '新增用户' }, pvData: [], rules: { username: [{ required: true, message: '必填项', trigger: 'change' }], loginname: [{ required: true, message: '必填项', trigger: 'change' }], deptid: [{ required: true, message: '必选项', trigger: 'change' }], roleid: [{ required: true, message: '必选项', trigger: 'change' }], password: [{ required: true, message: '必选项', trigger: 'change' }, { min: 6, max: 30, message: '长度在 6 到 30 个字符', trigger: 'change' }] }, downloadLoading: false, deptOptions: [], roleOptions: [] } }, created() { this.getList() }, methods: { getList() { this.listLoading = true fetchList(this.listQuery).then(response => { debugger this.list = response.attributes.list.list this.total = response.attributes.list.total this.listLoading = false }) }, handleFilter() { this.listQuery.page = 1 this.getList() }, resetTemp() { this.temp = { id: undefined, remark: '', username: '', rolename: '', deptid: '', roleid: '', password: '' } }, handleCreate() { this.resetTemp() this.dialogStatus = 'create' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) fetchRoleOptions().then(response => { this.roleOptions = response.attributes.list }) var aa = { code: 'dept' } fetchDicePropOptions(aa).then(response => { this.deptOptions = response.attributes.list }) }, createData() { this.$refs['dataForm'].validate((valid) => { if (valid) { const tempData = Object.assign({}, this.temp) saveSysUser(qs.stringify(tempData)).then(() => { this.dialogFormVisible = false this.$notify({ title: '提示', message: '保存成功!', type: 'success', duration: 2000 }) this.getList() }) } }) }, handleUpdate(row) { this.temp = Object.assign({}, row) // copy obj this.temp.roleid = Number(this.temp.roleid) this.temp.deptid = Number(this.temp.deptid) this.dialogStatus = 'update' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) fetchRoleOptions().then(response => { this.roleOptions = response.attributes.list }) var aa = { code: 'dept' } fetchDicePropOptions(aa).then(response => { this.deptOptions = response.attributes.list }) }, updateData() { this.$refs['dataForm'].validate((valid) => { if (valid) { const tempData = Object.assign({}, this.temp) updateSysUser(qs.stringify(tempData)).then(() => { this.dialogFormVisible = false this.$notify({ title: '提示', message: '保存成功!', type: 'success', duration: 2000 }) this.getList() }) } }) }, handleDelete(row, index) { deleteSysUser(row.id).then(response => { this.dialogFormVisible = false this.$notify({ title: '提示', message: '删除成功!', type: 'success', duration: 2000 }) this.getList() }) }, handleReloadPassword() { if (this.temp.password === '') { this.$notify({ title: '提示', message: '密码不能为空!', type: 'error', duration: 2000 }) return } var date = { id: this.temp.id, password: this.temp.password } reloadPassword(date).then(response => { if (response.code > 0) { this.dialogCzmmVisible = false this.$notify({ title: '提示', message: '重置密码成功!', type: 'success', duration: 2000 }) } }) }, handleRefresh() { this.listQuery.username = '' this.listQuery.rolename = '' this.listQuery.deptname = '' }, formatJson(filterVal) { return this.list.map(v => filterVal.map(j => { if (j === 'timestamp') { return parseTime(v[j]) } else { return v[j] } })) }, indexMethod(index) { return index + 1 }, handleCz(row) { this.resetTemp() this.dialogCzmmVisible = true this.temp.id = row.id this.$nextTick(() => { this.$refs['dataFormCx'].clearValidate() }) } }}import qs from 'qs'
方法调用就像这种这么做就可以
3 、 登录设置角色信息、过滤路由(根据角色动态的生成菜单)
- store/user.js
import { loginByPwd, logout, getLoginUserInfo } from '@/api/user'import { getToken, setToken, removeToken } from '@/utils/auth'import router, { resetRouter } from '@/router' const state = { token: getToken(), name: '', avatar: '', introduction: '', roles: []} c onst mutations = { SET_TOKEN: (state, token) => { state.token = token }, SET_INTRODUCTION: (state, introduction) => { state.introduction = introduction }, SET_NAME: (state, name) => { state.name = name }, SET_AVATAR: (state, avatar) => { state.avatar = avatar }, SET_ROLES: (state, roles) => { state.roles = roles }} const actions = { // user login loginByPwd({ commit }, userInfo) { const { userName, passWord } = userInfo return new Promise((resolve, reject) => { loginByPwd({ userName: userName.trim(), passWord: passWord }).then(response => { if (response.code === 2000) { commit('SET_TOKEN', response.data) setToken(response.data) } resolve(response) }).catch(error => { reject(error) }) }) }, // get user info getLoginUserInfo({ commit, state }) { return new Promise((resolve, reject) => { getLoginUserInfo(state.token).then(response => { // const { data } = response // console.log('getLoginUserInfo', response) if (!response) { reject('Verification failed, please Login again.') } if (response.resultFlag) { commit('SET_ROLES', response.data.roleList) commit('SET_NAME', response.data.likeName) commit('SET_AVATAR', response.data.imgUrl) commit('SET_INTRODUCTION', '我是一个超级管理员哦') // 一个用户可能有多个角色,这里返回的是角色的集合信息 // let allRole = response.data.roleList resolve(response) } else { console.error('获取当前登录用户信息出错了') } }).catch(error => { reject(error) }) }) }, // user logout logout({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() resetRouter() resolve() location.reload() }).catch(error => { reject(error) }) }) }, // remove token resetToken({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() resolve() }) }, // dynamically modify permissions changeRoles({ commit, dispatch }, role) { return new Promise(async resolve => { const token = role + '-token' commit('SET_TOKEN', token) setToken(token) const { roles } = await dispatch('getInfo') resetRouter() // generate accessible routes map based on roles const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true }) // dynamically add accessible routes router.addRoutes(accessRoutes) // reset visited views and cached views dispatch('tagsView/delAllViews', null, { root: true }) resolve() }) }} export default { // namespaced: true, state, mutations, actions}
这个文件就是登录,成功后获取当前用户拥有的资源信息。
当然下载下来的这个文件菜单权限是写死的是直接使用roles来做的判断,就是在router.js文件中直接把菜单需要的角色设置在里边。
缺点是添加了角色就要修改代码打包发布。这简直是灾难。
所以这里面将原系统修改了一下实现动态菜单读取,就好多了
vue-element-admin-mastersrcouterindex.jsimport Vue from 'vue'import Router from 'vue-router'Vue.use(Router)/* Layout */import Layout from '@/layout'/* Router Modules */// import componentsRouter from './modules/components'// import chartsRouter from './modules/charts'// import tableRouter from './modules/table'// import nestedRouter from './modules/nested'export const constantRoutes = [ { path: '/redirect', component: Layout, hidden: true, children: [ { path: '/redirect/:path*', component: () => import('@/views/redirect/index') } ] }, { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/auth-redirect', component: () => import('@/views/login/auth-redirect'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', component: () => import('@/views/dashboard/index'), name: 'Dashboard', meta: { title: 'dashboard', icon: 'dashboard', affix: true } } ] }]/** * asyncRoutes * the routes that need to be dynamically loaded based on user roles */export const asyncRoutes = [ /** when your routing map is too long, you can split it into small modules **/ // componentsRouter, // chartsRouter, // nestedRouter, // tableRouter, // 404 page must be placed at the end !!!]const createRouter = () => new Router({ // mode: 'history', // require service support mode: 'hash', scrollBehavior: () => ({ y: 0 }), routes: constantRoutes})const router = createRouter()// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router}export default router
首先先将静态路由调好,一些常用的不需要权限的先加上
然后找到srcpermission.js
import router, { resetRouter } from '@/router'import store from './store'import { Message } from 'element-ui'import NProgress from 'nprogress' // progress barimport 'nprogress/nprogress.css' // progress bar styleimport { getToken } from '@/utils/auth' // get token from cookieimport getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login', '/auth-redirect'] // no redirect whitelistlet add1 = falseconst matched1 = falserouter.beforeEach(async(to, from, next) => { // start progress bar NProgress.start() // set page title document.title = getPageTitle(to.meta.title) // determine whether the user has logged in const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page next({ path: '/' }) NProgress.done() } else { // determine whether the user has obtained his permission roles through getInfo const hasRoles = store.getters.roles && store.getters.roles.length > 0 if (hasRoles) { next() } else { try { // get user info // note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] // const { roles } = await store.dispatch('user/getInfo') // generate accessible routes map based on roles debugger // const fromPath = GetUrlRelativePath(window.location.href) if (!add1) { //注意在这里,将后台拼装好的功能菜单添加到当前路由中,切记一定要加变量控制 //不然会死循环,如果谁有好的解决办法请回复我谢谢 // router.resetRouter() let accessRoutes = [] accessRoutes = await store.dispatch('permission/getAuthMenu') router.addRoutes(accessRoutes) add1 = true // 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 }) } else { // debugger // if (to.matched.length === 0) { // router.resetRouter() // let accessRoutes = [] // accessRoutes = await store.dispatch('permission/getAuthMenu') // router.addRoutes(accessRoutes) // next({ ...to, replace: true }) // } else { next() // } } } catch (error) { // remove token and go to login page to re-login await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } }})export function GetUrlRelativePath(url) { var arrUrl = url.split('//') var start = arrUrl[1].indexOf('/') var relUrl = arrUrl[1].substring(start) if (relUrl.indexOf('?') !== -1) { relUrl = relUrl.split('?')[0] } return relUrl}router.afterEach(() => { // finish progress bar NProgress.done()})
import { asyncRoutes, constantRoutes } from '@/router'const state = { routes: [], roles: [], username: '', addRoutes: [], isChange: false}const mutations = { isChange: (state, isChanged) => { state.isChange = isChanged }, SET_USER: (state, username) => { state.username = username }, SET_ROLES: (state, roles) => { state.roles = roles }, SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) }}export function formatMenus(routes, data) { data.forEach(item => { const menu = {} menu.path = '/' + item.path menu.name = item.name menu.component = Layout menu.redirect = '/' menu.meta = { title: item.title, icon: 'excel', noCache: false } if (item.children.length > 0) { menu.children = [] item.children.forEach(item1 => { if (item1.name === 'examinationInfo') { const menu1 = {} menu1.path = 'detail' menu1.component = resolve => require(['@/views/' + item1.component + '/' + item1.path + '.vue'], resolve) menu1.name = 'examinationInfo' menu1.meta = { title: item1.title, noCache: false, activeMenu: '@/views/Examination/examination' } menu1.hidden = true menu.children.push(menu1) } else { const menu1 = {} menu1.path = item1.path menu1.component = resolve => require(['@/views/' + item1.component + '/' + item1.path + '.vue'], resolve) menu1.name = item1.name menu1.meta = { title: item1.title } menu.children.push(menu1) } }) } debugger routes.push(menu) })}const actions = { getAuthMenu({ commit }) { return new Promise(resolve => { getAuthMenu({}).then(response => { const data = response.attributes.menus const roles = response.attributes.rolesgetAuthMenu const username = response.attributes.username formatMenus(asyncRoutes, data) commit('SET_ROUTES', asyncRoutes) commit('SET_ROLES', roles) commit('SET_USER', username) // const isChanged = true // commit('isChange', isChanged) asyncRoutes.push({ path: '*', redirect: '/404', hidden: true }) //注意404界面一定要加载最后,不然会报错 resolve(asyncRoutes) }) }) }}export default { namespaced: true, state, mutations, actions}import Layout from '@/layout'import { getAuthMenu } from '@/api/user'
在请求头部如果需要添加cookie