菜单权限
按钮和菜单权限介绍
用户绑定角色,角色绑定菜单权限和按钮权限
# 用户管理页面
# 创建用户,分配角色,编辑用户,删除用户
# 角色管理(岗位)页面
添加角色,删除角色,编辑角色,角色下发菜单权限设置和按钮的权限(增删改查)
# 菜单管理页面
添加菜单,删除菜单,编辑菜单,
权限管理业务
配置权限管理模块静态页面
# 配置路由
# 发送请求
# 获取数据
# 渲染页面
重构动态路由
src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router';
//静态路由:常量路由
export const staticRoutes: Array<RouteRecordRaw> = [
{
// 路径全是小写的
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
// 路由元数据
meta: {
hidden: true
}
},
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
meta: {
hidden: true
}
},
{
path: '/',
component: () => import('@/layout/index.vue'),
redirect: '/home',
children: [{
path: 'home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
icon: 'ele-HomeFilled',
}
}]
},
];
// 定义动态路由
export const asyncRoutes: Array<RouteRecordRaw> = [
// 商品管理模块 一级路由
{
path: '/product', // 一级路由展示 layout组件
component: () => import('@/layout/index.vue'),
name: 'Product',
meta: {
title: '商品管理',
icon: 'ele-Goods'
},
children: [
{
path: 'trademark',
component: () => import('@/views/product/trademark/index.vue'),
name: 'Trademark',
meta: {
title: '品牌管理',
icon: 'ele-Apple'
}
},
{
path: 'attr',
component: () => import('@/views/product/attr/index.vue'),
name: 'Attr',
meta: {
title: '属性管理',
icon: 'ele-IceTea'
}
},
{
path: 'spu',
component: () => import('@/views/product/spu/index.vue'),
name: 'Spu',
meta: {
title: 'SPU管理',
icon: 'ele-Burger'
}
},
{
path: 'sku',
component: () => import('@/views/product/sku/index.vue'),
name: 'Sku',
meta: {
title: 'SKU管理',
icon: 'ele-Goblet'
}
}
]
},
// 权限管理 一级路由
{
name: 'Acl',
path: '/acl',
component: () => import('@/layout/index.vue'),
redirect: '/acl/user/list',
meta: { title: '权限管理', icon: 'ele-Setting' },
children: [
{
name: 'User',
path: '/acl/user/list',
component: () => import('@/views/acl/user/index.vue'),
meta: { title: '用户管理' }
},
{
name: 'Role',
path: '/acl/role/list',
component: () => import('@/views/acl/role/index.vue'),
meta: {
title: '角色管理',
},
},
{
name: 'RoleAuth',
path: '/acl/role/auth',
component: () => import('@/views/acl/role/roleAuth.vue'),
meta: {
title: '角色管理',
hidden: true,
activeMenu: '/acl/role/list',
},
},
{
name: 'Permission',
path: '/acl/permission/list',
component: () => import('@/views/acl/permission/index.vue'),
meta: {
title: '菜单管理',
}
}
]
},
];
// 任意路由
export const anyRoute = [
/* 匹配任意的路由 必须最后注册 */
{
path: '/:pathMatch(.*)',
name: 'Any',
redirect: '/404',
meta: {
hidden: true
}
}
]
用户管理页面
封装接口请求
src/api/acl/user.ts
import request from '@/utils/request'
import type { UserObj, UserPageArr } from './type/user'
import type { UserRolesArr } from './type/role'
// 定义请求地址的枚举
enum Api {
GetUserList = '/admin/acl/user', // 获取用户列表的接口地址
RemoveUser = '/admin/acl/user/remove', // 移除用户的接口地址
RemoveUsers = '/admin/acl/user/batchRemove', // 批量移除用户的接口地址
SaveUser = '/admin/acl/user/save', // 保存用户的接口地址
UpdateUser = '/admin/acl/user/update', // 更新用户的接口地址
GetUserRoleList = '/admin/acl/user/toAssign', // 获取用户角色列表的接口地址
AssignUserRoleList = '/admin/acl/user/doAssign', // 设置用户角色的接口地址
}
// 获取用户列表
export const getUserListApi = (page: number, limit: number, searchParams: object) => {
return request.get<any, UserPageArr>(Api.GetUserList + `/${page}/${limit}`, {
params: searchParams
})
}
// 编辑和添加用户
export const saveOrUpdateUserApi = (user: UserObj) => {
if (user.id) {
return request.put<any, null>(Api.UpdateUser, user);
}else{
return request.post<any, null>(Api.SaveUser, user);
}
}
// 删除用户(单个)
export const removeUserApi = (id: string) => {
return request.delete<any, null>(Api.RemoveUser + `/${id}`);
};
// 删除用户(批量)
export const removeUsersApi = (ids: string[]) => {
return request.delete<any, null>(Api.RemoveUsers, {
data: ids
})
}
// 获取用户角色
export const getUserRoleListApi = (userId: string) => {
return request.get<any, UserRolesArr>(Api.GetUserRoleList + `/${userId}`);
};
/**
* 给用户分配角色
* @param userId 用户id
* @param roleId 包含所有角色id的字符串 3,5,6
* @returns
*/
export const assignUserRoleListApi = (userId: string, roleId: string) => {
return request.post<any, void>(Api.AssignUserRoleList, null, {
params: {
userId,
roleId
}
});
};
封装接口类型
src/api/acl/type/user.ts
/*******************************************************************/
//登录接口需要携带请求体参数-对象
export interface loginData {
username: string,
password: string
}
//登录接口返回值的类型
export interface loginResponseData {
token: string
}
//用户信息接口返回数据类型
export interface userInfoResponseData {
name:string,
avatar:string,
roles:string[],
buttons:string[],
routes:string[]
}
/*******************************************************************/
export interface UserObj {
id?: string; // 用户id
roleName?: string[]; // 用户的角色数组名字
username: string; // 用户名字
nickName: string; // 昵称
password: string; // 密码
}
// 管理员用户列表
export type UserArr = UserObj[];
// 管理员用户分页列表
export interface UserPageArr {
items: UserArr; // 用户列表数据
total: number; // 总条数
}
数据渲染(增删改查)
src/views/acl/user/index.vue
<template>
<el-card>
<el-form inline>
<el-form-item>
<el-input placeholder="用户名" v-model="paramsState.username"/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="getUsers()">查询</el-button>
<el-button type="default" @click="resetInp">清空</el-button>
</el-form-item>
</el-form>
<div style="margin-bottom: 10px;">
<el-button type="primary" @click="showAddUser">添 加</el-button>
<el-button type="danger" @click="removeUsersBat" :disabled="tableState.selectedIds.length===0">批量删除</el-button>
</div>
<el-table border stripe :data="tableState.users" v-loading="listLoading" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="username" label="用户名" width="150" />
<el-table-column prop="nickName" label="用户昵称" />
<el-table-column prop="roleName" label="角色列表" width="200" />
<el-table-column prop="gmtCreate" label="创建时间" width="180" />
<el-table-column prop="gmtModified" label="更新时间" width="180" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template v-slot="{row}">
<el-button type="info" size="small" :icon="UserFilled" title="分配角色" @click="showAssignRole(row)"/>
<el-button type="primary" size="small" :icon="Edit" title="修改用户" @click="showEditUser(row)" />
<el-popconfirm :title="`确定删除 ${row.username} 吗?`" @confirm="removeUser(row.id)">
<template #reference>
<el-button style="margin-left:10px" type="danger" size="small" :icon="Delete" title="删除用户" />
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="paramsState.page"
v-model:page-size="paramsState.limit"
:page-sizes="[3, 5, 7]"
style="margin-top: 10px;"
background
layout="prev, pager, next, jumper,->,sizes,total"
:total="tableState.total"
@current-change="getUsers"
@size-change="SizeChange"
/>
<!-- 添加用户 -->
<el-dialog v-model="userState.dialogUserVisible"
:title="userState.user?.id ? '修改用户' : '添加用户'">
<el-form ref="userFormRef"
label-width="120px"
:model="userState.user"
:rules="userRules">
<el-form-item label="用户名" prop="username">
<el-input v-model="userState.user.username"/>
</el-form-item>
<el-form-item label="用户昵称">
<el-input v-model="userState.user.nickName"/>
</el-form-item>
<el-form-item label="用户密码" v-if="!userState.user.id" prop="password">
<el-input v-model="userState.user.password"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelUser">取 消</el-button>
<el-button :loading="userState.userLoading" @click="addOrUpdate" type="primary" >确 定</el-button>
</div>
</template>
</el-dialog>
<!-- 设置角色 -->
<el-dialog title="设置角色" v-model="roleState.dialogRoleVisible" :before-close="resetRoleData">
<el-form label-width="80px">
<el-form-item label="用户名">
<el-input disabled :value="userState.user.username"></el-input>
</el-form-item>
<el-form-item label="角色列表">
<el-checkbox
:indeterminate="roleState.isIndeterminate"
@change="handleCheckAllChange"
v-model="roleState.checkAll">全选</el-checkbox>
<div style="margin:15px 0;"></div>
<el-checkbox-group v-model="roleState.userRoleIds" @change="handleCheckedChange">
<el-checkbox v-for="role in roleState.allRoles" :key="role.id" :label="role.id" :value="role.id" >{{role.roleName}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<template #footer>
<el-button :loading="roleState.roleLoading" type="primary" @click="assignRole">保存</el-button>
<el-button @click="resetRoleData">取消</el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang='ts'>
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus'
import { Search, Edit, UserFilled, Delete } from '@element-plus/icons-vue'
import { reactive, ref, onMounted, nextTick } from 'vue'
import { assignUserRoleListApi, getUserListApi, getUserRoleListApi, removeUserApi, removeUsersApi, saveOrUpdateUserApi } from '@/api/acl/user';
import type { UserArr, UserObj } from '@/api/acl/type/user';
import type { AllRolesArr } from '@/api/acl/type/role';
const userFormRef = ref()
const listLoading = ref(false) // 是否显示列表加载的提示
// 定义请求体数据
const paramsState = reactive({
username: '',
page: 1, // 当前页码
limit: 5 // 每页数量
})
// TableState类型
interface TableState {
total: number
users: UserArr
selectedIds: string[]
}
// TableState数据
const tableState = reactive<TableState>({
total: 0,
users: [],
selectedIds: []
})
// 用户表单类型
interface UserState {
dialogUserVisible: boolean
userLoading: boolean
user: UserObj
}
const userState = reactive<UserState>({
dialogUserVisible: false,
userLoading: false,
user: {
username: '',
nickName: '',
password: ''
}
})
// 用户校验规则
const validatePassword = (rule: any, value: any, callback: any) => {
if (!value) {
callback('密码必须输入')
} else if (!value || value.length < 6) {
callback('密码不能小于6位')
} else {
callback()
}
}
const userRules = {
username: [
{ required: true, message: '用户名必须输入' },
{ min: 4, message: '用户名不能小于4位' }
],
password: [{ required: true, validator: validatePassword }]
}
interface RoleState {
dialogRoleVisible?: boolean
roleLoading: boolean
checkAll: boolean
isIndeterminate: boolean
userRoleIds: string[]
allRoles: AllRolesArr
}
const roleState = reactive<RoleState>({
dialogRoleVisible: false,
roleLoading: false,
checkAll: false,
isIndeterminate: false,
userRoleIds: [],
allRoles: []
})
// 生命周期
onMounted(() => {
// 调用获取用户方法
getUsers()
})
// 定义搜索的方法
const getUsers = async (page = paramsState.page, limit = paramsState.limit) => {
// 修改分页数据
paramsState.page = page
paramsState.limit = limit
// 设置loading状态
listLoading.value = true
// 发送请求
const result = await getUserListApi(page, limit, {
username: paramsState.username
})
// loading为false
listLoading.value = false
// 设置表格数据
const { items, total } = result
tableState.users = items
tableState.total = total
tableState.selectedIds = []
}
// 分页变化
const SizeChange = (val:number)=>{
paramsState.limit = val;
getUsers();
}
// 清空搜索input
const resetInp = ()=>{
paramsState.username = ''
getUsers()
}
// 添加用户
const showAddUser = ()=>{
// 初始化数据
resetUserList()
// 显示对话框
userState.dialogUserVisible = true
// 清除校验规则结果
nextTick(() => userFormRef.value?.clearValidate())
}
// 编辑用户
const showEditUser = (row:UserObj) =>{
// 获取行对象中的数据并赋值
userState.user = { ...row }
// 显示对话框
userState.dialogUserVisible = true
}
// 提交按钮
const addOrUpdate = async ()=>{
// 进行表单校验
await userFormRef.value?.validate()
const { user } = userState
if (user.username === 'admin') {
ElMessage({
type: "error",
message: "admin账号不能添加或更新",
})
return
}
// 加载
userState.userLoading = true
try {
// 发送请求
await saveOrUpdateUserApi(user)
// 加载状态取消
userState.userLoading = false
// 提示消息
ElMessage({
type: "success",
message: user.id ? "更新成功!":"添加成功!",
})
// 更新停留在本页,否则回到第一页
getUsers(user.id ? paramsState.page : 1)
// 清空表单内容
resetUserList()
// 隐藏对话框
userState.dialogUserVisible = false
} catch (error) {
console.log(error)
userState.userLoading = false
}
}
// 取消按钮
const cancelUser = () =>{
userState.dialogUserVisible = false
resetUserList()
}
// 清空用户表单数据
const resetUserList = () =>{
userState.user = {
username: '',
nickName: '',
password: ''
}
}
// 删除用户(单个)
const removeUser = async (id:string) =>{
await removeUserApi(id)
ElMessage({
type: "success",
message: "删除成功",
})
// 获取用户数据,当用户只剩一个时
getUsers(
tableState.users.length === 1 ? paramsState.page - 1 : paramsState.page
)
}
// 用户复选框勾选
const handleSelectionChange = (selection: any[]) => {
// 由id作为数组元素的 列表
tableState.selectedIds = selection.map((item) => item.id)
}
// 批量删除()
const removeUsersBat = ()=>{
ElMessageBox.confirm('确定删除吗?').then( async () => {
await removeUsersApi(tableState.selectedIds)
ElMessage.success('用户删除成功!')
getUsers()
}).catch(() => {
ElMessage.info('取消删除')
})
}
// 获取角色列表
const getRoles = async() =>{
// 从当前用户重解构数据id 发请求
const result = await getUserRoleListApi(userState.user?.id as string)
// 解构
const { allRolesList, assignRoles } = result
// 修改数据
roleState.allRoles = allRolesList
roleState.userRoleIds = assignRoles.map((item) => item.id as string)
// 设置全选和 未全选标记
roleState.checkAll = allRolesList.length === assignRoles.length
roleState.isIndeterminate =
assignRoles.length > 0 && assignRoles.length < allRolesList.length
}
// 展示分配角色
const showAssignRole =(row:UserObj) => {
// 存储当前选中用户
userState.user = row
// 显示角色对话框
roleState.dialogRoleVisible = true
// 获取角色
getRoles()
}
// 关闭前钩子
const resetRoleData = () =>{
// 重置
Object.assign(roleState, {
dialogRoleVisible: false,
allRoles: [],
userRoleIds: [],
isIndeterminate: false,
checkAll: false
})
}
// 全选钩子函数
const handleCheckAllChange = (value: string | number | boolean)=>{
// value 当前勾选状态true/false
// 如果当前全选, userRoleIds就是所有角色id的数组, 否则是空数组
if(value){
roleState.userRoleIds = roleState.allRoles.map((item) => item.id as string)
}else{
roleState.userRoleIds = []
}
// 不确定状态 设为false
roleState.isIndeterminate = false
}
// 改变勾选钩子
const handleCheckedChange =()=>{
// 解构用户角色ids
const { userRoleIds, allRoles } = roleState
// 设置全选标记
roleState.checkAll =
userRoleIds.length === allRoles.length && allRoles.length > 0
// 设置不确定标记
roleState.isIndeterminate =
userRoleIds.length > 0 && userRoleIds.length < allRoles.length
}
// 提交角色分配
const assignRole = async () =>{
// 从当前用户中解构出用户id
const userId = userState.user?.id
// 在用户 角色数组中,将数组转为字符串
const roleIds = roleState.userRoleIds.join(',')
// 加载
roleState.roleLoading = true
// 发请求
await assignUserRoleListApi(userId as string, roleIds)
// 加载完成
roleState.roleLoading = false
// 提示
ElMessage({
type: "success",
message: "角色分配成功",
})
// 清空数据返回
resetRoleData()
}
</script>
<script lang="ts">
export default {
name: 'User'
}
</script>
<style scoped lang="less">
</style>
角色管理页面
封装接口请求 src/api/role.ts
import request from '@/utils/request';
import type { Role, RolePageResponseData } from './type/role';
// 定义请求地址的枚举
enum Api {
GetRoleList = '/admin/acl/role', // 获取角色列表数据
RemoveRole = '/admin/acl/role/remove', // 移除角色
RemoveRoles = '/admin/acl/role/batchRemove', // 批量移除角色
SaveRole = '/admin/acl/role/save', // 保存角色
UpdateRole = '/admin/acl/role/update', // 删除角色
AssignRole = '/admin/acl/permission/doAssign', // 授权角色
GetAssignRole = '/admin/acl/permission/toAssign', // 获取角色列表
}
// 获取角色分页列表(带搜索)
export const getRoleListApi = (page: number, limit: number, roleName: string) => {
return request.get<any, RolePageResponseData>(Api.GetRoleList + `/${page}/${limit}`, {
params: {
roleName
}
})
}
/**
* 保存一个新角色
* @param role 角色对象
* @returns null
*/
export const saveRoleApi = (role: Role) => {
return request.post<any, null>(Api.SaveRole, role);
}
/**
* 获取一个角色的所有权限列表
* @param roleId 角色id
* @returns PermissionModel
*/
export const getAssignRoleApi = (roleId: string) => {
return request.get<any, any>( Api.GetAssignRole + `/${roleId}`);
};
/**
* 给某个角色授权
* @param roleId 角色ID
* @param permissionId 多个权限id组成的字符串 2,3,4
* @returns null
*/
export const assignRoleApi = (roleId: string, permissionId: string) => {
return request.post<any, null>(Api.AssignRole, null, {
params: {
roleId,
permissionId
},
});
};
/**
* 更新一个角色
* @param role 角色对象
* @returns null
*/
export const updateRoleApi = (role: Role) => {
return request.put<any, null>( Api.UpdateRole, role);
};
/**
* 删除某个角色
* @param id 角色id
* @returns null
*/
export const removeRoleApi = (id: string) => {
return request.delete<any, null>( Api.RemoveRole + `/${id}`);
};
/**
* 批量删除多个角色
* @param ids 角色id的数组
* @returns null
*/
export const removeRolesApi = (ids: string[]) => {
return request.delete<any, null>(Api.RemoveRoles, {
data: ids
})
}
封装接口类型
角色类型 src/api/type/role.ts
/********角色*********/
export interface Role{
id?: string; // id
roleName: string; // 角色名字
originRoleName?: string // 页面是否需要的标识
edit?: boolean // 是否可进行编译
}
// 角色列表
export type AllRolesArr = Role[]
// 角色分页
export interface RolePageResponseData{
items: AllRolesArr
total: number
}
// 某个用户的角色列表
export interface UserRolesArr{
// 所有角色列表
allRolesList: AllRolesArr;
// 分配的角色列表
assignRoles: AllRolesArr;
}
权限类型 src/api/type/permission.ts
import request from '@/utils/request';
import type { PermissionListResponseModel, PermissionModel } from './type/permission';
// 定义请求地址的枚举
enum Api {
GetPermissionList = '/admin/acl/permission', // 获取权限列表数据
RemovePermission = '/admin/acl/permission/remove', // 移除
SavePermission = '/admin/acl/permission/save', // 添加
UpdatePermission = '/admin/acl/permission/update', // 更新
}
/**
* 获取权限列表
* @returns PermissionListResponseModel
*/
export const getPermissionListApi = () => {
return request.get<any, PermissionListResponseModel>(Api.GetPermissionList);
};
/**
* 删除权限
* @param id 权限id
* @returns null
*/
export const removePermissionApi = (id: string) => {
return request.delete<any, null>(Api.RemovePermission + `/${id}`);
};
/**
* 添加或更新权限
* @param permission 权限对象
* @returns null
*/
export const saveOrUpdatePermissionApi = (permission: PermissionModel) => {
return request.put<any, null>(permission.id ? Api.UpdatePermission : Api.SavePermission, permission);
};
数据渲染(增删改查) src/views/acl/role/index.vue
<template>
<el-card>
<el-form inline>
<el-form-item>
<el-input placeholder="角色名称" v-model="roleNames.roleName"/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="getRoles()">查询</el-button>
<el-button @click="resetSearch">清空</el-button>
</el-form-item>
</el-form>
<div style="margin-bottom: 10px">
<el-button type="primary" @click="addRole">添加</el-button>
<el-button type="danger" @click="removeRoles" :disabled="selectedRoles.length === 0">批量删除</el-button>
</div>
<el-table border stripe style="width: 960px" v-loading="listLoading" :data="rolesList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
<el-table-column label="角色名称">
<template v-slot="{row}">
<template v-if="row.edit">
<el-input v-model="row.roleName" class="edit-input" size="small" @blur="toLook(row)" ref="inputRef" />
<el-button class="cancel-btn" size="small" :icon="Refresh" type="warning" @click="cancelEdit(row)">
取 消
</el-button>
</template>
<span v-else>{{ row.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<template v-slot="{row}">
<el-button size="small" type="info" :icon="InfoFilled" title="分配权限"
@click="$router.push(`/acl/role/auth?id=${row.id}&roleName=${row.roleName}`)"
/>
<el-button size="small" type="primary" :icon="Check" title="确定" @click="updateRole(row)" />
<el-button size="small" type="primary" :icon="Edit" title="修改角色" @click="EditRule(row)"/>
<el-button size="small" type="danger" :icon="Delete" title="删除角色" @click="removeRole(row)"/>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
v-model:current-page="page"
v-model:page-size="limit"
:page-sizes="[3, 5, 7]"
style="margin-top: 10px;"
background
layout="prev, pager, next, jumper,->,sizes,total"
:total="total"
@current-change="getRoles"
@size-change="SizeChange"
/>
</el-card>
</template>
<script lang="ts">
export default {
name: 'Role'
}
</script>
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { Search,Edit,Delete,Refresh,InfoFilled,Check} from '@element-plus/icons-vue'
import type { AllRolesArr } from '@/api/acl/type/role';
import { getRoleListApi, removeRoleApi, removeRolesApi, saveRoleApi,updateRoleApi } from '@/api/acl/role';
import { ElMessage, ElMessageBox } from 'element-plus';
const selectedRoles = ref<AllRolesArr>([]) // 所有选中的角色列表
const listLoading = ref(false) // 数据是否正在加载
const rolesList = ref<AllRolesArr>([]) // 角色列表
const total = ref(0) // 总记录数
const page = ref(1) // 当前页码
const limit = ref(5) // 每页记录数
const inputRef = ref() // input输入框对象
const roleNames = reactive({
// 用户搜索的用户名
roleName: '',
searchRoleName: ''
})
// 获取角色分页列表
const getRoles = (p = page.value, size = limit.value) => {
// 设置页码,每页显示
page.value = p
limit.value = size
// 加载
listLoading.value = true
// 发请求
getRoleListApi(p, size, roleNames.roleName)
.then((result) => {
// 存储角色列表数据
rolesList.value = result.items.map((item) => {
item.edit = false // 用于标识是否显示编辑输入框的属性
item.originRoleName = item.roleName // 缓存角色名称, 用于取消
return item
})
// 存储总数
total.value = result.total
})
.finally(() => {
// 取消加载
listLoading.value = false
})
}
// 重置搜索内容
const resetSearch = () => {
roleNames.roleName = ''
getRoles()
}
// 分页变化
const SizeChange = (val:number)=>{
limit.value = val;
getRoles();
}
// 添加角色
const addRole = () =>{
// 显示添加界面
ElMessageBox.prompt('请输入新名称', '添加角色', {
confirmButtonText: '确定',
cancelButtonText: '取消'
})
.then(({ value }) => {
saveRoleApi({ roleName: value }).then(() => {
ElMessage.success('添加角色成功')
getRoles()
}).catch(()=>{
ElMessage.success('添加角色失败')
})
})
.catch(() => {
ElMessage.warning('取消添加')
})
}
/*
删除指定的角色
*/
const removeRole = (role: any) => {
ElMessageBox.confirm( `确定删除 '${role.roleName}' 吗?`, '提示', {
type: 'warning'
})
.then(async () => {
// 发送删除角色请求
await removeRoleApi(role.id as string)
// 重新获取数据,如果本页只有一个 回到前一页
getRoles(rolesList.value.length === 1 ? page.value - 1 : page.value)
ElMessage.success('删除成功!')
})
.catch(() => {
ElMessage.info('已取消删除')
})
}
// 更新角色
const updateRole = (role: any) => {
updateRoleApi(role).then(() => {
ElMessage.success('更新角色成功!')
getRoles()
})
}
// 批量删除
const removeRoles = () => {
ElMessageBox.confirm('此操作将永久删除该记录, 是否继续?', '提示', {
type: 'warning'
})
.then(async () => {
// 获取id数组
const ids = selectedRoles.value.map((role) => role.id as string)
// 发送请求
await removeRolesApi(ids)
// 重新获取数据,如果本页只有一个 回到前一页
getRoles(rolesList.value.length === 1 ? page.value - 1 : page.value)
// 提示信息
ElMessage({
type: 'success',
message: '批量删除成功!'
})
})
.catch((error) => {
if (error === 'cancel') {
ElMessage({
type: 'info',
message: '已取消删除'
})
}
})
}
// table勾选变化
const handleSelectionChange = (selection: AllRolesArr) =>{
selectedRoles.value = selection
}
// 取消编辑
const cancelEdit = (row:any) =>{
// 取出之前存的
row.roleName = row.originRoleName as string
// 编辑标记为false
row.edit = false
// 提示
ElMessage.warning('取消角色修改')
}
// 编辑
const EditRule = (row:any) =>{
// 设置编辑标记
row.edit= true
// 获取光标
nextTick(() => {
inputRef.value.focus();
});
}
// 确认编辑
const toLook = (row:any) =>{
row.edit = false
}
onMounted(() => {
getRoles()
})
</script>
编辑角色列表 src/views/acl/role/roleAuth.vue
<template>
<el-card>
<el-input disabled :value="$route.query.roleName"></el-input>
<el-tree
style="margin: 20px 0"
ref="treeRef"
:data="allPermissions"
node-key="id"
show-checkbox
default-expand-all
:props="defaultProps"
v-loading="listLoading"
/>
<el-button :loading="loading" type="primary" @click="save">保存</el-button>
<el-button @click="cancel">取消</el-button>
</el-card>
</template>
<script lang="ts">
export default {
name: 'roleAuth'
}
</script>
<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router'
import {ElMessage } from 'element-plus'
import { ref, onMounted } from 'vue'
import { getAssignRoleApi, assignRoleApi } from '@/api/acl/role'
import { useUserInfoStore } from '@/stores/userInfo'
// 用户数据仓库
const userInfoStore = useUserInfoStore()
// 路由器对象
const router = useRouter()
// 路由配置对象
const route = useRoute()
// 加载标记
const loading = ref(false)
// 完整的权限
const allPermissions = ref<PermissionListModel>([])
// 树形结构对象
const treeRef = ref<InstanceType<typeof ElTree>>()
// 设置属性结构别名
const defaultProps = {
children: 'children',
label: 'name'
}
// 设置loading状态
const listLoading = ref<boolean>(false)
// 设置勾选数组状态
const checkedIds = ref<any>()
onMounted(() => init())
/*
初始化
*/
const init = () => {
// 获取路由地址中的id
const roleId = route.query.id
listLoading.value = true
// 调用获取权限的方法
getPermissions(roleId as string)
listLoading.value = false
}
/*
获取指定角色的权限列表
*/
const getPermissions = (roleId: string) => {
// 发送请求
getAssignRoleApi(roleId).then((result) => {
// 存储服务器数据
allPermissions.value = result.children as PermissionListModel
// 存储勾选数组
checkedIds.value = getCheckedIds(allPermissions.value)
// console.log('getPermissions() checkedIds', checkedIds)
// 设置勾选状态
treeRef.value?.setCheckedKeys(checkedIds.value)
})
}
/*
得到所有选中的id列表
只用得到所有选中的按钮权限数据的id
*/
const getCheckedIds = (auths: PermissionListModel, initArr: string[] = []) => {
// 遍历权限列表数组
auths.forEach((item) => {
// 判断如果元素存在 select标记 并且 等级是4
if (item.select && item.level === 4) {
// 在新数组中添加元素
initArr.push(item.id as string)
} else if (item.children) { // 如果存在孩子
// 递归调用
getCheckedIds(item.children, initArr)
}
})
// 返回新数组
return initArr
}
/*
保存权限列表
*/
const save = () => {
// 得到所有全选的id
const checkedIds = treeRef.value?.getCheckedKeys()
// console.log("选中状态的数据",checkedIds)
// 得到所有半选的id
const halfCheckedIds = treeRef.value?.getHalfCheckedKeys()
// console.log("半选状态的数据",halfCheckedIds)
// 合并全选和半选的id, 并用逗号连接成串
var ids = checkedIds?.concat(halfCheckedIds as []).join(',')
loading.value = true
// 发送请求
assignRoleApi(route.query.id as string, ids as string).then(async () => {
loading.value = false
ElMessage.success('分配权限成功')
// 跳转到角色列表页面
await router.replace('/acl/role/list')
// 跳转完成后, 如果分配的是当前用户角色的权限, 刷新一下浏览器
const roleName = route.query.roleName
// if (userInfoStore.roles.includes(roleName as string)) {
// window.location.reload()
// }
})
}
/*
取消
*/
const cancel = () => {
// 跳转到角色列表
router.replace('/acl/role/list')
}
</script>
菜单管理页面
封装接口请求 src/api/acl/permission.ts
import request from '@/utils/request';
import type { PermissionListResponseModel, PermissionModel } from './type/permission';
// 定义请求地址的枚举
enum Api {
GetPermissionList = '/admin/acl/permission', // 获取权限列表数据
RemovePermission = '/admin/acl/permission/remove', // 移除
SavePermission = '/admin/acl/permission/save', // 添加
UpdatePermission = '/admin/acl/permission/update', // 更新
}
/**
* 获取权限列表
* @returns PermissionListResponseModel
*/
export const getPermissionListApi = () => {
return request.get<any, PermissionListResponseModel>(Api.GetPermissionList);
};
/**
* 删除权限
* @param id 权限id
* @returns null
*/
export const removePermissionApi = (id: string) => {
return request.delete<any, null>(Api.RemovePermission + `/${id}`);
};
/**
* 添加或更新权限
* @param permission 权限对象
* @returns null
*/
export const saveOrUpdatePermissionApi = (permission: PermissionModel) => {
return request.put<any, null>(permission.id ? Api.UpdatePermission : Api.SavePermission, permission);
};
封装接口类型: src/api/acl/type/permission.ts
/*
权限数据
*/
export interface PermissionModel {
id?: string;
pid?: string; // 上一级id
code?: string; // 按钮id
select?: boolean; // 是否勾选
toCode?: string; //
children?: PermissionListModel; // 是否是菜单
name: string; // 名字
level: PermissionLevelModel; // 等级
type: 1 | 2; // 类型
}
// 1. 树行表格
// * table展示数据menuPermissionList, 每一行是一个row, 需要数据有 row 中包含 children
// * 需要给 el-table 组件设置 row-key 属性, row-key属性的作用作为表格中数据的唯一标识
// row-key="id" ----> 将 row(每行展示的数据)中的字段 id 作为这一行数据的唯一标识
// * expand-row-keys 设置表格默认展开的是哪一行
// expand-row-keys 这个的属性值是一个数组,数字中放需要展开行的 row-key 标识(唯一标识)
// expand-row-keys="menuPermissionList[0].id"
// 2. 保存数据
// level 字段 代表的是等级的意思
// level 支持 1, 2, 3, 4
// 1, 2, 3 代表的都是菜单,此时type值应该是1
// 4 代表的是按钮,此时type值应该是2
// type值是1,代表的是菜单,最后拥有这个权限的时候,数据会展示在当前用户返回的个人信息的 routes 中
// type值是2,代表的是按钮,最后拥有这个权限的时候,数据会展示在当前用户返回的个人信息的 buttons 中
// 权限等级
export type PermissionLevelModel = 0 | 1 | 2 | 3 | 4
// 权限列表
export type PermissionListModel = PermissionModel[]
// 权限列表接口返回的数据
export interface PermissionListResponseModel {
children: PermissionListModel;
}
数据渲染(增删改查) src/views/acl/menu/index.vue
<template>
<el-card>
<!-- 表格 -->
<el-table border style="margin-bottom: 20px;" :data="menuPermissionList" :expand-row-keys="expandKeys" row-key="id" v-loading="listLoading">
<el-table-column prop="name" label="名称" />
<el-table-column prop="code" label="权限值" />
<el-table-column prop="toCode" label="跳转权限值" />
<el-table-column label="操作">
<template v-slot="{row}">
<el-button :disabled="row.level===4" type="primary" :icon="Plus" size="small" @click="toAddPermission(row)" :title="getAddTitle(row.level)" />
<el-button type="primary" :icon="Edit" size="small" :disabled="row.level===1" @click="toUpdatePermission(row)" :title="row.level===4 ? '修改功能' : '修改菜单'" />
<el-button :disabled="row.level===1" type="danger" :icon="Delete" size="small" @click="removePermission(row)" title="删除" />
</template>
</el-table-column>
</el-table>
<!-- 对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" @close="resetData">
<el-form ref="permissionRef" :model="permission" :rules="permissionRules" label-width="120px">
<el-form-item label="名称" prop="name">
<el-input v-model="permission.name" />
</el-form-item>
<el-form-item label="功能权限值" prop="code">
<el-input v-model="permission.code" />
</el-form-item>
<el-form-item label="跳转路由权限值" prop="toCode" v-if="permission.level===4">
<el-input v-model="permission.toCode" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetData">取 消</el-button>
<el-button type="primary" @click="addOrUpdatePermission">确 定</el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang='ts'>
import { ElMessage, ElMessageBox } from "element-plus";
import {Plus, Edit, Delete} from '@element-plus/icons-vue'
import { reactive, ref, computed, onMounted, nextTick } from "vue";
import type { PermissionListModel, PermissionModel, PermissionLevelModel } from "@/api/acl/type/permission";
import { getPermissionListApi, removePermissionApi, saveOrUpdatePermissionApi } from "@/api/acl/permission";
import cloneDeep from 'lodash/cloneDeep'
// 权限设置表单对象
const permissionRef = ref<FormInstance>()
// 菜单权限列表
const menuPermissionList = ref<PermissionListModel>([])
// 展开的值
const expandKeys = ref<string[]>([])
// 对话框的显示与隐藏
const dialogVisible = ref<boolean>(false)
// 权限对象 默认值
const permission = reactive<PermissionModel>({ // 要操作的菜单权限对象
type: 1,
level: 0, // 菜单级别
name: '', // 菜单名字
code: '', // 菜单按钮
toCode: '' // 菜单
})
// 加载状态
const listLoading = ref<boolean>(false)
onMounted(() => {
fetchPermissionList()
})
/*
动态计算得到Dialog的标题
*/
const dialogTitle = computed(() => {
const {id, level} = permission
if (id) {
return level===4 ? '修改功能' : '修改菜单'
} else {
return level===4 ? '添加功能' : '添加菜单'
}
})
/*
根据权限的等级来计算确定校验规则
*/
const permissionRules = computed(() => {
// 菜单权限校验的规则
const menuRules = {
name: [{required: true, message: '名称必须输入'}],
code: [{required: true, message: '权限值必须输入'}],
}
// 按钮功能权限校验的规则
const btnRules = {
name: [{required: true, message: '名称必须输入'}],
code: [{required: true, trigger: 'blur', message: '功能权限值必须输入'}]
}
return permission.level===4 ? btnRules : menuRules
})
/*
根据级别得到要显示的添加dialog的标题
*/
const getAddTitle = (level: number) => {
if (level===1 || level===2) {
return '添加菜单'
} else if (level===3){
return '添加功能'
}
}
/*
请求获取权限菜单数据列表
*/
const fetchPermissionList = async () => {
// 设置状态
listLoading.value = true
// 发送请求
const result = await getPermissionListApi()
// 存储数据
menuPermissionList.value = result.children
listLoading.value = false
// 默认展开行 展开第一行
expandKeys.value = [menuPermissionList.value[0].id as string]
}
/*
显示添加权限的界面(菜单或功能)
*/
const toAddPermission = (row: PermissionModel) => {
// 打开添加对话框
dialogVisible.value = true
permission.id = ''
// 设置pid为当前行对象id
permission.pid = row.id // pid ==> parentId 父权限数据的id
// 设置权限对象 等级为 顺等加1
permission.level = (row.level + 1) as PermissionLevelModel
// 设置权限类型
permission.type = permission.level===4 ? 2 : 1
// 清除校验(必须在界面更新之后)
// 注意: 校验规则是动态的, nextTick清除后还会显示错误信息, 应该是在nextTick后又进行了校验
// nextTick(() => {
// permissionRef.value?.clearValidate()
// })
setTimeout(() => {
permissionRef.value?.clearValidate()
}, 0);
}
/*
显示菜单添加或更新的dialog
*/
const toUpdatePermission = (row: PermissionModel) => {
// 设置对话框显示
dialogVisible.value = true
// 把行对象内容显示
Object.assign(permission, row)
// 设置permission的类型
permission.type = permission.level===4 ? 2 : 1
// 清除校验(必须在界面更新之后)
// nextTick(() => permissionRef.value?.clearValidate())
// 注意: 校验规则是动态的, nextTick清除后还会显示错误信息, 应该是在nextTick后又进行了校验
setTimeout(() => {
permissionRef.value?.clearValidate()
}, 0);
}
/*
删除某个权限节点
*/
const removePermission = (permission: PermissionModel) => {
ElMessageBox.confirm('此操作将永久删除该记录, 是否继续?', '提示', {
type: 'warning'
}).then(async () => {
// 发送删除请求
await removePermissionApi(permission.id as string)
ElMessage.success('删除成功!')
// 重新获取数据
fetchPermissionList()
}).catch((error) => {
// 提示错误
if (error==='cancel') {
ElMessage({
type: 'info',
message: '已取消删除'
})
}
})
}
/*
添加或更新功能权限
*/
const addOrUpdatePermission = () => {
// 校验表单数据
permissionRef.value?.validate(async valid => { // 校验的回调函数
if (valid) {
// 解构出id
const { id } = permission
// console.log("valid",valid); // 通过校验返回 true
// 发送请求
await saveOrUpdatePermissionApi(permission)
// 提示信息
ElMessage.success(`${id ? '修改' : '添加'}成功!`)
// 重置数据
resetData()
// 重新获取请求
fetchPermissionList()
}
})
}
/*
重置数据
*/
const resetData = () => {
// 关闭对话框
dialogVisible.value = false
// 合并属性
Object.assign(permission,cloneDeep({
id:null,
type: 1,
level: 0,
name: '',
code: '',
toCode: ''
}))
}
</script>
<script lang="ts">
export default {
name: 'Permission'
}
</script>
<style scoped lang="less">
</style>
动态校验清除校验
/*
根据权限的等级来计算确定校验规则
*/
const permissionRules = computed(() => {
// 菜单权限校验的规则
const menuRules = {
name: [{required: true, message: '名称必须输入'}],
code: [{required: true, message: '权限值必须输入'}],
}
// 按钮功能权限校验的规则
const btnRules = {
name: [{required: true, message: '名称必须输入'}],
code: [{required: true, trigger: 'blur', message: '功能权限值必须输入'}]
}
return permission.level===4 ? btnRules : menuRules
})
// 清除校验(必须在界面更新之后)
// 注意: 校验规则是动态的, nextTick清除后还会显示错误信息, 应该是在nextTick后又进行了校验
// nextTick(() => {
// permissionRef.value?.clearValidate()
// })
// 使用 定时器异步 清除校验
setTimeout(() => {
permissionRef.value?.clearValidate()
}, 0);
按钮权限控制
用v-if不要用v-show(有风险)
按钮的权限:有的账号有,有的人没有(没有权限)
控制按钮的权限是根据用户的信息来控制的
用户信息中(在用户数据仓库中的登陆方法中获得),包含了用户名,用户头像,角色,按钮(数据是一个数组["btn.Category.add",""]),路由(数据是一个数组["",""])。
数组中有这个属性标识才展示,没有则不展示
把按钮数组数据存放在用户仓库中(在state添加buttons属性)
然后在每个按钮上设置v-if判断显示与隐藏
在页面中引入小仓库,然后在按钮上设置 v-if="userStore.buttons.includes('btn.Trademark.add')"
方式一:使用v-if设置按钮权限
在按钮上通过 v-if 设置是否渲染按钮
方式二:使用自定义指令全局设置按钮权限(优点在于不需要在每个页面引入用户仓库数据)
# 在 src/utils/文件夹中新增 directive.ts 文件
# 在用户信息仓库中 存储 按钮数组
# 在按钮上通过 v-自定义 设置是否渲染按钮
项目代码:(方式二)
修改用户仓库中的用户数据类型 添加按钮类型
src/store/interface/index.ts
import type { RouteRecordRaw } from "vue-router";
// 用户信息包括权限数据
export interface UserInfoState {
token: string;
avatar: string;
name: string;
menuRoutes: RouteRecordRaw[] // 用于生成导航菜单的路由列表
buttons:string[]
}
定义自定义指令:src/utils/directive.ts
// 在非组件页面下使用小仓库,需要先引入大仓库
import pinia from "@/stores"
import {useUserInfoStore} from "@/stores/userInfo"
// 注入大仓库
const userStore = useUserInfoStore(pinia)
// 暴漏一个函数
//对外暴露一个函数
export const has = (app:any)=>{
//生成按钮权限全局自定义指令
//第一个参数:自定义指令的名字
app.directive("has",{
//当使用v-has这个全局自定义指令的DOM|组件挂载完毕的时候会立即执行一次
//element:使用这个指令的DOM元素
//options:能获取有自定义只有右侧数值 v-has="btn.Trademark.add"
mounted(element:any,options:any) {
//判断自定义指令右侧数值在仓库的buttons数组当中是否出现!!!
if(!userStore.buttons.includes(options.value)){
//获取当前绑定自定义指令DOM元素父组件,通过removeChild方法将当前元素删除!!!
element.parentNode.removeChild(element);
}
console.log("vue ele is mounted",options)
},
})
}
在用户仓库中存储 按钮 数组数据
src/store/userinfo.ts
// 引入定义小仓库方法
import { defineStore } from 'pinia';
// 本地存储操作token
import { getToken, removeToken, setToken } from '../utils/token-utils';
// state类型的数据类型
import type { UserInfoState } from './interface';
// 消息提示
import { ElMessage } from 'element-plus'
// 静态路由
import { staticRoutes } from '@/router/routes'
// 导入用户相关的API
import { getUserInfo, reqUserLogin, reqUserlogout } from '@/api/user';
// 引入类型
import type {LoginResponseData, UserInfoResponseData} from '@/api/user/type/index'
/**
* 用户信息
* @methods setUserInfos 设置用户信息
*/
export const useUserInfoStore = defineStore('userInfo', {
state: (): UserInfoState => {
return {
token: getToken() as string,
name: '',
avatar: '',
menuRoutes: [],
buttons:[]
}
},
actions: {
// 登陆
async login(username: string, password: string) {
// 定义请求体
const data = {
username,
password
}
// 获取响应数据
const result:LoginResponseData = await reqUserLogin(data)
// 存储token数据(pinia)
this.token = result.token
// 存储token数据(localstore)
setToken(result.token)
},
// 获取用户信息(路由鉴权那里进行调用)
async getInfo() {
const result:UserInfoResponseData = await getUserInfo()
// console.log("result ",result)
// 设置小仓库中的数据状态
this.name = result.name
this.avatar = result.avatar
//存储当前用户拥有哪些按钮权限标识 ['btn.模块的名字.xxxx']
this.buttons = result.buttons;
// 设置用户的路由
this.menuRoutes = staticRoutes
},
// 退出登陆
async reset() {
// 发送请求
await reqUserlogout()
// 删除local中保存的token
removeToken()
// 提交重置用户信息的mutation
this.token = ''
this.name = ''
this.avatar = ''
},
},
});
在入口文件 注册为全局指令
src/main.ts
import {has} from '@/utils/directive';
//引入自定义指令文件函数
has(app);
在组件中使用:src/views/product/spu/index.vue
<!-- 添加按钮 -->
<el-button
type="primary"
:icon="Plus"
@click="addSpu"
v-has="'btn.Spu.add'"
:disabled="categoryStore.c3Id ? false : true">添加SPU
</el-button>
菜单权限控制
用户仓库中的 this.menuRoutes 属性能决定左侧菜单展示有哪些路由
菜单的权限
# 对路由进行拆分
静态路由:(大家都拥有的路由)
异步路由:(有的人有,有的没有)
任意路由:
# 管理员账号下发给用户的权限包含了异步路由
# 最后要把静态路由,异步路由,任意路由合并在一起
1. 路由拆分
src/routes/routes.ts
静态路由
异步路由
任意路由
封装函数过滤路由
2. 从用户信息中过滤异步路由
// 定义一个函数 用于过滤路由
//项目的异步路由
// 定义一个完整异步路由
const asyncRoute = [
{
path: '/acl',
name: 'Acl',
children: [
{
path: 'user',
name: 'User'
},
{
path: 'role',
name: 'Role'
},
{
path: 'permisstion',
name: 'Permisstion'
}
]
},
{
path: '/product',
name: 'Product',
children:[
{
path: 'trademark',
name: 'Trademark'
},
{
path: 'attr',
name: "Attr"
},
{
path: 'spu',
name: 'Spu'
},
{
path: "sku",
name: 'Sku'
}
]
}
];
// 定义一个数组,里边存放用户路由标识,(第一个元素是一级路由,第二个元素之后都是二级路由)
const routes = ["Product", "Trademark", "Spu"];
//封装一个过滤函数,参数是(完整异步路由 , 用户路由数组)
function findUserAsycRoute(asyncRoute, routes) {
//过滤路由
const result = asyncRoute.filter((item) => {
if (routes.includes(item.name)) {
if (item.children && item.children.length > 0) {
// 递归调用,并把递归的结果(二级路由)返回给当前对象的 children属性(二级路由)
// 这行语句 会 修改原数组asyncRoute中的数据
item.children = findUserAsycRoute(item.children, routes);
}
// 返回为真
return true;
}
})
// 把当前元素加入到数组中
return result;
}
// 调用函数,在这里使用深拷贝,传入一个备份就不会影响原数组
const newArr = findUserAsycRoute(cloneDeep(asyncRoute),routes);
3. 在用户仓库中过滤路由
# 导入静态路由,异步路由,任意路由
# 定义方法
# 调用方法(在获取用户信息时)
# 合并路由
# 在路由器注册异步路由,动态追加路由
# 登出账号时,路由器注销全部异步路由注册
# 然后再把静态路由注册进来
修改路由配置
src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router';
//静态路由:常量路由
export const staticRoutes: Array<RouteRecordRaw> = [
//登录
{
path: '/login',
component: () => import('@/views/login/index.vue'),
name: 'Login',
meta: {
//hidden:决定当前路由是否在左侧菜单展示 true:隐藏 false:显示
//title:决定路由在左侧展示标题
//icon:左侧菜单文字前面的图标
hidden: true,
}
},
//404
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
meta: {
hidden: true
}
},
// 首页
{
path: '/',
component: () => import('@/layout/index.vue'),
redirect: '/home',
children: [{
path: 'home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
icon: 'ele-HomeFilled',
}
}]
},
];
//存储异步路由
export const asyncRoute = [
// 权限管理
{
name: 'Acl',
path: '/acl',
component: () => import('@/layout/index.vue'),
redirect: '/acl/user/list',
meta: { title: '权限管理', icon: 'ele-Setting' },
children: [
{
name: 'User',
path: '/acl/user/list',
component: () => import('@/views/acl/user/index.vue'),
meta: { title: '用户管理' }
},
{
name: 'Role',
path: '/acl/role/list',
component: () => import('@/views/acl/role/index.vue'),
meta: {
title: '角色管理',
},
},
{
name: 'RoleAuth',
path: '/acl/role/auth',
component: () => import('@/views/acl/role/roleAuth.vue'),
meta: {
title: '角色管理',
hidden: true,
activeMenu: '/acl/role/list',
},
},
{
name: 'Permission',
path: '/acl/permission/list',
component: () => import('@/views/acl/menu/index.vue'),
meta: {
title: '菜单管理',
}
}
]
},
//商品管理模块的路由
{
path: '/product',
component: () => import('@/layout/index.vue'),
name: 'Product',
meta: {
title: '商品管理',//菜单的标题
icon: "ele-Goods"
},
children: [
{
path: 'trademark',
component: () => import('@/views/product/trademark/index.vue'),
name: 'Trademark',
meta: {
title: '品牌管理',
icon: 'ele-Operation'
}
},
{
path: 'attr',
component: () => import('@/views/product/attr/index.vue'),
name: 'Attr',
meta: {
title: '属性管理',
icon: 'ele-Files'
}
},
{
path: 'spu',
component: () => import('@/views/product/spu/index.vue'),
name: 'Spu',
meta: {
title: 'Spu管理',
icon: 'ele-Platform'
}
}
,
{
path: 'sku',
component: () => import('@/views/product/sku/index.vue'),
name: 'Sku',
meta: {
title: 'sku管理',
icon: 'ele-Football'
}
}
]
}
]
//存储任意路由
export const anyRoute = {
path: '/:pathMatch(.*)',
name: 'Any',
redirect: '/404',
meta: {
hidden: true
}
}
用户仓库中定义方法,调用
src/store/userInfo.ts
// 引入定义小仓库方法
import { defineStore } from 'pinia';
// 本地存储操作token
import { getToken, removeToken, setToken } from '../utils/token-utils';
// state类型的数据类型
import type { UserInfoState } from './interface';
// 消息提示
import { ElMessage } from 'element-plus'
//引入常量路由、异步路由、任意路由
import { staticRoutes, asyncRoute, anyRoute } from '@/router/routes'
// 导入用户相关的API
import { getUserInfo, reqUserLogin, reqUserlogout } from '@/api/user';
// 引入类型
import type {LoginResponseData, UserInfoResponseData} from '@/api/user/type/index'
//引入路由器
import router from '@/router';
//引入lodash深拷贝
import cloneDeep from "lodash/cloneDeep";
//过滤异步路由
/**
*
* @param asyncRoute 所有异步路由
* @param routes 用户信息中包含的路由数组
* @returns 返回一个路由配置对象
*/
function findUserAsyncRoute(asyncRoute: any, routes: any) { // 返回一个路由配置对象
// 遍历异步路由
return asyncRoute.filter((route: any) => {
// 如果用户路由数组中包含
if (routes.includes(route.name)) {
// 存在children属性,并children属性长度大于0
if (route.children && route.children.length > 0) {
// 递归调用 把递归的结果(二级路由)返回给当前对象的 children属性(二级路由)
// 这行语句 会 修改原数组asyncRoute中的数据
route.children = findUserAsyncRoute(route.children, routes);
}
// 返回为真
return true;
}
})
}
/**
* 用户信息
* @methods setUserInfos 设置用户信息
*/
export const useUserInfoStore = defineStore('userInfo', {
state: (): UserInfoState => {
return {
token: getToken() as string,
name: '',
avatar: '',
menuRoutes: [],
buttons:[]
}
},
actions: {
// 登陆
async login(username: string, password: string) {
// 定义请求体
const data = {
username,
password
}
// 获取响应数据
const result:LoginResponseData = await reqUserLogin(data)
// 存储token数据(pinia)
this.token = result.token
// 存储token数据(localstore)
setToken(result.token)
},
// 获取用户信息(路由鉴权那里进行调用)
async getInfo() {
const result:UserInfoResponseData = await getUserInfo()
// console.log("result ",result)
// 设置小仓库中的数据状态
this.name = result.name
this.avatar = result.avatar
//存储当前用户拥有哪些按钮权限标识 ['btn.模块的名字.xxxx']
this.buttons = result.buttons;
//过滤出这个用户需要展示异步路由 在这里使用深拷贝,传入一个备份就不会影响原数组
const userAsyncRoute = findUserAsyncRoute(cloneDeep(asyncRoute), result.routes);
//左侧菜单展示:决定左侧菜单(导航)显示与否
//router注册路由只有静态路由
this.menuRoutes = [...staticRoutes,...userAsyncRoute,anyRoute];
//动态追加路由:异步路由、任意路由即可!!! router.getRoutes获取路由器全部的路由!!!
[...userAsyncRoute,anyRoute].forEach((route:any)=>{
// 动态追加路由
router.addRoute(route);
})
},
// 退出登陆
async reset() {
// 发送请求
await reqUserlogout()
// 删除local中保存的token
removeToken()
// 提交重置用户信息的mutation
this.token = ''
this.name = ''
this.avatar = ''
//清空当前用户全部的路由
router.getRoutes().forEach((route:any)=>{
//退出登录清空全部路由
router.removeRoute(route.name);
});
//退出登录保留全部的常量路由
staticRoutes.forEach((route:any)=>{
router.addRoute(route);
})
},
},
});