@pubinfo/core 2.0.0-rc.3 → 2.0.0-rc.5

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.
Files changed (130) hide show
  1. package/dist/{AppSetting-3wJKvibc.js → AppSetting-DqVYDIHj.js} +15 -15
  2. package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-17EywJvs.js → HCheckList.vue_vue_type_script_setup_true_lang-SrNklW3P.js} +1 -1
  3. package/dist/{HToggle-B-ZjSh6S.js → HToggle-DGTP9jYA.js} +1 -1
  4. package/dist/{PreferencesContent-xT4paU7N.js → PreferencesContent-5NtwK9RQ.js} +4 -4
  5. package/dist/{SettingBreadcrumb-CYnO51Ek.js → SettingBreadcrumb-BudqQsuJ.js} +3 -3
  6. package/dist/{SettingCopyright-FOW5ObHK.js → SettingCopyright-VUberG4R.js} +2 -2
  7. package/dist/{SettingEnableTransition-Q5cvubmF.js → SettingEnableTransition-C6NYf021.js} +2 -2
  8. package/dist/SettingHome-BTaeKgwN.js +46 -0
  9. package/dist/{SettingMenu-BNAJ3el9.js → SettingMenu-D9Aon2LP.js} +3 -3
  10. package/dist/{SettingMode-LzlRsBL9.js → SettingMode-DaqVd9Mq.js} +1 -1
  11. package/dist/{SettingNavSearch-BA08vYuw.js → SettingNavSearch-N4JIheIk.js} +2 -2
  12. package/dist/{SettingOther-BE8dDCYD.js → SettingOther-tLulcors.js} +2 -2
  13. package/dist/{SettingPage-D061yXCv.js → SettingPage-CEjWB45R.js} +2 -2
  14. package/dist/{SettingTabbar-COwdPPKy.js → SettingTabbar-DyeLhcCT.js} +3 -3
  15. package/dist/{SettingThemes-BHaYERNp.js → SettingThemes-C2M3tsVl.js} +1 -1
  16. package/dist/{SettingToolbar-fSuzu6ND.js → SettingToolbar-DI7de6i0.js} +24 -31
  17. package/dist/{SettingTopbar-D7GgP0KB.js → SettingTopbar-BgIoXeAq.js} +3 -3
  18. package/dist/{SettingWidthMode-CNjzChe1.js → SettingWidthMode-DIAU4s5e.js} +1 -1
  19. package/dist/{TopThinMode-B-28sBJD.js → TopThinMode-JNUHrJI2.js} +1 -1
  20. package/dist/built-in/authentication/alova/helper.d.ts +34 -0
  21. package/dist/built-in/authentication/alova/token.d.ts +16 -0
  22. package/dist/built-in/authentication/helper.d.ts +10 -1
  23. package/dist/built-in/index.d.ts +1 -1
  24. package/dist/built-in/layout-component/components/Tools/SearchPanel.vue.d.ts +7 -2
  25. package/dist/built-in/layout-component/components/ui/HSlideover.vue.d.ts +1 -1
  26. package/dist/built-in/layout-component/composables/useContext.d.ts +1 -1
  27. package/dist/built-in/layout-component/composables/useGlobalSearch.d.ts +7 -0
  28. package/dist/built-in/layout-component/composables/useTitle.d.ts +1 -1
  29. package/dist/built-in/system-info/components/SystemInfo.vue.d.ts +2 -0
  30. package/dist/built-in/system-info/index.d.ts +5 -0
  31. package/dist/{colors-BIQSd520.js → colors-DxWfHM_v.js} +1 -1
  32. package/dist/core/interface.d.ts +14 -5
  33. package/dist/core/request.d.ts +2 -8
  34. package/dist/features/api/modules/auth/renzhengfuwu.d.ts +8 -8
  35. package/dist/features/api/modules/configData/heibaimingdanfuwu.d.ts +5 -5
  36. package/dist/features/api/modules/configData/xitongpeizhifuwu.d.ts +14 -14
  37. package/dist/features/api/modules/configData/zidifuwu.d.ts +10 -10
  38. package/dist/features/api/modules/rbac/gangweijiekou.d.ts +6 -6
  39. package/dist/features/api/modules/rbac/jiaosejiekou.d.ts +7 -7
  40. package/dist/features/api/modules/rbac/pubJiaosezukongzhiqi.d.ts +7 -7
  41. package/dist/features/api/modules/rbac/shujuquanxianzhubiaokongzhiqi.d.ts +9 -9
  42. package/dist/features/api/modules/rbac/yonghujiekou.d.ts +15 -15
  43. package/dist/features/api/modules/rbac/yonghushoucangbiaojiekou.d.ts +5 -5
  44. package/dist/features/api/modules/rbac/yonghuzuijinchangyongbiaojiekou.d.ts +4 -4
  45. package/dist/features/api/modules/rbac/ziyuanjiekou.d.ts +13 -13
  46. package/dist/features/api/modules/rbac/zuhuguanlijiekou.d.ts +5 -5
  47. package/dist/features/api/modules/rbac/zuzhijiaosebiaokongzhiqi.d.ts +4 -4
  48. package/dist/features/api/modules/rbac/zuzhijiekou.d.ts +9 -9
  49. package/dist/features/api/system/user.d.ts +4 -4
  50. package/dist/features/components/PubinfoIcon/PrismBox.vue.d.ts +21 -0
  51. package/dist/features/components/PubinfoIcon/SquareBox.vue.d.ts +17 -0
  52. package/dist/features/components/PubinfoIcon/index.vue.d.ts +13 -9
  53. package/dist/features/components/PubinfoIcon/props.d.ts +58 -0
  54. package/dist/features/components/index.d.ts +2 -0
  55. package/dist/{index-Dlf6GQBd.js → index-5fRpGyLW.js} +4 -4
  56. package/dist/{index-DNdH93AP.js → index-BFRIv97x.js} +2 -2
  57. package/dist/{index-WubcSL0v.js → index-BH-vHGvk.js} +1 -1
  58. package/dist/{index-YSjb6X1D.js → index-C7xIGcDc.js} +4 -7
  59. package/dist/{index-DYMBkmAp.js → index-CNVn3Ubv.js} +2 -2
  60. package/dist/{index-CPRiufg0.js → index-Cf-u1Zqh.js} +1 -1
  61. package/dist/{index-wxEEuQXu.js → index-D4v4g8FJ.js} +111 -97
  62. package/dist/{index-IscoZG-Y.js → index-DQGnbEGS.js} +2 -2
  63. package/dist/{index-Beb7_0-E.js → index-Dv7UUFkD.js} +24237 -23807
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +55 -48
  66. package/dist/{pick-D_XPbQHB.js → pick-VFuUwFn-.js} +1 -1
  67. package/dist/style.css +1 -1
  68. package/dist/utils/global.d.ts +33 -0
  69. package/dist/utils/index.d.ts +2 -1
  70. package/package.json +10 -7
  71. package/src/built-in/authentication/alova/helper.ts +158 -0
  72. package/src/built-in/authentication/alova/token.ts +122 -0
  73. package/src/built-in/authentication/helper.ts +7 -3
  74. package/src/built-in/authentication/index.ts +6 -20
  75. package/src/built-in/index.ts +1 -1
  76. package/src/built-in/layout-component/components/Header/TopMode/index.vue +30 -9
  77. package/src/built-in/layout-component/components/Menu/item.vue +44 -9
  78. package/src/built-in/layout-component/components/SettingBar/components/SettingHome.vue +1 -4
  79. package/src/built-in/layout-component/components/SettingBar/components/SettingToolbar.vue +0 -6
  80. package/src/built-in/layout-component/components/Sidebar/MainSidebar.vue +3 -3
  81. package/src/built-in/layout-component/components/Tools/SearchBar.vue +1 -3
  82. package/src/built-in/layout-component/components/Tools/SearchPanel.vue +125 -57
  83. package/src/built-in/layout-component/components/Tools/index.vue +9 -12
  84. package/src/built-in/layout-component/components/Topbar/Tabbar/MoreAction.vue +64 -11
  85. package/src/built-in/layout-component/components/Topbar/Tabbar/index.vue +73 -18
  86. package/src/built-in/layout-component/components/Topbar/Toolbar/Favorites.vue +4 -7
  87. package/src/built-in/layout-component/components/Topbar/Toolbar/index.vue +6 -6
  88. package/src/built-in/layout-component/composables/useContext.ts +1 -1
  89. package/src/built-in/layout-component/composables/useGlobalSearch.ts +40 -1
  90. package/src/built-in/layout-component/composables/useTitle.ts +8 -14
  91. package/src/built-in/layout-component/provider.ts +2 -2
  92. package/src/built-in/system-info/components/SystemInfo.vue +53 -0
  93. package/src/built-in/system-info/index.ts +16 -0
  94. package/src/core/ctx.ts +7 -1
  95. package/src/core/interface.ts +18 -5
  96. package/src/core/request.ts +35 -15
  97. package/src/core/resolver/icon.ts +9 -5
  98. package/src/features/components/PubinfoIcon/PrismBox.vue +203 -0
  99. package/src/features/components/PubinfoIcon/SquareBox.vue +59 -0
  100. package/src/features/components/PubinfoIcon/index.vue +128 -37
  101. package/src/features/components/PubinfoIcon/props.ts +54 -0
  102. package/src/features/components/index.ts +4 -1
  103. package/src/features/context/index.ts +1 -16
  104. package/src/features/router/systemRoutes.ts +0 -1
  105. package/src/features/settings/index.ts +0 -1
  106. package/src/features/stores/modules/favorites.ts +0 -1
  107. package/src/features/stores/modules/route.ts +2 -9
  108. package/src/features/stores/modules/tabbar.ts +0 -3
  109. package/src/features/stores/utils/routerHelper.ts +35 -4
  110. package/src/index.ts +7 -2
  111. package/src/utils/global.ts +161 -0
  112. package/src/utils/index.ts +2 -1
  113. package/src/utils/proxy.ts +7 -8
  114. package/types/global.d.ts +7 -0
  115. package/types/menu.d.ts +10 -0
  116. package/types/settings.d.ts +0 -7
  117. package/types/vue-router.d.ts +0 -3
  118. package/dist/SettingHome-Df7-AlWB.js +0 -55
  119. package/dist/built-in/locales/helpler.d.ts +0 -594
  120. package/dist/built-in/locales/index.d.ts +0 -5
  121. package/dist/built-in/locales/lang/en.json.d.ts +0 -99
  122. package/dist/built-in/locales/lang/zh-cn.json.d.ts +0 -100
  123. package/dist/built-in/locales/lang/zh-tw.json.d.ts +0 -100
  124. package/dist/built-in/locales/ui.d.ts +0 -3
  125. package/src/built-in/locales/helpler.ts +0 -76
  126. package/src/built-in/locales/index.ts +0 -20
  127. package/src/built-in/locales/lang/en.json +0 -96
  128. package/src/built-in/locales/lang/zh-cn.json +0 -97
  129. package/src/built-in/locales/lang/zh-tw.json +0 -97
  130. package/src/built-in/locales/ui.ts +0 -3
