【UniApp小程序开发】解决无法使用Vue自定义指令的完美替代方案:权限组件封装
在 UniApp 开发中,你是否遇到过这样的困惑:明明在 Vue Web 项目中用得顺手的v-permission自定义指令,一到小程序端就完全失效?本文将深入剖析其原因,并提供一套可直接复用的组件化解决方案,让你在小程序中也能优雅地实现权限控制。
一、问题背景
在传统 Vue 项目中,我们经常通过自定义指令来控制按钮或模块的显隐,例如:
<button v-permission="'admin'">删除</button>这种方式简洁高效。但当我们将代码迁移到UniApp 小程序(微信小程序、支付宝小程序等)时,会发现自定义指令完全不起作用,控制台也没有任何报错,内容始终显示或始终隐藏。
二、为什么小程序不支持 Vue 自定义指令?
要理解这个问题,首先需要清楚 UniApp 的编译原理:
| 环境 | 编译方式 | 是否支持自定义指令 |
|---|---|---|
| Vue Web | 运行时直接操作 DOM,指令钩子(inserted、update等)正常执行 | ✅ 支持 |
| UniApp 小程序 | 模板先编译为对应平台的 WXML(微信)、AXML(支付宝)等,所有 Vue 语法需转换为平台原生语法 | ❌ 不支持 |
简单来说:小程序的模板不支持运行时的 DOM 操作,UniApp 在编译阶段会将<template>中的内容转换成静态的 WXML 节点,自定义指令的 JS 逻辑无法被注入。即使在main.js中全局注册了指令,也不会报错,但不会产生任何效果。
三、解决方案思路
既然指令不可用,我们可以换一种声明式的方式:封装一个权限控制组件,利用组件的插槽(<slot>)包裹需要控制的内容,组件内部根据权限决定是否渲染插槽。
核心优势:
✅ 完全兼容小程序和 H5
✅ 支持OR(满足任意一个权限) 和AND(满足所有权限)两种模式
✅ 响应式:权限数据变化时自动更新视图
✅ 代码复用性强,一处封装,全局使用
四、完整代码实现
1. 权限枚举CheckMode
新建文件enums/CheckMode.ts:
// 权限判断模式 export enum CheckMode { OR = 'or', // 或关系 AND = 'and' // 与关系 }2. 权限组件Perms.vue
新建文件components/Perms.vue:
<template> <view v-if="hasPerms"> <slot></slot> </view> </template> <script setup lang="ts"> import { storeToRefs } from "pinia"; import { computed , ref } from "vue"; import { CheckMode } from "@/enums/CheckMode" interface Props { code: Array<string>; mode : Enum; } const props = withDefaults(defineProps<Props>(),{ code: () => [], mode:() => CheckMode.OR }); let permissions = ref<Array>(['admin']); const hasPerms = computed(() => { const userPerms = permissions.value || []; const requiredCodes = props.code; if (requiredCodes.length == 0) { return false; } if (props.mode === CheckMode.OR) { // OR 模式:只要用户拥有任意一个所需权限即可 return requiredCodes.some(code => userPerms.includes(code)); } else { // AND 模式:用户必须拥有所有所需权限 return requiredCodes.every(code => userPerms.includes(code)); } }) </script> <style lang="scss" scoped> </style>说明:
如果权限数据不在 Pinia 中,也可以从全局变量、本地存储或 props 传入,自行调整即可。
推荐使用
computed而非watch + ref,避免手动触发且性能更优。
3. 在页面中使用
<template> <view style="padding: 20px;display: flex;flex-direction: column;gap: 12px;"> <view>权限控制演示</view> <Perms :code="['admin', 'root']" :mode="CheckMode.OR"> <text>amind or root角色可见</text> </Perms> <Perms :code="['root']" :mode="CheckMode.AND"> <view class="admin-panel"> <text>只能root可见</text> </view> </Perms> </view> </template> <script setup lang="ts"> import Perms from "@/element/safe/Perms.vue"; import { CheckMode } from "@/enums/CheckMode"; </script>五、进阶用法与注意事项
1. 权限数据从哪里来?
通常在用户登录后,后端返回权限码列表,存入 Pinia store。例如:
// stores/user.ts export const useUserStore = defineStore('user', { state: () => ({ permissions: [] as string[] }), actions: { setPermissions(perms: string[]) { this.permissions = perms } } })2. 支持自定义无权限时的占位内容
如果需要“无权限时显示灰色按钮或提示文字”,可以扩展组件,增加一个fallback插槽:
<template> <view v-if="hasPerms"> <slot></slot> </view> <view v-else> <slot name="fallback">暂无权限</slot> </view> </template>使用方式:
<Perms :code="['admin']"> <button>删除</button> <template #fallback> <button disabled>无权限</button> </template> </Perms>4. 如果是原生小程序(非 UniApp)怎么办?
原生小程序也提供了类似方案:使用<block wx:if>+ 自定义组件,思路完全一致。
六、总结
| 方案 | 是否支持小程序 | 优点 | 缺点 |
|---|---|---|---|
| Vue 自定义指令 | ❌ | 简洁 | 无法在小程序运行 |
| 组件 + 插槽 | ✅ | 跨端兼容、声明式、功能完整 | 需额外封装组件 |
最终建议:在 UniApp 项目中,放弃自定义指令,统一使用<Perms>组件进行权限控制。这样不仅能完美运行于小程序,还能保持代码清晰、易维护。
