1. 项目准备
1.1 项目介绍
1.2 模板下载
1.3 删除多余代码
1.3.1 删除多余的组件
- 删除 view 目录下的 form、nested、table、tree组件 (也可以选择不删除, 如果用的上的话)
-
删除对应组件的路由 : 找到 router 目录下的 index.js 文件, 删除如下内容
{
path: '/example',
component: Layout,
redirect: '/example/table',
name: 'Example',
meta: { title: 'Example', icon: 'el-icon-s-help' },
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/table/index'),
meta: { title: 'Table', icon: 'table' }
},
{
path: 'tree',
name: 'Tree',
component: () => import('@/views/tree/index'),
meta: { title: 'Tree', icon: 'tree' }
}
]
},
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'form' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: {
title: 'Nested',
icon: 'nested'
},
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index'),
name: 'Menu1',
meta: { title: 'Menu1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1'),
name: 'Menu1-1',
meta: { title: 'Menu1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2'),
name: 'Menu1-2',
meta: { title: 'Menu1-2' },
children: [
{
path: 'menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
name: 'Menu1-2-1',
meta: { title: 'Menu1-2-1' }
},
{
path: 'menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
name: 'Menu1-2-2',
meta: { title: 'Menu1-2-2' }
}
]
},
{
path: 'menu1-3',
component: () => import('@/views/nested/menu1/menu1-3'),
name: 'Menu1-3',
meta: { title: 'Menu1-3' }
}
]
},
{
path: 'menu2',
component: () => import('@/views/nested/menu2/index'),
name: 'Menu2',
meta: { title: 'menu2' }
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
meta: { title: 'External Link', icon: 'link' }
}
]
},
1.4 修改登录页面
- 找到 view 目录下的 login 目录的 index.vue 文件
1.4.1 修改页面标题
<div class="title-container">
<h3 class="title">权限管理前端框架</h3>
</div>
1.4.2 页面背景
-
在下面的位置修改添加页面背景图片, 背景图片放在 assets 目录下面 /* reset element-ui css */
.login-container {
// 修改背景图片
background: url("../../assets/bg.jpg");
// 设置背景图片覆盖样式
background-size: cover;
.el-input {
1.4.3 修改密码验证规则
export default {
name: 'Login',
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
1.5 修改配置环境
1.5.1 环境配置
-
找到 utils 目录下的 request.js 文件
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
-
process.env.VUE_APP_BASE_API 对应的是 .env.development 或者 .env.production 文件 # just a flag
# TODO process.env.NODE_ENV
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
-
我们需要手动修改成自己的公共api
1.5.1 修改请求token
-
在下面的位置修改, 将 config.headers['X-Token'] 中的 X-Token 修改为我们自己常用的, 一般是 token service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['X-Token'] = getToken()
}
return config
},
-
这个地方的作用是, 我们登陆成功以后, 后端会生成一个toke存储在本地, 我们每次发送请求都需要携带这个token
1.5.2 修改登陆失败提示
-
在下面 if (res.code !== 20000 && res.code !== 200) { 这个位置加上 && res.code !== 200 根据自己的项目情况修改, 有的登陆成功的信息是 200, 有的是 20000 response => {
const res = response.data
if (res.code !== 20000 && res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
-
如果登陆失败会给出相应的提示!
1.6 修改代理
1.6.1 禁用mock数据
- 根据自己的情况来, 知道即可, 不一定非要注释掉, 后端没开发完时,可以使用mock数据
- 修改 vue.config.js , 注释掉
before: require('./mock/mock-server.js')
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
1.6.2 添加代理
-
在 before: require('./mock/mock-server.js') 下面添加 代理配置, 配置根据自己的后端进行配置 devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/dev-api': {
target: 'http://127.0.0.1:8081',
pathRewrite: {
'^/dev-api': ''
}
}
}
},
-
这个配置的意思是 当请求 : /dev-api/user/add 转换为 => http://127.0.0.1:8081/user/add proxy: {
'/dev-api': {
target: 'http://127.0.0.1:8081',
pathRewrite: {
'^/dev-api': ''
}
}
}
1.6.3 修改api请求
-
找到 api 目录下的user.js修改为自己真正的请求路径 -
比如 user.js中的 import request from '@/utils/request'
export function login(data) {
return request({
url: '/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/logout',
method: 'post'
})
}
1.7 修改Vuex
1.7.1 修改用户信息
- 打开 store 目录下的 modules 文件夹里的 user.js 文件
- 将下面设置名称和头像的代码封装成一个方法
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_USERINFO(state, userInfo){
state.name = userInfo.name
state.avatar = userInfo.avatar
}
}
- 然后再下面的
getInfo 方法里配置使用 SET_USERINFO 设置用户名称和头像
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
commit('SET_USERINFO', data)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
1.8 修改头像下拉菜单
1.8.1 修改下拉菜单
- 当我们点击头像的时候会有下拉菜单 分别的 Home, Github … 这些我们可以自行修改
- 找到 layout => components => Navbar.vue, 在下面的位置可以修改
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<!-- TODO : 这里可以删除点击头像的下拉菜单 -->
<a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
<el-dropdown-item>Docs</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">退出登陆</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
1.8.2 路由名称修改
-
以下面的路由配置为例, 如果 name 和 meta的 title一致的话, 会有冲突, 控制台会有报错 -
所以我们一般将 name 设置的和 path一致, 首字母大写 {
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}
1.8.3 修改header上的路径
-
找到 commponent => Breadcrumb => index.vue, 这里 title 也要修改 getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
}
1.9 开发和生产环境的配置
1.9.1 配置生产环境
"scripts": {
"dev": "vue-cli-service serve",
"dev-production": "vue-cli-service serve --mode production",
"build:prod": "vue-cli-service build",
- 添加代码
"dev-production": "vue-cli-service serve --mode production", "--mode production", 就是 读取生产环境配置 , 读取 .env.production 配置文件- 我们在启动的时候可以直接使用
npm run dev-production
1.9.2 不同环境的Mock数据
- vue-admin-template 使用的有两种方式的 mock数据
- 开发环境使用的是启动一个 mock-server
- 线上(生产)环境使用的是 mockjs
- 二者的区别是 mockjs 是直接重写浏览器的
XMLHttpRequest 对象,从而才能拦截所有请求,代理到本地, 所以开发者工具的网络选项里看不到发送的请求, 还有一些别的兼容问题 - 推荐使用
mock-server - 参考文档 : MockData 新方案
2. 系统管理
-
新建用户管理, 角色管理, 权限管理路由组件 -
配置系统管理的一级路由 {
path: '/system',
component: Layout,
name: 'System',
meta: { title: '系统管理', icon: 'el-icon-s-tools' },
}
2.1 路由配置
-
配置完整的路由如下, 这里注意, 由于我创建的每一个目录下都是 index.vue, 所以可以直接使用 -
@/views/system/user 这个路径, 当然 @/views/system/user/index.vue 也是可以的
{
path: '/system',
component: Layout,
name: 'System',
meta: { title: '系统管理', icon: 'el-icon-s-tools' },
children: [
{
path: 'user/list',
name: 'User',
meta: { title: '用户管理', icon: 'el-icon-user-solid' },
component: () => import('@/views/system/user')
},
{
path: 'role/list',
name: 'Role',
meta: { title: '角色管理', icon: 'el-icon-s-help' },
component: () => import('@/views/system/role')
},
{
path: 'rights/list',
name: 'Rights',
meta: { title: '权限管理', icon: 'el-icon-menu' },
component: () => import('@/views/system/rights')
}
]
},
-
显示结果
2.2 权限管理核心概念
2.2.1 用户登录成功信息
-
用户登录成功, 后端返回token, 前端拿到token, 根据token发送请求获取用户权限信息 {
code: 20000,
data: {
avatar: ''
buttons: [user.add, user.delete, user.update .... ]
roltes: ['admin' ... ]
routes: ['User', 'Role', 'Rights' ... ]
} ,
message: '成功',
...
}
-
其中最重要的是 routes 前端接收到以后, 根据这个 routes 过滤路由表, 确定左侧栏显示的菜单列表
2.2.2 权限获取流程
2.2.3 路由表动态生成
- 一般分为常量路由、异步路由、静态路由
- 常量路由就是所有用户不需要任何权限也可以看到的路由
- 异步路由就是自己定义的, 需要用户有相应的权限才可以看到的路由
- 任意路由是: 用户输入的所有非法路由都会转到404路由界面,注册这个路由的时候, 一定要放到最后面,放后面的原因是, 路由是从上到下匹配的, 上面匹配不到就是非法路由,非法路由在最后面拦截到 404 页面
2.3 路由配置
2.3.1 常量路由
-
常量路由就是所有用户不需要任何权限也可以看到的路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
}
]
2.3.2 异步路由
-
异步路由, 一般是自己定义的, 需要用户有相应的权限, 也就是配置里面的 name 才能访问的路由
export const allAsyncRoutes = [
{
path: '/system',
component: Layout,
name: 'System',
redirect: 'user/list',
meta: { title: '系统管理', icon: 'el-icon-s-tools' },
children: [
{
path: 'user/list',
name: 'User',
meta: { title: '用户管理', icon: 'el-icon-user-solid' },
component: () => import('@/views/system/user/index')
},
{
path: 'role/list',
name: 'Role',
meta: { title: '角色管理', icon: 'el-icon-s-help' },
component: () => import('@/views/system/role/index')
},
{
path: 'rights/list',
name: 'Rights',
meta: { title: '权限管理', icon: 'el-icon-menu' },
component: () => import('@/views/system/rights/index')
}
]
}
]
2.3.3 任意路由
-
任意路由, 用户输入的所有非法路由都会转到404路由界面 -
注册这个路由的时候, 一定要放到最后面 -
放后面的原因是, 路由是从上到下匹配的, 上面匹配不到就是非法路由,非法路由在最后面拦截到 404 页面 export const anyRoute = { path: '*', redirect: '/404', hidden: true }
2.4 动态路由表生成
2.4.1 添加路由state
-
找到 store目录下的 user.js 文件, 添加 roles , buttons , routes -
后端返回的是路由名称的数组 : [‘User’, ‘Role’, ‘Rights’] 也就是路由配置中的 name 属性 -
asyncRoutes 保存用户当前所属的路由配置数组(router里面的), 而不是后端返回的routes(这个是路由名称数组), 注意区分 const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
roles: [],
buttons: [],
asyncRoutes: [],
routes: []
}
}
2.4.2 修改 SET_USEROINFO
- 修改下面的
SET_USEROINFO , 为 roles 和 buttons 赋值
SET_USERINFO(state, userInfo) {
state.name = userInfo.name
state.avatar = userInfo.avatar
state.roles = userInfo.roles
state.buttons = userInfo.buttons
}
2.4.3 过滤异步路由
-
通过返回的用户异步路由名称数组,从所有的异步路由数组当中,过滤出用户的异步路由数组 -
const actions = {} 上面创建方法 filterAsyncRoutes , 用于过滤异步路由 -
allAsyncRoutes : 是所有的异步路由 -
routeNames : 是后端传过来的用户所拥有的权限的路由名称数组
function filterAsyncRoutes(allAsyncRoutes, routeNames) {
const asyncRoutes = allAsyncRoutes.filter(item => {
if (routeNames.indexOf(item.name) !== -1) {
if (item.children && item.children.length > 0) {
item.children = filterAsyncRoutes(item.children, routeNames)
}
return true
}
})
return asyncRoutes
}
const actions = {
2.4.4 配置过滤的路由
-
编写完过滤方法后, 我们需要调用这个方法去过滤路由数据, 位置在 actions 里的 getInfo 方法 -
使用 commit('SET_ROUTES', filterAsyncRoutes(allAsyncRoutes, data.routes)) 调用 -
导入 import cloneDeep from 'lodash/cloneDeep' 深拷贝工具
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
commit('SET_USERINFO', data)
commit('SET_ROUTES', filterAsyncRoutes(cloneDeep(allAsyncRoutes), data.routes))
resolve(data)
}).catch(error => {
reject(error)
})
})
},
-
导入常量路由以及任意路由 import { resetRouter, allAsyncRoutes, anyRoute, constantRoutes } from '@/router'
import router from '@/router'
-
在 mutations 添加 SET_ROUTES , SET_ROUTES(state, asyncRoutes) {
state.asyncRoutes = asyncRoutes
state.routes = constantRoutes.concat(asyncRoutes, anyRoute)
router.addRoutes([...asyncRoutes, anyRoute])
}
-
路由总配置参考 import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter, allAsyncRoutes, anyRoute, constantRoutes } from '@/router'
import router from '@/router'
import cloneDeep from 'lodash/cloneDeep'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
roles: [],
buttons: [],
asyncRoutes: [],
routes: []
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_USERINFO(state, userInfo) {
state.name = userInfo.name
state.avatar = userInfo.avatar
state.roles = userInfo.roles
state.buttons = userInfo.buttons
},
SET_ROUTES(state, asyncRoutes) {
state.asyncRoutes = asyncRoutes
state.routes = constantRoutes.concat(asyncRoutes, anyRoute)
router.addRoutes([...asyncRoutes, anyRoute])
}
}
function filterAsyncRoutes(allAsyncRoutes, routeNames) {
const asyncRoutes = allAsyncRoutes.filter(item => {
if (routeNames.indexOf(item.name) !== -1) {
if (item.children && item.children.length > 0) {
item.children = filterAsyncRoutes(item.children, routeNames)
}
return true
}
})
return asyncRoutes
}
const actions = {
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
commit('SET_USERINFO', data)
commit('SET_ROUTES', filterAsyncRoutes(cloneDeep(allAsyncRoutes), data.routes))
resolve(data)
}).catch(error => {
reject(error)
})
})
},
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken()
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
},
resetToken({ commit }) {
return new Promise(resolve => {
removeToken()
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
2.4.5 NavBar读取配置路由
-
上面配置完但是还并不能生效, 我们需要修改NavBar的代码 -
在 store 下 getter.js 文件里暴露路由接口数据 const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
roles: state => state.user.roles,
buttons: state => state.user.buttons,
routes: state => state.user.routes
}
export default getters
-
找到 layout=>component=>Sidebar 下的 index.vue 文件, 在 mapGetters 里添加 routes -
然后删除掉下面的 routes() 方法 export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'sidebar',
'routes'
]),
2.5 修改 permission.js
-
找到 permission.js 文件 将 next() 修改为 next({ ...to }) , 这是为了解决页面刷新空白的bug if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
await store.dispatch('user/getInfo')
next({ ...to })
} catch (error) {
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
3. Mock工具的使用
3.1 创建MockApi文件
-
在 mock 目录下创建一个 role.js 文件, user 前面加上 * 表示匹配任意前缀, 这个很有必要加上 -
因为这样我们可以任意的在 mock数据和真实后端接口数据切换, 因为项目会默认在请求路径前加上 \dev-api , 如果不加 * 我们每次都要在 api 请求上加上 前缀, 因为使用mock数据时, dev-api 是不会被替换的 module.exports = [
{
url: '*/user/login',
type: 'post',
response: config => {
return {
code: 20000,
data: 'success'
}
}
}
]
-
如上面代码 :
- url: 请求的路径
- type : 请求类型
- config : 请求体 请求参数之类的. 可以使用
config.body 得到请求参数, 可以参考 user.js - return {} : 响应数据
-
然后需要我们在 api 请求里保证二者一致即可 export function getInfo(token) {
return request({
url: '/user/login',
method: 'get',
params: { token }
})
}
3.2 配置Mock
-
在index.js中配置 const user = require('./user')
const table = require('./table')
const role = require('./role')
const mocks = [
...user,
...table,
...role
]
-
如上述代码所示, 添加 const role = require('./role') , 以及下面的 mocks
|