@@ -0,0 +1,33 @@
1
+ import { UseContext } from 'unctx';
2
+ type PubinfoNamespace = Record<string, unknown>;
3
+ /**
4
+ * Return the shared __PUBINFO__ namespace, creating it if necessary.
5
+ */
6
+ export declare function getPubinfoNamespace(): PubinfoNamespace;
7
+ interface PersistedContextOptions {
8
+ /**
9
+ * Optional key used to hydrate persisted state.
10
+ * Defaults to transforming `FooCtx` into `FooState`.
11
+ */
12
+ stateKey?: string;
13
+ /**
14
+ * Toggle hydration behaviour. Enabled by default.
15
+ */
16
+ hydrate?: boolean;
17
+ /**
18
+ * Automatically persist whenever ctx.set() is called.
19
+ */
20
+ persistOnSet?: boolean;
21
+ }
22
+ /**
23
+ * Create (or reuse) a context stored on the global namespace and
24
+ * hydrate it from a persisted state if available.
25
+ */
26
+ export declare function createContext<T>(contextKey: string, options?: PersistedContextOptions): UseContext<T>;
27
+ /** Persist a value inside the __PUBINFO__ namespace. */
28
+ export declare function setPersistedState<T>(stateKey: string, state: T): void;
29
+ /** Retrieve a value from the __PUBINFO__ namespace. */
30
+ export declare function getPersistedState<T>(stateKey: string): T | undefined;
31
+ /** Remove a value from the __PUBINFO__ namespace. */
32
+ export declare function clearPersistedState(stateKey: string): void;
33
+ export {};
@@ -1,5 +1,6 @@
1
1
  export * from './cleanup';
