@mu-cabin/opms-permission 0.1.0

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 ADDED
@@ -0,0 +1,86 @@
1
+ # @mu-cabin/opms-permission
2
+
3
+ ## 简介
4
+
5
+ `@mu-cabin/opms-permission` 是一个用于 OPMS 权限与认证管理的前端 SDK,支持资源、菜单、组织、用户信息的统一获取与本地缓存,适用于需要接入 OPMS 权限体系的前端项目。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pnpm add @mu-cabin/opms-permission
11
+ # 或
12
+ yarn add @mu-cabin/opms-permission
13
+ # 或
14
+ npm install @mu-cabin/opms-permission
15
+ ```
16
+
17
+ ## 主要功能
18
+ - SSO 单点登录/登出跳转
19
+ - 用户信息获取与本地缓存
20
+ - 资源、菜单、控件权限获取与处理
21
+ - 组织树、公司信息查询
22
+ - 本地存储自动过期管理
23
+
24
+ ## 快速开始
25
+
26
+ ```ts
27
+ import { OpmsPermission, jumpToSSOLogin, jumpToSSOLogout } from '@mu-cabin/opms-permission';
28
+
29
+ const permission = new OpmsPermission({
30
+ systemId: 1001, // 系统ID
31
+ baseUrl: 'https://your-api-domain/opms', // OPMS后端接口地址
32
+ ssoBaseUrl: 'https://your-sso-domain', // SSO登录地址
33
+ });
34
+
35
+ // 登录(需URL带有code参数)
36
+ await permission.login();
37
+
38
+ // 获取用户信息
39
+ const userInfo = await permission.getUserInfo();
40
+
41
+ // 获取资源、菜单、控件权限
42
+ const { resources, menuList, widgetMap } = await permission.getResources(userInfo.account);
43
+
44
+ // 跳转到SSO登录
45
+ jumpToSSOLogin({ baseUrl: 'https://your-sso-domain' });
46
+
47
+ // 跳转到SSO登出
48
+ jumpToSSOLogout({ baseUrl: 'https://your-sso-domain', redirectToUrl: window.location.origin });
49
+ ```
50
+
51
+ ## 主要API
52
+
53
+ ### 类:OpmsPermission
54
+
55
+ | 方法 | 说明 |
56
+ | ---- | ---- |
57
+ | `login()` | SSO登录,获取并缓存token |
58
+ | `logout()` | 登出并清除本地缓存 |
59
+ | `getUserInfo()` | 获取当前用户信息 |
60
+ | `getResources(uniAccount)` | 获取资源、菜单、控件权限 |
61
+ | `queryOrgs()` | 获取组织树 |
62
+ | `queryCompanies()` | 获取公司信息 |
63
+ | `isLogin()` | 判断当前是否已登录 |
64
+ | `getToken()` | 获取当前token |
65
+
66
+ ### SSO 工具方法
67
+ - `jumpToSSOLogin({ baseUrl, redirectUrl?, clientId? })` 跳转到SSO登录页
68
+ - `jumpToSSOLogout({ baseUrl, redirectToUrl, clientId? })` 跳转到SSO登出页
69
+
70
+ ## 类型说明
71
+
72
+ - `Resource` 资源对象,包含资源ID、类型、路径、子节点等
73
+ - `MenuItem` 菜单项对象,包含菜单路径、名称、子菜单等
74
+ - `UserInfo` 用户信息对象,包含账号、姓名、工号、组织等
75
+ - `UserOrganization` 用户组织信息
76
+
77
+ ## 常见问题
78
+
79
+ 1. **为什么获取不到资源/菜单?**
80
+ - 请确保已正确登录(URL带有code参数),并且接口地址、systemId 配置正确。
81
+ 2. **本地缓存多久失效?**
82
+ - 资源、菜单等默认缓存24小时,超时会自动重新拉取。
83
+ 3. **如何自定义SSO clientId?**
84
+ - `jumpToSSOLogin` 和 `jumpToSSOLogout` 方法均支持传入 `clientId` 参数。
85
+
86
+ ## License
package/dist/index.cjs ADDED
@@ -0,0 +1,529 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ OpmsPermission: () => Permission,
34
+ jumpToSSOLogin: () => jumpToSSOLogin,
35
+ jumpToSSOLogout: () => jumpToSSOLogout
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/utils/storage.ts
40
+ var Storage = class {
41
+ setSystemId(systemId) {
42
+ this.systemId = systemId;
43
+ }
44
+ prefixKey(key) {
45
+ return this.systemId !== void 0 ? `systemId-${this.systemId}-${key}` : key;
46
+ }
47
+ /**
48
+ * Set an item in localStorage, with optional expiration in minutes
49
+ */
50
+ setItem(key, value, expireMinutes) {
51
+ const data = {
52
+ value,
53
+ expire: expireMinutes ? Date.now() + expireMinutes * 60 * 1e3 : null
54
+ };
55
+ localStorage.setItem(this.prefixKey(key), JSON.stringify(data));
56
+ }
57
+ /**
58
+ * Get an item from localStorage, returns undefined if expired or not found
59
+ */
60
+ getItem(key) {
61
+ const raw = localStorage.getItem(this.prefixKey(key));
62
+ if (!raw) return void 0;
63
+ try {
64
+ const data = JSON.parse(raw);
65
+ if (data.expire && Date.now() > data.expire) {
66
+ localStorage.removeItem(this.prefixKey(key));
67
+ return void 0;
68
+ }
69
+ return data.value;
70
+ } catch {
71
+ return void 0;
72
+ }
73
+ }
74
+ /**
75
+ * Remove an item from localStorage
76
+ */
77
+ removeItem(key) {
78
+ localStorage.removeItem(this.prefixKey(key));
79
+ }
80
+ };
81
+ var storage = new Storage();
82
+
83
+ // src/utils/dataHandler.ts
84
+ function getCompletePath(item) {
85
+ const menuUrl = item.openIndicator === "INNER_COMPONENT" ? item.componentPath : item.defaultUrl;
86
+ return menuUrl ?? "";
87
+ }
88
+ function getMenuName(item) {
89
+ return item.resourceType !== "FOLDER" ? item.resourceName || "" : (item.showName ?? item.resourceName) || "";
90
+ }
91
+ function createResourceMap(items) {
92
+ const resourceMap = {};
93
+ const widgetMap = {};
94
+ function addToMap(items2) {
95
+ items2.forEach((item) => {
96
+ const newItem = { ...item };
97
+ if (newItem.defaultResourceType === "WIDGET") {
98
+ if (newItem.componentPath) widgetMap[newItem.componentPath] = newItem;
99
+ } else {
100
+ const path = getCompletePath(newItem);
101
+ if (path) {
102
+ resourceMap[path] = newItem;
103
+ }
104
+ }
105
+ if (newItem.children) {
106
+ addToMap(newItem.children);
107
+ }
108
+ });
109
+ }
110
+ addToMap(items);
111
+ return { resourceMap, widgetMap };
112
+ }
113
+ function isMenuItem(item) {
114
+ const resourceType = item.resourceType ?? item.defaultResourceType;
115
+ return resourceType !== "WIDGET";
116
+ }
117
+ function createMenuList(items) {
118
+ const menus = [];
119
+ const menuMap = {};
120
+ const filterMenus = (items2, container) => {
121
+ items2.forEach((item) => {
122
+ const icon = item.icon || item.defaultIcon;
123
+ const path = getCompletePath(item);
124
+ const name = getMenuName(item);
125
+ const newItem = {
126
+ icon,
127
+ path,
128
+ name,
129
+ children: [],
130
+ openIndicator: item.openIndicator,
131
+ resourceId: item.resourceId
132
+ };
133
+ menuMap[newItem.resourceId] = newItem;
134
+ if (isMenuItem(item)) {
135
+ container.push(newItem);
136
+ }
137
+ if (item.children) {
138
+ filterMenus(item.children, newItem.children);
139
+ }
140
+ });
141
+ };
142
+ filterMenus(items, menus);
143
+ return { menuList: menus, menuMap };
144
+ }
145
+ function iterateNestedArray(items, callback) {
146
+ function trevel(items2, parent) {
147
+ return items2.map((item) => {
148
+ const handledItem = callback(item, parent);
149
+ if (handledItem && handledItem.children) {
150
+ handledItem.children = trevel(handledItem.children, handledItem);
151
+ }
152
+ return handledItem;
153
+ }).filter((v) => v);
154
+ }
155
+ return trevel(items);
156
+ }
157
+ function findFirstEnableCode(data) {
158
+ if (!Array.isArray(data) || data.length === 0) {
159
+ return null;
160
+ }
161
+ let queue = [...data];
162
+ while (queue.length > 0) {
163
+ const node = queue.shift();
164
+ if (!node?.disabled) {
165
+ return node?.value || null;
166
+ }
167
+ if (Array.isArray(node.children) && node.children.length > 0) {
168
+ queue = queue.concat(node.children);
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ function findAllEnableTopLevelCodes(nodes) {
174
+ const codes = [];
175
+ function findEnabledNodes(nodes2) {
176
+ for (const node of nodes2) {
177
+ if (!node.disabled) {
178
+ codes.push(node.value);
179
+ } else if (node.children) {
180
+ findEnabledNodes(node.children);
181
+ }
182
+ }
183
+ }
184
+ findEnabledNodes(nodes);
185
+ return codes;
186
+ }
187
+
188
+ // src/api.ts
189
+ var import_axios = __toESM(require("axios"));
190
+
191
+ // src/config.ts
192
+ var USER_INFO_KEY = "opms_user_info";
193
+ var RESOURCE_KEY = "opms_resources";
194
+ var TOKEN_KEY = "omps_authorization";
195
+ var ORG_COMPANY_KEY = "opms_org_company";
196
+
197
+ // src/api.ts
198
+ var axiosClient = import_axios.default.create();
199
+ axiosClient.interceptors.request.use((config) => {
200
+ const token = storage.getItem(TOKEN_KEY);
201
+ if (token) {
202
+ config.headers.Authorization = token;
203
+ }
204
+ return config;
205
+ });
206
+ axiosClient.interceptors.response.use(
207
+ (response) => {
208
+ const res = response.data;
209
+ const { code, msg } = res;
210
+ if (code !== 200) {
211
+ throw new Error(msg);
212
+ }
213
+ return res;
214
+ },
215
+ (error) => {
216
+ return Promise.reject(error);
217
+ }
218
+ );
219
+ async function login(baseUrl, authorizationCode) {
220
+ return await axiosClient.get(
221
+ `${baseUrl}/opmsDefaultAuth/oauthLogin`,
222
+ {
223
+ params: { authorizationCode }
224
+ }
225
+ );
226
+ }
227
+ async function logout(baseUrl) {
228
+ return await axiosClient.post(
229
+ `${baseUrl}/opmsDefaultAuth/logout`
230
+ );
231
+ }
232
+ async function getUserInfo(baseUrl) {
233
+ return await axiosClient.post(
234
+ `${baseUrl}/opmsDefaultUser/userInfo`,
235
+ {}
236
+ );
237
+ }
238
+ async function queryResource(baseUrl, params) {
239
+ return await axiosClient.post(
240
+ `${baseUrl}/opmsDefaultUser/userResource`,
241
+ params
242
+ );
243
+ }
244
+ async function getUserOrgTree(baseUrl, params) {
245
+ return await axiosClient.post(
246
+ `${baseUrl}/opmsDefaultUser/orgTree`,
247
+ params
248
+ );
249
+ }
250
+ async function queryOrgCompanies(baseUrl, params) {
251
+ return await axiosClient.post(
252
+ `${baseUrl}/opmsDefaultUser/branches`,
253
+ params
254
+ );
255
+ }
256
+
257
+ // src/permission.ts
258
+ var Permission = class {
259
+ constructor(options) {
260
+ this._userInfo = null;
261
+ this._orgTree = null;
262
+ this._orgCompany = null;
263
+ this.resources = [];
264
+ this.resourceMap = {};
265
+ this.widgetMap = {};
266
+ this.menuList = [];
267
+ this.menuMap = {};
268
+ this.baseUrl = options.baseUrl;
269
+ this.systemId = options.systemId;
270
+ storage.setSystemId(this.systemId);
271
+ }
272
+ /**
273
+ * Login using code from URL, save userInfo
274
+ */
275
+ async login() {
276
+ const url = new URL(window.location.href);
277
+ const code = url.searchParams.get("code");
278
+ if (!code) throw new Error("No code found in URL");
279
+ const { obj, success, msg } = await login(this.baseUrl, code);
280
+ if (!success) {
281
+ throw new Error(msg);
282
+ }
283
+ const { token } = obj;
284
+ url.searchParams.delete("code");
285
+ storage.setItem(TOKEN_KEY, token);
286
+ return token;
287
+ }
288
+ /**
289
+ * Logout and clear userInfo
290
+ */
291
+ async logout() {
292
+ await logout(this.baseUrl);
293
+ this.clear();
294
+ }
295
+ clear() {
296
+ this._userInfo = null;
297
+ this._orgTree = null;
298
+ this._orgCompany = null;
299
+ this.resources = [];
300
+ this.resourceMap = {};
301
+ this.widgetMap = {};
302
+ this.menuList = [];
303
+ this.menuMap = {};
304
+ storage.removeItem(RESOURCE_KEY);
305
+ storage.removeItem(TOKEN_KEY);
306
+ storage.removeItem(USER_INFO_KEY);
307
+ storage.removeItem(ORG_COMPANY_KEY);
308
+ }
309
+ async getUserInfo() {
310
+ const data = await getUserInfo(this.baseUrl);
311
+ const { obj, success, msg } = data;
312
+ if (!success) {
313
+ return Promise.reject(new Error(msg));
314
+ }
315
+ this._userInfo = obj;
316
+ return obj;
317
+ }
318
+ /**
319
+ * Get resources and process to menuList, menuMap, widgetMap (matches app store logic)
320
+ */
321
+ async getResources() {
322
+ const resourcesObj = storage.getItem(RESOURCE_KEY) || null;
323
+ if (resourcesObj) {
324
+ const { menuList: menuList2, menuMap: menuMap2 } = createMenuList(resourcesObj.resources);
325
+ return {
326
+ resources: resourcesObj.resources,
327
+ resourceMap: resourcesObj.resourceMap,
328
+ widgetMap: resourcesObj.widgetMap,
329
+ menuList: menuList2,
330
+ menuMap: menuMap2
331
+ };
332
+ }
333
+ const { obj, success, msg } = await queryResource(this.baseUrl, {
334
+ systemId: this.systemId
335
+ });
336
+ if (!success) {
337
+ return Promise.reject(new Error(msg));
338
+ }
339
+ const resources = obj;
340
+ const { resourceMap, widgetMap } = createResourceMap(resources);
341
+ const { menuList, menuMap } = createMenuList(resources);
342
+ this.resources = resources;
343
+ this.resourceMap = resourceMap;
344
+ this.widgetMap = widgetMap;
345
+ this.menuList = menuList;
346
+ this.menuMap = menuMap;
347
+ storage.setItem(
348
+ RESOURCE_KEY,
349
+ {
350
+ resources,
351
+ resourceMap,
352
+ widgetMap
353
+ },
354
+ 60 * 24
355
+ // 24 hours
356
+ );
357
+ return {
358
+ resources,
359
+ resourceMap,
360
+ widgetMap,
361
+ menuList,
362
+ menuMap
363
+ };
364
+ }
365
+ /**
366
+ * Query and process organization tree
367
+ */
368
+ async queryOrgs() {
369
+ try {
370
+ const data = await getUserOrgTree(this.baseUrl, {
371
+ systemId: this.systemId,
372
+ name: "COS"
373
+ });
374
+ const { obj, success, msg } = data;
375
+ if (!success) {
376
+ return Promise.reject(new Error(msg));
377
+ }
378
+ const orgTree = obj;
379
+ const newTree = iterateNestedArray([orgTree], (item) => {
380
+ return {
381
+ label: item.orgShortName,
382
+ value: item.orgCode,
383
+ title: item.orgShortName,
384
+ key: item.orgCode,
385
+ nodeLevel: item.nodeLevel,
386
+ children: item.children,
387
+ disabled: !item.hasPermission,
388
+ orgType: item.orgType
389
+ };
390
+ })?.[0];
391
+ this._orgTree = newTree;
392
+ return newTree;
393
+ } catch (error) {
394
+ console.log(error);
395
+ return null;
396
+ }
397
+ }
398
+ async queryCompanies() {
399
+ try {
400
+ let orgCompanyList = storage.getItem(ORG_COMPANY_KEY);
401
+ if (!orgCompanyList) {
402
+ const { obj } = await queryOrgCompanies(this.baseUrl, { queryAllBranches: true });
403
+ orgCompanyList = obj;
404
+ storage.setItem(ORG_COMPANY_KEY, orgCompanyList);
405
+ }
406
+ this._orgCompany = orgCompanyList;
407
+ } catch (error) {
408
+ console.log(error);
409
+ }
410
+ }
411
+ isLogin() {
412
+ return !!storage.getItem(TOKEN_KEY);
413
+ }
414
+ getToken() {
415
+ return storage.getItem(TOKEN_KEY);
416
+ }
417
+ // --- Getters ---
418
+ get userInfo() {
419
+ if (!this._userInfo) {
420
+ this._userInfo = storage.getItem(USER_INFO_KEY);
421
+ }
422
+ if (!this._userInfo) return null;
423
+ const { account, name, ehrId, crewCode, crewId } = this._userInfo;
424
+ return { account, name, ehrId, crewCode, crewId };
425
+ }
426
+ get hasRootAuth() {
427
+ const orgs = this._userInfo?.organizations || [];
428
+ const first = orgs[0];
429
+ if (!first) return false;
430
+ return first.searchPath === "#1";
431
+ }
432
+ get userOrganizations() {
433
+ return this._userInfo?.userOrganizations || [];
434
+ }
435
+ get allCompanyOptions() {
436
+ const orgCompany = this._orgCompany;
437
+ if (!orgCompany) return [];
438
+ const list = orgCompany.map((v) => ({
439
+ label: v.orgName,
440
+ value: v.orgCode
441
+ }));
442
+ list.unshift({ label: "\u5168\u516C\u53F8", value: "EA" });
443
+ return list;
444
+ }
445
+ get companyOptions() {
446
+ const orgTree = this._orgTree;
447
+ if (!orgTree) return [];
448
+ const list = (orgTree.children || []).map(
449
+ ({ children, ...others }) => ({ ...others, disabled: false })
450
+ );
451
+ if (this.hasRootAuth) {
452
+ list.unshift({ ...orgTree, label: "\u5168\u516C\u53F8", children: [] });
453
+ }
454
+ return list;
455
+ }
456
+ get firstCompanyOrgCode() {
457
+ return this.companyOptions?.[0]?.value ?? "";
458
+ }
459
+ get unitOptions() {
460
+ const orgTree = this._orgTree;
461
+ if (!orgTree) return [];
462
+ return orgTree.disabled ? orgTree.children ?? [] : [orgTree];
463
+ }
464
+ get allUnitOptions() {
465
+ const orgTree = this._orgTree;
466
+ if (!orgTree) return [];
467
+ const newTree = iterateNestedArray(
468
+ [orgTree],
469
+ ({ disabled, ...other }) => ({ ...other })
470
+ )?.[0];
471
+ return newTree ? newTree.children ?? [] : [];
472
+ }
473
+ get firstUnitOrgCode() {
474
+ if (this.hasRootAuth) {
475
+ return this.companyOptions[1]?.value ?? "";
476
+ }
477
+ const orgTree = this._orgTree;
478
+ if (!orgTree) return "";
479
+ return findFirstEnableCode([orgTree]) ?? "";
480
+ }
481
+ get topLevelUnitOrgCodes() {
482
+ const orgTree = this._orgTree;
483
+ if (!orgTree) return [];
484
+ return findAllEnableTopLevelCodes(
485
+ Array.isArray(orgTree) ? orgTree : [orgTree]
486
+ );
487
+ }
488
+ };
489
+
490
+ // src/sso.ts
491
+ function jumpToSSOLogin({
492
+ baseUrl,
493
+ redirectUrl,
494
+ clientId = "testSA19"
495
+ }) {
496
+ const { location } = window;
497
+ const ssoUrl = new URL(`${baseUrl}/idp/oauth2/authorize`);
498
+ const defaultUrl = new URL(
499
+ location.origin + location.pathname + location.search
500
+ );
501
+ ssoUrl.searchParams.set("client_id", clientId);
502
+ ssoUrl.searchParams.set("response_type", "code");
503
+ ssoUrl.searchParams.set("redirect_uri", redirectUrl || defaultUrl.toString());
504
+ window.location.href = ssoUrl.toString();
505
+ }
506
+ function jumpToSSOLogout({
507
+ baseUrl,
508
+ redirectToUrl,
509
+ clientId = "testSA19"
510
+ }) {
511
+ if (!baseUrl) {
512
+ throw new Error("baseUrl is required");
513
+ }
514
+ const { location } = window;
515
+ const ssoLoginUrl = `${baseUrl}/idp/oauth2/authorize?client_id=${clientId}&response_type=code&redirect_uri=${location.origin}/`;
516
+ const redirectUrl = encodeURIComponent(redirectToUrl || ssoLoginUrl);
517
+ const logoutUrl = new URL(`${baseUrl}/idp/profile/OAUTH2/Redirect/GLO`);
518
+ logoutUrl.searchParams.set("response_type", "code");
519
+ logoutUrl.searchParams.set("entityId", clientId);
520
+ logoutUrl.searchParams.set("redirectToLogin", "true");
521
+ logoutUrl.searchParams.set("redirctToUrl", redirectUrl);
522
+ window.location.href = logoutUrl.toString();
523
+ }
524
+ // Annotate the CommonJS export names for ESM import in node:
525
+ 0 && (module.exports = {
526
+ OpmsPermission,
527
+ jumpToSSOLogin,
528
+ jumpToSSOLogout
529
+ });