Vue3按钮权限控制:解锁前端安全交互新姿势

背景引入

在 Vue3 项目开发中,权限管理是不可或缺的一部分,而按钮权限控制更是其中的关键环节。它就像是一扇精准的 "门禁系统",能够依据用户的角色和权限,决定是否展示或启用特定的按钮,从而保证系统的安全性和用户体验。

以一个常见的管理系统为例,系统中存在管理员、普通用户和访客等不同角色。管理员拥有全面的操作权限,能够执行添加、编辑、删除等所有操作;普通用户可能仅具备查看和部分编辑的权限;而访客或许只能进行简单的查看操作。假设在用户管理页面中,有 "添加用户""编辑用户""删除用户" 这几个按钮,若不进行权限控制,普通用户和访客也能看到并尝试点击这些按钮,这不仅会造成操作上的混乱,还可能导致严重的数据安全问题。因此,实现按钮权限控制,能够让不同角色的用户在登录系统后,只看到自己有权限操作的按钮,既提升了系统的安全性,又优化了用户体验 ,避免了用户因误操作而产生的困惑和错误。

实现思路剖析

(一)数据获取与存储

在 Vue3 项目中,实现按钮权限控制的第一步是获取用户的按钮权限数据。通常,这些数据由后端提供,我们可以在用户登录成功后,通过发送 HTTP 请求来获取。例如,使用 Axios 库向服务器发送请求:

javascript 复制代码
import axios from 'axios';

// 登录成功后的回调函数

export const loginSuccess = async (data) => {

try {

// 发送请求获取按钮权限数据

const response = await axios.get('/api/button-permissions', {

headers: {

Authorization: `Bearer ${data.token}` // 假设使用JWT认证

}

});

// 这里将获取到的按钮权限数据存储到Pinia中

const permissionStore = usePermissionStore();

permissionStore.setButtonPermissions(response.data);

} catch (error) {

console.error('获取按钮权限失败', error);

}

};

获取到数据后,我们使用 Pinia 进行持久化存储,以便在页面刷新或用户重新访问时,无需再次请求数据。在 Pinia 的 store 中定义如下:

javascript 复制代码
import { defineStore } from 'pinia';

export const usePermissionStore = defineStore('permission', {

state: () => ({

buttonPermissions: []

}),

actions: {

setButtonPermissions(permissions) {

this.buttonPermissions = permissions;

},

clearButtonPermissions() {

this.buttonPermissions = [];

// 同时清除本地存储中的数据,这里假设使用localStorage

localStorage.removeItem('buttonPermissions');

}

},

// 使用pinia-plugin-persist插件进行持久化存储

persist: {

enabled: true,

strategies: [

{

key: 'buttonPermissions',

storage: localStorage

}

]

}

});

在用户登录时,调用loginSuccess方法获取并存储权限数据;当用户刷新页面时,Pinia 会自动从本地存储中读取数据;而在用户退出登录时,调用clearButtonPermissions方法清除权限数据和本地存储。

(二)自定义指令核心原理

自定义指令是 Vue3 实现按钮权限控制的核心机制。通过自定义指令,我们可以在 DOM 元素挂载或更新时,根据用户的权限数据来判断是否显示该按钮。自定义指令的基本语法如下:

javascript 复制代码
import { Directive } from 'vue';

const permissionDirective: Directive = {

mounted(el, binding) {

const { value } = binding;

const permissionStore = usePermissionStore();

const hasPermission = permissionStore.buttonPermissions.includes(value);

if (!hasPermission) {

// 如果没有权限,隐藏或移除按钮

el.style.display = 'none';

}

},

updated(el, binding) {

const { value } = binding;

const permissionStore = usePermissionStore();

const hasPermission = permissionStore.buttonPermissions.includes(value);

const oldHasPermission = el.dataset.hasPermission === 'true';

if (hasPermission!== oldHasPermission) {

if (!hasPermission) {

el.style.display = 'none';

} else {

el.style.display = 'block';

}

el.dataset.hasPermission = hasPermission.toString();

}

}

};

export default permissionDirective;

在上述代码中,mounted钩子函数在指令绑定元素挂载到 DOM 时被调用,它会检查当前按钮的权限标识是否在用户的权限列表中,如果不在,则隐藏按钮。updated钩子函数在指令绑定元素的父组件更新时被调用,它会重新检查权限,以确保按钮的显示状态与用户权限保持一致。在模板中使用该指令时,只需在按钮元素上添加v-permission="权限标识",例如:

html 复制代码
<template>

<button v-permission="'addUser'" @click="addUser">添加用户</button>

</template>

这样,当用户登录后,系统会根据其权限数据来决定是否显示 "添加用户" 按钮,从而实现了按钮权限的精准控制。

实战代码实现

(一)创建权限存储模块

使用 Pinia 定义权限存储模块,用于存储用户的按钮权限数据。在src/store/modules/permission.ts文件中编写如下代码:

javascript 复制代码
import { defineStore } from 'pinia';

