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 元素挂载和更新时进行权限判断,我们能够精准地控制按钮的显示与隐藏,确保不同角色的用户只能执行其被授权的操作。

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom8 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom8 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试