@litianxiang/portal-core 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -41
- package/dist/index.d.ts +41 -1
- package/dist/index.js +207 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @litianxiang/portal-core 使用说明
|
|
2
2
|
|
|
3
|
-
`@litianxiang/portal-core`
|
|
3
|
+
`@litianxiang/portal-core` 是一套门户子系统通用的路由 / 认证 / 请求 内核,负责统一处理:
|
|
4
4
|
|
|
5
5
|
- 登录态校验 + SSO 恢复
|
|
6
6
|
- 菜单加载与动态路由挂载
|
|
7
7
|
- 根路径重定向到第一个业务页面
|
|
8
8
|
- 统一退出到认证站、空闲超时判断等通用认证逻辑
|
|
9
|
+
- http 请求拦截(包含全局 Loading、Token 过期刷新、401 统一退出等)
|
|
9
10
|
|
|
10
11
|
各子系统只需注入自己的静态路由、用户 Store、(可选)Tab Store 和“第一个页面”的计算函数,即可接入。
|
|
11
12
|
|
|
@@ -32,7 +33,9 @@ npm install @litianxiang/portal-core
|
|
|
32
33
|
import {
|
|
33
34
|
createAppRouter,
|
|
34
35
|
createLogoutToAuth,
|
|
35
|
-
calcInactivityAction
|
|
36
|
+
calcInactivityAction,
|
|
37
|
+
createAuthHttpClient,
|
|
38
|
+
createPermissionHelper
|
|
36
39
|
} from '@litianxiang/portal-core'
|
|
37
40
|
```
|
|
38
41
|
|
|
@@ -80,6 +83,49 @@ function calcInactivityAction(
|
|
|
80
83
|
bufferMs: number
|
|
81
84
|
}
|
|
82
85
|
```
|
|
86
|
+
|
|
87
|
+
`http` 相关工具简化说明:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
interface CreateAuthHttpClientOptions {
|
|
91
|
+
axios: any
|
|
92
|
+
baseURL?: string
|
|
93
|
+
timeout?: number
|
|
94
|
+
getUserStore: () => any
|
|
95
|
+
getLoadingStore: () => any
|
|
96
|
+
authLoginUrl?: string
|
|
97
|
+
ssoStorageKey?: string
|
|
98
|
+
refreshUrl: string
|
|
99
|
+
inactiveExpireMs?: number
|
|
100
|
+
inactiveBufferMs?: number
|
|
101
|
+
onShowError?: (msg: string) => void
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createAuthHttpClient(options: CreateAuthHttpClientOptions): {
|
|
105
|
+
http: any // 已挂好请求/响应拦截器的 axios 实例
|
|
106
|
+
logoutToAuth: () => void // 同 createLogoutToAuth,便于复用
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`permission` 相关工具简化说明:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
interface PermissionHelperOptions {
|
|
114
|
+
// 返回用户 Store 实例,需至少提供 getUserALLMenu 或 getUserPageMenu
|
|
115
|
+
getUserStore: () => any
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface PermissionHelper {
|
|
119
|
+
// 判断是否拥有某个页面(路由)权限
|
|
120
|
+
hasPagePermission: (path: string) => boolean
|
|
121
|
+
// 判断是否拥有某个按钮权限(约定使用菜单 path 作为唯一标识)
|
|
122
|
+
hasButtonPermission: (codeOrPath: string) => boolean
|
|
123
|
+
// 判断是否拥有给定列表中的任意一个权限
|
|
124
|
+
hasAnyPermission: (codesOrPaths: string[]) => boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createPermissionHelper(options: PermissionHelperOptions): PermissionHelper
|
|
128
|
+
```
|
|
83
129
|
```
|
|
84
130
|
|
|
85
131
|
## 子系统接入步骤
|
|
@@ -159,19 +205,19 @@ export const useUserStore = defineStore('user', {
|
|
|
159
205
|
|
|
160
206
|
### 3. 准备 Tab Store(可选)
|
|
161
207
|
|
|
162
|
-
Tab Store 主要用于和各系统的 Tab
|
|
208
|
+
Tab Store 主要用于和各系统的 Tab 控件协作,例如在“手动退出”时统一关闭所有可关闭的 Tab。当前路由内核在菜单加载 / 刷新场景下不会自动清空 Tab。
|
|
163
209
|
|
|
164
210
|
建议至少包含:
|
|
165
211
|
|
|
166
|
-
- `
|
|
212
|
+
- `closeAll()`:关闭所有可关闭的 Tab(保留固定首页等不可关闭项)
|
|
167
213
|
|
|
168
214
|
示例:
|
|
169
215
|
|
|
170
216
|
```ts
|
|
171
217
|
export const useTabStore = defineStore('tab', {
|
|
172
218
|
actions: {
|
|
173
|
-
|
|
174
|
-
//
|
|
219
|
+
closeAll() {
|
|
220
|
+
// 关闭所有可关闭的 Tab(保留固定首页等不可关闭项)
|
|
175
221
|
}
|
|
176
222
|
}
|
|
177
223
|
})
|
|
@@ -211,61 +257,84 @@ const router = createAppRouter({
|
|
|
211
257
|
|
|
212
258
|
export default router
|
|
213
259
|
```
|
|
214
|
-
### 6
|
|
260
|
+
### 6. 使用 createAuthHttpClient 统一封装 http(推荐)
|
|
215
261
|
|
|
216
|
-
在各子系统的 http
|
|
262
|
+
在各子系统的 http 封装中,推荐直接使用 `createAuthHttpClient`,即可一次性接入:
|
|
263
|
+
|
|
264
|
+
- 全局 Loading 控制(基于 `getLoadingStore` 的 `startLoading/stopLoading`)
|
|
265
|
+
- 基于最后操作时间的“2 小时未操作自动退出”
|
|
266
|
+
- 距离过期 5 分钟以内自动刷新 access_token
|
|
267
|
+
- 401 统一退出到认证站并弹出错误提示
|
|
268
|
+
|
|
269
|
+
示例:
|
|
217
270
|
|
|
218
271
|
```ts
|
|
219
272
|
// src/utils/http.ts
|
|
220
273
|
import axios from 'axios'
|
|
221
|
-
import {
|
|
274
|
+
import { ElMessage } from 'element-plus'
|
|
275
|
+
import { createAuthHttpClient } from '@litianxiang/portal-core'
|
|
222
276
|
import { useUserStore } from '@/stores/user'
|
|
223
277
|
import { useLoadingStore } from '@/stores/loading'
|
|
224
278
|
|
|
225
|
-
const
|
|
279
|
+
const AUTH_LOGIN_URL =
|
|
280
|
+
import.meta.env.VITE_AUTH_LOGIN_URL || `${window.location.origin}/auth/#/login`
|
|
281
|
+
|
|
282
|
+
const { http, logoutToAuth } = createAuthHttpClient({
|
|
283
|
+
axios,
|
|
284
|
+
baseURL: '',
|
|
285
|
+
timeout: 10000,
|
|
226
286
|
getUserStore: () => useUserStore(),
|
|
227
287
|
getLoadingStore: () => useLoadingStore(),
|
|
228
|
-
authLoginUrl:
|
|
288
|
+
authLoginUrl: AUTH_LOGIN_URL,
|
|
289
|
+
ssoStorageKey: 'user-store',
|
|
290
|
+
refreshUrl: '/proxy/auth/api/token/refresh',
|
|
291
|
+
inactiveExpireMs: 2 * 60 * 60 * 1000,
|
|
292
|
+
inactiveBufferMs: 5 * 60 * 1000,
|
|
293
|
+
onShowError: (msg: string) => {
|
|
294
|
+
ElMessage.error(msg)
|
|
295
|
+
}
|
|
229
296
|
})
|
|
230
297
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const { shouldLogout, shouldRefresh } = calcInactivityAction(
|
|
234
|
-
userStore.lastActiveTime,
|
|
235
|
-
Date.now()
|
|
236
|
-
)
|
|
298
|
+
// 业务代码直接使用 http 即可
|
|
299
|
+
// http.post('/api/demo', data)
|
|
237
300
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
301
|
+
export default http
|
|
302
|
+
export { logoutToAuth }
|
|
303
|
+
```
|
|
242
304
|
|
|
243
|
-
|
|
244
|
-
// 此处调用后端刷新 token 接口,成功后更新 userStore 中的 token
|
|
245
|
-
}
|
|
305
|
+
如需完全自定义 http 行为,也可以直接基于前面的 `auth` 工具自行编写拦截器。
|
|
246
306
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
config.headers.Authorization = `Bearer ${userStore.getUserAccessToken}`
|
|
251
|
-
userStore.updateLastActiveTime?.()
|
|
252
|
-
}
|
|
307
|
+
### 7.(可选)接入菜单 / 按钮权限判断
|
|
308
|
+
|
|
309
|
+
在需要做“是否显示某个按钮”或“是否放行某个页面”时,可以使用 `createPermissionHelper` 来基于菜单数据做权限判断:
|
|
253
310
|
|
|
254
|
-
|
|
311
|
+
```ts
|
|
312
|
+
// 例如在某个业务系统的全局 helper 中:
|
|
313
|
+
import { createPermissionHelper } from '@litianxiang/portal-core'
|
|
314
|
+
import { useUserStore } from '@/stores/user'
|
|
315
|
+
|
|
316
|
+
const permission = createPermissionHelper({
|
|
317
|
+
getUserStore: () => useUserStore()
|
|
255
318
|
})
|
|
256
319
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
320
|
+
// 页面/路由级权限判断(基于菜单的 path,忽略 query 部分)
|
|
321
|
+
permission.hasPagePermission('/system/menu')
|
|
322
|
+
|
|
323
|
+
// 按钮权限判断(约定后台为按钮类型菜单配置唯一的 path 作为权限码)
|
|
324
|
+
permission.hasButtonPermission('/system/menu:add')
|
|
325
|
+
|
|
326
|
+
// 任一权限命中即通过(可用于复杂场景)
|
|
327
|
+
permission.hasAnyPermission([
|
|
328
|
+
'/system/menu:add',
|
|
329
|
+
'/system/menu:edit'
|
|
330
|
+
])
|
|
266
331
|
```
|
|
267
332
|
|
|
268
|
-
|
|
333
|
+
> 说明:
|
|
334
|
+
> - `createPermissionHelper` 内部会优先使用 `userStore.getUserPageMenu`(包含 page + button),若不存在则退回 `userStore.getUserALLMenu`;
|
|
335
|
+
> - 匹配时仅按基础路径(去掉 `?query`)比较,避免 query 影响权限判断。
|
|
336
|
+
|
|
337
|
+
### 8. 项目视图目录约定
|
|
269
338
|
|
|
270
339
|
`portal-core` 会根据后端菜单返回的 `path` 去匹配前端视图,规则如下:
|
|
271
340
|
|
package/dist/index.d.ts
CHANGED
|
@@ -50,4 +50,44 @@ interface InactivityResult {
|
|
|
50
50
|
*/
|
|
51
51
|
declare function calcInactivityAction(lastActiveTime: number | null | undefined, now: number, config?: InactivityConfig): InactivityResult;
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
interface CreateAuthHttpClientOptions {
|
|
54
|
+
axios: any;
|
|
55
|
+
baseURL?: string;
|
|
56
|
+
timeout?: number;
|
|
57
|
+
getUserStore: () => any;
|
|
58
|
+
getLoadingStore: () => any;
|
|
59
|
+
authLoginUrl?: string;
|
|
60
|
+
ssoStorageKey?: string;
|
|
61
|
+
refreshUrl: string;
|
|
62
|
+
inactiveExpireMs?: number;
|
|
63
|
+
inactiveBufferMs?: number;
|
|
64
|
+
onShowError?: (msg: string) => void;
|
|
65
|
+
}
|
|
66
|
+
declare function createAuthHttpClient(options: CreateAuthHttpClientOptions): {
|
|
67
|
+
http: any;
|
|
68
|
+
logoutToAuth: () => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
interface PermissionHelperOptions {
|
|
72
|
+
/** 返回用户 Store 实例,需至少提供 getUserALLMenu 或 getUserPageMenu */
|
|
73
|
+
getUserStore: () => any;
|
|
74
|
+
}
|
|
75
|
+
interface PermissionHelper {
|
|
76
|
+
/**
|
|
77
|
+
* 判断当前用户是否拥有某个页面(路由)权限
|
|
78
|
+
* @param path 完整路径,支持携带 query(内部只按基础路径匹配)
|
|
79
|
+
*/
|
|
80
|
+
hasPagePermission: (path: string) => boolean;
|
|
81
|
+
/**
|
|
82
|
+
* 判断当前用户是否拥有某个按钮权限
|
|
83
|
+
* 约定:按钮权限同样使用菜单的 path 字段作为唯一标识
|
|
84
|
+
*/
|
|
85
|
+
hasButtonPermission: (codeOrPath: string) => boolean;
|
|
86
|
+
/**
|
|
87
|
+
* 判断当前用户是否拥有给定列表中的任意一个权限(页面或按钮)
|
|
88
|
+
*/
|
|
89
|
+
hasAnyPermission: (codesOrPaths: string[]) => boolean;
|
|
90
|
+
}
|
|
91
|
+
declare function createPermissionHelper(options: PermissionHelperOptions): PermissionHelper;
|
|
92
|
+
|
|
93
|
+
export { type AppRouterOptions, type CreateAuthHttpClientOptions, type InactivityConfig, type InactivityResult, type LogoutToAuthOptions, type PermissionHelper, type PermissionHelperOptions, calcInactivityAction, createAppRouter, createAuthHttpClient, createLogoutToAuth, createPermissionHelper };
|
package/dist/index.js
CHANGED
|
@@ -222,8 +222,214 @@ function calcInactivityAction(lastActiveTime, now, config = {}) {
|
|
|
222
222
|
bufferMs
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
+
|
|
226
|
+
// src/http.ts
|
|
227
|
+
function createAuthHttpClient(options) {
|
|
228
|
+
const {
|
|
229
|
+
axios,
|
|
230
|
+
baseURL = "",
|
|
231
|
+
timeout = 1e4,
|
|
232
|
+
getUserStore,
|
|
233
|
+
getLoadingStore,
|
|
234
|
+
authLoginUrl,
|
|
235
|
+
ssoStorageKey = "user-store",
|
|
236
|
+
refreshUrl,
|
|
237
|
+
inactiveExpireMs,
|
|
238
|
+
inactiveBufferMs,
|
|
239
|
+
onShowError
|
|
240
|
+
} = options;
|
|
241
|
+
const http = axios.create({
|
|
242
|
+
baseURL,
|
|
243
|
+
timeout
|
|
244
|
+
});
|
|
245
|
+
const logoutToAuth = createLogoutToAuth({
|
|
246
|
+
getUserStore,
|
|
247
|
+
getLoadingStore,
|
|
248
|
+
authLoginUrl,
|
|
249
|
+
ssoStorageKey
|
|
250
|
+
});
|
|
251
|
+
let isRefreshing = false;
|
|
252
|
+
let isHandling401 = false;
|
|
253
|
+
http.interceptors.request.use(async (config) => {
|
|
254
|
+
const loadingStore = getLoadingStore();
|
|
255
|
+
loadingStore?.startLoading?.();
|
|
256
|
+
const userStore = getUserStore();
|
|
257
|
+
const accessToken = userStore.getUserAccessToken;
|
|
258
|
+
const refreshToken = userStore.getUserRefreshToken;
|
|
259
|
+
if (!accessToken || !refreshToken) {
|
|
260
|
+
return config;
|
|
261
|
+
}
|
|
262
|
+
const lastActiveTime = userStore.getUserLastActiveTime ?? 0;
|
|
263
|
+
const currentTime = Date.now();
|
|
264
|
+
const { shouldLogout, shouldRefresh } = calcInactivityAction(lastActiveTime, currentTime, {
|
|
265
|
+
expireMs: inactiveExpireMs,
|
|
266
|
+
bufferMs: inactiveBufferMs
|
|
267
|
+
});
|
|
268
|
+
if (shouldLogout) {
|
|
269
|
+
logoutToAuth();
|
|
270
|
+
return Promise.reject(new Error("INACTIVE_LOGOUT"));
|
|
271
|
+
}
|
|
272
|
+
if (!lastActiveTime) {
|
|
273
|
+
userStore.setUserLastActiveTime?.(currentTime);
|
|
274
|
+
} else {
|
|
275
|
+
if (shouldRefresh && !isRefreshing) {
|
|
276
|
+
isRefreshing = true;
|
|
277
|
+
try {
|
|
278
|
+
const response = await axios.post(refreshUrl, {}, {
|
|
279
|
+
headers: { Authorization: `Bearer ${refreshToken}` }
|
|
280
|
+
});
|
|
281
|
+
const result = response.data;
|
|
282
|
+
if (result.code === 200 && result.data?.access_token) {
|
|
283
|
+
const newAccessToken = result.data.access_token;
|
|
284
|
+
config.headers = config.headers || {};
|
|
285
|
+
config.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
286
|
+
userStore.setUserAccessToken?.(newAccessToken);
|
|
287
|
+
userStore.setUserLastActiveTime?.(Date.now());
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logoutToAuth();
|
|
291
|
+
return Promise.reject(error);
|
|
292
|
+
} finally {
|
|
293
|
+
isRefreshing = false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
config.headers = config.headers || {};
|
|
298
|
+
config.headers.Authorization = `Bearer ${accessToken}`;
|
|
299
|
+
userStore.setUserLastActiveTime?.(Date.now());
|
|
300
|
+
return config;
|
|
301
|
+
});
|
|
302
|
+
function buildErrorMessage(status, message, statusText) {
|
|
303
|
+
let msg = "";
|
|
304
|
+
switch (status) {
|
|
305
|
+
case 400:
|
|
306
|
+
msg = "\u8BF7\u6C42\u53C2\u6570\u9519\u8BEF";
|
|
307
|
+
break;
|
|
308
|
+
case 401:
|
|
309
|
+
msg = "\u672A\u6388\u6743\uFF0C\u8BF7\u767B\u5F55";
|
|
310
|
+
break;
|
|
311
|
+
case 403:
|
|
312
|
+
msg = "\u65E0\u6743\u9650\u8BBF\u95EE\u6216\u670D\u52A1\u6682\u4E0D\u53EF\u7528";
|
|
313
|
+
break;
|
|
314
|
+
case 404:
|
|
315
|
+
msg = "\u8BF7\u6C42\u5730\u5740\u51FA\u9519";
|
|
316
|
+
break;
|
|
317
|
+
case 408:
|
|
318
|
+
msg = "\u8BF7\u6C42\u8D85\u65F6";
|
|
319
|
+
break;
|
|
320
|
+
case 500:
|
|
321
|
+
msg = "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF";
|
|
322
|
+
break;
|
|
323
|
+
case 501:
|
|
324
|
+
msg = "\u670D\u52A1\u672A\u5B9E\u73B0";
|
|
325
|
+
break;
|
|
326
|
+
case 502:
|
|
327
|
+
msg = "\u7F51\u5173\u9519\u8BEF";
|
|
328
|
+
break;
|
|
329
|
+
case 503:
|
|
330
|
+
msg = "\u670D\u52A1\u4E0D\u53EF\u7528";
|
|
331
|
+
break;
|
|
332
|
+
case 504:
|
|
333
|
+
msg = "\u7F51\u5173\u8D85\u65F6";
|
|
334
|
+
break;
|
|
335
|
+
case 505:
|
|
336
|
+
msg = "HTTP\u7248\u672C\u4E0D\u53D7\u652F\u6301";
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
msg = "\u8BF7\u6C42\u9519\u8BEF";
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
const baseMsg = message || msg;
|
|
343
|
+
if (!status) return baseMsg;
|
|
344
|
+
return `\u8BF7\u6C42\u72B6\u6001\uFF1A${status}\uFF0C${baseMsg}\u3002\u9519\u8BEF\u4FE1\u606F\uFF1A${statusText || ""}`;
|
|
345
|
+
}
|
|
346
|
+
http.interceptors.response.use(
|
|
347
|
+
(response) => {
|
|
348
|
+
const loadingStore = getLoadingStore();
|
|
349
|
+
loadingStore?.stopLoading?.();
|
|
350
|
+
return response.data;
|
|
351
|
+
},
|
|
352
|
+
(error) => {
|
|
353
|
+
const loadingStore = getLoadingStore();
|
|
354
|
+
loadingStore?.stopLoading?.();
|
|
355
|
+
const status = error.response?.status;
|
|
356
|
+
const rawMsg = error.response?.data?.message;
|
|
357
|
+
const statusText = error.response?.statusText;
|
|
358
|
+
const fullMsg = buildErrorMessage(status, rawMsg, statusText);
|
|
359
|
+
if (status === 401) {
|
|
360
|
+
if (!isHandling401) {
|
|
361
|
+
isHandling401 = true;
|
|
362
|
+
onShowError?.(fullMsg);
|
|
363
|
+
logoutToAuth();
|
|
364
|
+
}
|
|
365
|
+
return Promise.reject(error);
|
|
366
|
+
}
|
|
367
|
+
onShowError?.(fullMsg);
|
|
368
|
+
return Promise.reject(error);
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
return { http, logoutToAuth };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/permission.ts
|
|
375
|
+
function normalizePath(raw) {
|
|
376
|
+
if (!raw) return "";
|
|
377
|
+
const [base] = raw.split("?");
|
|
378
|
+
if (!base) return "";
|
|
379
|
+
return base.startsWith("/") ? base : `/${base}`;
|
|
380
|
+
}
|
|
381
|
+
function findMenuByPath(menuList, targetPath, category) {
|
|
382
|
+
const target = normalizePath(targetPath);
|
|
383
|
+
if (!target || !Array.isArray(menuList)) return null;
|
|
384
|
+
for (const item of menuList) {
|
|
385
|
+
const current = normalizePath(item?.path);
|
|
386
|
+
if (current === target) {
|
|
387
|
+
if (!category || item?.category === category) {
|
|
388
|
+
return item;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (item?.children && item.children.length > 0) {
|
|
392
|
+
const found = findMenuByPath(item.children, targetPath, category);
|
|
393
|
+
if (found) return found;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
function createPermissionHelper(options) {
|
|
399
|
+
const { getUserStore } = options;
|
|
400
|
+
const getMenuTree = () => {
|
|
401
|
+
const userStore = getUserStore();
|
|
402
|
+
const pageMenu = userStore?.getUserPageMenu;
|
|
403
|
+
const allMenu = userStore?.getUserALLMenu;
|
|
404
|
+
if (Array.isArray(pageMenu)) return pageMenu;
|
|
405
|
+
if (Array.isArray(allMenu)) return allMenu;
|
|
406
|
+
return [];
|
|
407
|
+
};
|
|
408
|
+
const hasPagePermission = (path) => {
|
|
409
|
+
const menuTree = getMenuTree();
|
|
410
|
+
const item = findMenuByPath(menuTree, path);
|
|
411
|
+
if (!item) return false;
|
|
412
|
+
return item.category === "page" || item.category === "flow-page";
|
|
413
|
+
};
|
|
414
|
+
const hasButtonPermission = (codeOrPath) => {
|
|
415
|
+
const menuTree = getMenuTree();
|
|
416
|
+
const item = findMenuByPath(menuTree, codeOrPath, "button");
|
|
417
|
+
return !!item;
|
|
418
|
+
};
|
|
419
|
+
const hasAnyPermission = (codesOrPaths) => {
|
|
420
|
+
if (!Array.isArray(codesOrPaths) || codesOrPaths.length === 0) return false;
|
|
421
|
+
return codesOrPaths.some((code) => hasPagePermission(code) || hasButtonPermission(code));
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
hasPagePermission,
|
|
425
|
+
hasButtonPermission,
|
|
426
|
+
hasAnyPermission
|
|
427
|
+
};
|
|
428
|
+
}
|
|
225
429
|
export {
|
|
226
430
|
calcInactivityAction,
|
|
227
431
|
createAppRouter,
|
|
228
|
-
|
|
432
|
+
createAuthHttpClient,
|
|
433
|
+
createLogoutToAuth,
|
|
434
|
+
createPermissionHelper
|
|
229
435
|
};
|