// 定义权限存储模块

export const usePermissionStore = defineStore('permission', {

// 定义状态,存储按钮权限列表

state: () => ({

buttonPermissions: []

}),

// 定义获取权限方法

actions: {

// 设置按钮权限

setButtonPermissions(permissions) {

this.buttonPermissions = permissions;

},

// 清除按钮权限

clearButtonPermissions() {

this.buttonPermissions = [];

}

},

// 持久化配置,使用localStorage存储权限数据

persist: {

enabled: true,

strategies: [

{

key: 'buttonPermissions',

storage: localStorage

}

]

}

});

在上述代码中,state定义了一个buttonPermissions数组,用于存储用户的按钮权限。actions中定义了setButtonPermissions方法,用于将获取到的权限数据存储到buttonPermissions中;clearButtonPermissions方法用于在用户退出登录时清除权限数据。persist配置使用了pinia-plugin-persist插件,实现权限数据的持久化存储,确保在页面刷新或用户重新访问时,权限数据依然可用。

(二)编写自定义指令

在src/directives/permission.ts文件中编写自定义指令,用于判断按钮是否有权限显示。代码如下:

javascript 复制代码
import { Directive } from 'vue';

import { usePermissionStore } from '@/store/modules/permission';

// 定义按钮权限判断函数

const hasPermission = (value: string) => {

const permissionStore = usePermissionStore();

return permissionStore.buttonPermissions.includes(value);

};

// 定义添加元素的函数

const addElement = (el: HTMLElement) => {

el.style.display = 'block';

};

// 定义移除元素的函数

const removeElement = (el: HTMLElement) => {

el.style.display = 'none';

};

// 定义自定义指令

const permissionDirective: Directive = {

// 当指令绑定元素挂载到DOM时调用

mounted(el, binding) {

const { value } = binding;

if (!hasPermission(value)) {

removeElement(el);

}

},

// 当指令绑定元素的父组件更新时调用

updated(el, binding) {

const { value } = binding;

const oldValue = binding.oldValue;

if (value!== oldValue) {

if (hasPermission(value)) {

addElement(el);

} else {

removeElement(el);

}

}

}

};

export default permissionDirective;

在这段代码中,hasPermission函数用于判断当前用户是否拥有指定按钮的权限,它通过usePermissionStore获取存储的权限数据,并检查当前按钮的权限标识是否在权限列表中。addElement和removeElement函数分别用于显示和隐藏按钮元素。permissionDirective自定义指令在mounted钩子函数中,检查按钮权限,若用户没有权限则隐藏按钮;在updated钩子函数中,当按钮的权限标识发生变化时,重新检查权限并更新按钮的显示状态。

(三)全局注册指令

在main.ts文件中全局注册自定义指令,使其在整个 Vue 应用中可用。代码如下:

javascript 复制代码
import { createApp } from 'vue';

import App from './App.vue';

import permissionDirective from './directives/permission';

const app = createApp(App);

// 全局注册自定义指令

app.directive('permission', permissionDirective);

app.mount('#app');

上述代码中,通过app.directive方法将自定义指令permissionDirective注册为全局指令,指令名为permission。这样,在任何 Vue 组件的模板中,都可以使用v-permission来控制按钮的权限显示。

(四)在页面中使用指令

在 Vue 组件的模板中,使用自定义指令v-permission来实现按钮权限控制。例如,在src/views/UserManagement.vue组件中:

javascript 复制代码
<template>

<div>

<h1>用户管理</h1>

<button v-permission="'addUser'" @click="addUser">添加用户</button>

<button v-permission="'editUser'" @click="editUser">编辑用户</button>

<button v-permission="'deleteUser'" @click="deleteUser">删除用户</button>

</div>

</template>

<script setup>

const addUser = () => {

console.log('添加用户');

};

const editUser = () => {

console.log('编辑用户');

};

const deleteUser = () => {

console.log('删除用户');

};

</script>

在这个例子中,v-permission="'addUser'"表示只有当用户拥有addUser权限时,"添加用户" 按钮才会显示,否则按钮将被隐藏。同理,"编辑用户" 和 "删除用户" 按钮也会根据用户的权限进行显示或隐藏。这样,通过简单地在按钮元素上添加v-permission指令,并传入相应的权限标识,就可以轻松实现按钮权限的控制 。

解决潜在问题

(一)动态权限值更改

在实际应用中,可能会遇到需要动态更改按钮 DOM 权限值的情况。例如,在用户切换角色或权限动态更新时,按钮的显示状态也需要相应改变。如果仅依赖mounted钩子函数,无法及时响应这些变化,导致按钮的显示状态与用户实际权限不一致。

为了解决这个问题,我们可以利用updated钩子函数和watchEffect。updated钩子函数会在指令绑定元素的父组件更新时被调用,我们可以在这个钩子函数中重新检查按钮的权限。同时,使用watchEffect来收集按钮权限数据的依赖,当权限数据发生变化时,自动触发updated钩子函数中的更新逻辑。示例代码如下:

