顶级菜单支持拖拽排序,侧边栏菜单会按照顶级菜单拖放顺序生成
点击左侧节点 编辑
添加节点
路由name目前没用到 之前想通过路由name筛选路由
3-1 菜单管理页面
src/views/system/menu/index.vue
<template>
<div class="menu-container">
<!-- 菜单树 -->
<el-card class="tree-card">
<template #header>
<el-button @click="handleCreateRootMenu">新增顶级菜单</el-button>
</template>
<div class="block">
<div class="menu-tree">
<el-tree
ref="menuTreeRef"
:data="menus"
highlight-current
node-key="id"
:expand-on-click-node="false"
:check-strictly="true"
@node-click="handleNodeClick"
:props="defaultProps"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
@node-drop="handleNodeDrop"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<el-button type="text" @click.stop="handleCreateChildMenu(data)">
添加
</el-button>
<el-button type="text" @click.stop="handleRemoveMenu(node, data)">
删除
</el-button>
</span>
</span>
</template>
</el-tree>
</div>
</div>
</el-card>
<el-card class="edit-card">
<template #header>
编辑菜单
</template>
<editor-menu v-show="editData && editData.id" :data="editData" />
<span v-if="editData == null">从菜单列表选择一项后,进行编辑</span>
</el-card>
<!-- 添加菜单 -->
<right-panel v-model="dialogVisible" :title="panelTitle">
<div class="menu-form">
<el-form
ref="menuFormRef"
:model="menuFormData"
:rules="menuFormRules"
label-width="100px"
>
<el-form-item label="菜单名称" prop="title">
<el-input
v-model="menuFormData.title"
placeholder="请输入菜单名称"
/>
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input
v-model="menuFormData.path"
placeholder="请输入路由路径"
/>
</el-form-item>
<el-form-item label="路由Name" prop="name">
<el-input
v-model="menuFormData.name"
placeholder="请输入路由名称"
/>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input
v-model="menuFormData.icon"
placeholder="请输入icon名称"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitMenuForm"
>创建菜单</el-button
>
</el-form-item>
</el-form>
</div>
</right-panel>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, onMounted, watch, getCurrentInstance } from 'vue'
import { ElTree, ElForm } from 'element-plus'
import RightPanel from '@/components/RightPanel/index.vue'
import { addNewMenu, removeMenuByID, updateBulkMenu } from '@/api/menu'
import { ITreeItemData, MenuData } from '@/store/modules/menu'
import { useStore } from '@/store'
import EditorMenu from './components/editorMenu.vue'
import { useReloadPage } from '@/hooks/useReload'
interface ITreeNode {
id: number
title: string
children: ITreeNode[]
parentId?: number
sortId: number
parent: {
data: ITreeNode
},
data: ITreeItemData
}
type IMenuTree = InstanceType<typeof ElTree>
type IMenuForm = InstanceType<typeof ElForm>
type IMenuItemNotID = Omit<ITreeItemData, 'id'>
export default defineComponent({
name: 'Menu',
components: {
RightPanel,
EditorMenu
},
setup() {
const store = useStore()
const { proxy } = getCurrentInstance()!
const menuTreeRef = ref<IMenuTree | null>(null)
const treeData = computed(() => store.getters.menusTree)
const menus = ref<ITreeItemData[]>([])
const editData = ref<MenuData|null>()
watch(treeData, (value: ITreeItemData[]) => {
menus.value = JSON.parse(JSON.stringify(value))
editData.value = null
})
onMounted(() => { // 获取全部菜单
store.dispatch('menu/getAllMenuList')
})
// tree props
const defaultProps = ref({
children: 'children',
label: 'title'
})
// 重新刷新整个系统
const { reloadPage } = useReloadPage()
// 添加菜单panel
const dialogVisible = ref(false)
watch(dialogVisible, value => {
if (!value) {
(menuFormRef.value as IMenuForm).resetFields()
}
})
// 分配sortId 根据最后一个数据sortId+1
const getMenuNodeSortID = (list: ITreeItemData[]) => {
if (list && list.length > 0) {
return list[list.length - 1].sort_id + 1
}
return 0
}
// 移除节点
const removeNode = (node: ITreeNode, childId: number) => {
const parent = node.parent
const children = parent.data.children || parent.data
const index = children.findIndex(d => d.id === childId)
children.splice(index, 1)
menus.value = [...menus.value]
}
/**
* node: 当前node对象
* menuData: 当前节点数据
*/
const handleRemoveMenu = (node: ITreeNode, menuData: ITreeItemData) => {
proxy?.$confirm(`您确认要删除菜单${menuData.title}吗?`, '删除确认', {
type: 'warning'
}).then(() => {
// 根据id删除菜单
removeMenuByID(menuData.id).then(res => {
if (res.code === 0) {
proxy?.$message.success('删除成功')
removeNode(node, menuData.id)
// 如果删除的是当前编辑的菜单 就重置编辑表单
if (editData.value && menuData.id === editData.value.id) {
editData.value = null
}
// 是否重新刷新整个系统
reloadPage()
}
})
}).catch(() => {
proxy?.$message({
type: 'info',
message: '已取消删除'
})
})
}
// 新增顶级菜单
// 添加菜单表单
const menuFormRef = ref<IMenuForm | null>(null)
// 菜单表单数据
const menuFormData = reactive<IMenuItemNotID>({
title: '',
path: '',
name: '',
icon: '',
parent_id: '',
sort_id: 0
})
const menuType = ref(0) // 添加菜单类型 0顶级 1子级
// 面板title
const panelTitle = computed(() =>
menuType.value === 0 ? '添加顶级菜单' : '添加子菜单'
)
// 重置添加菜单状态
const resetStatus = () => {
dialogVisible.value = false
menuFormRef.value?.resetFields()
parentData.value = null
}
// ············· 添加顶级菜单 ······················
// 点击添加顶级菜单
const handleCreateRootMenu = () => {
menuType.value = 0
dialogVisible.value = true
}
// 顶级菜单分配partentId和sortId
const allocRootMenuId = (data: IMenuItemNotID) => {
const sortId = getMenuNodeSortID(menus.value)
data.sort_id = sortId
data.parent_id = '0'
}
// 顶级菜单 添加到 tree组件中
const appendRootMenu = (id: number, data: IMenuItemNotID) => {
const node = { id, ...data, children: [] }
menus.value.push(node)
menus.value = [...menus.value]
}
// 添加顶级菜单
const handleAddRootMenu = async (data: IMenuItemNotID) => {
allocRootMenuId(data)
await addNewMenu(data).then(res => {
if (res.code === 0) {
const { id } = res.data
appendRootMenu(id, data)
proxy?.$message.success('菜单创建成功')
// 是否重新刷新整个系统
reloadPage()
}
})
}
// ············· 添加子菜单 ······················
// 子菜单分配sortid 和 parentId
const allocChildMenuId = (data: IMenuItemNotID, parentData: ITreeItemData): IMenuItemNotID => {
const pid = parentData.id as number
let sortId = 0
if (!parentData.children) {
parentData.children = []
}
if (parentData.children.length > 0) {
sortId = getMenuNodeSortID(parentData.children)
}
data.sort_id = sortId
data.parent_id = pid
return data
}
// 添加子菜单到tree组件中
const appendChildMenu = (child: ITreeItemData, parentData: ITreeItemData) => {
(parentData.children!).push(child)
menus.value = [...menus.value]
}
// 添加子菜单
const parentData = ref<ITreeItemData | null>(null) // 缓存父菜单data
const handleAddChildMenu = async (data: IMenuItemNotID) => {
const child = allocChildMenuId(data, parentData.value!)
await addNewMenu(data).then(res => {
if (res.code === 0) {
const { id } = res.data
;(child as ITreeItemData).id = id
appendChildMenu(child as ITreeItemData, parentData.value!)
proxy?.$message.success('菜单创建成功')
// 是否重新刷新整个系统
reloadPage()
}
})
}
// 新增子菜单
const handleCreateChildMenu = (data: ITreeItemData) => {
menuType.value = 1
dialogVisible.value = true
parentData.value = data
}
// 菜单编辑
const handleNodeClick = (data: MenuData) => {
editData.value = { ...data }
}
// 提交menuForm
const submitMenuForm = () => {
(menuFormRef.value as IMenuForm).validate(async valid => {
if (valid) {
if (menuType.value === 0) {
// 添加根菜单
await handleAddRootMenu({ ...menuFormData })
} else if (menuType.value === 1) {
// 添加子菜单
await handleAddChildMenu({ ...menuFormData })
}
// 重置相关状态
resetStatus()
}
})
}
// 实现顶级菜单 拖拽排序
// 拖拽一级节点
const allowDrag = (draggingNode: ITreeNode) => {
const data = draggingNode.data
return data.parent_id === 0 || data.parent_id == null
}
// 拖放一级节点
type DropType = 'before' | 'after' | 'inner'
const allowDrop = (draggingNode: ITreeNode, dropNode: ITreeNode, type: DropType) => {
if (dropNode.data.parent_id === 0 || dropNode.data.parent_id == null) {
return type !== 'inner'
}
}
// 拖放完成事件
const handleNodeDrop = () => {
menus.value.forEach((menu, index) => {
menu.sort_id = index
})
// 批量更新菜单状态 这里是为了更新sort_id
const menuList = menus.value.map(menu => {
const temp = { ...menu }
delete menu.children
return temp
})
// 批量更新
updateBulkMenu(menuList).then(res => {
if (res.code === 0) {
// 重新生成菜单 1 代表是菜单排序更新
store.dispatch('permission/generateRoutes', 1)
}
})
}
// 验证规则
const menuFormRules = reactive({
title: {
required: true,
message: '请输入菜单名称',
trigger: 'blur'
},
path: {
required: true,
message: '请输入路由路径',
trigger: 'blur'
},
name: {
required: true,
message: '请输入路由名称',
trigger: 'blur'
}
})
return {
menus,
handleCreateRootMenu,
handleCreateChildMenu,
handleRemoveMenu,
menuTreeRef,
handleNodeClick,
dialogVisible,
menuFormData,
menuFormRules,
menuFormRef,
submitMenuForm,
defaultProps,
panelTitle,
editData,
allowDrag,
allowDrop,
handleNodeDrop
}
}
})
</script>
<style lang="scss">
.menu-container {
display: flex;
padding: 20px;
justify-content: space-around;
.menu-tree {
height: 400px;
overflow-y: scroll;
}
.tree-card {
min-width: 500px;
padding-bottom: 30px;
}
.edit-card {
flex: 1;
margin-left: 15px;
}
.el-form-item__content {
min-width: 220px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
.menu-form {
padding: 20px 10px 20px 0;
box-sizing: border-box;
}
}
</style>
创建编辑菜单组件
src/views/system/menu/components/editorMenu.vue
<template>
<div class="editor-container">
<el-form
ref="editFormRef"
:model="editData"
:rules="menuFormRules"
label-width="100px"
>
<el-form-item label="菜单名称" prop="title">
<el-input
v-model="editData.title"
placeholder="请输入菜单名称"
/>
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input
v-model="editData.path"
placeholder="请输入路由路径"
/>
</el-form-item>
<el-form-item label="路由Name" prop="name">
<el-input
v-model="editData.name"
placeholder="请输入路由名称"
/>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input
v-model="editData.icon"
placeholder="请输入icon名称"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="submitMenuForm"
:loading="loading"
>编辑菜单</el-button>
<el-button @click="submitReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch, getCurrentInstance } from 'vue'
import { MenuData } from '@/store/modules/menu'
import { ElForm } from 'element-plus'
import { updateMenuByID } from '@/api/menu'
import { useStore } from '@/store'
import { useReloadPage } from '@/hooks/useReload'
type FormInstance = InstanceType<typeof ElForm>
export default defineComponent({
name: 'EditorMenu',
props: {
data: {
type: Object as PropType<MenuData>
}
},
emits: ['updateEdit'],
setup(props) {
const store = useStore()
const { proxy } = getCurrentInstance()!
const loading = ref(false)
const editFormRef = ref<FormInstance|null>(null)
const editData = ref({
id: '',
title: '',
name: '',
path: '',
icon: ''
})
// 验证规则
const menuFormRules = {
title: {
required: true,
message: '请输入菜单名称',
trigger: 'blur'
},
path: {
required: true,
message: '请输入路由路径',
trigger: 'blur'
},
name: {
required: true,
message: '请输入路由名称',
trigger: 'blur'
}
}
const resetFormData = (data: MenuData) => {
if (data) {
const { id, title, name, path, icon } = data
editData.value = { id: String(id), title, name, path, icon }
}
}
watch(() => props.data, (value) => {
if (value) {
resetFormData(value)
}
})
// 刷新系统
const { reloadPage } = useReloadPage()
// 提交编辑菜单
const submitMenuForm = () => {
(editFormRef.value as FormInstance).validate(valid => {
if (valid) {
loading.value = true
updateMenuByID(Number(editData.value.id), editData.value).then(res => {
if (res.code === 0) {
proxy?.$message.success('菜单编辑成功')
// 重新获取菜单
store.dispatch('menu/getAllMenuList')
reloadPage()
}
}).finally(() => {
loading.value = false
})
}
})
}
// 重置编辑菜单
const submitReset = () => {
resetFormData(props.data as MenuData)
}
return {
editData,
submitMenuForm,
submitReset,
editFormRef,
menuFormRules,
loading
}
}
})
</script>
3-2 菜单store
src/store/modules/menu.ts
import { Module, MutationTree, ActionTree } from 'vuex'
import { IRootState } from '@/store'
import { getAllMenus } from '@/api/menu'
import generateTree from '@/utils/generateTree'
import generateMenuTree from '@/utils/generateMenuTree'
import { getAccessByRoles } from '@/api/roleAccess'
/* eslint-disable camelcase */
export interface MenuData {
id: number;
title: string;
path: string;
name: string;
icon: string;
parent_id: string | number;
sort_id: number;
}
export interface ITreeItemData extends MenuData {
children?: ITreeItemData[]
}
// state类型
export interface IMenusState {
menuTreeData: Array<ITreeItemData>; // 树形菜单数据
menuList: Array<MenuData>; // 原始菜单列表数据
authMenuTreeData: Array<ITreeItemData>; // 树形菜单数据
authMenuList: Array<MenuData>; // 原始菜单列表数据
}
// mutations类型
type IMutations = MutationTree<IMenusState>
// actions类型
type IActions = ActionTree<IMenusState, IRootState>
// 定义state
const state: IMenusState = {
menuTreeData: [],
menuList: [],
authMenuTreeData: [],
authMenuList: []
}
// 定义mutations
const mutations: IMutations = {
SET_MENU_LIST(state, data: IMenusState['menuList']) {
state.menuList = data
},
SET_MENU_TREE_DATA(state, data: IMenusState['menuTreeData']) {
state.menuTreeData = data
},
SET_AUTH_MENU_LIST(state, data: IMenusState['menuList']) {
state.authMenuList = data
},
SET_AUTH_MENU_TREE_DATA(state, data: IMenusState['menuTreeData']) {
state.authMenuTreeData = data
}
}
// 定义actions
const actions: IActions = {
getAllMenuList({ dispatch, commit }) {
return new Promise<MenuData[]>((resolve, reject) => {
getAllMenus().then(response => {
const { data } = response
dispatch('generateTreeData', [...data])
commit('SET_MENU_LIST', data)
resolve([...data])
}).catch(reject)
})
},
generateTreeData({ commit }, data: IMenusState['menuList']) {
const treeData = generateTree(data)
commit('SET_MENU_TREE_DATA', treeData)
},
generateAuthTreeData({ commit }, data: IMenusState['menuList']) {
const treeData = generateMenuTree(data)
commit('SET_AUTH_MENU_TREE_DATA', treeData)
},
getAllMenuListByAdmin({ dispatch, commit }) {
return new Promise<MenuData[]>((resolve, reject) => {
getAllMenus().then(response => {
const { data } = response
dispatch('generateAuthTreeData', [...data])
commit('SET_AUTH_MENU_LIST', data)
resolve([...data])
}).catch(reject)
})
},
getAccessByRoles({ dispatch, commit }, roles: number[]) {
return new Promise<MenuData[]>((resolve, reject) => {
getAccessByRoles(roles).then(response => {
const { access } = response.data
dispatch('generateAuthTreeData', [...access])
commit('SET_AUTH_MENU_LIST', access)
resolve([...access])
}).catch(reject)
})
}
}
// 定义menu module
const menu: Module<IMenusState, IRootState> = {
namespaced: true,
state,
mutations,
actions
}
export default menu
store.ts
src/store/index.ts
import { InjectionKey } from 'vue'
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import app, { IAppState } from '@/store/modules/app'
import tagsView, { ITagsViewState } from '@/store/modules/tagsView'
import settings, { ISettingsState } from '@/store/modules/settings'
import user, { IUserState } from '@/store/modules/user'
import getters from './getters'
import menu, { IMenusState } from './modules/menu'
import role, { IRoleState } from './modules/role'
import permission, { IPermissionState } from './modules/permission'
// 模块声明在根状态下
export interface IRootState {
app: IAppState;
user: IUserState;
menu: IMenusState;
role: IRoleState;
tagsView: ITagsViewState;
settings: ISettingsState;
permission: IPermissionState;
}
// 通过下面方式使用 TypeScript 定义 store 能正确地为 store 提供类型声明。
// https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage
// eslint-disable-next-line symbol-description
export const key: InjectionKey<Store<IRootState>> = Symbol()
// 对于getters在组件使用时没有类型提示
// 有人提交了pr #1896 为getters创建泛型 应该还未发布
// https://github.com/vuejs/vuex/pull/1896
// 代码pr内容详情
// https://github.com/vuejs/vuex/pull/1896/files#diff-093ad82a25aee498b11febf1cdcb6546e4d223ffcb49ed69cc275ac27ce0ccce
// vuex store持久化 默认使用localstorage持久化
const persisteAppState = createPersistedState({
storage: window.sessionStorage, // 指定storage 也可自定义
key: 'vuex_app', // 存储名 默认都是vuex 多个模块需要指定 否则会覆盖
// paths: ['app'] // 针对app这个模块持久化
// 只针对app模块下sidebar.opened状态持久化
paths: ['app.sidebar.opened', 'app.size'] // 通过点连接符指定state路径
})
const persisteSettingsState = createPersistedState({
storage: window.sessionStorage, // 指定storage 也可自定义
key: 'vuex_setting', // 存储名 默认都是vuex 多个模块需要指定 否则会覆盖
// paths: ['app'] // 针对app这个模块持久化
// 只针对app模块下sidebar.opened状态持久化
paths: ['settings.theme', 'settings.originalStyle', 'settings.tagsView', 'settings.sidebarLogo'] // 通过点连接符指定state路径
})
export default createStore<IRootState>({
plugins: [
persisteAppState,
persisteSettingsState
],
getters,
modules: {
app,
user,
tagsView,
settings,
menu,
role,
permission
}
})
// 定义自己的 `useStore` 组合式函数
// https://next.vuex.vuejs.org/zh/guide/typescript-support.html#%E7%AE%80%E5%8C%96-usestore-%E7%94%A8%E6%B3%95
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function useStore () {
return baseUseStore(key)
}
// vuex持久化 vuex-persistedstate文档说明
// https://www.npmjs.com/package/vuex-persistedstate
3-3 菜单api
src/api/menu.ts
import request from '@/api/config/request'
import { MenuData } from '@/store/modules/menu'
import { ApiResponse } from './type'
// 添加新菜单
export const addNewMenu = (data: Omit<MenuData, 'id'>): Promise<ApiResponse> => {
return request.post(
'/access/menu',
data
)
}
// 获取全部菜单
export const getAllMenus = (): Promise<ApiResponse<MenuData[]>> => {
return request.get('/access/menus')
}
// 删除指定菜单
export const removeMenuByID = (id: number): Promise<ApiResponse<null>> => {
return request.delete(`/access/menu/${id}`)
}
// 更新指定菜单
type UpdateMenuData = Omit<MenuData, 'id'|'parent_id'|'sort_id'>
export const updateMenuByID = (id: number, data: UpdateMenuData): Promise<ApiResponse<null>> => {
return request.put(`/access/menu/${id}`, data)
}
// 批量更新菜单
export const updateBulkMenu = (data: MenuData[]): Promise<ApiResponse<null>> => {
return request.patch('/access/menu/update', {
access: data
})
}
import request from '@/api/config/request'
import { MenuData } from '@/store/modules/menu'
import { ApiResponse } from './type'
// 添加新菜单
export const addNewMenu = (data: Omit<MenuData, 'id'>): Promise<ApiResponse> => {
return request.post(
'/access/menu',
data
)
}
// 获取全部菜单
export const getAllMenus = (): Promise<ApiResponse<MenuData[]>> => {
return request.get('/access/menus')
}
// 删除指定菜单
export const removeMenuByID = (id: number): Promise<ApiResponse<null>> => {
return request.delete(`/access/menu/${id}`)
}
// 更新指定菜单
type UpdateMenuData = Omit<MenuData, 'id'|'parent_id'|'sort_id'>
export const updateMenuByID = (id: number, data: UpdateMenuData): Promise<ApiResponse<null>> => {
return request.put(`/access/menu/${id}`, data)
}
3-4 角色和菜单
api
src/api/roleAccess.ts
import request from '@/api/config/request'
import { MenuData } from '@/store/modules/menu'
import { IRole, IRoleAccessList } from '@/store/modules/role'
import { ApiResponse } from './type'
/**
* 根据角色分配权限
* @param id 角色id
* @param data 权限id列表
*/
export const allocRoleAccess = (id: number, data: number[]): Promise<ApiResponse> => {
return request.post(`/role_access/${id}`, {
access: data
})
}
/**
* 根据角色获取权限
* @param id 角色id
* @param data 权限id列表
*/
export const getRoleAccess = (id: number): Promise<ApiResponse<IRoleAccessList>> => {
return request.get(`/role_access/${id}`)
}
// 根据用户角色获取用户菜单
type RolesAccess = MenuData & {
roles: IRole[]
}
interface ApiRolesAccess {
access: RolesAccess[]
}
export const getAccessByRoles = (roles: number[]): Promise<ApiResponse<ApiRolesAccess>> => {
return request.post('/role_access/role/access', {
roles
})
}
3-5 权限store
src/store/modules/permission.ts
import { Module, MutationTree, ActionTree } from 'vuex'
import { RouteRecordRaw } from 'vue-router'
import store, { IRootState } from '../index'
import { asyncRoutes } from '../../router/index'
import { MenuData } from './menu'
import path from 'path'
// 生成路由路径数组
const generateRoutePaths = (menus: Array<MenuData>): string[] => {
return menus.map(menu => menu.path)
}
// 白名单
const whiteList = ['/:pathMatch(.*)*']
// 生成可访问路由表
const generateRoutes = (routes: Array<RouteRecordRaw>, routePaths: string[], basePath = '/') => {
const routerData: Array<RouteRecordRaw> = []
routes.forEach(route => {
const routePath = path.resolve(basePath, route.path)
if (route.children) { // 先看子路由 是否有匹配上的路由
route.children = generateRoutes(route.children, routePaths, routePath)
}
// 如果当前路由子路由 数量大于0有匹配上 或 paths中包含当面路由path 就需要把当前父路由添加上
if (routePaths.includes(routePath) || (route.children && route.children.length >= 1) || whiteList.includes(routePath)) {
routerData.push(route)
}
})
return routerData
}
const filterAsyncRoutes = (menus: Array<MenuData>, routes: Array<RouteRecordRaw>) => {
// 生成要匹配的路由path数组
const routePaths = generateRoutePaths(menus)
// 生成匹配path的路由表
const routerList = generateRoutes(routes, routePaths)
return routerList
}
// 定义state类型
export interface IPermissionState {
routes: Array<RouteRecordRaw>;
accessRoutes: Array<RouteRecordRaw>;
}
// mutations类型
type IMutations = MutationTree<IPermissionState>
// actions类型
type IActions = ActionTree<IPermissionState, IRootState>
// 定义state
const state: IPermissionState = {
routes: [],
accessRoutes: []
}
// 定义mutation
const mutations: IMutations = {
SET_ROUTES(state, data: Array<RouteRecordRaw>) {
state.routes = data
},
SET_ACCESS_ROUTES(state, data: Array<RouteRecordRaw>) {
state.accessRoutes = data
}
}
// 定义actions
const actions: IActions = {
generateRoutes({ dispatch }, type?: number) { // 1 针对菜单排序更新
return new Promise((resolve, reject) => {
let accessedRoutes: Array<RouteRecordRaw> = []
if (store.getters.roleNames.includes('super_admin')) { // 超级管理员角色
accessedRoutes = asyncRoutes
dispatch('menu/getAllMenuListByAdmin', null, { root: true })
resolve(accessedRoutes)
} else { // 根据角色过滤菜单
const roles = store.getters.roleIds
dispatch('menu/getAccessByRoles', roles, { root: true }).then(menus => {
if (type !== 1) { // 菜单重新排序 不需要再过次滤路由
accessedRoutes = filterAsyncRoutes(menus, asyncRoutes)
}
resolve(accessedRoutes)
}).catch(reject)
}
})
}
}
// 定义user module
const permission: Module<IPermissionState, IRootState> = {
namespaced: true,
state,
mutations,
actions
}
export default permission