当前位置: 首页 > news >正文

Vue3+Vite+TypeScript+Element Plus开发-20.按钮权限

 系列文档目录

Vue3+Vite+TypeScript安装

Element Plus安装与配置

主页设计与router配置

静态菜单设计

Pinia引入

Header响应式菜单缩展

Mockjs引用与Axios封装

登录设计

登录成功跳转主页

多用户动态加载菜单

Pinia持久化

动态路由 -动态增加路由

动态路由-动态删除路由 

路由守卫-无路由跳转404

 路由守卫-未登录跳转登录界面

 登录退出

Tags-组件构建

Tags-与菜单联动 

Pinia持久化优化

按钮权限

客制按钮组件

客制Table组件

客制Form组件

国际化

配置文件


 文章目录

目录

 系列文档目录

 文章目录

 前言

权限数据模拟

Mock模拟 

增加API

菜单增加

stores存储

权限处理

权限展示

运行效果

后续


 前言

前面已讲解基础架构构造,实战中按钮权限是关键环节,本章重点讲解按钮权限构建。


权限数据模拟

在src/mock/mockData下新建permissionsData.ts文件

        数据结构:菜单信息与权限信息。  permissionsInfoOne  下,该账号具有  demo1:view  、  demo1:create  、  demo1:edit  、  demo1:delete  权限。

{ index: 'permissionsInfoOne', label: '权限演示-1',path: 'views/SysSettings/UserInfo',permissions: ["demo1:view","demo1:create","demo1:edit","demo1:delete"]},
完整代码:
// src/mock/mockData/permissionsData.ts
import Mock from 'mockjs';// 动态生成菜单数据
export default (data: any) => {// 解析传入的 data 参数const { username, password } = data;// 根据用户名和密码生成不同的响应if (username === 'admin') {return Mock.mock({status_code: 200,status: 'success',message: 'Operation successful.',data:  [{ index: 'permissionsInfoOne', label: '权限演示-1',path: 'views/SysSettings/UserInfo',permissions: ["demo1:view","demo1:create","demo1:edit","demo1:delete"]},{ index: 'permissionsInfoTow', label: '权限演示-2',path: 'views/SysSettings/AccountSetting' ,permissions: ["demo2:view","demo2:create","demo2:edit","demo2:delete"]},],});} else if (username === 'user' ) {return Mock.mock({status_code: 200,status: 'success',message: 'Operation successful.',data:  [{ index: 'permissionsInfoOne', label: '权限演示-1',path: 'views/SysSettings/UserInfo',permissions: ["demo1:view","demo1:create","demo1:edit"                 ]},{   index: 'permissionsInfoTow', label: '权限演示-2',path: 'views/SysSettings/AccountSetting' ,permissions: ["demo2:view","demo2:create",                 "demo2:delete"]},],});} else {return Mock.mock({status_code: 401,status: 'fail',message: 'Invalid username ,No permissions Data.',data: [],});}
};

Mock模拟 

在/src/mock/index.ts文件增加permission

import permissionData from '@/mock/mockData/permissionData' ;
// 权限接口
Mock.mock(/permission/, 'get', (options) => {const { url } = options;// 使用 URLSearchParams 解析查询字符串const queryParams = new URLSearchParams(new URL(url).search);const username = queryParams.get('username');const password = queryParams.get('password');// console.log('username:', username);// console.log('password:', password);return permissionData({ username, password });
});

增加API

在  src/api/  目录下新增  permission.ts  文件。

代码如下

// src/api/permission.ts// 引入 request、post 和 get 函数import { get } from '@/api/request'; // 绝对路径// 权限接口 增加dataexport const permissionAPI = async (data: any) => {try {const result = await get('/permission',data); // 使用封装的 get 方法// console.log('result',result);// console.log('result data',result.data );return result.data  ;// result.data  为了返回值统一,增加data} catch (error) {console.error('获取权限数据失败:', error);return [];}};

菜单增加

