最近项目需求就是要做菜单权限,按钮权限这些,我们是用vue开发的,先看一下我们项目后台的页面样式,如下图,用户中心分为用户管理,菜单管理及角色管理。用户管理顾名思义就是管理这个系统有哪些人在用,包括一些新增账号,修改账号,禁用和激活等常见功能,我们系统都设置的有统一登录密码,所以在新增账号时并没有让其填密码这一项,个人中心就看下个人信息,可以改其手机号与登录密码。这些实现不难,就调接口增删改查,写下业务逻辑代码完事。
用户管理与个人中心模块
菜单管理模块
这个菜单管理模块用到了vue-treeselect组件,然后这里的代号就是我们的路由组件名或者是按钮的唯一标识,有唯一性,与页面的菜单权限或按钮权限相绑定,这个父级就是当前目录或按钮的父级,用到了vue-treeselect组件。对于这个组件不熟悉的可以去看看vue-treeselect官网,先命令npm install --save @riophae/vue-treeselect安装再引入注册再使用,贴上代码:
//安装
npm install --save @riophae/vue-treeselect
//引入
import Treeselect from '@riophae/vue-treeselect'
// import the styles
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
//注册局部组件
components: {
Treeselect },
<template>
<div class="app-container">
<div style="margin-top: 10px">
<el-input
v-model="search"
placeholder="输入权限名称进行搜索"
style="width: 200px"
class="filter-item"
@keyup.native="handleFilter"
/>
<!-- :disabled="!$checkPermission(['menu_add'])" -->
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!$checkPermission(['menu_add'])"
@click="handleAdd"
>新增</el-button
>
</div>
<el-table
v-loading="listLoading"
:data="
tableData.filter(
data =>
!search || data.name.toLowerCase().includes(search.toLowerCase())
)
"
style="width: 100%; margin-top: 10px"
border
fit
stripe
highlight-current-row
max-height="600"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="菜单名称">
<template slot-scope="scope">{
{
scope.row.name }}</template>
</el-table-column>
<el-table-column label="类型">
<template slot-scope="scope">{
{
TypeWays(scope.row.type) }}</template>
</el-table-column>
<el-table-column label="代号">
<template slot-scope="scope">{
{
scope.row.url }}</template>
</el-table-column>
<el-table-column label="排序">
<template slot-scope="scope">{
{
scope.row.status }}</template>
</el-table-column>
<el-table-column align="center" label="操作" width="170">
<template slot-scope="scope">
<el-button
type="primary"
:disabled="!$checkPermission(['menu_edit'])"
size="small"
@click="handleEdit(scope)"
>编辑</el-button
>
<el-button
type="danger"
size="small"
:disabled="!$checkPermission(['menu_delete'])"
@click="handleDelete(scope)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<el-dialog
:visible.sync="dialogVisible"
:title="dialogType === 'edit' ? '编辑' : '新增'"
>
<el-form
ref="Form"
:model="perm"
label-width="80px"
label-position="right"
:rules="rule1"
>
<el-form-item label="类型">
<el-radio-group v-model="perm.type" @change="typeChange">
<el-radio :label="1">目录</el-radio>
<el-radio :label="2">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="perm.name" placeholder="名称" />
</el-form-item>
<el-form-item label="代号" prop="url">
<el-input v-model="perm.url" placeholder="代号" />
</el-form-item>
<el-form-item label="父级" prop="pid">
<treeselect
v-model="perm.pid"
:multiple="false"
:options="TreeData"
placeholder="父级"
:normalizer="normalizer"
/>
</el-form-item>
<el-form-item label="排序" prop="status">
<el-input-number
v-model="perm.status"
:min="1"
label="排序"
></el-input-number>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input
type="textarea"
:rows="2"
placeholder="请输入备注"
v-model="perm.remarks"
>
</el-input>
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmPerm('Form')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// import { getPermAll, createPerm, deletePerm, updatePerm } from "@/api/perm";
import * as user from "@/api/datax-user";
import {
menuList,
menuSaveForm,
menuDetail,
menuEdit,
menuDelete,
menuGetTree,
menuGetTreeNotBtn
} from "@/api/datax-menu";
import {
genTree, genTree2 } from "@/utils";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
const defaultPerm = {
id: null,
name: "",
type: 1,
url: "",
status: 1,
c: null,
remarks: "",
icon: ""
};
export default {
components: {
Treeselect },
data() {
return {
perm: defaultPerm,
search: "",
tableData: [],
TreeData: [],
permList: [],
listLoading: true,
dialogVisible: false,
dialogType: "new",
rule1: {
name: [{
required: true, message: "请输入名称", trigger: "blur" }],
url: [{
required: true, message: "请输入代号", trigger: "blur" }]
},
normalizer(node) {
if (node.childList && !node.childList.length) {
delete node.childList;
}
return {
id: node.id,
pid: node.pid,
value: node.id,
label: node.name,
name: node.name,
children: node.childList
};
}
};
},
computed: {
},
created() {
this.getList();
},
methods: {
getList() {
this.listLoading = true;
menuList({
current: 1,
size: 1000000,
name: this.search,
status: "",
type: ""
}).then(response => {
console.log("菜单列表", response);
if (response.code == 200) {
this.permList = response.content.listpg;
const data = genTree(response.content.listpg);
this.tableData = data;
this.listLoading = false;
}
});
},
traversalTree(arrs, result) {
arrs.map((item, index) => {
let obj = {
key: item.id,
title: item.name,
children: []
};
result.push(obj);
obj.children = obj.childList;
if (item.childList.length !== 0) {
traversalTree(item.childList, result[index].children);
}
});
},
//获取菜单树不包含按钮
getTreeList() {
menuGetTreeNotBtn({
type: 0
})
.then(res => {
this.TreeData = res.content;
})
.catch(err => {
});
},
resetFilter() {
this.getList();
},
handleFilter() {
const newData = this.permList.filter(
data =>
!this.search ||
data.name.toLowerCase().includes(this.search.toLowerCase())
);
this.tableData = genTree(newData);
},
handleAdd() {
this.getTreeList();
this.perm = Object.assign({
}, defaultPerm);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleEdit(scope) {
// console.log("点了编辑", scope);
this.getTreeList();
var dataT = JSON.parse(JSON.stringify(scope.row));
this.perm = Object.assign({
}, dataT); // copy obj
if (scope.row.pid == 0) {
this.perm.pid = null;
}
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error"
})
.then(async () => {
await menuDelete(scope.row);
this.getList();
this.getTreeList();
this.$message({
type: "success",
message: "成功删除!"
});
})
.catch(err => {
console.error(err);
});
},
async confirmPerm(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
this.perm.children = [];
console.log("编辑入参", this.perm);
menuEdit(this.perm).then(() => {
this.getList();
this.dialogVisible = false;
this.$message({
message: "编辑成功",
type: "success"
});
});
} else {
let addParams = {
...this.perm,
pid: !this.perm.pid ? 0 : this.perm.pid
};
if (addParams.hasOwnProperty("id")) {
delete addParams.id;
}
// console.log("新增入参", addParams);
menuSaveForm(addParams).then(res => {
// this.perm = res.data
// this.tableData.unshift(this.perm)
this.getList();
this.dialogVisible = false;
this.$message({
message: "新增成功",
type: "success"
});
});
}
} else {
return false;
}
});
},
TypeWays(type) {
if (type == 1) {
return "目录";
} else if (type == 2) {
return "按钮";
}
},
typeChange(val) {
this.perm.pid = null;
}
}
};
</script>
角色管理模块
我们的角色管理里就这四种角色,然后我们的按钮权限都是在创建角色的时候菜单目录里打钩配置的,编辑角色同理也可以顺带编辑该角色的按钮权限,增删改查调接口无难度,代码如下:
<template>
<!-- 角色列表 -->
<div class="app-container">
<div class="filter-container">
<el-input
v-model.trim="listQuery.name"
placeholder="角色名称"
style="width: 200px;"
class="filter-item"
clearable
/>
<!-- <el-input
v-model.trim="listQuery.roleId"
placeholder="角色ID"
style="width: 200px;"
class="filter-item"
/> -->
<el-button
v-waves
class="filter-item"
type="primary"
icon="el-icon-search"
@click="fetchData"
>
搜索
</el-button>
<el-button
class="filter-item"
style="margin-left: 0px;"
type="primary"
icon="el-icon-plus"
:disabled="!$checkPermission(['role_add'])"
@click="handleCreate"
>
添加
</el-button>
</div>
<div class="cardWhite">
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
:border="border"
fit
highlight-current-row
>
<el-table-column align="left" label="序号" width="95">
<template slot-scope="scope">{
{
scope.$index + 1 }}</template>
</el-table-column>
<el-table-column label="角色名称" align="left">
<template slot-scope="scope">{
{
scope.row.name }}</template>
</el-table-column>
<el-table-column label="创建时间" align="left">
<template slot-scope="scope">{
{
scope.row.createTime }}</template>
</el-table-column>
<el-table-column label="修改时间" align="left">
<template slot-scope="scope">{
{
scope.row.modifyTime }}</template>
</el-table-column>
<el-table-column label="角色是否启用" align="left">
<template slot-scope="scope">
<span>{
{
scope.row.status == 1 ? "启用" : "禁用" }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="left">
<template slot-scope="scope">
<span>{
{
scope.row.remarks }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="left"
width="180"
class-name="small-padding fixed-width"
>
<template slot-scope="{ row }">
<el-button
type="primary"
size="mini"
@click="handleUpdate(row)"
:disabled="!$checkPermission(['role_edit'])"
>
编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(row)"
:disabled="!$checkPermission(['role_delete'])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total > 0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog
:title="textMap[dialogStatus]"
:visible.sync="dialogFormVisible"
:close-on-click-modal="false"
width="800px"
>
<el-form
ref="dataForm"
:rules="rules"
:model="temp"
label-position="right"
label-width="160px"
>
<el-row>
<el-col :span="12" :pull="1">
<el-form-item label="角色名称" prop="name">
<el-input v-model.trim="temp.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色类型" prop="systemAdmin">
<el-select
v-model="temp.systemAdmin"
class="filter-item"
placeholder="角色类型"
style="width:100%"
>
<el-option key="超级管理员" label="超级管理员" :value="1" />
<el-option key="审计员" label="审计员" :value="2" />
<el-option key="操作员" label="操作员" :value="3" />
<el-option key="管理员" label="管理员" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="角色是否启用" prop="status">
<el-select
v-model="temp.status"
class="filter-item"
placeholder="角色是否启用"
style="width:100%"
>
<el-option key="启用" label="启用" :value="1" />
<el-option key="禁用" label="禁用" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model.trim="temp.remarks" placeholder="请输入备注" />
</el-form-item>
</el-col>
<el-col :span="12" :pull="1">
<el-form-item label="菜单目录" prop="menuList">
<!-- :default-checked-keys="temp.menuList" -->
<el-tree
:data="treeData"
show-checkbox
:check-strictly="true"
ref="treeRef"
node-key="id"
:props="defaultProps"
:default-checked-keys="defaultCheckedKeys"
@check="checkTree"
>
</el-tree>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button
type="primary"
@click="dialogStatus === 'create' ? createData() : updateData()"
>
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as user from "@/api/datax-user";
import * as api from "@/api/datax-role.js";
import * as apiMenu from "@/api/datax-menu";
import waves from "@/directive/waves"; // waves directive
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
export default {
name: "User",
components: {
Pagination },
directives: {
waves },
filters: {
statusFilter(status) {
const statusMap = {
published: "success",
draft: "gray",
deleted: "danger"
};
return statusMap[status];
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
name: "",
status: "",
type: ""
},
roles: ["ROLE_USER", "ROLE_ADMIN"],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: "",
textMap: {
update: "编辑角色",
create: "创建角色"
},
rules: {
name: [{
required: true, message: "请输入角色名称", trigger: "blur" }],
systemAdmin: [
{
required: true,
message: "请选择角色类型",
trigger: "change"
}
],
status: [
{
required: true, message: "请选择角色是否启用", trigger: "change" }
],
remarks: [{
required: false, message: "请输入备注", trigger: "blur" }],
menuList: [
{
required: false, message: "请选择菜单列表", trigger: "change" }
]
},
temp: {
name: "",
systemAdmin: 1,
status: 1,
remarks: "",
menuList: []
},
resetTemp() {
this.temp = this.$options.data().temp;
},
border: window.g.borderFlag,
selectList: [],
treeData: [],
defaultProps: {
children: "childList",
label: "name"
},
defaultCheckedKeys: []
};
},
created() {
this.getMenuList();
this.fetchData();
},
methods: {
getMenuList() {
apiMenu
.menuGetTree()
.then(response => {
console.log("菜单树", response);
this.treeData = response.content;
// this.listLoading = false;
})
.catch(err => {
// this.listLoading = false;
});
},
fetchData() {
this.listLoading = true;
api
.roleList(this.listQuery)
.then(response => {
// console.log("角色列表", response);
const {
content } = response;
this.total = content.totalResult;
this.list = content.listpg;
this.listLoading = false;
})
.catch(err => {
});
},
handleCreate() {
this.resetTemp();
this.dialogStatus = "create";
this.dialogFormVisible = true;
this.$nextTick(() => {
this.defaultCheckedKeys = [];
this.$refs.treeRef.setCheckedKeys([]);
this.$refs["dataForm"].clearValidate();
});
},
createData() {
if (this.temp.menuList.length <= 0) {
this.$notify({
title: "提示",
message: "请先选择菜单后再提交",
type: "error",
duration: 2000
});
return false;
}
this.$refs["dataForm"].validate(valid => {
if (valid) {
api.roleSaveForm(this.temp).then(() => {
this.fetchData();
this.dialogFormVisible = false;
this.$notify({
title: "完成",
message: "创建成功",
type: "success",
duration: 2000
});
});
}
});
},
handleUpdate(row) {
// this.temp = Object.assign({}, row); // copy obj
this.dialogStatus = "update";
this.defaultCheckedKeys = [];
this.dialogFormVisible = true;
this.$nextTick(() => {
this.$refs.treeRef.setCheckedKeys([]);
this.$refs["dataForm"].clearValidate();
});
let obj = {
id: row.id
};
api
.roleDetail(obj)
.then(res => {
this.temp = res.content ? res.content : {
};
this.defaultCheckedKeys = res.content.menuList;
// console.log("角色详情", this.defaultCheckedKeys);
})
.catch(() => {
});
},
updateData() {
if (this.temp.menuList.length <= 0) {
this.$notify({
title: "提示",
message: "请先选择菜单后再提交",
type: "error",
duration: 2000
});
return false;
}
this.$refs["dataForm"].validate(valid => {
if (valid) {
const tempData = Object.assign({
}, this.temp);
api.roleEdit(tempData).then(() => {
this.fetchData();
this.dialogFormVisible = false;
this.$notify({
title: "完成",
message: "编辑成功",
type: "success",
duration: 2000
});
});
}
});
},
handleDelete(row) {
this.$confirm("确定删除这条数据吗", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
api.roleDelete(row).then(response => {
this.fetchData();
this.$notify({
title: "完成",
message: "删除成功",
type: "success",
duration: 2000
});
});
})
.catch(() => {
});
},
checkTree(node, data) {
console.log("role的checkTree", node, data);
this.temp.menuList = data.checkedKeys;
}
}
};
</script>
动态路由配置
前面我们说明了用户管理,菜单管理,角色管理,这些都是写些业务代码,问题不大,现在我们想根据不同的角色登录系统进来看到不同的侧边导航菜单,然后不同的角色可以有不同的按钮权限,这里一起来学习一下。我们在登录界面输入用户名和密码后,登录成功后后端会返回给前端当前角色拥有的菜单权限集合和按钮权限集合,比如我这里登录的是管理员admin账号,菜单权限有menuList: [“home”, “system”, “user”, “menu”, “role”] 按钮权限有btnList: [“user_add”, “user_edit”, “user_delete”]
本地路由如下:
完整代码如下:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
import LayoutEx from '@/layoutEx'
var showAgent = window.g.showAgent
/* Router Modules */
import toolRouter from './modules/tool'
//===固定路由===
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: '/404',
component: () => import('@/views/error-page/404'),
hidden: true,
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true,
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
// {
// path: 'dashboard',
// component: () => import('@/views/dashboard/admin/index'),
// name: '首 页',
// meta: { title: '首页', icon: 'el-icon-s-home', affix: true }
// },
{
path: 'dashboard',
component: () => import('@/views/homePage/index'),
// name: '首 页',
name: 'HomePage',
meta: {
title: '首页', icon: 'el-icon-s-home' },
},
],
}
]
//===动态路由 menuName就是前端页面配置菜单里的代号,要保持一致===
export const asyncRoutes = [
{
path: '/datax/project',
component: Layout,
redirect: '/datax/project/jobProject',
name: 'datasource',
meta: {
title: '任务组管理',
icon: 'el-icon-s-grid',
menuName: 'datasource',
},
children: [
{
path: 'jobProject',
name: 'jobProject',
component: () => import('@/views/datax/jobProject/index'),
meta: {
title: '任务组管理',
icon: 'el-icon-s-grid',
menuName: 'jobProject',
},
},
],
},
{
path: '/datax/job',
component: Layout,
redirect: '/datax/job/jobInfo',
name: 'job',
meta: {
title: '任务管理', icon: 'el-icon-set-up', menuName: 'job' },
children: [
{
path: 'jobInfo',
name: 'JobInfo',
component: () => import('@/views/datax/jobInfo/fileJob'),
meta: {
title: '文件任务',
icon: 'el-icon-set-up',
menuName: 'JobInfo',
},
},
{
path: 'missionData',
name: 'missionData',
component: () => import('@/views/datax/jobInfo/dataJob'),
meta: {
title: '数据库任务',
icon: 'el-icon-set-up',
menuName: 'missionData',
},
},
{
path: 'jsonBuild',
name: 'JsonBuild',
hidden: true,
component: () => import('@/views/datax/json-build/index'),
meta: {
title: '任务构建',
icon: 'guide',
noCache: false,
menuName: 'jsonBuild',
},
},
{
path: 'jsonBuildBatch',
name: 'JsonBuildBatch',
hidden: true,
component: () => import('@/views/datax/json-build-batch/index'),
meta: {
title: '任务批量构建',
icon: 'batch-create',
noCache: false,
menuName: 'jsonBuildBatch',
},
},
{
path: 'jobTemplate',
name: 'JobTemplate',
hidden: true,
component: () => import('@/views/datax/jobTemplate/index'),
meta: {
title: 'DataX任务模板',
icon: 'task-tmp',
menuName: 'jobTemplate',
},
},
],
},
{
path: '/datax/datasource',
component: Layout,
redirect: '/datax/datasource/fileDatasource',
name: 'datasource',
meta: {
title: '资源管理', icon: 'el-icon-files', menuName: 'datasource' },
children: [
{
path: 'fileDatasource',
name: 'fileDatasource',
component: () => import('@/views/datax/jdbc-datasource/fileControl'),
meta: {
title: '文件资源',
icon: 'el-icon-files',
menuName: 'fileDatasource',
},
},
{
path: 'jdbcDatasource',
name: 'JdbcDatasource',
component: () => import('@/views/datax/jdbc-datasource/dataControl'),
meta: {
title: '数据库资源',
icon: 'el-icon-files',
menuName: 'jdbcDatasource',
},
},
],
},
{
path: '/datax/jobLog',
component: Layout,
redirect: '/datax/jobLog/jobLog',
name: 'log',
meta: {
title: '日志管理', icon: 'el-icon-date', menuName: 'log' },
children: [
{
path: 'jobLog',
name: 'JobLog',
component: () => import('@/views/datax/jobLog/index'),
meta: {
title: '业务日志管理',
icon: 'el-icon-date',
menuName: 'JobLog',
},
},
{
path: 'auditLog',
name: 'auditLog',
component: () => import('@/views/datax/jobLog/auditLog'),
meta: {
title: '审计日志管理',
icon: 'el-icon-date',
menuName: 'auditLog',
},
},
{
path: 'normalLog',
name: 'normalLog',
component: () => import('@/views/datax/jobLog/normalLog'),
meta: {
title: '日志', icon: 'el-icon-date', menuName: 'normalLog' },
},
],
},
{
path: '/datax/executor',
component: Layout,
redirect: '/datax/executor/executor',
name: 'executor',
meta: {
title: '链路管理',
icon: 'el-icon-set-up',
menuName: 'executorManage',
},
children: [
{
path: 'executor',
name: 'Executor',
component: () => import('@/views/datax/executor/index'),
// meta: { title: '链路管理', icon: 'exe-cfg' },
meta: {
title: '链路管理',
icon: 'el-icon-set-up',
menuName: 'Executor',
},
},
],
},
{
path: '/service/exchangeTask',
component: Layout,
redirect: '/service/exchangeTask/index',
name: 'exchangeTask',
meta: {
title: '服务请求交换管理',
icon: 'el-icon-date',
menuName: 'exchangeTaskManage',
},
children: [
{
path: 'exchangeTask',
name: 'exchangeTask',
component: () => import('@/views/service/exchangeTask/index'),
meta: {
title: '交换任务管理',
icon: 'el-icon-date',
menuName: 'exchangeTask',
},
},
{
path: 'exchangeLog',
name: 'exchangeLog',
component: () => import('@/views/service/exchangeLog/index'),
meta: {
title: '交换日志管理',
icon: 'el-icon-date',
menuName: 'exchangeLog',
},
},
{
path: 'resourcesManage',
name: 'resourcesManage',
component: () => import('@/views/service/resourcesManage/index'),
meta: {
title: '资源组管理',
icon: 'el-icon-date',
menuName: 'resourcesManage',
},
},
{
path: 'visitManage',
name: 'visitManage',
component: () => import('@/views/service/visitManage/index'),
meta: {
title: '访问组管理',
icon: 'el-icon-date',
menuName: 'visitManage',
},
},
{
path: 'limitManage',
name: 'limitManage',
component: () => import('@/views/service/limitManage/index'),
meta: {
title: '限制组管理',
icon: 'el-icon-date',
menuName: 'limitManage',
},
},
{
path: 'boundaryManage',
name: 'boundaryManage',
component: () => import('@/views/service/boundaryManage/index'),
meta: {
title: '边界地址管理',
icon: 'el-icon-date',
menuName: 'boundaryManage',
},
},
],
},
{
path: '/datax/registry',
component: Layout,
redirect: '/datax/registry/registry',
name: 'registry',
hidden: true,
meta: {
title: '资源监控', icon: 'work', menuName: 'registryManage' },
children: [
{
path: 'registry',
name: 'Registry',
component: () => import('@/views/datax/registry/index'),
meta: {
title: '资源监控', icon: 'battery-line', menuName: 'registry' },
},
],
},
{
path: '/system',
component: Layout,
redirect: '/system/user/index',
meta: {
title: '用户中心', icon: 'el-icon-date', menuName: 'system' },
children: [
{
path: '/user',
name: 'user',
component: () => import('@/views/system/user/index'),
meta: {
title: '用户管理', icon: 'el-icon-s-custom', menuName: 'user' },
},
{
path: 'menu',
name: 'menu',
component: () => import('@/views/system/menu/index'),
meta: {
title: '菜单管理', icon: 'el-icon-menu', menuName: 'menu' },
},
{
path: 'role',
name: 'role',
component: () => import('@/views/system/role/index'),
meta: {
title: '角色管理', icon: 'el-icon-menu', menuName: 'role' },
},
{
path: 'personal',
name: 'personal',
hidden: true,
component: () => import('@/views/system/personal/index'),
meta: {
title: '个人中心', icon: 'el-icon-menu', menuName: 'personal' },
},
],
},
{
path: '*', redirect: '/404', hidden: true },
]
const createRouter = () =>
new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({
y: 0 }),
routes: constantRoutes.concat(asyncRoutes),
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
因为我的登录方法写在了store里,登录成功后,我会缓存一些数据到本地,比如我这里缓存了用户名和用户id,然后通过commit方法改变了state里的token和name及当前角色的权限集合roles,通过dispatch生成可访问的路由表并触发permission模块下的actions里的异步方法generateRoutesMe,在generateRoutesMe这个方法里通过另一个方法filterAsyncRoutes对本地路由进行处理,如果说菜单权限列表集合menuList里与本地路由里的menuName一致就设置其 hidden为false,说明显示这个菜单,有权限,否则设置为true无权限不展示。按钮权限就简单更多,在拿到按钮权限集合btnList后对模块的按钮通过一个全局公共方法 c h e c k P e r m i s s i o n 并传递指定代号参数来处理,这个参数是我们在页面配置的代号,我们对按钮 b u t t o n 设置其权限,有两种效果展示,,如果没权限不能点击那或者说没权限直接不显示,代码就是 : d i s a b l e d = " ! checkPermission并传递指定代号参数来处理,这个参数是我们在页面配置的代号,我们对按钮button设置其权限,有两种效果展示,, 如果没权限不能点击那或者说没权限直接不显示,代码就是 :disabled="! checkPermission并传递指定代号参数来处理,这个参数是我们在页面配置的代号,我们对按钮button设置其权限,有两种效果展示,,如果没权限不能点击那或者说没权限直接不显示,代码就是:disabled="!checkPermission([‘user_add’])" 或 v-show=“!$checkPermission([‘user_add’])” 。这就是菜单权限和按钮权限的思路,下面贴上代码:
在utils目录下的permission.js里封装全局公共方法checkPermission,然后main.js里引入并挂载到原型上,这样就能全局使用,如下图:
代码如下:
//=====utils目录下的permission.js里=====
import store from '@/store'
/**
* @param {Array} value
* @returns {Boolean}
* @example see @/views/permission/directive.vue
*/
export default function checkPermission(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles
const permissionRoles = value
// console.log('验证按钮权限', store, roles, permissionRoles)
const hasPermission = roles.some((role) => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
return false
} else {
return true
}
} else {
console.error(`need roles! Like v-permission="['admin','editor']"`)
return false
}
}
//=========main.js里=========
//将验证按钮权限的方法挂载到this以便全局使用
import checkPermission from './utils/permission.js'
Vue.prototype.$checkPermission = checkPermission
store目录下的index.j
s代码如下:
//================store目录下的getters.js代码如下:================
const getters = {
sidebar: (state) => state.app.sidebar,
size: (state) => state.app.size,
device: (state) => state.app.device,
visitedViews: (state) => state.tagsView.visitedViews,
cachedViews: (state) => state.tagsView.cachedViews,
token: (state) => state.user.token,
avatar: (state) => state.user.avatar,
name: (state) => state.user.name,
introduction: (state) => state.user.introduction,
roles: (state) => state.user.roles,
permission_routes: (state) => state.permission.routes,
addRoutes: (state) => state.permission.addRoutes,
ifchange: (state) => state.permission.ifchange,
errorLogs: (state) => state.errorLog.logs,
}
export default getters
//========store目录下的modules里的user.js代码如下:===============
import {
login, logout } from '@/api/user'
import {
menuGetTree } from '@/api/datax-menu'
import {
getToken, setToken, removeToken } from '@/utils/auth'
import router, {
resetRouter } from '@/router'
import store from '../index.js'
const state = {
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: [],
}
const mutations = {
SET_TOKEN: (state, token) => {
// console.log('SET_TOKEN', 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
login({
commit }, userInfo) {
const {
username, password, code } = userInfo
return new Promise((resolve, reject) => {
login({
username: username.trim(),
password: password,
// rememberMe: 1,
code: code,
})
.then(async (response) => {
console.log('登录进来', response)
const {
data } = response.content
const {
roles } = response.content
commit('SET_TOKEN', response.content.token)
commit('SET_NAME', response.content.username)
sessionStorage.setItem('name', response.content.username)
if (roles) {
localStorage.setItem('roles', JSON.stringify(roles))
}
setToken(response.content.token)
//缓存当前角色菜单树
// sessionStorage.setItem('menuList', response.content.menuList)
//缓存当前角色拥有的按钮权限集合
commit('SET_ROLES', response.content.btnList)
let menuIdArr = response.content.menuList
//生成可访问的路由表
const accessRoutes = await store.dispatch(
'permission/generateRoutesMe',
menuIdArr,
)
// console.log('user的accessRoutes', accessRoutes)
// store.commit('permission/SET_ROUTES', accessRoutes)
//缓存修改密码的id
localStorage.setItem('userId', JSON.stringify(response.content.id))
resolve()
})
.catch((error) => {
reject(error)
})
})
},
// get user info
getInfo({
commit, state }) {
return new Promise((resolve, reject) => {
const data = {
}
try {
if (localStorage.getItem('roles')) {
data.roles = JSON.parse(localStorage.getItem('roles'))
}
} catch (err) {
}
// console.log('data.roles123', data.roles)
commit('SET_ROLES', data.roles)
resolve(data)
})
},
// user logout
logout({
commit, state }) {
return new Promise((resolve, reject) => {
logout({
username: sessionStorage.getItem('name'),
})
.then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_NAME', '')
removeToken()
resetRouter()
resolve()
})
.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,
}
//========store目录下的modules里的permission.js代码如下:===============
import {
asyncRoutes, constantRoutes } from '@/router'
import router from '../../router'
import Layout from '@/layout'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
// console.log('hasPermission里', roles, route)
if (route.meta && route.meta.roles) {
if (roles) {
return roles.some((role) => route.meta.roles.includes(role))
} else {
return true
}
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles, menuIdArr) {
const res = []
// console.log('filterAsyncRoutes里', routes, roles, menuIdArr)
routes.forEach((route) => {
const tmp = {
...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
} else {
res = routes
}
})
return res
}
export function filterAsyncRoutesMe(routes, menuIdArr) {
// console.log(
// 'filterAsyncRoutesMe里',
// routes,
// menuIdArr,
// Object.prototype.toString.call(menuIdArr),
// )
if (Object.prototype.toString.call(menuIdArr) == '[object String]') {
menuIdArr = menuIdArr.split(',')
}
let data
var findRoute = function (arr) {
data = arr.map((item) => {
// console.log('filterAsyncRoutesMe', item)
let myItem = {
...item,
hidden:
(item.meta &&
Array.isArray(menuIdArr) &&
menuIdArr.length > 0 &&
menuIdArr.some((i) => i == item.meta.menuName)) ||
!item.hasOwnProperty('hidden') ||
!item.hidden
? false
: true,
children:
item.children && item.children.length > 0
? findRoute(item.children)
: [],
}
return {
...myItem,
}
})
return data
}
let finallyRoutes = findRoute(routes)
// console.log('finallyRoutes', finallyRoutes)
return finallyRoutes
}
// const state = {
// routes: [],
// addRoutes: [],
// ifchange: false,
// }
const state = {
routes: constantRoutes,
addRoutes: [],
ifchange: false,
}
const mutations = {
SET_ROUTES: (state, routes) => {
// state.routes = []
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
// state.routes = [...constantRoutes, ...routes]
// console.log('pe132321', state.routes)
// router.addRoutes(routes)
state.ifchange = true
},
}
const actions = {
generateRoutes({
commit }, roles, menuIdArr) {
return new Promise((resolve) => {
let accessedRoutes
// console.log('generateRoutes里', asyncRoutes, roles, menuIdArr)
if (roles && roles.includes('ROLE_ADMIN')) {
accessedRoutes = asyncRoutes || []
} else {
// accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
let myRoutes = filterAsyncRoutes(asyncRoutes, roles, menuIdArr)
// console.log('myRoutes', myRoutes)
accessedRoutes = myRoutes
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
},
generateRoutesMe({
commit }, menuIdArr) {
return new Promise((resolve) => {
let accessedRoutes = filterAsyncRoutesMe(asyncRoutes, menuIdArr)
// console.log('generateRoutesMe里', asyncRoutes, menuIdArr,accessedRoutes)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}
然后我当时遇到的一个问题就是路由出现死循环,那是因为router.beforeEach的出口next没有写对,我之前的代码都如下注释那样:
import router from './router'
import store from './store'
import {
Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import {
getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({
showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
router.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 {
const hasRoles = store.getters.roles && store.getters.roles.length > 0
// console.log(
// 'hasRoles看看',
// hasRoles,
// store,
// store.getters.addRoutes,
// store.getters.permission_routes,
// JSON.parse(localStorage.getItem('MenuPrmStore')),
// )
if (hasRoles) {
next()
} else {
try {
//拉取info信息
// const { roles } = await store.dispatch('user/getInfo')
//获取当前角色所拥有的菜单
// let menuIdArr = await sessionStorage.getItem('menuList')
// //生成可访问的路由表
// const accessRoutes = await store.dispatch(
// 'permission/generateRoutesMe',
// menuIdArr,
// )
next()
// console.log('roles4444', menuIdArr, accessRoutes)
// if (
// store.getters.permission_routes.length > 0 ||
// to.name != null ||
// store.getters.addRoutes.length > 0
// ) {
// //放行
// next()
// } else {
// //动态添加可访问路由表
// store.commit('SET_ROUTES', accessRoutes)
// router.addRoutes(accessRoutes)
// // next({ ...to, replace: true })
// // for (let i = 0; i < accessRoutes.length; i += 1) {
// // const element = accessRoutes[i]
// // router.addRoute(element)
// // }
// // router.options.isAddAsyncMenuData = true
// next({ ...to, replace: true })
// }
// if (!store.getters.ifchange) {
// //动态添加可访问路由表
// router.addRoutes(accessRoutes)
// // store.commit('SET_ROUTES', accessRoutes)
// next({ ...to, replace: true })
// } else {
// next()
// }
//=====直接下面这样addRoutes会死循环=====
// router.addRoutes(accessRoutes)
// next()
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
// Message.error(error || 'Has Error')
Message.error({
message: 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()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
我这里当时出现死循环是因为我最开始直接就router.addRoutes(accessRoutes) 然后就next( )放行,最后浏览器崩溃了,然后我又尝试了通过判断 permission_routes的长度,如果大于0说明addRoutes了一次直接next( )放行,否则就先遍历accessRoutes然后一个一个加或直接一次性注入,结果还不行,然后我又尝试在state里定义一个变量ifchange,如果说其为false就accessRoutes然后next({ …to, replace: true }),否则就next()放行,其实跟我判断 permission_routes的长度的做法差不多,但结局就是都是要么找不到路由,要么死循环。后面发现是我在注册路由时没有写对,代码如下:
我应该是注册路由时应该concat一下动态路由asyncRoutes,这里当时就写成了固定路由 routes: constantRoutes这种是错的。正确的是routes: constantRoutes.concat(asyncRoutes)
刷新页面后store里的数据丢失问题解决
刷新页面时,vue实例重新加载,从而,store也被重置了。store是用来存储组件状态的,而不是用来做本地数据存储的。所以,对于不希望页面刷新之后被重置的数据,使用本地存储来进行存储。就比如此例里,我只要一刷新页面,我store里的菜单和角色数据都会因此而丢失导致页面空白,我是直接在App.vue里添加一个监听页面刷新的事件beforeunload,而beforeunload事件在页面刷新之前先触发,这样就给我们时间去缓存store里的数据了,然后判断缓存里有无数据,有数据就改变store里state里的数据,代码如下:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
import store from "./store";
export default {
name: "App",
mounted() {
var that = this;
window.onresize = function() {
that.$EventBus.$emit("resize");
};
},
created() {
this.refresh();
},
methods: {
//刷新时重新用本地缓存的路由替换之前router里的路由
refresh() {
// console.log(
// "store去去去",
// store,
// JSON.parse(localStorage.getItem("MenuPrmStore"))
// );
// console.log("store.state", store.state);
try {
if (localStorage.getItem("MenuPrmStore")) {
store.commit(
"permission/SET_ROUTES",
JSON.parse(localStorage.getItem("MenuPrmStore"))
);
}
if (localStorage.getItem("roleList")) {
store.commit(
"user/SET_ROLES",
JSON.parse(localStorage.getItem("roleList"))
);
}
} catch (e) {
console.log("刷新出错", e);
}
// console.log(
// "目前store.state.permission.routes",
// store.state.permission.routes
// );
// }
// 在页面刷新时将vuex里的信息保存到localStorage里
// beforeunload事件在页面刷新之前先触发
window.addEventListener("beforeunload", () => {
//存菜单列表
localStorage.setItem(
"MenuPrmStore",
JSON.stringify(store.state.permission.addRoutes)
);
//存角色权限列表
localStorage.setItem(
"roleList",
JSON.stringify(store.state.user.roles)
);
});
}
}
};
</script>