diff --git a/components/Workbench/Workbench.vue b/components/Workbench/Workbench.vue index 5f844d9..1d67a88 100644 --- a/components/Workbench/Workbench.vue +++ b/components/Workbench/Workbench.vue @@ -15,8 +15,9 @@ v-for="item in items" :key="item.key" @click="handleClick(item)" + v-permission="item.permission" > - + {{ item.text }} @@ -34,7 +35,7 @@ import { ref } from 'vue'; import FabPlus from '@/components/FabPlus.vue'; const items = ref([ - { key: 'verify', text: '审批管理', icon: '/static/workbench/verify.png' }, + { key: 'verify', text: '审批管理', icon: '/static/workbench/verify.png', permission: ['bst:verify:list'] }, // { key: 'customer', text: '客户管理', icon: '/static/workbench/customer.png' }, { key: 'project', text: '项目管理', icon: '/static/workbench/project.png' }, { key: 'task', text: '任务管理', icon: '/static/workbench/task.png' }, diff --git a/directives/permission.js b/directives/permission.js new file mode 100644 index 0000000..eb93bb1 --- /dev/null +++ b/directives/permission.js @@ -0,0 +1,114 @@ +import { useUserStore } from '@/store/user' + +const ADMIN_ROLES = ['admin', 'sys_admin'] + +const normalizePermissionList = (permissions) => { + if (!permissions) return [] + if (Array.isArray(permissions)) return permissions.filter(Boolean) + if (typeof permissions === 'object') { + return Object.keys(permissions).filter(key => !!permissions[key]) + } + if (typeof permissions === 'string') { + return permissions + .split(',') + .map((perm) => perm.trim()) + .filter(Boolean) + } + return [] +} + +const resolveRequiredPermissions = (value) => { + if (!value) return [] + if (Array.isArray(value)) return value.filter(Boolean) + if (typeof value === 'string') { + return value + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + } + return [] +} + +const evaluatePermission = (userStore, requiredPermissions, modifiers = {}) => { + if (!userStore) return false + const { requireAll = modifiers.all } = modifiers + const permissionsToCheck = resolveRequiredPermissions(requiredPermissions) + if (permissionsToCheck.length === 0) return false + + const userInfo = userStore.userInfo + const userRoles = userInfo?.roles || [] + const isAdmin = Array.isArray(userRoles) && userRoles.some((role) => ADMIN_ROLES.includes(role)) + if (isAdmin) return true + + const userPermissions = normalizePermissionList(userInfo?.permissions) + if (userPermissions.length === 0) return false + + if (requireAll) { + return permissionsToCheck.every((permission) => userPermissions.includes(permission)) + } + + return permissionsToCheck.some((permission) => userPermissions.includes(permission)) +} + +const getUserStore = (binding, vnode) => { + const piniaInstance = + binding?.instance?.proxy?.$pinia || + binding?.instance?.appContext?.app?.$pinia || + vnode?.context?.$pinia || + binding?.instance?.appContext?.appContext?.provides?.pinia + + try { + if (piniaInstance) { + return useUserStore(piniaInstance) + } + return useUserStore() + } catch (error) { + console.warn('[v-permission] failed to access pinia instance:', error) + return null + } +} + +const toggleElementVisibility = (el, shouldShow) => { + if (shouldShow) { + el.style.display = el.__vOriginalDisplay || '' + } else { + if (el.style.display !== 'none') { + el.__vOriginalDisplay = el.style.display + } + el.style.display = 'none' + } + el.__vPermissionVisible = shouldShow +} + +const handleDirective = (el, binding, vnode) => { + const userStore = getUserStore(binding, vnode) + const shouldShow = evaluatePermission(userStore, binding.value, binding.modifiers) + toggleElementVisibility(el, shouldShow) +} + +export const registerPermissionDirective = (appOrVue) => { + if (!appOrVue) return + + const directiveConfig = { + mounted: handleDirective, + updated: handleDirective, + beforeMount: handleDirective, + beforeUpdate: handleDirective + } + + if (typeof appOrVue.directive === 'function') { + // Vue3 + appOrVue.directive('permission', directiveConfig) + } else if (typeof appOrVue.prototype?.directive === 'function' || typeof appOrVue.directive === 'function') { + // Vue2 global Vue constructor + const VueCtor = appOrVue.prototype?.constructor || appOrVue + VueCtor.directive('permission', { + inserted: handleDirective, + update: handleDirective, + componentUpdated: handleDirective + }) + } +} + +export default registerPermissionDirective + diff --git a/main.js b/main.js index ef0abad..b56d6d4 100644 --- a/main.js +++ b/main.js @@ -1,6 +1,7 @@ import App from './App' import uvUI from '@climblee/uv-ui' import { Request } from '@/utils/request/index' +import { registerPermissionDirective } from '@/directives/permission' // #ifndef VUE3 import Vue from 'vue' @@ -9,6 +10,8 @@ import './uni.promisify.adaptor' Vue.config.productionTip = false App.mpType = 'app' +registerPermissionDirective(Vue) + const app = new Vue({ ...App }) @@ -27,6 +30,7 @@ export function createApp() { app.use(pinia) app.use(uvUI); Request(app) + registerPermissionDirective(app) // 调用setConfig方法,方法内部会进行对象属性深度合并,可以放心嵌套配置 // 需要在Vue.use(uvUI)之后执行 diff --git a/manifest.json b/manifest.json index ee538da..c9c789a 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name" : "OfficeSystem", "appid" : "__UNI__53A0BE0", "description" : "", - "versionName" : "1.0.7", + "versionName" : "1.0.8", "versionCode" : "100", "transformPx" : false, /* 5+App特有相关 */ diff --git a/store/user.js b/store/user.js index 8b16600..943c788 100644 --- a/store/user.js +++ b/store/user.js @@ -1,5 +1,22 @@ import { defineStore } from 'pinia' +const ADMIN_ROLES = ['admin', 'sys_admin'] + +const normalizePermissions = (permissions) => { + if (!permissions) return [] + if (Array.isArray(permissions)) return permissions.filter(Boolean) + if (typeof permissions === 'object') { + return Object.keys(permissions).filter(key => !!permissions[key]) + } + if (typeof permissions === 'string') { + return permissions + .split(',') + .map((perm) => perm.trim()) + .filter(Boolean) + } + return [] +} + /** * 用户信息 Store * 用于管理用户登录状态、token等信息 @@ -56,6 +73,63 @@ export const useUserStore = defineStore('user', { return state.privateView }, + /** + * 获取用户角色列表 + */ + getRoles: (state) => { + return Array.isArray(state.userInfo?.roles) ? state.userInfo.roles : [] + }, + + /** + * 是否拥有管理员角色 + */ + isAdmin: (state) => { + const roles = Array.isArray(state.userInfo?.roles) ? state.userInfo.roles : [] + return roles.some((role) => ADMIN_ROLES.includes(role)) + }, + + /** + * 获取权限列表 + */ + getPermissions: (state) => { + return normalizePermissions(state.userInfo?.permissions) + }, + + /** + * 判断是否拥有指定权限 + * 支持字符串、数组或以逗号分隔的字符串 + * @param {string|string[]} requiredPermissions + * @param {object} options - { requireAll?: boolean } + */ + hasPermission: (state) => (requiredPermissions, options = {}) => { + const permissionsToCheck = (() => { + if (!requiredPermissions) return [] + if (Array.isArray(requiredPermissions)) { + return requiredPermissions.filter(Boolean) + } + if (typeof requiredPermissions === 'string') { + return requiredPermissions + .split(',') + .map((perm) => perm.trim()) + .filter(Boolean) + } + return [] + })() + + if (permissionsToCheck.length === 0) return false + if (Array.isArray(state.userInfo?.roles) && state.userInfo.roles.some((role) => ADMIN_ROLES.includes(role))) { + return true + } + + const userPermissions = normalizePermissions(state.userInfo?.permissions) + if (userPermissions.length === 0) return false + + const requireAll = options.requireAll || options.all + return requireAll + ? permissionsToCheck.every((permission) => userPermissions.includes(permission)) + : permissionsToCheck.some((permission) => userPermissions.includes(permission)) + }, + /** * 判断 token 是否过期(如果设置了过期时间) */