2
+ export * from './global';
2
3
  export * from './path';
3
4
  export * from './proxy';
4
5
  export * from './storage';
5
- export { createContext } from 'unctx';
6
+ export { createContext as createRawContext } from 'unctx';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pubinfo/core",
3
3
  "type": "module",
4
- "version": "2.0.0-rc.3",
4
+ "version": "2.0.0-rc.5",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -18,17 +18,20 @@
18
18
  "src",
19
19
  "types"
20
20
  ],
21
+ "engines": {
22
+ "node": "^20.19.0 || >=22.12.0"
23
+ },
21
24
  "peerDependencies": {
22
25
  "alova": "^3.3.4",
23
26
  "pinia": "^3.0.3",
24
27
  "vue": "^3.5.17",
25
- "vue-i18n": "^10.0.7",
26
28
  "vue-router": "^4.5.1",
27
- "@pubinfo/config": "2.0.0-rc.3",
28
- "@pubinfo/vite": "2.0.0-rc.3"
29
+ "@pubinfo/config": "2.0.0-rc.5",
30
+ "@pubinfo/vite": "2.0.0-rc.5"
29
31
  },
30
32
  "dependencies": {
31
33
  "@alova/adapter-axios": "^2.0.16",
34
+ "@alova/shared": "^1.3.1",
32
35
  "@ant-design/icons-vue": "^7.0.1",
33
36
  "@headlessui/vue": "^1.7.23",
34
37
  "@imengyu/vue3-context-menu": "^1.5.1",
@@ -58,6 +61,7 @@
58
61
  "zxcvbn": "^4.4.2"
59
62
  },
60
63
  "devDependencies": {
64
+ "@alova/mock": "^2.0.17",
61
65
  "@iconify/json": "^2.2.354",
62
66
  "@iconify/vue": "^5.0.0",
63
67
  "@pubinfo/openapi": "^0.9.0",
@@ -80,10 +84,9 @@
80
84
  "vite": "^7.1.2",
81
85
  "vite-plugin-dts": "^4.5.4",
82
86
  "vue": "^3.5.17",
83
- "vue-i18n": "^10.0.7",
84
87
  "vue-router": "^4.5.1",
85
- "@pubinfo/config": "2.0.0-rc.3",
86
- "@pubinfo/vite": "2.0.0-rc.3"
88
+ "@pubinfo/config": "2.0.0-rc.5",
89
+ "@pubinfo/vite": "2.0.0-rc.5"
87
90
  },
