|
| 1 | +import { computed, reactive, unref } from 'vue'; |
| 2 | +import type { Ref } from 'vue'; |
| 3 | +import createComponent from './createComponent'; |
| 4 | +import createDirective from './createDirective'; |
| 5 | + |
| 6 | +export interface Access { |
| 7 | + hasAccess: (accessId: string | number) => Promise<boolean>; |
| 8 | + hasAccessSync: (accessId: string | number) => boolean; |
| 9 | + isDataReady: () => boolean; |
| 10 | + setRole: (roleId: string | Promise<string>) => void; |
| 11 | + getRole: () => string; |
| 12 | + setAccess: (accessIds: Array<string | number> | Promise<Array<string | number>>) => void; |
| 13 | + getAccess: () => string[]; |
| 14 | + match: (path: string, accessIds: string[]) => boolean; |
| 15 | + setPresetAccess: (accessId: string | string[]) => void; |
| 16 | +} |
| 17 | + |
| 18 | +/** |
| 19 | + * Checks if a given value is a plain object. |
| 20 | + * |
| 21 | + * @param {object} object - The value to check. |
| 22 | + * @returns {boolean} - True if the value is a plain object, otherwise false. |
| 23 | + * |
| 24 | + * @example |
| 25 | + * console.log(isPlainObject({})); // true |
| 26 | + * console.log(isPlainObject([])); // false |
| 27 | + * console.log(isPlainObject(null)); // false |
| 28 | + * console.log(isPlainObject(Object.create(null))); // true |
| 29 | + * console.log(Buffer.from('hello, world')); // false |
| 30 | + */ |
| 31 | +function isPlainObject(object: object): boolean { |
| 32 | + if (typeof object !== 'object') { |
| 33 | + return false; |
| 34 | + } |
| 35 | + |
| 36 | + if (object == null) { |
| 37 | + return false; |
| 38 | + } |
| 39 | + |
| 40 | + if (Object.getPrototypeOf(object) === null) { |
| 41 | + return true; |
| 42 | + } |
| 43 | + |
| 44 | + if (object.toString() !== '[object Object]') { |
| 45 | + return false; |
| 46 | + } |
| 47 | + |
| 48 | + let proto = object; |
| 49 | + |
| 50 | + while (Object.getPrototypeOf(proto) !== null) { |
| 51 | + proto = Object.getPrototypeOf(proto); |
| 52 | + } |
| 53 | + |
| 54 | + return Object.getPrototypeOf(object) === proto; |
| 55 | +} |
| 56 | + |
| 57 | +function isPromise(obj) { |
| 58 | + return ( |
| 59 | + !!obj && |
| 60 | + (typeof obj === 'object' || typeof obj === 'function') && |
| 61 | + typeof obj.then === 'function' |
| 62 | + ); |
| 63 | +} |
| 64 | + |
| 65 | +const state = reactive({ |
| 66 | + roles: {{{ roles }}}, |
| 67 | + currentRoleId: "", |
| 68 | + currentAccessIds: [] |
| 69 | +}); |
| 70 | +const rolePromiseList: Promise<any>[] = []; |
| 71 | +const accessPromiseList: Promise<any>[] = []; |
| 72 | + |
| 73 | +// 预设的 accessId |
| 74 | +const presetAccessIds = []; |
| 75 | +const setPresetAccess = (access) => { |
| 76 | + const accessIds = Array.isArray(access) ? access : [access]; |
| 77 | + |
| 78 | + presetAccessIds.push(...accessIds.filter(id => !presetAccessIds.includes(id))); |
| 79 | +}; |
| 80 | + |
| 81 | +const getAllowAccessIds = () => { |
| 82 | + const result = [...presetAccessIds, ...state.currentAccessIds]; |
| 83 | + |
| 84 | + const roleAccessIds = state.roles[state.currentRoleId]; |
| 85 | + if (Array.isArray(roleAccessIds) && roleAccessIds.length > 0) { |
| 86 | + result.push(...roleAccessIds); |
| 87 | + } |
| 88 | + |
| 89 | + return result; |
| 90 | +}; |
| 91 | + |
| 92 | +const _syncSetAccessIds = (promise) => { |
| 93 | + accessPromiseList.push(promise); |
| 94 | + promise |
| 95 | + .then((accessIds) => { |
| 96 | + setAccess(accessIds); |
| 97 | + }) |
| 98 | + .catch((e) => { |
| 99 | + console.error(e); |
| 100 | + }) |
| 101 | + .then(() => { |
| 102 | + const index = accessPromiseList.indexOf(promise); |
| 103 | + if (index !== -1) { |
| 104 | + accessPromiseList.splice(index, 1); |
| 105 | + } |
| 106 | + }); |
| 107 | +}; |
| 108 | + |
| 109 | +const setAccess = (accessIds) => { |
| 110 | + if (isPromise(accessIds)) { |
| 111 | + return _syncSetAccessIds(accessIds); |
| 112 | + } |
| 113 | + if (isPlainObject(accessIds)) { |
| 114 | + if (accessIds.accessIds) { |
| 115 | + setAccess(accessIds.accessIds); |
| 116 | + } |
| 117 | + if (accessIds.roleId) { |
| 118 | + setRole(accessIds.roleId); |
| 119 | + } |
| 120 | + return; |
| 121 | + } |
| 122 | + if (!Array.isArray(accessIds)) { |
| 123 | + throw new Error('[plugin-access]: argument to the setAccess() must be array or promise or object'); |
| 124 | + } |
| 125 | + state.currentAccessIds = accessIds; |
| 126 | +}; |
| 127 | + |
| 128 | +const _syncSetRoleId = (promise) => { |
| 129 | + rolePromiseList.push(promise); |
| 130 | + promise |
| 131 | + .then((roleId) => { |
| 132 | + setRole(roleId); |
| 133 | + }) |
| 134 | + .catch((e) => { |
| 135 | + console.error(e); |
| 136 | + }) |
| 137 | + .then(() => { |
| 138 | + const index = rolePromiseList.indexOf(promise); |
| 139 | + if (index !== -1) { |
| 140 | + rolePromiseList.splice(index, 1); |
| 141 | + } |
| 142 | + }); |
| 143 | +}; |
| 144 | + |
| 145 | +const setRole = async (roleId) => { |
| 146 | + if (isPromise(roleId)) { |
| 147 | + return _syncSetRoleId(roleId); |
| 148 | + } |
| 149 | + if (typeof roleId !== 'string') { |
| 150 | + throw new Error('[plugin-access]: argument to the setRole() must be string or promise'); |
| 151 | + } |
| 152 | + state.currentRoleId = roleId; |
| 153 | +}; |
| 154 | + |
| 155 | +const match = (path, accessIds) => { |
| 156 | + if (path === null || path === undefined) { |
| 157 | + return false; |
| 158 | + } |
| 159 | + if (!Array.isArray(accessIds) || accessIds.length === 0) { |
| 160 | + return false; |
| 161 | + } |
| 162 | + path = path.split('?')[0]; |
| 163 | + // 进入"/"路由时,此时path为“” |
| 164 | + if (path === '') { |
| 165 | + path = '/'; |
| 166 | + } |
| 167 | + const len = accessIds.length; |
| 168 | + for (let i = 0; i < len; i++) { |
| 169 | + if (path === accessIds[i]) { |
| 170 | + return true; |
| 171 | + } |
| 172 | + // 支持*匹配 |
| 173 | + const reg = new RegExp(`^${accessIds[i].replace('*', '.+')}$`); |
| 174 | + if (reg.test(path)) { |
| 175 | + return true; |
| 176 | + } |
| 177 | + } |
| 178 | + return false; |
| 179 | +}; |
| 180 | + |
| 181 | +const isDataReady = () => { |
| 182 | + return rolePromiseList.length || accessPromiseList.length; |
| 183 | +}; |
| 184 | + |
| 185 | +const hasAccess = async (path) => { |
| 186 | + if (!isDataReady()) { |
| 187 | + return match(path, getAllowAccessIds()); |
| 188 | + } |
| 189 | + await Promise.all(rolePromiseList.concat(accessPromiseList)); |
| 190 | + return match(path, getAllowAccessIds()); |
| 191 | +}; |
| 192 | + |
| 193 | +export const install = (app) => { |
| 194 | + app.directive('access', createDirective(useAccess)); |
| 195 | + app.component('Access', createComponent(useAccess)); |
| 196 | +}; |
| 197 | + |
| 198 | +export const hasAccessSync = (path) => { |
| 199 | + return match(unref(path), getAllowAccessIds()); |
| 200 | +}; |
| 201 | + |
| 202 | +export const access: Access = { |
| 203 | + hasAccess, |
| 204 | + hasAccessSync, |
| 205 | + isDataReady, |
| 206 | + setRole, |
| 207 | + getRole: () => { |
| 208 | + return state.currentRoleId; |
| 209 | + }, |
| 210 | + setAccess, |
| 211 | + match, |
| 212 | + getAccess: getAllowAccessIds, |
| 213 | + setPresetAccess |
| 214 | +}; |
| 215 | + |
| 216 | +type UseAccessFunction = (accessId: string | number) => Ref<boolean>; |
| 217 | + |
| 218 | +export const useAccess: UseAccessFunction = (path) => { |
| 219 | + const allowPageIds = computed(getAllowAccessIds); |
| 220 | + const result = computed(() => { |
| 221 | + return match(unref(path), allowPageIds.value); |
| 222 | + }); |
| 223 | + return result; |
| 224 | +}; |
0 commit comments