javascript 复制代码
import { Directive, watchEffect } from 'vue';

import { usePermissionStore } from '@/store/modules/permission';

const permissionDirective: Directive = {

mounted(el, binding) {

const { value } = binding;

const permissionStore = usePermissionStore();

const hasPermission = permissionStore.buttonPermissions.includes(value);

if (!hasPermission) {

el.style.display = 'none';

}

},

updated(el, binding) {

const { value } = binding;

const permissionStore = usePermissionStore();

const update = () => {

const hasPermission = permissionStore.buttonPermissions.includes(value);

const oldHasPermission = el.dataset.hasPermission === 'true';

if (hasPermission!== oldHasPermission) {

if (!hasPermission) {

el.style.display = 'none';

} else {

el.style.display = 'block';

}

el.dataset.hasPermission = hasPermission.toString();

}

};

if (!el._watchEffect) {

el._watchEffect = watchEffect(() => {

update();

});

} else {

update();

}

}

};

export default permissionDirective;

在上述代码中,updated钩子函数中定义了update方法,用于检查权限并更新按钮的显示状态。watchEffect会自动收集permissionStore.buttonPermissions的依赖,当权限数据变化时,watchEffect会触发update方法的执行,从而确保按钮的显示状态与用户的最新权限一致。

(二)特殊权限控制(数组类型)

在某些复杂的业务场景中,权限控制可能采用数组类型,例如一个按钮可能需要多个权限标识才能显示。此时,我们需要兼容入参的code值为数组类型,并正确判断权限。假设后端返回的权限数据如下:

javascript 复制代码
[

{

"buttonCode": "addUser",

"permissions": ["admin", "editor"]

},

{

"buttonCode": "deleteUser",

"permissions": ["admin"]

}

]

在自定义指令中,我们需要修改权限判断逻辑,以适应这种数组类型的权限控制。示例代码如下:

javascript 复制代码
import { Directive } from 'vue';

import { usePermissionStore } from '@/store/modules/permission';

const permissionDirective: Directive = {

mounted(el, binding) {

const { value } = binding;

const permissionStore = usePermissionStore();

const buttonPermission = permissionStore.buttonPermissions.find(perm => perm.buttonCode === value);

if (buttonPermission) {

const userPermissions = permissionStore.userPermissions; // 假设用户权限存储在userPermissions中

const hasPermission = buttonPermission.permissions.some(perm => userPermissions.includes(perm));

if (!hasPermission) {

el.style.display = 'none';

}

} else {

el.style.display = 'none';

}

},

updated(el, binding) {

// 与mounted类似的逻辑,用于处理权限更新

const { value } = binding;

const permissionStore = usePermissionStore();

const buttonPermission = permissionStore.buttonPermissions.find(perm => perm.buttonCode === value);

if (buttonPermission) {

const userPermissions = permissionStore.userPermissions;

const hasPermission = buttonPermission.permissions.some(perm => userPermissions.includes(perm));

const oldHasPermission = el.dataset.hasPermission === 'true';

if (hasPermission!== oldHasPermission) {

if (!hasPermission) {

el.style.display = 'none';

} else {

el.style.display = 'block';

}

el.dataset.hasPermission = hasPermission.toString();

}

} else {

el.style.display = 'none';

}

}

};

export default permissionDirective;

在这段代码中,mounted和updated钩子函数中,首先通过find方法找到与按钮code对应的权限配置。然后,使用some方法检查用户的权限是否包含按钮所需的权限,只要用户有其中一个权限,按钮就会显示。这样,就实现了对数组类型权限控制的兼容 。

总结

在 Vue3 项目中,实现按钮权限控制对于提升系统的安全性和用户体验至关重要。通过获取用户权限数据并利用 Pinia 进行存储,结合自定义指令在 DOM 元素挂载和更新时进行权限判断,我们能够精准地控制按钮的显示与隐藏,确保不同角色的用户只能执行其被授权的操作。

相关推荐
沙尘暴炒饭4 分钟前
vuex持久化vuex-persistedstate,存储的数据刷新页面后导致数据丢失
开发语言·前端·javascript
2401_837088506 分钟前
CSS清楚默认样式
前端·javascript·css
zwjapple18 分钟前
React 的 useEffect 清理函数详解
前端·react.js·前端框架
Jewel10528 分钟前
如何配置Telegram Mini-App?
前端·vue.js·app
lswzw30 分钟前
Ubuntu K8s集群安全加固方案
安全·ubuntu·kubernetes
s11show_1631 小时前
hz修改后台新增keyword功能
android·java·前端
Pasregret1 小时前
中介者模式:解耦对象间复杂交互的设计模式
设计模式·交互·中介者模式
二个半engineer1 小时前
Web常见攻击方式及防御措施
前端
co松柏1 小时前
程序员必备——AI 画技术图技巧
前端·后端·ai编程
前端大白话1 小时前
前端人速码!10个TypeScript神仙技巧,看完直接拿捏项目实战
前端·javascript·typescript