88
91
  "scripts": {
89
92
  "dev": "vite build -w -m watch",
@@ -0,0 +1,158 @@
1
+ import type {
2
+ AlovaRequestAdapter,
3
+ Method,
4
+ RespondedHandler,
5
+ ResponseCompleteHandler,
6
+ ResponseErrorHandler,
7
+ StatesHook,
8
+ } from 'alova';
9
+ import type {
10
+ AlovaResponded,
11
+ MetaMatches,
12
+ ResponseAuthorizationInterceptor,
13
+ StateHookAdapter2AG,
14
+ } from 'alova/client';
15
+ import {
16
+ falseValue,
17
+ forEach,
18
+ instanceOf,
19
+ isFn,
20
+ isPlainObject,
21
+ len,
22
+ newInstance,
23
+ noop,
24
+ PromiseCls,
25
+ pushItem,
26
+ splice,
27
+ trueValue,
28
+ undefinedValue,
29
+ } from '@alova/shared';
30
+
31
+ export type PosibbleAuthMap
32
+ = | {
33
+ metaMatches?: MetaMatches
34
+ handler: any
35
+ }
36
+ | undefined;
37
+ export type WaitingRequestList = {
38
+ method: Method
39
+ resolve: () => void
40
+ }[];
41
+
42
+ export const defaultVisitorMeta = {
43
+ authRole: null,
44
+ };
45
+ export const defaultLoginMeta = {
46
+ authRole: 'login',
47
+ };
48
+ export const defaultLogoutMeta = {
49
+ authRole: 'logout',
50
+ };
51
+ export const defaultRefreshTokenMeta = {
52
+ authRole: 'refreshToken',
53
+ };
54
+ export function checkMethodRole({ meta }: Method, metaMatches: MetaMatches) {
55
+ if (isPlainObject(meta)) {
56
+ for (const key in meta) {
57
+ if (Object.prototype.hasOwnProperty.call(meta, key)) {
58
+ const matchedMetaItem = metaMatches[key];
59
+ if (instanceOf(matchedMetaItem, RegExp) ? matchedMetaItem.test(meta[key]) : meta[key] === matchedMetaItem) {
60
+ return trueValue;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return falseValue;
66
+ }
67
+ export function waitForTokenRefreshed(method: Method, waitingList: WaitingRequestList) {
68
+ return newInstance(PromiseCls, (resolve) => {
69
+ pushItem(waitingList, {
70
+ method,
71
+ resolve,
72
+ });
73
+ });
74
+ }
75
+ export function callHandlerIfMatchesMeta(method: Method,
76
+ authorizationInterceptor: ResponseAuthorizationInterceptor<AlovaRequestAdapter<any, any, any>> | undefined,
77
+ defaultMeta: MetaMatches,
78
+ response: any) {
79
+ if (checkMethodRole(method, (authorizationInterceptor as PosibbleAuthMap)?.metaMatches || defaultMeta)) {
80
+ const handler = isFn(authorizationInterceptor)
81
+ ? authorizationInterceptor
82
+ : isPlainObject(authorizationInterceptor) && isFn(authorizationInterceptor.handler)
83
+ ? authorizationInterceptor.handler
84
+ : noop;
85
+ return handler(response, method);
86
+ }
87
+ }
88
+ export async function refreshTokenIfExpired(method: Method,
89
+ waitingList: WaitingRequestList,
90
+ updateRefreshStatus: (status: boolean) => void,
91
+ handlerParams: any[],
92
+ refreshToken?: {
93
+ isExpired: (...args: any[]) => boolean | Promise<boolean>
94
+ handler: (...args: any[]) => Promise<void>
95
+ },
96
+ tokenRefreshing?: boolean) {
97
+ // When the number of handle params is greater than 2, it means that this function is called from the response, and the original interface needs to be requested again.
98
+ const fromResponse = len(handlerParams) >= 2;
99
+ let isExpired = refreshToken?.isExpired(...handlerParams);
100
+ // Compatible with synchronous and asynchronous functions
101
+ if (instanceOf(isExpired, PromiseCls)) {
102
+ isExpired = await isExpired;
103
+ }
104
+
105
+ if (isExpired) {
106
+ try {
107
+ // Make another judgment in the response to prevent multiple requests to refresh the token, intercept and wait for the token sent before the token refresh is completed.
108
+ let intentToRefreshToken = trueValue;
109
+ if (fromResponse && tokenRefreshing) {
110
+ intentToRefreshToken = falseValue; // The requests waiting here indicate that the token is being refreshed. When they pass, there is no need to refresh the token again.
111
+ await waitForTokenRefreshed(method, waitingList);
112
+ }
113
+
114
+ if (intentToRefreshToken) {
115
+ updateRefreshStatus(trueValue);
116
+ // Call refresh token
117
+ await refreshToken?.handler(...handlerParams);
118
+ updateRefreshStatus(falseValue);
119
+ // After the token refresh is completed, the requests in the waiting list are notified.
120
+ forEach(waitingList, ({ resolve }) => resolve());
121
+ }
122
+ if (fromResponse) {
123
+ // Because the original interface is being requested again, superposition with the previous request will result in repeated calls to transform, so it is necessary to leave transform empty to remove one call.
124
+ const { config } = method;
125
+ const methodTransformData = config.transform;
126
+ config.transform = undefinedValue;
127
+ const resentData = await method;
128
+ config.transform = methodTransformData;
129
+ return resentData;
130
+ }
131
+ }
132
+ finally {
133
+ updateRefreshStatus(falseValue);
134
+ splice(waitingList, 0, len(waitingList)); // Clear waiting list
135
+ }
136
+ }
137
+ }
138
+
139
+ export function onResponded2Record<SH extends StatesHook<any>, RA extends AlovaRequestAdapter<any, any, any>>(onRespondedHandlers?: AlovaResponded<SH, RA>) {
140
+ type AG = StateHookAdapter2AG<SH, RA>;
141
+ let successHandler: RespondedHandler<AG> | undefined = undefinedValue;
142
+ let errorHandler: ResponseErrorHandler<AG> | undefined = undefinedValue;
143
+ let onCompleteHandler: ResponseCompleteHandler<AG> | undefined = undefinedValue;
144
+ if (isFn(onRespondedHandlers)) {
145
+ successHandler = onRespondedHandlers;
146
+ }
147
+ else if (isPlainObject(onRespondedHandlers)) {
148
+ const { onSuccess, onError, onComplete } = onRespondedHandlers;
149
+ successHandler = isFn(onSuccess) ? onSuccess : successHandler;
150
+ errorHandler = isFn(onError) ? onError : errorHandler;
151
+ onCompleteHandler = isFn(onComplete) ? onComplete : onCompleteHandler;
152
+ }
153
+ return {
154
+ onSuccess: successHandler,
155
+ onError: errorHandler,
156
+ onComplete: onCompleteHandler,
157
+ };
158
+ }
@@ -0,0 +1,122 @@
1
+ import type { AlovaRequestAdapter, Method, StatesHook } from 'alova';
2
+ import type {
3
+ AlovaRequestAdapterUnified,
4
+ BeforeRequestType,
5
+ ServerTokenAuthenticationOptions,
6
+ } from 'alova/client';
7
+ import type { FetchRequestAdapter } from 'alova/fetch';
8
+ import type {
9
+ PosibbleAuthMap,
10
+ WaitingRequestList } from './helper';
11
+ import { $self, falseValue, noop } from '@alova/shared';
12
+ import {
13
+ callHandlerIfMatchesMeta,
14
+ checkMethodRole,
15
+ defaultLoginMeta,
16
+ defaultLogoutMeta,
17
+ defaultRefreshTokenMeta,
18
+ defaultVisitorMeta,
19
+ refreshTokenIfExpired,
20
+ waitForTokenRefreshed,
21
+ } from './helper';
22
+
23
+ /**
24
+ * Create a server-side token authentication interceptor
25
+ * @returns token authentication interceptor function
26
+ */
27
+ export function createServerTokenAuthentication<
28
+ SH extends StatesHook<any>,
29
+ RA extends
30
+ | AlovaRequestAdapter<any, any, any>
31
+ | ((...args: any[]) => AlovaRequestAdapter<any, any, any>) = FetchRequestAdapter,
32
+ >({
33
+ visitorMeta,
34
+ login,
35
+ logout,
36
+ refreshTokenOnSuccess,
37
+ refreshTokenOnError,
38
+ assignToken = noop,
39
+ }: ServerTokenAuthenticationOptions<AlovaRequestAdapterUnified<RA>>) {
40
+ let tokenRefreshing = falseValue;
41
+ const waitingList: WaitingRequestList = [];
42
+
43
+ const onAuthRequired: BeforeRequestType<SH, AlovaRequestAdapterUnified<RA>> = onBeforeRequest => async (method) => {
44
+ const isVisitorRole = checkMethodRole(method, visitorMeta || defaultVisitorMeta);
45
+ const isLoginRole = checkMethodRole(method, (login as PosibbleAuthMap)?.metaMatches || defaultLoginMeta);
46
+ // Ignored, login, and token refresh requests do not perform token authentication.
47
+ if (
48
+ !isVisitorRole
49
+ && !isLoginRole
50
+ && !checkMethodRole(method, (refreshTokenOnSuccess as PosibbleAuthMap)?.metaMatches || defaultRefreshTokenMeta)
51
+ && !checkMethodRole(method, (refreshTokenOnError as PosibbleAuthMap)?.metaMatches || defaultRefreshTokenMeta)
52
+ ) {
53
+ // If the token is being refreshed, wait for the refresh to complete before sending a request.
54
+ if (tokenRefreshing) {
55
+ await waitForTokenRefreshed(method, waitingList);
56
+ }
57
+ }
58
+ if (!isVisitorRole && !isLoginRole) {
59
+ await assignToken(method);
60
+ }
61
+ return onBeforeRequest?.(method);
62
+ };
63
+
64
+ const onResponseRefreshToken = () => {
65
+ return {
66
+ onSuccess: async (response: any, method: Method, stop: any) => {
67
+ if (
68
+ !checkMethodRole(method, visitorMeta || defaultVisitorMeta)
69
+ && !checkMethodRole(method, (login as PosibbleAuthMap)?.metaMatches || defaultLoginMeta)
70
+ && !checkMethodRole(method, (refreshTokenOnSuccess as PosibbleAuthMap)?.metaMatches || defaultRefreshTokenMeta)
71
+ ) {
72
+ const dataResent = await refreshTokenIfExpired(
73
+ method,
74
+ waitingList,
75
+ (refreshing) => {
76
+ tokenRefreshing = refreshing;
77
+ },
78
+ [response, method],
79
+ refreshTokenOnSuccess,
80
+ tokenRefreshing,
81
+ );
82
+ if (dataResent) {
83
+ stop();
84
+ return dataResent;
85
+ }
86
+ }
87
+
88
+ await callHandlerIfMatchesMeta(method, login, defaultLoginMeta, response);
89
+ await callHandlerIfMatchesMeta(method, logout, defaultLogoutMeta, response);
90
+ return $self(response);
91
+ },
92
+ onError: async (error: any, method: Method) => {
93
+ if (
94
+ !checkMethodRole(method, visitorMeta || defaultVisitorMeta)
95
+ && !checkMethodRole(method, (login as PosibbleAuthMap)?.metaMatches || defaultLoginMeta)
96
+ && !checkMethodRole(method, (refreshTokenOnError as PosibbleAuthMap)?.metaMatches || defaultRefreshTokenMeta)
97
+ ) {
98
+ const dataResent = await refreshTokenIfExpired(
99
+ method,
100
+ waitingList,
101
+ (refreshing) => {
102
+ tokenRefreshing = refreshing;
103
+ },
104
+ [error, method],
105
+ refreshTokenOnError,
106
+ tokenRefreshing,
107
+ );
108
+ if (dataResent) {
109
+ return dataResent;
110
+ }
111
+ }
112
+ return noop;
113
+ },
114
+ };
115
+ };
116
+
117
+ return {
118
+ waitingList,
119
+ onAuthRequired,
120
+ onResponseRefreshToken,
121
+ };
122
+ }
@@ -1,13 +1,16 @@
1
- import type { axiosRequestAdapter } from '@alova/adapter-axios';
1
+ import type { AxiosRequestAdapter, axiosRequestAdapter } from '@alova/adapter-axios';
2
+ import type { AlovaRequestAdapterUnified, ServerTokenAuthenticationOptions } from 'alova/client';
2
3
  import type VueHook from 'alova/vue';
3
4
  import type { AxiosResponse } from 'axios';
4
5
  import type { RequestResult } from './interface';
5
- import { createServerTokenAuthentication } from 'alova/client';
6
6
  import { postAuthTokenRefresh } from '@/features/api/modules/auth';
7
7
  import { useUserStore } from '@/features/stores';
8
+ import { createServerTokenAuthentication } from './alova/token';
8
9
  import { RESPONSE_CODE } from './enum';
9
10
 
10
- export function createTokenAuthentication() {
11
+ export function createTokenAuthentication(
12
+ options: ServerTokenAuthenticationOptions<AlovaRequestAdapterUnified<AxiosRequestAdapter>> = {},
13
+ ) {
11
14
  return createServerTokenAuthentication<typeof VueHook, typeof axiosRequestAdapter>({
12
15
  /**
13
16
  * 给权限接口添加Authorization
@@ -51,5 +54,6 @@ export function createTokenAuthentication() {
51
54
  userStore.setToken(accessToken, refreshToken);
52
55
  },
53
56
  },
57
+ ...options,
54
58
  });
55
59
  }
@@ -1,5 +1,5 @@
1
1
  import type { ModuleOptions } from '@/core';
2
- // import { createTokenAuthentication } from './helper';
2
+ import { createTokenAuthentication } from './helper';
3
3
  import { createRouterGuard } from './router';
4
4
 
5
5
  /**
@@ -8,21 +8,7 @@ import { createRouterGuard } from './router';
8
8
  * 双 token 校验,确认用户身份,注册修改密码和切换组织页面
9
9
  */
10
10
  export function Authentication(): ModuleOptions {
11
- // TODO hooks 的形式注册 alova 的 Token 认证拦截器,
12
- // Token 过期时的请求,在刷新完 Token 后虽然会重新请求,但是过期请求还是会继续抛出错误。
13
- // 若是用如下形式注册,则没有问题:
14
- // const alova = createAlova({
15
- // beforeRequest: onAuthRequired(method => {
16
- // }),
17
- // responded: onResponseRefreshToken({
18
- // onSuccess(response, method) {
19
- // },
20
- // onError(error, method) {
21
- // },
22
- // })
23
- // });
24
-
25
- // const { onAuthRequired, onResponseRefreshToken } = createTokenAuthentication();
11
+ const { onAuthRequired, onResponseRefreshToken } = createTokenAuthentication();
26
12
 
27
13
  return {
28
14
  name: 'built-in:authentication',
@@ -30,10 +16,10 @@ export function Authentication(): ModuleOptions {
30
16
  const { router } = ctx;
31
17
  createRouterGuard(router);
32
18
  },
33
- // hooks: {
34
- // 'http:request': onAuthRequired(),
35
- // 'http:response': onResponseRefreshToken(),
36
- // },
19
+ hooks: {
20
+ 'http:request': onAuthRequired(),
21
+ 'http:response': onResponseRefreshToken(),
22
+ },
37
23
  };
38
24
  }
39
25
 
@@ -1,8 +1,8 @@
1
1
  export * from './authentication';
2
2
  export * from './authorization';
3
3
  export * from './layout-component';
4
- export * from './locales';
5
4
  export * from './n-progress';
6
5
  export * from './pinia-plugin';
7
6
  export * from './pre-access';
8
7
  export * from './settings';
8
+ export * from './system-info';
@@ -12,7 +12,7 @@ defineOptions({
12
12
  name: 'TopMode',
13
13
  });
14
14
 
15
- const { settingsStore, menuStore, generateI18nTitle } = useContext();
15
+ const { settingsStore, menuStore, generateTitle } = useContext();
16
16
  const { switchTo } = useMenu();
17
17
 
18
18
  function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
@@ -113,18 +113,39 @@ onMounted(() => {
113
113
  'text-[var(--g-header-menu-active-color)]! bg-[var(--g-header-menu-active-bg)]!': index === menuStore.actived,
114
114
  'rounded-2': settingsStore.settings.menu.isRounded,
115
115
  }"
116
- :title="generateI18nTitle(item.meta?.i18n, item.meta?.title)"
116
+ :title="generateTitle(item.meta?.title)"
117
117
  @click="switchTo(index, item)"
118
118
  >
119
119
  <div class="flex flex-row flex-1 items-center justify-center px-8px">
120
- <PubinfoIcon
121
- v-if="iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon)"
122
- :name="iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon)!"
123
- :size="18"
124
- class="menu-item-container-icon transition-transform group-hover:scale-120 mr-5px"
125
- />
120
+ <template v-if="iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon)">
121
+ <!-- 当存在 iconOptions boxType 不为 'null' 时,展示带边框/渐变的盒子效果 -->
122
+ <PubinfoIcon
123
+ v-if="item.meta?.iconOptions"
124
+ :name="iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon)!"
125
+ small
126
+ :box="(item.meta.iconOptions.boxType as any) || 'prism'"
127
+ :size="18"
128
+ :angle="item.meta.iconOptions.angle"
129
+ :background="item.meta.iconOptions.background"
130
+ :radius="item.meta.iconOptions.radius"
131
+ :color="(iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon) || '').startsWith('antd:')
132
+ ? (item.meta.iconOptions.iconColor || '#ffffff')
133
+ : undefined"
134
+ class="menu-item-container-icon transition-transform group-hover:scale-120 mr-5px"
135
+ />
136
+ <!-- 否则与原行为一致(不带盒子),但若提供了图标色也一并生效 -->
137
+ <PubinfoIcon
138
+ v-else
139
+ :name="iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon)!"
140
+ :size="18"
141
+ :color="(iconName(index === menuStore.actived, item.meta?.icon, item.meta?.activeIcon) || '').startsWith('antd:')
142
+ ? (item.meta?.iconOptions?.iconColor || undefined)
143
+ : undefined"
144
+ class="menu-item-container-icon transition-transform group-hover:scale-120 mr-5px"
145
+ />
146
+ </template>
126
147
  <span class="w-full flex-1 truncate text-center text-sm transition-height transition-opacity transition-width">
127
- {{ generateI18nTitle(item.meta?.i18n, item.meta?.title) }}
148
+ {{ generateTitle(item.meta?.title) }}
128
149
  </span>
129
150
  </div>
130
151
  </div>
@@ -18,7 +18,7 @@ const props = withDefaults(
18
18
  );
19
19
 
20
20
  const rootMenu = inject(rootMenuInjectionKey)!;
21
- const { generateI18nTitle } = useContext();
21
+ const { generateTitle } = useContext();
22
22
 
23
23
  const itemRef = ref<HTMLElement>();
24
24
 
@@ -48,6 +48,24 @@ const icon = computed(() => {
48
48
  return icon;
49
49
  });
50
50
 
51
+ // 读取菜单条目的 iconOptions(后端可能未定义,使用 any 安全读取)
52
+ const iconOptions = computed(() => ((props.item.meta as any)?.iconOptions) as undefined | {
53
+ boxType?: 'square' | 'prism' | 'null'
54
+ angle?: number | string
55
+ background?: any
56
+ radius?: number | string
57
+ iconColor?: string
58
+ });
59
+
60
+ // 统一盒子类型:存在 iconOptions 时启用盒子,未指定或为 'null' 时默认 prism
61
+ const computedBoxType = computed(() => {
62
+ const bt = iconOptions.value?.boxType as any;
63
+ return bt && bt !== 'null' ? bt : 'prism';
64
+ });
65
+
66
+ // 统一安全的图标颜色(仅 AntD 图标使用);无配置则为 undefined
67
+ const safeIconColor = computed<string | undefined>(() => iconOptions.value?.iconColor || undefined);
68
+
51
69
  // 缩进样式
52
70
  const indentStyle = computed(() => {
53
71
  return !rootMenu.isMenuPopup
@@ -78,7 +96,7 @@ defineExpose({
78
96
  'px-2!': rootMenu.isMenuPopup && level === 0,
79
97
  'py-3!': rootMenu.props.rounded && rootMenu.isMenuPopup && level !== 0,
80
98
  }"
81
- :title="generateI18nTitle(item.meta?.i18n, item.meta?.title)"
99
+ :title="generateTitle(item.meta?.title)"
82
100
  >
83
101
  <div
84
102
  class="menu-item-container-layout inline-flex flex-1 items-center justify-center gap-[12px]"
@@ -91,12 +109,29 @@ defineExpose({
91
109
  :style="indentStyle"
92
110
  >
93
111
  <!-- 特殊化处理,有些时候想要的是一个点 -->
94
- <PubinfoIcon
95
- v-if="icon && icon !== 'system-point'"
96
- :name="icon"
97
- :size="20"
98
- class="menu-item-container-icon transition-transform group-hover:scale-115"
99
- />
112
+ <template v-if="icon && icon !== 'system-point'">
113
+ <!-- iconOptions 时启用带边框/渐变的盒子效果 -->
114
+ <PubinfoIcon
115
+ v-if="iconOptions"
116
+ small
117
+ :name="icon"
118
+ :box="computedBoxType as any"
119
+ :size="20"
120
+ :angle="iconOptions?.angle"
121
+ :background="iconOptions?.background"
122
+ :radius="iconOptions?.radius"
123
+ :color="(icon || '').startsWith('antd:') ? (safeIconColor || '#ffffff') : undefined"
124
+ class="menu-item-container-icon transition-transform group-hover:scale-115"
125
+ />
126
+ <!-- 无 iconOptions 时保持原样(纯图标),但若提供图标色也生效 -->
127
+ <PubinfoIcon
128
+ v-else
129
+ :name="icon"
130
+ :size="20"
131
+ :color="(icon || '').startsWith('antd:') ? safeIconColor : undefined"
132
+ class="menu-item-container-icon transition-transform group-hover:scale-115"
133
+ />
134
+ </template>
100
135
  <span
101
136
  v-if="!(rootMenu.isMenuPopup && level === 0 && !rootMenu.props.showCollapseName)"
102
137
  class="title w-0 flex-1 truncate text-sm transition-height transition-opacity transition-width"
@@ -106,7 +141,7 @@ defineExpose({
106
141
  'system-point': icon === 'system-point' && isItemActive,
107
142
  }"
108
143
  >
109
- {{ generateI18nTitle(item.meta?.i18n, item.meta?.title) }}
144
+ {{ generateTitle(item.meta?.title) }}
110
145
  </span>
111
146
  <HBadge
112
147
  v-if="item.meta?.badge"
@@ -23,11 +23,8 @@ const { home, toolbar } = toRefs(settingsStore.settings);
23
23
  <div class="setting-item">
24
24
  <div class="label">
25
25
  主页名称
26
- <HTooltip text="开启国际化时,该设置无效">
27
- <RiQuestionLine />
28
- </HTooltip>
29
26
  </div>
30
- <HInput v-model="home.title" :disabled="toolbar.enableI18n" />
27
+ <HInput v-model="home.title" />
31
28
  </div>
32
29
  </HDivider>
33
30
  </template>
@@ -20,12 +20,6 @@ const toolbar = toRef(settingsStore.settings, 'toolbar');
20
20
  </div>
21
21
  <HToggle v-model="toolbar.enableNotification" />
22
22
  </div>
23
- <div class="setting-item">
24
- <div class="label">
25
- 国际化
26
- </div>
27
- <HToggle v-model="toolbar.enableI18n" />
28
- </div>
29
23
  <div v-if="settingsStore.mode === 'pc'" class="setting-item">
30
24
  <div class="label">
31
25
  全屏
@@ -11,7 +11,7 @@ defineOptions({
11
11
 
12
12
  const route = useRoute();
13
13
 
14
- const { settingsStore, menuStore, generateI18nTitle } = useContext();
14
+ const { settingsStore, menuStore, generateTitle } = useContext();
15
15
  const { switchTo } = useMenu();
16
16
 
17
17
  function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
@@ -50,7 +50,7 @@ function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
50
50
  'text-[var(--g-main-sidebar-menu-active-color)]! bg-[var(--g-main-sidebar-menu-active-bg)]!': index === menuStore.actived,
51
51
  'rounded-2': settingsStore.settings.menu.isRounded,
52
52
  }"
53
- :title="generateI18nTitle(item.meta?.i18n, item.meta?.title)"
53
+ :title="generateTitle(item.meta?.title)"
54
54
  @click="switchTo(index, item)"
55
55
  >
56
56
  <div class="w-full inline-flex flex-1 flex-col items-center justify-center gap-[2px]">
@@ -62,7 +62,7 @@ function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
62
62
  />
63
63
  <!-- 不需要title? -->
64
64
  <!-- <span class="w-full flex-1 truncate text-center text-sm transition-height transition-opacity transition-width">
65
- {{ generateI18nTitle(item.meta?.i18n, item.meta?.title) }}
65
+ {{ generateTitle(item.meta?.title) }}
66
66
  </span> -->
67
67
  </div>
68
68
  </div>