@npm.365xs.cn/vue-admin-framework 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,63 @@
1
+ # @shinesun/vue-admin-framework
2
+
3
+ Vue 3 公共管理框架,提供 OAuth 2.0 Authorization Code + PKCE 客户端、登录回调页面、
4
+ 动态侧栏、顶部用户区和访问页签。
5
+
6
+ 消费应用负责提供 Vue Router、菜单树、用户资料、品牌配置和业务页面。包要求项目已安装
7
+ Vue 3、Vue Router、Element Plus 和 `@element-plus/icons-vue`。
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ npm install @shinesun/vue-admin-framework
13
+ ```
14
+
15
+ 应用入口同时引入样式:
16
+
17
+ ```ts
18
+ import '@shinesun/vue-admin-framework/style.css'
19
+ ```
20
+
21
+ ## 使用
22
+
23
+ ```vue
24
+ <script setup lang="ts">
25
+ import { AdminFrameworkLayout } from '@shinesun/vue-admin-framework'
26
+ </script>
27
+
28
+ <template>
29
+ <AdminFrameworkLayout
30
+ brand-name="示例系统"
31
+ :menus="menus"
32
+ :navigation-loaded="navigationLoaded"
33
+ :username="username"
34
+ :login-account="loginAccount"
35
+ :initials="initials"
36
+ tab-storage-key="example_visited_tabs"
37
+ @logout="logout"
38
+ />
39
+ </template>
40
+ ```
41
+
42
+ OAuth 客户端通过 `createOAuthPkceClient` 创建。每个应用必须提供独立的
43
+ `storageNamespace`、`clientId`、`redirectUri` 和默认返回路径。
44
+
45
+ 菜单节点类型 `FrameworkMenuNode` 可携带 `menuDomain?: string | null`,表示该菜单
46
+ 所属应用的 Origin;未配置时为 `null`。框架保留此字段,不自动将其与相对 `route`
47
+ 拼接或执行跨域跳转。消费应用可按自身导航策略使用这两个字段构造跨应用地址。
48
+
49
+ ## 发布产物
50
+
51
+ 包只发布 ESM,并包含:
52
+
53
+ - `dist/index.js`
54
+ - `dist/index.d.ts`
55
+ - `dist/style.css`
56
+ - 组件和公共类型声明
57
+
58
+ 发布前运行:
59
+
60
+ ```bash
61
+ npm run build
62
+ npm pack --dry-run
63
+ ```
@@ -0,0 +1,40 @@
1
+ import type { FrameworkMenuNode } from '../menu';
2
+ type __VLS_Props = {
3
+ brandName: string;
4
+ brandSubtitle?: string;
5
+ consoleName?: string;
6
+ menus: FrameworkMenuNode[];
7
+ resolveExternalMenuHref?: (item: FrameworkMenuNode) => string | null;
8
+ navigationLoaded: boolean;
9
+ navigationError?: string;
10
+ username?: string;
11
+ loginAccount?: string;
12
+ initials?: string;
13
+ tabStorageKey?: string;
14
+ maxTabs?: number;
15
+ showSettings?: boolean;
16
+ showNotifications?: boolean;
17
+ };
18
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ logout: () => any;
20
+ settings: () => any;
21
+ notifications: () => any;
22
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
23
+ onLogout?: (() => any) | undefined;
24
+ onSettings?: (() => any) | undefined;
25
+ onNotifications?: (() => any) | undefined;
26
+ }>, {
27
+ resolveExternalMenuHref: (item: FrameworkMenuNode) => string | null;
28
+ brandSubtitle: string;
29
+ consoleName: string;
30
+ navigationError: string;
31
+ username: string;
32
+ loginAccount: string;
33
+ initials: string;
34
+ tabStorageKey: string;
35
+ maxTabs: number;
36
+ showSettings: boolean;
37
+ showNotifications: boolean;
38
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
+ declare const _default: typeof __VLS_export;
40
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { FrameworkMenuNode } from '../menu';
2
+ type __VLS_Props = {
3
+ item: FrameworkMenuNode;
4
+ resolveExternalMenuHref?: (item: FrameworkMenuNode) => string | null;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,10 @@
1
+ export { default as AdminFrameworkLayout } from './components/AdminFrameworkLayout.vue';
2
+ export { default as FrameworkMenuItem } from './components/FrameworkMenuItem.vue';
3
+ export { default as OAuthCallbackView } from './views/OAuthCallbackView.vue';
4
+ export type { FrameworkMenuNode } from './menu';
5
+ export { findMenuByRoute, findMenuTrail, flattenRoutableMenus, } from './navigation';
6
+ export { createOAuthPkceClient, sanitizeReturnUrl, } from './oauth';
7
+ export type { OAuthLocation, OAuthPkceClient, OAuthPkceClientOptions, OAuthSession, } from './oauth';
8
+ export { sha256 } from './sha256';
9
+ export { closeVisitedTab, normalizeVisitedTabs, retainActiveTab, upsertVisitedTab, } from './tabs';
10
+ export type { ClosedTabResult, FrameworkTab, } from './tabs';
package/dist/index.js ADDED
@@ -0,0 +1,903 @@
1
+ import { defineComponent as oe, computed as B, resolveComponent as C, openBlock as i, createBlock as T, withCtx as m, createElementBlock as p, Fragment as j, renderList as W, createVNode as v, resolveDynamicComponent as G, createElementVNode as d, toDisplayString as E, createTextVNode as z, ref as q, onMounted as de, watch as re, normalizeClass as X, unref as M, createCommentVNode as L, withModifiers as ke, KeepAlive as xe, nextTick as Te } from "vue";
2
+ import { useRoute as fe, useRouter as me } from "vue-router";
3
+ import { UserFilled as Se, User as Ce, Tickets as Ae, Key as ae, DataAnalysis as Ee, OfficeBuilding as Me, Menu as Y, Lock as he, List as Ie, Grid as $e, Document as Ue, Connection as se, Avatar as Le, Aim as ie, Setting as Ne, ArrowRight as Ve, ArrowLeft as Oe, Bell as Re, SwitchButton as Fe, Close as Be, MoreFilled as ze, Refresh as He, Loading as Ke } from "@element-plus/icons-vue";
4
+ function Z(e) {
5
+ return e.flatMap((o) => [
6
+ ...o.route ? [o] : [],
7
+ ...Z(o.children)
8
+ ]);
9
+ }
10
+ function qe(e, o, r = []) {
11
+ for (const s of e) {
12
+ const t = [...r, s];
13
+ if (s.route === o) return t;
14
+ const l = qe(s.children, o, t);
15
+ if (l.length) return l;
16
+ }
17
+ return [];
18
+ }
19
+ function Rt(e, o) {
20
+ return Z(e).find((r) => r.route === o);
21
+ }
22
+ function ne(e) {
23
+ return `menu:${e.applicationCode}:${e.menuCode}`;
24
+ }
25
+ function ve(e, o, r = []) {
26
+ for (const s of e) {
27
+ const t = [...r, s];
28
+ if (s === o) return t;
29
+ const l = ve(s.children, o, t);
30
+ if (l.length) return l;
31
+ }
32
+ return [];
33
+ }
34
+ const le = /* @__PURE__ */ oe({
35
+ name: "FrameworkMenuItem",
36
+ __name: "FrameworkMenuItem",
37
+ props: {
38
+ item: {},
39
+ resolveExternalMenuHref: { type: Function }
40
+ },
41
+ setup(e) {
42
+ const o = e, r = {
43
+ api: se,
44
+ avatar: Le,
45
+ connection: se,
46
+ document: Ue,
47
+ grid: $e,
48
+ key: ae,
49
+ list: Ie,
50
+ lock: he,
51
+ menu: Y,
52
+ "office-building": Me,
53
+ overview: Ee,
54
+ shield: ae,
55
+ tickets: Ae,
56
+ user: Ce,
57
+ users: Se
58
+ }, s = B(() => {
59
+ const u = o.item.icon?.toLowerCase();
60
+ return u ? r[u] || Y : Y;
61
+ }), t = B(() => ne(o.item)), l = B(() => o.resolveExternalMenuHref?.(o.item) ?? null);
62
+ return (u, f) => {
63
+ const c = C("el-icon"), b = C("FrameworkMenuItem", !0), h = C("el-sub-menu"), k = C("el-menu-item");
64
+ return e.item.children.length ? (i(), T(h, {
65
+ key: 0,
66
+ index: t.value
67
+ }, {
68
+ title: m(() => [
69
+ v(c, null, {
70
+ default: m(() => [
71
+ (i(), T(G(s.value)))
72
+ ]),
73
+ _: 1
74
+ }),
75
+ d("span", null, E(e.item.name), 1)
76
+ ]),
77
+ default: m(() => [
78
+ (i(!0), p(j, null, W(e.item.children, (_) => (i(), T(b, {
79
+ key: `${_.applicationCode}:${_.menuCode}`,
80
+ item: _,
81
+ "resolve-external-menu-href": e.resolveExternalMenuHref
82
+ }, null, 8, ["item", "resolve-external-menu-href"]))), 128))
83
+ ]),
84
+ _: 1
85
+ }, 8, ["index"])) : e.item.route ? (i(), T(k, {
86
+ key: 1,
87
+ index: t.value,
88
+ disabled: l.value === "",
89
+ title: l.value === "" ? "跨应用菜单缺少有效域名或路径" : l.value || void 0
90
+ }, {
91
+ title: m(() => [
92
+ z(E(e.item.name), 1)
93
+ ]),
94
+ default: m(() => [
95
+ v(c, null, {
96
+ default: m(() => [
97
+ (i(), T(G(s.value)))
98
+ ]),
99
+ _: 1
100
+ })
101
+ ]),
102
+ _: 1
103
+ }, 8, ["index", "disabled", "title"])) : (i(), T(k, {
104
+ key: 2,
105
+ index: t.value,
106
+ disabled: ""
107
+ }, {
108
+ title: m(() => [
109
+ z(E(e.item.name), 1)
110
+ ]),
111
+ default: m(() => [
112
+ v(c, null, {
113
+ default: m(() => [
114
+ (i(), T(G(s.value)))
115
+ ]),
116
+ _: 1
117
+ })
118
+ ]),
119
+ _: 1
120
+ }, 8, ["index"]));
121
+ };
122
+ }
123
+ });
124
+ function ee(e) {
125
+ if (!Array.isArray(e)) return [];
126
+ const o = /* @__PURE__ */ new Set();
127
+ return e.flatMap((r) => {
128
+ if (!r || typeof r != "object") return [];
129
+ const s = "path" in r && typeof r.path == "string" ? r.path : "", t = "title" in r && typeof r.title == "string" ? r.title : "";
130
+ return !je(s) || !t.trim() || o.has(s) ? [] : (o.add(s), [{ path: s, title: t.trim() }]);
131
+ });
132
+ }
133
+ function Pe(e, o, r) {
134
+ const s = ee(e), t = s.findIndex((f) => f.path === o.path), l = t >= 0 ? s.map((f, c) => c === t ? o : f) : [...s, o], u = Math.max(1, r);
135
+ for (; l.length > u; ) {
136
+ const f = l.findIndex((c) => c.path !== o.path);
137
+ if (f < 0) break;
138
+ l.splice(f, 1);
139
+ }
140
+ return l;
141
+ }
142
+ function De(e, o, r) {
143
+ const s = ee(e), t = s.findIndex((f) => f.path === o);
144
+ if (t < 0 || s.length <= 1)
145
+ return { tabs: s, navigateTo: null };
146
+ const l = s.filter((f) => f.path !== o);
147
+ if (o !== r) return { tabs: l, navigateTo: null };
148
+ const u = l[Math.min(t, l.length - 1)];
149
+ return { tabs: l, navigateTo: u?.path || null };
150
+ }
151
+ function Je(e, o) {
152
+ const r = ee(e).find((s) => s.path === o);
153
+ return r ? [r] : [];
154
+ }
155
+ function je(e) {
156
+ return e.startsWith("/") && !e.startsWith("//");
157
+ }
158
+ const We = { class: "saf-sidebar saf-desktop-sidebar" }, Ge = { class: "saf-brand-mark" }, Qe = { key: 0 }, Xe = { key: 0 }, Ye = { class: "saf-sidebar-nav" }, Ze = { key: 0 }, et = ["title"], tt = { key: 2 }, nt = { class: "saf-sidebar-footer" }, ot = { key: 0 }, rt = ["title"], at = { key: 0 }, st = { class: "saf-main-shell" }, it = { class: "saf-topbar" }, lt = { class: "saf-topbar-context" }, ct = { class: "saf-topbar-actions" }, ut = {
159
+ key: 1,
160
+ class: "saf-topbar-divider"
161
+ }, dt = { class: "saf-operator" }, ft = { class: "saf-page-tabs" }, mt = { class: "saf-page-tabs__scroller" }, ht = ["onClick"], vt = { class: "saf-content-frame" }, gt = { class: "saf-workspace" }, wt = {
162
+ key: 0,
163
+ class: "saf-route-breadcrumbs"
164
+ }, yt = { class: "saf-sidebar saf-mobile-sidebar" }, pt = { class: "saf-brand" }, bt = { class: "saf-brand-mark" }, _t = { key: 0 }, kt = { class: "saf-sidebar-nav" }, xt = {
165
+ key: 1,
166
+ class: "saf-nav-feedback"
167
+ }, Tt = /* @__PURE__ */ oe({
168
+ __name: "AdminFrameworkLayout",
169
+ props: {
170
+ brandName: {},
171
+ brandSubtitle: { default: "" },
172
+ consoleName: { default: "管理控制台" },
173
+ menus: {},
174
+ resolveExternalMenuHref: { type: Function, default: void 0 },
175
+ navigationLoaded: { type: Boolean },
176
+ navigationError: { default: "" },
177
+ username: { default: "" },
178
+ loginAccount: { default: "" },
179
+ initials: { default: "?" },
180
+ tabStorageKey: { default: "admin_visited_tabs" },
181
+ maxTabs: { default: 12 },
182
+ showSettings: { type: Boolean, default: !0 },
183
+ showNotifications: { type: Boolean, default: !0 }
184
+ },
185
+ emits: ["logout", "settings", "notifications"],
186
+ setup(e, { emit: o }) {
187
+ const r = e, s = o, t = fe(), l = me(), u = q(!1), f = q(!1), c = q([]), b = q({}), h = q(!0), k = B(() => new Map(
188
+ Z(r.menus).map((n) => [ne(n), n])
189
+ )), _ = B(() => Z(r.menus).find(
190
+ (n) => n.route === t.path && (r.resolveExternalMenuHref?.(n) ?? null) === null
191
+ )), S = B(() => _.value ? ne(_.value) : ""), A = B(
192
+ () => _.value ? ve(r.menus, _.value) : []
193
+ ), R = B(
194
+ () => _.value?.name || String(t.meta.title || r.consoleName)
195
+ ), F = B(
196
+ () => `${t.path}:${b.value[t.path] || 0}`
197
+ );
198
+ de(() => {
199
+ c.value = H(), y();
200
+ }), re(
201
+ () => [t.path, t.meta.title, r.menus],
202
+ y,
203
+ { deep: !0 }
204
+ ), re(
205
+ c,
206
+ (n) => {
207
+ typeof window < "u" && window.sessionStorage.setItem(r.tabStorageKey, JSON.stringify(n));
208
+ },
209
+ { deep: !0 }
210
+ );
211
+ function H() {
212
+ if (typeof window > "u") return [];
213
+ try {
214
+ return ee(
215
+ JSON.parse(window.sessionStorage.getItem(r.tabStorageKey) || "[]")
216
+ );
217
+ } catch {
218
+ return window.sessionStorage.removeItem(r.tabStorageKey), [];
219
+ }
220
+ }
221
+ function y() {
222
+ t.name !== "oauth-callback" && (c.value = Pe(
223
+ c.value,
224
+ { path: t.path, title: R.value },
225
+ r.maxTabs
226
+ ));
227
+ }
228
+ async function U(n) {
229
+ const a = k.value.get(n);
230
+ if (!a?.route) return;
231
+ const g = r.resolveExternalMenuHref?.(a) ?? null;
232
+ if (g !== null) {
233
+ g && window.location.assign(g), f.value = !1;
234
+ return;
235
+ }
236
+ await N(a.route);
237
+ }
238
+ async function N(n) {
239
+ n !== t.path && await l.push(n), f.value = !1;
240
+ }
241
+ async function I(n) {
242
+ const a = De(c.value, n, t.path);
243
+ c.value = a.tabs, a.navigateTo && await l.push(a.navigateTo);
244
+ }
245
+ function V() {
246
+ c.value = Je(c.value, t.path);
247
+ }
248
+ function K() {
249
+ V();
250
+ }
251
+ async function D() {
252
+ h.value = !1, b.value = {
253
+ ...b.value,
254
+ [t.path]: (b.value[t.path] || 0) + 1
255
+ }, await Te(), h.value = !0;
256
+ }
257
+ function te() {
258
+ c.value = [], typeof window < "u" && window.sessionStorage.removeItem(r.tabStorageKey), s("logout");
259
+ }
260
+ return (n, a) => {
261
+ const g = C("el-icon"), x = C("el-menu"), $ = C("el-button"), P = C("el-avatar"), J = C("el-dropdown-item"), Q = C("el-dropdown-menu"), ye = C("el-dropdown"), pe = C("RouterView"), be = C("el-drawer");
262
+ return i(), p("div", {
263
+ class: X(["saf-shell", { "saf-shell--collapsed": u.value }])
264
+ }, [
265
+ d("aside", We, [
266
+ d("div", {
267
+ class: X(["saf-brand", { "saf-brand--compact": u.value }])
268
+ }, [
269
+ d("span", Ge, [
270
+ v(g, null, {
271
+ default: m(() => [
272
+ v(M(ie))
273
+ ]),
274
+ _: 1
275
+ })
276
+ ]),
277
+ u.value ? L("", !0) : (i(), p("div", Qe, [
278
+ d("strong", null, E(e.brandName), 1),
279
+ e.brandSubtitle ? (i(), p("small", Xe, E(e.brandSubtitle), 1)) : L("", !0)
280
+ ]))
281
+ ], 2),
282
+ d("nav", Ye, [
283
+ e.menus.length ? (i(), T(x, {
284
+ key: 0,
285
+ "default-active": S.value,
286
+ collapse: u.value,
287
+ "collapse-transition": !1,
288
+ onSelect: U
289
+ }, {
290
+ default: m(() => [
291
+ (i(!0), p(j, null, W(e.menus, (w) => (i(), T(le, {
292
+ key: `${w.applicationCode}:${w.menuCode}`,
293
+ item: w,
294
+ "resolve-external-menu-href": e.resolveExternalMenuHref
295
+ }, null, 8, ["item", "resolve-external-menu-href"]))), 128))
296
+ ]),
297
+ _: 1
298
+ }, 8, ["default-active", "collapse"])) : (i(), p("div", {
299
+ key: 1,
300
+ class: X(["saf-nav-feedback", { "saf-nav-feedback--compact": u.value }])
301
+ }, [
302
+ e.navigationLoaded ? e.navigationError ? (i(), p("span", {
303
+ key: 1,
304
+ title: e.navigationError
305
+ }, "菜单加载失败", 8, et)) : (i(), p("span", tt, "当前账号暂无菜单")) : (i(), p("span", Ze, "正在加载菜单..."))
306
+ ], 2))
307
+ ]),
308
+ d("div", nt, [
309
+ e.showSettings ? (i(), p("button", {
310
+ key: 0,
311
+ type: "button",
312
+ title: "系统设置",
313
+ onClick: a[0] || (a[0] = (w) => s("settings"))
314
+ }, [
315
+ v(g, null, {
316
+ default: m(() => [
317
+ v(M(Ne))
318
+ ]),
319
+ _: 1
320
+ }),
321
+ u.value ? L("", !0) : (i(), p("span", ot, "系统设置"))
322
+ ])) : L("", !0),
323
+ d("button", {
324
+ type: "button",
325
+ title: u.value ? "展开侧栏" : "收起侧栏",
326
+ onClick: a[1] || (a[1] = (w) => u.value = !u.value)
327
+ }, [
328
+ v(g, null, {
329
+ default: m(() => [
330
+ (i(), T(G(u.value ? M(Ve) : M(Oe))))
331
+ ]),
332
+ _: 1
333
+ }),
334
+ u.value ? L("", !0) : (i(), p("span", at, "收起侧栏"))
335
+ ], 8, rt)
336
+ ])
337
+ ]),
338
+ d("div", st, [
339
+ d("header", it, [
340
+ v($, {
341
+ class: "saf-mobile-menu-button",
342
+ text: "",
343
+ circle: "",
344
+ icon: M(Y),
345
+ "aria-label": "打开导航",
346
+ onClick: a[2] || (a[2] = (w) => f.value = !0)
347
+ }, null, 8, ["icon"]),
348
+ d("div", lt, [
349
+ d("span", null, [
350
+ v(g, null, {
351
+ default: m(() => [
352
+ v(M(he))
353
+ ]),
354
+ _: 1
355
+ }),
356
+ z(E(e.consoleName), 1)
357
+ ]),
358
+ a[5] || (a[5] = d("i", null, null, -1)),
359
+ d("strong", null, E(R.value), 1)
360
+ ]),
361
+ d("div", ct, [
362
+ e.showNotifications ? (i(), T($, {
363
+ key: 0,
364
+ text: "",
365
+ circle: "",
366
+ icon: M(Re),
367
+ "aria-label": "通知",
368
+ onClick: a[3] || (a[3] = (w) => s("notifications"))
369
+ }, null, 8, ["icon"])) : L("", !0),
370
+ e.showNotifications ? (i(), p("i", ut)) : L("", !0),
371
+ v(P, { size: 30 }, {
372
+ default: m(() => [
373
+ z(E(e.initials), 1)
374
+ ]),
375
+ _: 1
376
+ }),
377
+ d("div", dt, [
378
+ d("strong", null, E(e.username), 1),
379
+ d("span", null, E(e.loginAccount), 1)
380
+ ]),
381
+ v($, {
382
+ text: "",
383
+ circle: "",
384
+ icon: M(Fe),
385
+ "aria-label": "退出登录",
386
+ title: "退出登录",
387
+ onClick: te
388
+ }, null, 8, ["icon"])
389
+ ])
390
+ ]),
391
+ d("div", ft, [
392
+ d("div", mt, [
393
+ (i(!0), p(j, null, W(c.value, (w) => (i(), p("button", {
394
+ key: w.path,
395
+ type: "button",
396
+ class: X(["saf-page-tab", { "saf-page-tab--active": M(t).path === w.path }]),
397
+ onClick: (_e) => N(w.path)
398
+ }, [
399
+ d("span", null, E(w.title), 1),
400
+ c.value.length > 1 ? (i(), T(g, {
401
+ key: 0,
402
+ class: "saf-page-tab__close",
403
+ title: "关闭",
404
+ onClick: ke((_e) => I(w.path), ["stop"])
405
+ }, {
406
+ default: m(() => [
407
+ v(M(Be))
408
+ ]),
409
+ _: 1
410
+ }, 8, ["onClick"])) : L("", !0)
411
+ ], 10, ht))), 128))
412
+ ]),
413
+ v(ye, { trigger: "click" }, {
414
+ dropdown: m(() => [
415
+ v(Q, null, {
416
+ default: m(() => [
417
+ v(J, {
418
+ icon: M(He),
419
+ onClick: D
420
+ }, {
421
+ default: m(() => [...a[6] || (a[6] = [
422
+ z(" 刷新当前 ", -1)
423
+ ])]),
424
+ _: 1
425
+ }, 8, ["icon"]),
426
+ v(J, {
427
+ disabled: c.value.length <= 1,
428
+ onClick: V
429
+ }, {
430
+ default: m(() => [...a[7] || (a[7] = [
431
+ z(" 关闭其他 ", -1)
432
+ ])]),
433
+ _: 1
434
+ }, 8, ["disabled"]),
435
+ v(J, {
436
+ disabled: c.value.length <= 1,
437
+ divided: "",
438
+ onClick: K
439
+ }, {
440
+ default: m(() => [...a[8] || (a[8] = [
441
+ z(" 关闭全部 ", -1)
442
+ ])]),
443
+ _: 1
444
+ }, 8, ["disabled"])
445
+ ]),
446
+ _: 1
447
+ })
448
+ ]),
449
+ default: m(() => [
450
+ v($, {
451
+ class: "saf-tab-menu-button",
452
+ text: "",
453
+ icon: M(ze),
454
+ "aria-label": "页签操作"
455
+ }, null, 8, ["icon"])
456
+ ]),
457
+ _: 1
458
+ })
459
+ ]),
460
+ d("div", vt, [
461
+ d("main", gt, [
462
+ A.value.length > 1 ? (i(), p("div", wt, [
463
+ (i(!0), p(j, null, W(A.value, (w) => (i(), p("span", {
464
+ key: `${w.applicationCode}:${w.menuCode}`
465
+ }, E(w.name), 1))), 128))
466
+ ])) : L("", !0),
467
+ v(pe, null, {
468
+ default: m(({ Component: w }) => [
469
+ (i(), T(xe, { max: e.maxTabs }, [
470
+ h.value ? (i(), T(G(w), { key: F.value })) : L("", !0)
471
+ ], 1032, ["max"]))
472
+ ]),
473
+ _: 1
474
+ })
475
+ ])
476
+ ])
477
+ ]),
478
+ v(be, {
479
+ modelValue: f.value,
480
+ "onUpdate:modelValue": a[4] || (a[4] = (w) => f.value = w),
481
+ direction: "ltr",
482
+ size: "248px",
483
+ "with-header": !1,
484
+ class: "saf-mobile-nav-drawer"
485
+ }, {
486
+ default: m(() => [
487
+ d("aside", yt, [
488
+ d("div", pt, [
489
+ d("span", bt, [
490
+ v(g, null, {
491
+ default: m(() => [
492
+ v(M(ie))
493
+ ]),
494
+ _: 1
495
+ })
496
+ ]),
497
+ d("div", null, [
498
+ d("strong", null, E(e.brandName), 1),
499
+ e.brandSubtitle ? (i(), p("small", _t, E(e.brandSubtitle), 1)) : L("", !0)
500
+ ])
501
+ ]),
502
+ d("nav", kt, [
503
+ e.menus.length ? (i(), T(x, {
504
+ key: 0,
505
+ "default-active": S.value,
506
+ onSelect: U
507
+ }, {
508
+ default: m(() => [
509
+ (i(!0), p(j, null, W(e.menus, (w) => (i(), T(le, {
510
+ key: `${w.applicationCode}:${w.menuCode}`,
511
+ item: w,
512
+ "resolve-external-menu-href": e.resolveExternalMenuHref
513
+ }, null, 8, ["item", "resolve-external-menu-href"]))), 128))
514
+ ]),
515
+ _: 1
516
+ }, 8, ["default-active"])) : (i(), p("div", xt, E(e.navigationError || "当前账号暂无菜单"), 1))
517
+ ])
518
+ ])
519
+ ]),
520
+ _: 1
521
+ }, 8, ["modelValue"])
522
+ ], 2);
523
+ };
524
+ }
525
+ }), ge = (e, o) => {
526
+ const r = e.__vccOpts || e;
527
+ for (const [s, t] of o)
528
+ r[s] = t;
529
+ return r;
530
+ }, Ft = /* @__PURE__ */ ge(Tt, [["__scopeId", "data-v-6ef4786f"]]), St = { class: "saf-oauth-callback" }, Ct = {
531
+ key: 1,
532
+ class: "saf-oauth-loading"
533
+ }, At = /* @__PURE__ */ oe({
534
+ __name: "OAuthCallbackView",
535
+ props: {
536
+ client: {},
537
+ retryPath: { default: "/" }
538
+ },
539
+ emits: ["completed"],
540
+ setup(e, { emit: o }) {
541
+ const r = e, s = o, t = fe(), l = me(), u = q(""), f = q(!1);
542
+ de(c);
543
+ async function c() {
544
+ const h = typeof t.query.code == "string" ? t.query.code : "", k = typeof t.query.state == "string" ? t.query.state : "", _ = typeof t.query.error_description == "string" ? t.query.error_description : typeof t.query.error == "string" ? t.query.error : "";
545
+ try {
546
+ if (_) throw new Error(_);
547
+ if (!h || !k) throw new Error("登录回调缺少 code 或 state。");
548
+ const S = await r.client.completeLogin(h, k);
549
+ s("completed", S), await l.replace(S);
550
+ } catch (S) {
551
+ u.value = S instanceof Error ? S.message : "登录失败。";
552
+ }
553
+ }
554
+ async function b() {
555
+ f.value = !0;
556
+ try {
557
+ await r.client.beginLogin(r.retryPath);
558
+ } catch (h) {
559
+ u.value = h instanceof Error ? h.message : "重新登录失败。", f.value = !1;
560
+ }
561
+ }
562
+ return (h, k) => {
563
+ const _ = C("el-button"), S = C("el-result"), A = C("el-icon");
564
+ return i(), p("main", St, [
565
+ u.value ? (i(), T(S, {
566
+ key: 0,
567
+ icon: "error",
568
+ title: "登录失败",
569
+ "sub-title": u.value
570
+ }, {
571
+ extra: m(() => [
572
+ v(_, {
573
+ type: "primary",
574
+ loading: f.value,
575
+ onClick: b
576
+ }, {
577
+ default: m(() => [...k[0] || (k[0] = [
578
+ z(" 重新登录 ", -1)
579
+ ])]),
580
+ _: 1
581
+ }, 8, ["loading"])
582
+ ]),
583
+ _: 1
584
+ }, 8, ["sub-title"])) : (i(), p("div", Ct, [
585
+ v(A, {
586
+ class: "is-loading",
587
+ size: 28
588
+ }, {
589
+ default: m(() => [
590
+ v(M(Ke))
591
+ ]),
592
+ _: 1
593
+ }),
594
+ k[1] || (k[1] = d("span", null, "正在完成登录...", -1))
595
+ ]))
596
+ ]);
597
+ };
598
+ }
599
+ }), Bt = /* @__PURE__ */ ge(At, [["__scopeId", "data-v-ef3407f8"]]), Et = new Uint32Array([
600
+ 1116352408,
601
+ 1899447441,
602
+ 3049323471,
603
+ 3921009573,
604
+ 961987163,
605
+ 1508970993,
606
+ 2453635748,
607
+ 2870763221,
608
+ 3624381080,
609
+ 310598401,
610
+ 607225278,
611
+ 1426881987,
612
+ 1925078388,
613
+ 2162078206,
614
+ 2614888103,
615
+ 3248222580,
616
+ 3835390401,
617
+ 4022224774,
618
+ 264347078,
619
+ 604807628,
620
+ 770255983,
621
+ 1249150122,
622
+ 1555081692,
623
+ 1996064986,
624
+ 2554220882,
625
+ 2821834349,
626
+ 2952996808,
627
+ 3210313671,
628
+ 3336571891,
629
+ 3584528711,
630
+ 113926993,
631
+ 338241895,
632
+ 666307205,
633
+ 773529912,
634
+ 1294757372,
635
+ 1396182291,
636
+ 1695183700,
637
+ 1986661051,
638
+ 2177026350,
639
+ 2456956037,
640
+ 2730485921,
641
+ 2820302411,
642
+ 3259730800,
643
+ 3345764771,
644
+ 3516065817,
645
+ 3600352804,
646
+ 4094571909,
647
+ 275423344,
648
+ 430227734,
649
+ 506948616,
650
+ 659060556,
651
+ 883997877,
652
+ 958139571,
653
+ 1322822218,
654
+ 1537002063,
655
+ 1747873779,
656
+ 1955562222,
657
+ 2024104815,
658
+ 2227730452,
659
+ 2361852424,
660
+ 2428436474,
661
+ 2756734187,
662
+ 3204031479,
663
+ 3329325298
664
+ ]), Mt = new Uint32Array([
665
+ 1779033703,
666
+ 3144134277,
667
+ 1013904242,
668
+ 2773480762,
669
+ 1359893119,
670
+ 2600822924,
671
+ 528734635,
672
+ 1541459225
673
+ ]);
674
+ function It(e) {
675
+ const o = Math.ceil((e.length + 9) / 64) * 64, r = new Uint8Array(o);
676
+ r.set(e), r[e.length] = 128;
677
+ const s = BigInt(e.length) * 8n;
678
+ for (let b = 0; b < 8; b += 1)
679
+ r[o - 1 - b] = Number(s >> BigInt(b * 8) & 0xffn);
680
+ const t = new Uint32Array(Mt), l = new Uint32Array(64), u = new DataView(r.buffer);
681
+ for (let b = 0; b < o; b += 64) {
682
+ for (let y = 0; y < 16; y += 1)
683
+ l[y] = u.getUint32(b + y * 4);
684
+ for (let y = 16; y < 64; y += 1) {
685
+ const U = l[y - 15], N = l[y - 2], I = O(U, 7) ^ O(U, 18) ^ U >>> 3, V = O(N, 17) ^ O(N, 19) ^ N >>> 10;
686
+ l[y] = l[y - 16] + I + l[y - 7] + V >>> 0;
687
+ }
688
+ let h = t[0], k = t[1], _ = t[2], S = t[3], A = t[4], R = t[5], F = t[6], H = t[7];
689
+ for (let y = 0; y < 64; y += 1) {
690
+ const U = O(A, 6) ^ O(A, 11) ^ O(A, 25), N = A & R ^ ~A & F, I = H + U + N + Et[y] + l[y] >>> 0, V = O(h, 2) ^ O(h, 13) ^ O(h, 22), K = h & k ^ h & _ ^ k & _, D = V + K >>> 0;
691
+ H = F, F = R, R = A, A = S + I >>> 0, S = _, _ = k, k = h, h = I + D >>> 0;
692
+ }
693
+ t[0] = t[0] + h >>> 0, t[1] = t[1] + k >>> 0, t[2] = t[2] + _ >>> 0, t[3] = t[3] + S >>> 0, t[4] = t[4] + A >>> 0, t[5] = t[5] + R >>> 0, t[6] = t[6] + F >>> 0, t[7] = t[7] + H >>> 0;
694
+ }
695
+ const f = new Uint8Array(32), c = new DataView(f.buffer);
696
+ return t.forEach((b, h) => c.setUint32(h * 4, b)), f;
697
+ }
698
+ function O(e, o) {
699
+ return e >>> o | e << 32 - o;
700
+ }
701
+ function zt(e) {
702
+ const o = e.now || Date.now, r = e.refreshSkewMs ?? 3e4, s = e.transactionLifetimeMs ?? 10 * 6e4, t = e.storageNamespace.trim();
703
+ if (!t) throw new Error("OAuth storageNamespace 不能为空。");
704
+ const l = `${t}_oauth_session`, u = `${t}_oauth_transaction`, f = `${t}-oauth-session-changed`;
705
+ let c = null;
706
+ function b() {
707
+ return e.mockMode === !0;
708
+ }
709
+ function h() {
710
+ const n = I(), a = n.getItem(l);
711
+ if (!a) return null;
712
+ try {
713
+ const g = JSON.parse(a);
714
+ return g.accessToken && g.refreshToken && Number.isFinite(g.expiresAt) ? g : null;
715
+ } catch {
716
+ return n.removeItem(l), null;
717
+ }
718
+ }
719
+ function k() {
720
+ I().removeItem(l), K().dispatchEvent(new Event(f));
721
+ }
722
+ function _(n) {
723
+ const a = K();
724
+ return a.addEventListener(f, n), () => a.removeEventListener(f, n);
725
+ }
726
+ async function S(n = Ut(V())) {
727
+ if (b()) return;
728
+ const a = I(), g = V(), x = D(), $ = ue(x, 64), P = ue(x, 32), J = {
729
+ state: P,
730
+ verifier: $,
731
+ redirectUri: e.redirectUri,
732
+ returnUrl: $t(n, e.defaultReturnUrl),
733
+ createdAt: o()
734
+ };
735
+ a.setItem(u, JSON.stringify(J));
736
+ const Q = ce(e.authority, "/oauth2/authorize", g.origin);
737
+ Q.search = new URLSearchParams({
738
+ response_type: "code",
739
+ client_id: e.clientId,
740
+ redirect_uri: e.redirectUri,
741
+ state: P,
742
+ code_challenge: await Lt(x, $),
743
+ code_challenge_method: "S256"
744
+ }).toString(), g.assign(Q.toString());
745
+ }
746
+ async function A(n, a) {
747
+ const g = I(), x = N();
748
+ if (g.removeItem(u), !x || x.state !== a || o() - x.createdAt > s)
749
+ throw new Error("登录状态校验失败,请重新登录。");
750
+ const $ = await U({
751
+ grant_type: "authorization_code",
752
+ client_id: e.clientId,
753
+ code: n,
754
+ redirect_uri: x.redirectUri,
755
+ code_verifier: x.verifier
756
+ });
757
+ return y($), x.returnUrl;
758
+ }
759
+ async function R() {
760
+ const n = h();
761
+ return n ? n.expiresAt > o() + r ? n.accessToken : F() : null;
762
+ }
763
+ async function F() {
764
+ return c || (c = (async () => {
765
+ const n = h();
766
+ if (!n) return null;
767
+ try {
768
+ const a = await U({
769
+ grant_type: "refresh_token",
770
+ client_id: e.clientId,
771
+ refresh_token: n.refreshToken
772
+ });
773
+ return y(a), a.access_token;
774
+ } catch {
775
+ return k(), null;
776
+ }
777
+ })().finally(() => {
778
+ c = null;
779
+ }), c);
780
+ }
781
+ function H(n) {
782
+ try {
783
+ const a = n.split(".")[1];
784
+ if (!a) return "";
785
+ const g = a.replace(/-/g, "+").replace(/_/g, "/"), x = g.padEnd(Math.ceil(g.length / 4) * 4, "=");
786
+ return JSON.parse(decodeURIComponent(
787
+ Array.from(atob(x)).map((P) => `%${P.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
788
+ )).sub || "";
789
+ } catch {
790
+ return "";
791
+ }
792
+ }
793
+ function y(n) {
794
+ if (n.token_type.toLowerCase() !== "bearer" || !n.access_token || !n.refresh_token || n.expires_in <= 0)
795
+ throw new Error("用户中心返回的 Token 响应无效。");
796
+ const a = {
797
+ accessToken: n.access_token,
798
+ refreshToken: n.refresh_token,
799
+ expiresAt: o() + n.expires_in * 1e3
800
+ };
801
+ I().setItem(l, JSON.stringify(a)), K().dispatchEvent(new Event(f));
802
+ }
803
+ async function U(n) {
804
+ const a = V(), g = await te()(
805
+ ce(e.authority, "/oauth2/token", a.origin),
806
+ {
807
+ method: "POST",
808
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
809
+ body: new URLSearchParams(n)
810
+ }
811
+ ), x = await g.json().catch(() => null);
812
+ if (!g.ok) {
813
+ const $ = x;
814
+ throw new Error(
815
+ $?.error_description || $?.error || "Token 请求失败。"
816
+ );
817
+ }
818
+ return x;
819
+ }
820
+ function N() {
821
+ const a = I().getItem(u);
822
+ if (!a) return null;
823
+ try {
824
+ return JSON.parse(a);
825
+ } catch {
826
+ return null;
827
+ }
828
+ }
829
+ function I() {
830
+ const n = e.storage || (typeof window > "u" ? void 0 : window.sessionStorage);
831
+ if (!n) throw new Error("OAuth 客户端缺少 Storage 运行环境。");
832
+ return n;
833
+ }
834
+ function V() {
835
+ const n = e.location || (typeof window > "u" ? void 0 : window.location);
836
+ if (!n) throw new Error("OAuth 客户端缺少 Location 运行环境。");
837
+ return n;
838
+ }
839
+ function K() {
840
+ const n = e.eventTarget || (typeof window > "u" ? void 0 : window);
841
+ if (!n) throw new Error("OAuth 客户端缺少 EventTarget 运行环境。");
842
+ return n;
843
+ }
844
+ function D() {
845
+ const n = e.crypto || globalThis.crypto;
846
+ if (!n) throw new Error("OAuth 客户端缺少 Crypto 运行环境。");
847
+ return n;
848
+ }
849
+ function te() {
850
+ const n = e.fetcher || globalThis.fetch;
851
+ if (!n) throw new Error("OAuth 客户端缺少 Fetch 运行环境。");
852
+ return n;
853
+ }
854
+ return {
855
+ isMockMode: b,
856
+ getSession: h,
857
+ clearSession: k,
858
+ onSessionChanged: _,
859
+ beginLogin: S,
860
+ completeLogin: A,
861
+ getValidAccessToken: R,
862
+ refreshAccessToken: F,
863
+ readAccessTokenSubject: H
864
+ };
865
+ }
866
+ function $t(e, o) {
867
+ return e.startsWith("/") && !e.startsWith("//") ? e : o;
868
+ }
869
+ function ce(e, o, r) {
870
+ return new URL(`${e.replace(/\/$/, "")}${o}`, r);
871
+ }
872
+ function Ut(e) {
873
+ return `${e.pathname}${e.search}${e.hash}`;
874
+ }
875
+ function ue(e, o) {
876
+ const r = e.getRandomValues(new Uint8Array(o));
877
+ return we(r);
878
+ }
879
+ async function Lt(e, o) {
880
+ const r = new TextEncoder().encode(o), s = e.subtle ? new Uint8Array(await e.subtle.digest("SHA-256", r)) : It(r);
881
+ return we(s);
882
+ }
883
+ function we(e) {
884
+ let o = "";
885
+ return e.forEach((r) => {
886
+ o += String.fromCharCode(r);
887
+ }), btoa(o).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
888
+ }
889
+ export {
890
+ Ft as AdminFrameworkLayout,
891
+ le as FrameworkMenuItem,
892
+ Bt as OAuthCallbackView,
893
+ De as closeVisitedTab,
894
+ zt as createOAuthPkceClient,
895
+ Rt as findMenuByRoute,
896
+ qe as findMenuTrail,
897
+ Z as flattenRoutableMenus,
898
+ ee as normalizeVisitedTabs,
899
+ Je as retainActiveTab,
900
+ $t as sanitizeReturnUrl,
901
+ It as sha256,
902
+ Pe as upsertVisitedTab
903
+ };
package/dist/menu.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export interface FrameworkMenuNode {
2
+ applicationCode: string;
3
+ menuDomain?: string | null;
4
+ menuCode: string;
5
+ name: string;
6
+ route: string | null;
7
+ icon: string | null;
8
+ sortOrder: number;
9
+ apiCodes: string[];
10
+ children: FrameworkMenuNode[];
11
+ }
@@ -0,0 +1,12 @@
1
+ import type { FrameworkMenuNode } from './menu';
2
+ export declare function flattenRoutableMenus(menus: FrameworkMenuNode[]): FrameworkMenuNode[];
3
+ export declare function findMenuTrail(menus: FrameworkMenuNode[], route: string, parents?: FrameworkMenuNode[]): FrameworkMenuNode[];
4
+ export declare function findMenuByRoute(menus: FrameworkMenuNode[], route: string): FrameworkMenuNode | undefined;
5
+ /**
6
+ * Keeps menu selection unique when different applications share the same route.
7
+ */
8
+ export declare function menuItemIndex(menu: FrameworkMenuNode): string;
9
+ /**
10
+ * Resolves a trail by node identity rather than route (which may occur in many apps).
11
+ */
12
+ export declare function findMenuTrailForItem(menus: FrameworkMenuNode[], target: FrameworkMenuNode, parents?: FrameworkMenuNode[]): FrameworkMenuNode[];
@@ -0,0 +1,41 @@
1
+ export interface OAuthSession {
2
+ accessToken: string;
3
+ refreshToken: string;
4
+ expiresAt: number;
5
+ }
6
+ export interface OAuthPkceClient {
7
+ isMockMode(): boolean;
8
+ getSession(): OAuthSession | null;
9
+ clearSession(): void;
10
+ onSessionChanged(listener: () => void): () => void;
11
+ beginLogin(returnUrl?: string): Promise<void>;
12
+ completeLogin(code: string, state: string): Promise<string>;
13
+ getValidAccessToken(): Promise<string | null>;
14
+ refreshAccessToken(): Promise<string | null>;
15
+ readAccessTokenSubject(accessToken: string): string;
16
+ }
17
+ export interface OAuthPkceClientOptions {
18
+ authority: string;
19
+ clientId: string;
20
+ redirectUri: string;
21
+ defaultReturnUrl: string;
22
+ storageNamespace: string;
23
+ mockMode?: boolean;
24
+ refreshSkewMs?: number;
25
+ transactionLifetimeMs?: number;
26
+ storage?: Storage;
27
+ location?: OAuthLocation;
28
+ eventTarget?: EventTarget;
29
+ crypto?: Crypto;
30
+ fetcher?: typeof fetch;
31
+ now?: () => number;
32
+ }
33
+ export interface OAuthLocation {
34
+ origin: string;
35
+ pathname: string;
36
+ search: string;
37
+ hash: string;
38
+ assign(url: string | URL): void;
39
+ }
40
+ export declare function createOAuthPkceClient(options: OAuthPkceClientOptions): OAuthPkceClient;
41
+ export declare function sanitizeReturnUrl(value: string, fallback: string): string;
@@ -0,0 +1 @@
1
+ export declare function sha256(input: Uint8Array): Uint8Array<ArrayBuffer>;
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .saf-shell[data-v-6ef4786f]{min-height:100vh;color:#27312d;--saf-sidebar: #1f2926;--saf-sidebar-active: #31443d;--saf-primary: #2f7866;--saf-muted: #78847f}.saf-sidebar[data-v-6ef4786f]{position:fixed;inset:0 auto 0 0;z-index:30;display:flex;width:236px;flex-direction:column;color:#ccd7d2;background:var(--saf-sidebar);transition:width .2s ease}.saf-shell--collapsed .saf-desktop-sidebar[data-v-6ef4786f]{width:70px}.saf-brand[data-v-6ef4786f]{display:flex;height:76px;flex:0 0 76px;align-items:center;gap:12px;padding:0 20px;border-bottom:1px solid #ffffff12}.saf-brand--compact[data-v-6ef4786f]{justify-content:center;padding:0}.saf-brand-mark[data-v-6ef4786f]{display:grid;width:34px;height:34px;flex:0 0 34px;place-items:center;color:#fff;border-radius:6px;background:#3a846f}.saf-brand-mark .el-icon[data-v-6ef4786f]{font-size:19px}.saf-brand>div[data-v-6ef4786f]{display:grid;min-width:0;gap:3px}.saf-brand strong[data-v-6ef4786f]{overflow:hidden;color:#fff;font-size:14px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.saf-brand small[data-v-6ef4786f]{overflow:hidden;color:#82938c;font-size:12px;letter-spacing:1px;text-overflow:ellipsis;white-space:nowrap}.saf-sidebar-nav[data-v-6ef4786f]{min-height:0;flex:1;padding:14px 10px;overflow-y:auto}.saf-sidebar-nav[data-v-6ef4786f] .el-menu{width:100%;border-right:0;background:transparent;--el-menu-bg-color: transparent;--el-menu-hover-bg-color: #ffffff0b;--el-menu-text-color: #aebdb7;--el-menu-active-color: #fff}.saf-sidebar-nav[data-v-6ef4786f] .el-menu-item,.saf-sidebar-nav[data-v-6ef4786f] .el-sub-menu__title{height:42px;margin-bottom:4px;border-radius:5px;line-height:42px}.saf-sidebar-nav[data-v-6ef4786f] .el-menu-item:hover,.saf-sidebar-nav[data-v-6ef4786f] .el-sub-menu__title:hover{color:#fff}.saf-sidebar-nav[data-v-6ef4786f] .el-menu-item.is-active{background:var(--saf-sidebar-active);box-shadow:inset 3px 0 #56a58d}.saf-sidebar-nav[data-v-6ef4786f] .el-menu-item .el-icon,.saf-sidebar-nav[data-v-6ef4786f] .el-sub-menu__title .el-icon{font-size:18px}.saf-sidebar-nav[data-v-6ef4786f] .el-menu--collapse .el-sub-menu__title,.saf-sidebar-nav[data-v-6ef4786f] .el-menu--collapse .el-menu-item{justify-content:center;padding:0!important}.saf-nav-feedback[data-v-6ef4786f]{padding:14px 12px;color:#8fa099;font-size:14px;line-height:1.6}.saf-nav-feedback--compact[data-v-6ef4786f]{padding-inline:0;text-align:center}.saf-sidebar-footer[data-v-6ef4786f]{display:grid;gap:4px;margin-top:auto;padding:10px;border-top:1px solid #ffffff12}.saf-sidebar-footer button[data-v-6ef4786f]{display:flex;width:100%;height:40px;align-items:center;gap:12px;padding:0 13px;color:#aebdb7;border:0;border-radius:5px;background:transparent;cursor:pointer}.saf-sidebar-footer button[data-v-6ef4786f]:hover{color:#fff;background:#ffffff0b}.saf-sidebar-footer button .el-icon[data-v-6ef4786f]{flex:0 0 auto;font-size:18px}.saf-sidebar-footer button span[data-v-6ef4786f]{font-size:14px;white-space:nowrap}.saf-shell--collapsed .saf-sidebar-footer button[data-v-6ef4786f]{justify-content:center;padding:0}.saf-main-shell[data-v-6ef4786f]{min-width:0;margin-left:236px;transition:margin-left .2s ease}.saf-shell--collapsed .saf-main-shell[data-v-6ef4786f]{margin-left:70px}.saf-topbar[data-v-6ef4786f]{position:sticky;top:0;z-index:20;display:flex;height:56px;align-items:center;justify-content:space-between;padding:0 20px;border-bottom:1px solid #e2e6e4;background:#fffffff2;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.saf-mobile-menu-button[data-v-6ef4786f]{display:none}.saf-topbar-context[data-v-6ef4786f],.saf-topbar-context span[data-v-6ef4786f],.saf-topbar-actions[data-v-6ef4786f]{display:flex;align-items:center}.saf-topbar-context[data-v-6ef4786f]{min-width:0;gap:12px}.saf-topbar-context span[data-v-6ef4786f]{gap:6px;color:var(--saf-muted);font-size:13px;white-space:nowrap}.saf-topbar-context>i[data-v-6ef4786f]{width:1px;height:16px;flex:0 0 auto;background:#dfe4e1}.saf-topbar-context strong[data-v-6ef4786f]{overflow:hidden;color:#36413d;font-size:15px;text-overflow:ellipsis;white-space:nowrap}.saf-topbar-actions[data-v-6ef4786f]{min-width:0;gap:9px}.saf-topbar-divider[data-v-6ef4786f]{width:1px;height:20px;flex:0 0 auto;background:#dfe4e1}.saf-operator[data-v-6ef4786f]{display:grid;min-width:0;gap:2px}.saf-operator strong[data-v-6ef4786f],.saf-operator span[data-v-6ef4786f]{max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.saf-operator strong[data-v-6ef4786f]{font-size:13px}.saf-operator span[data-v-6ef4786f]{color:var(--saf-muted);font-size:12px}.saf-page-tabs[data-v-6ef4786f]{display:grid;height:36px;grid-template-columns:minmax(0,1fr) 36px;border-bottom:1px solid #dde2df;background:#f8f9f8}.saf-page-tabs__scroller[data-v-6ef4786f]{display:flex;min-width:0;align-items:stretch;gap:2px;padding-left:18px;overflow-x:auto}.saf-page-tab[data-v-6ef4786f]{display:flex;min-width:max-content;align-items:center;gap:7px;padding:0 10px 0 12px;color:#707c77;font-size:13px;border:0;border-bottom:2px solid transparent;background:transparent;cursor:pointer}.saf-page-tab[data-v-6ef4786f]:hover{color:#303b36}.saf-page-tab--active[data-v-6ef4786f]{color:var(--saf-primary);border-bottom-color:var(--saf-primary);background:#fff}.saf-page-tab__close[data-v-6ef4786f]{width:18px;height:18px;justify-content:center;border-radius:3px}.saf-page-tab__close[data-v-6ef4786f]:hover{color:#b84848;background:#f5e8e8}.saf-tab-menu-button[data-v-6ef4786f]{width:36px;height:35px;border-left:1px solid #dde2df;border-radius:0}.saf-content-frame[data-v-6ef4786f]{min-width:0}.saf-workspace[data-v-6ef4786f]{min-width:0;padding:24px 24px 42px}.saf-route-breadcrumbs[data-v-6ef4786f]{display:flex;align-items:center;gap:8px;margin-bottom:12px;color:var(--saf-muted);font-size:14px}.saf-route-breadcrumbs span+span[data-v-6ef4786f]:before{margin-right:8px;color:#aab3af;content:"/"}@media(max-width:860px){.saf-desktop-sidebar[data-v-6ef4786f]{display:none}.saf-main-shell[data-v-6ef4786f],.saf-shell--collapsed .saf-main-shell[data-v-6ef4786f]{margin-left:0}.saf-mobile-menu-button[data-v-6ef4786f]{display:inline-flex;margin-right:6px}.saf-topbar[data-v-6ef4786f]{padding:0 12px}.saf-workspace[data-v-6ef4786f]{padding:20px 14px 34px}.saf-page-tabs__scroller[data-v-6ef4786f]{padding-left:12px}.saf-topbar-context span[data-v-6ef4786f],.saf-topbar-context>i[data-v-6ef4786f],.saf-operator[data-v-6ef4786f],.saf-topbar-divider[data-v-6ef4786f]{display:none}}@media(max-width:480px){.saf-topbar-actions[data-v-6ef4786f]{gap:4px}.saf-topbar-context strong[data-v-6ef4786f]{max-width:130px}}.saf-mobile-nav-drawer .el-drawer__body{padding:0;background:var(--saf-sidebar, #1f2926)}.saf-mobile-nav-drawer .saf-mobile-sidebar{position:static;width:100%;min-height:100%}.saf-oauth-callback[data-v-ef3407f8]{min-height:100vh;display:grid;place-items:center;background:#f4f6f8}.saf-oauth-loading[data-v-ef3407f8]{display:flex;align-items:center;gap:12px;color:#344054;font-size:15px}
package/dist/tabs.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface FrameworkTab {
2
+ path: string;
3
+ title: string;
4
+ }
5
+ export interface ClosedTabResult {
6
+ tabs: FrameworkTab[];
7
+ navigateTo: string | null;
8
+ }
9
+ export declare function normalizeVisitedTabs(value: unknown): FrameworkTab[];
10
+ export declare function upsertVisitedTab(tabs: FrameworkTab[], tab: FrameworkTab, maxTabs: number): FrameworkTab[];
11
+ export declare function closeVisitedTab(tabs: FrameworkTab[], path: string, activePath: string): ClosedTabResult;
12
+ export declare function retainActiveTab(tabs: FrameworkTab[], activePath: string): FrameworkTab[];
@@ -0,0 +1,14 @@
1
+ import type { OAuthPkceClient } from '../oauth';
2
+ type __VLS_Props = {
3
+ client: OAuthPkceClient;
4
+ retryPath?: string;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ completed: (returnUrl: string) => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ onCompleted?: ((returnUrl: string) => any) | undefined;
10
+ }>, {
11
+ retryPath: string;
12
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@npm.365xs.cn/vue-admin-framework",
3
+ "version": "0.1.0",
4
+ "description": "Reusable Vue 3 authentication and admin shell framework for Shinesun applications.",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./style.css": "./dist/style.css"
19
+ },
20
+ "sideEffects": [
21
+ "./dist/style.css"
22
+ ],
23
+ "scripts": {
24
+ "build": "vite build && vue-tsc -p tsconfig.json --declaration --emitDeclarationOnly",
25
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit"
26
+ },
27
+ "peerDependencies": {
28
+ "@element-plus/icons-vue": "^2.3.0",
29
+ "element-plus": "^2.11.0",
30
+ "vue": "^3.5.0",
31
+ "vue-router": "^4.5.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "license": "UNLICENSED"
37
+ }