修改src/mock/mockData/menuData.ts文件在admin与User中增加以下菜单

{index: 'Demo',label: '案例演示',icon: Setting,children: [{ index: 'permissionsInfoOne', label: '权限演示-1',path: 'views/Demos/permissionsInfoOne' },{ index: 'permissionsInfoTow', label: '权限演示-2',path: 'views/Demos/permissionsInfoTow' },],},

stores存储

在src/stores/index.ts中增加permissions、currentPagePath定义与赋值

 permissions: any[];// 用户的权限列表currentPagePath: string;// 当前页面路径// 权限setPermissions(val: any) {this.permissions = val;},getPermissions(): [] {return this.permissions  ;},// 当前页面路径setCurrentPagePath(val: any) {this.currentPagePath = val;},    getCurrentPagePath() {return this.currentPagePath  ;},

完整代码

// src/stores/index.tsimport { defineStore } from 'pinia';
import router from '../router';
import type { Component } from 'vue';
import { get } from 'http';type Modules = Record<string, () => Promise<{ default: Component }>>;function stateIni(): {isCollapse: boolean;username: string;token_key: string;menuData: any[];tabs: { path: string; index: string; label: string; icon: string }[];currentMenu: any;permissions: any[];// 用户的权限列表currentPagePath: string;// 当前页面路径
} {return {isCollapse: false,username: '',token_key: '',menuData: [],tabs: [{path: "/home",index: "Home",label: "home",icon: "home"}],currentMenu: null,permissions: [], // 用户的权限列表currentPagePath: '/' ,// 当前页面路径};
}// 定义公共 storeexport const useAllDataStore = defineStore('useAllData', {// 定义状态state: stateIni,// 定义 actionsactions: {// 设置用户名setUsername(username: string) {this.username = username;},// 获取用户名getUsername(): string {return this.username;},// 设置 token_keysetTokenKey(token_key: string) {// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: token_key, menuData: this.menuData}));this.token_key = token_key;},// 获取 token_keygetTokenKey(): string {/*const sessionData = sessionStorage.getItem('useAllData-session-store');console.log(sessionData)if (sessionData) {const data = JSON.parse(sessionData);this.token_key = data.token_key;this.menuData = data.menuData;}else{this.token_key = ''}*/return this.token_key;},// 设置菜单数据setMenuData(menuData: any){// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: this.token_key, menuData: menuData}));addRouter(menuData)this.menuData = menuData},// 获取菜单数据getMenuData(): [] {return this.menuData;},// tabssetTabsData(val: any)    {console.log('val',val)if(val.name==='home'){this.currentMenu=null;}else{let index=this.tabs.findIndex((item:any)=>item.index===val.index);console.log(index)index===-1?  this.tabs.push(val):"";console.log('tabs:',this.tabs)}},getTabsData(): [] {return this.tabs;},removeTagsData(val: any){let index=this.tabs.findIndex((item:any)=>item.index===val.index);this.tabs.splice(index,1)console.log('removeTagsData',this.tabs)},resetStore(){//console.log('sessionStorage clear:', sessionStorage.length === 0); // 应该输出 true   this.$reset(); // 重置 Pinia 状态//console.log('sessionStorage reset:', sessionStorage.length === 0); // 应该输出 true  //sessionStorage.clear(); // 清空 sessionStoragesessionStorage.removeItem('__persisted__useAllData-store');//console.log('sessionStorage cleared:', sessionStorage.length === 0); // 应该输出 true  // Object.assign(this, stateIni()); // 复制 Pinia 状态// sessionStorage.removeItem('useAllData-store'); // 删除 sessionStorage 中的数据},// 登出方法logout() {this.resetStore()     // console.log('sessionStorage reset:', sessionStorage.length === 0); // 应该输出 true  // sessionStorage.removeItem('useAllData-store'); // 删除 sessionStorage 中的数据// console.log('sessionStorage clear:', sessionStorage.length === 0); // 应该输出 true  // router.push({ name: 'login' }); // 重定向到登录页面// 检查登录后的逻辑router.push({ name: 'login' }).then(() => {// console.log('Navigated to login page');// console.log('sessionStorage after navigation:', sessionStorage.length === 0); // 确认是否仍然为 true});},// 权限setPermissions(val: any) {this.permissions = val;},getPermissions(): [] {return this.permissions  ;},// 当前页面路径setCurrentPagePath(val: any) {this.currentPagePath = val;},    getCurrentPagePath() {return this.currentPagePath  ;},},  persist: [{enabled: true,key: 'useAllData-store',storage: sessionStorage, // // localStorage  sessionStoragepick: ['token_key','tabs','menuData','permissions','currentPagePath'], // 指定持久化的字段},{key: 'useAllData-store',storage: localStorage, // // localStorage  sessionStoragepick: ['username','isCollapse'], // 指定持久化的字段},],/*persist: [{pick: ['menuData'],storage: localStorage,},{pick: ['token_key'],storage: sessionStorage,},],*//*persist: {enabled: true,key: 'useAllData-store',storage: sessionStorage, // // localStorage  sessionStoragepaths: ['token_key','tabs'], // 指定持久化的字段},*//*persist: {enabled: true,strategies: [{key: 'useAllData-store',storage: sessionStorage,// localStorage  sessionStoragepaths: ['token_key','menuData'], // 指定需要持久化的字段},],},*/});function addRouter(menuData: any){const routerList=router.getRoutes()const modules: Modules = import.meta.glob('../views/**/*.vue') as Modules;const routerArr=[]menuData.forEach((item:any) => {// console.log(item) if(item.children){item.children.forEach((child:any) => {const componentPath = `../${child.path}.vue`;const module = modules[componentPath];if (module) {/*module().then(({ default: component }) => {child.component = component;});*/child.component = module;routerArr.push(child)}});}else{const componentPath = `../${item.path}.vue`;const module = modules[componentPath];if(module){item.component = module;routerArr.push(item)}}});// 增加删除路由routerList.forEach((item:any) => {if (item.name === 'main' || item.name === 'home' || item.name === '404' || item.name === 'login'|| item.name === 'error'|| item.name === 'undefined'|| item.path === '/'|| item.path === '/main') returnrouter.removeRoute(item.name)});routerArr.forEach((item:any) => {router.addRoute('main',{path: item.index,name: item.label,component: item.component,});})
const routerListLast=router.getRoutes()
// console.log(routerListLast)}export function ReloadData() {const store = useAllDataStore();const menuData = store.getMenuData();addRouter(menuData);}

登录界面

修改login.vue中fetchMenuData中增加获取权限资料并存储。

// 获取菜单数据
const fetchMenuData = async () => {try {const resultMenu = await menuAPI(loginForm); // 假设 loginForm 包含必要的参数store.setMenuData(resultMenu);const resultPermission = await permissionAPI(loginForm); // 假设 loginForm 包含必要的参数store.setPermissions(resultPermission);// console.log('login result 返回的数据:', result);// console.log('login menuAPI 返回的数据:', store.getMenuData());} catch (error) {console.error('获取菜单数据失败:', error);}
};

以上代码 在后期被修改引入Promise,具体代码如下:

async function getMenu(): Promise<any> {const resultMenu =  await menuAPI(loginForm); // 假设 loginForm 包含必要的参数store.setMenuData(resultMenu);}async function getPermission(): Promise<any> {const resultPermission =  await permissionAPI(loginForm); // 假设 loginForm 包含必要的参数store.setPermissions(resultPermission);}
// 获取菜单数据
const fetchLoginAfterData = async () => {try {await Promise.all([getMenu(), getPermission()]);}catch (error) {ElMessage.error(`菜单或权限数据获取失败,请稍后再试: ${error.message || '未知错误'}`);}
};

权限处理

新增src/utils/permissionUtils.ts文件,主要对权限处理。

// src/utils/permissionUtils.ts
import { computed } from 'vue';
import { useAllDataStore } from '@/stores';
import { useRouter } from 'vue-router';function usePermission(): (permission: string) => boolean {const Store = useAllDataStore();const Router = useRouter();// 获取当前页面路径并去掉开头的 '/'let currentPagePath = Router.currentRoute.value.path;currentPagePath = currentPagePath.startsWith('/') ? currentPagePath.slice(1) : currentPagePath;// 获取用户权限列表,并添加默认值处理const Permissions = Store.getPermissions() || [];// 获取当前页面的按钮权限const userPermissions = computed(() => {if (!Array.isArray(Permissions)) {console.error('Permissions is not an array:', Permissions);return []; // 如果 Permissions 不是数组,返回空数组}const currentMenu = Permissions.find(permissions => permissions.index === currentPagePath);return currentMenu?.permissions || []; // 确保返回的是一个数组});// 返回一个函数,用于检查用户是否具有某种权限return (permission: string): boolean => {// 解包 ComputedRef 的值后再调用 includes 方法return userPermissions.value.includes(permission);};
}export { usePermission };

权限展示

        新增views\Demos\permissionsInfoOne.vue文件,在权限部分,新增、demo2:view刻意设定'permissionsInfoTwo中权限,预期效果是permissionsInfoOne不能显示,在实战中权限可以自定义。

<template ><div><div>权限演示-1</div><div v-if="hasPermission('demo1:view')">demo1:view</div><div v-if="hasPermission('demo2:view')">demo2:view</div><div><el-button :icon="Search" circle /><el-button v-if="hasPermission('demo2:create')" type="primary" :icon="Plus" circle /><el-button v-if="hasPermission('demo1:edit')" type="primary" :icon="Edit" circle /><el-button type="success" :icon="Check" circle /><el-button type="info" :icon="Message" circle /><el-button type="warning" :icon="Star" circle /><el-button v-if="hasPermission('demo1:delete')"  type="danger" :icon="Delete" circle /></div></div></template>
<script lang="ts" setup>
import { usePermission } from "@/utils/permissionUtils";
import {Plus,Check,Delete,Edit,Message,Search,Star,
} from '@element-plus/icons-vue'
const hasPermission = usePermission();</script>

运行效果

用admin登录,新增按钮未显示

 用user登录,删除与新增按钮未显示


后续

   本章重点讲解按钮权限构建,一下章节把按钮构建一个组件并用权限管控。

相关文章:

  • JavaScript-立即执行函数(Immediately Invoked Function Expression,IIFE)
  • 大模型在胃十二指肠溃疡预测及诊疗方案制定中的应用研究
  • 大M法处理非线性约束线性化
  • C语言数据类型取值范围
  • DataInputStream 终极解析与记忆指南
  • 期货跟单软件云端部署的重要性
  • 足球AI模型:一款用数据分析赛事的模型
  • vue入门:路由 router
  • 深度理解指针之例题
  • LeetCode算法题(Go语言实现)_51
  • 2025年考取特种设备安全管理 A 证,开启职业新征程
  • 依赖文件导入不进来
  • 科学视角下的打坐:身心获益的实证探究
  • 三款注册表清理工具
  • 深度学习模型
  • 非阻塞I/O操作
  • 什么是智算中心
  • 学习笔记十六——Rust Monad从头学
  • 过拟合和欠拟合
  • windows:右键jpg等格式文件后文件管理器卡住/崩溃
  • 海量数据处于“原矿”状态,数据价值释放如何破局?
  • 西藏阿里地区日土县连发两次地震,分别为4.8级和3.8级
  • 以“最美通缉犯”为噱头直播?光明网:违法犯罪不应成网红跳板
  • 网警侦破特大“刷量引流”网络水军案:涉案金额达2亿余元
  • 纪录电影《中国有戏:天幕计划》启动,有望太空播放
  • 哈马斯同意释放剩余所有以色列方面被扣押人员,以换取停火五年