@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
@@ -4,7 +4,6 @@ import ContextMenu from '@imengyu/vue3-context-menu';
4
4
  import { useMagicKeys } from '@vueuse/core';
5
5
  import hotkeys from 'hotkeys-js';
6
6
  import Sortable from 'sortablejs';
7
- import { useI18n } from 'vue-i18n';
8
7
  import Message from 'vue-m-message';
9
8
  import RiCloseFill from '~icons/ri/close-fill';
10
9
  import RiPushpin2Fill from '~icons/ri/pushpin-2-fill';
@@ -23,15 +22,13 @@ defineOptions({
23
22
  const route = useRoute();
24
23
  const router = useRouter();
25
24
 
26
- const { settingsStore, tabbarStore, generateI18nTitle } = useContext();
25
+ const { settingsStore, tabbarStore, generateTitle, routeStore } = useContext();
27
26
 
28
27
  const tabbar = useTabbar();
29
28
  const mainPage = useMainPage();
30
29
 
31
30
  const keys = useMagicKeys({ reactive: true });
32
31
 
33
- const { t } = useI18n();
34
-
35
32
  const activedTabId = computed(() => tabbar.getId());
36
33
  const isShowMoreAction = computed(() => tabbarStore.list.length > 1 && (tabbar.checkCloseOtherSide() || tabbar.checkCloseLeftSide() || tabbar.checkCloseRightSide()));
37
34
 
@@ -162,13 +159,13 @@ function onTabbarContextmenu(event: MouseEvent, routeItem: Tabbar.recordRaw) {
162
159
  customClass: 'tabbar-contextmenu',
163
160
  items: [
164
161
  {
165
- label: t('app.tabbar.reload'),
162
+ label: '重新加载',
166
163
  icon: 'i-ri-refresh-line',
167
164
  disabled: routeItem.tabId !== activedTabId.value,
168
165
  onClick: () => mainPage.reload(),
169
166
  },
170
167
  {
171
- label: t('app.tabbar.close'),
168
+ label: '关闭标签页',
172
169
  icon: 'i-ri-close-line',
173
170
  disabled: tabbarStore.list.length <= 1 || routeItem.isPin || routeItem.isPermanent,
174
171
  divided: true,
@@ -177,7 +174,7 @@ function onTabbarContextmenu(event: MouseEvent, routeItem: Tabbar.recordRaw) {
177
174
  },
178
175
  },
179
176
  {
180
- label: routeItem.isPin ? t('app.tabbar.unpin') : t('app.tabbar.pin'),
177
+ label: routeItem.isPin ? '取消固定' : '固定',
181
178
  icon: routeItem.isPin ? 'i-lucide-pin-off' : 'i-lucide-pin',
182
179
  // 主页不允许被固定,因为如果固定主页且主页未启用,会导致登录时进入路由死循环状态
183
180
  disabled: routeItem.fullPath === '/' || routeItem.isPermanent,
@@ -191,7 +188,7 @@ function onTabbarContextmenu(event: MouseEvent, routeItem: Tabbar.recordRaw) {
191
188
  },
192
189
  },
193
190
  {
194
- label: t('app.tabbar.maximize'),
191
+ label: '最大化',
195
192
  icon: 'i-ri-picture-in-picture-exit-line',
196
193
  onClick: () => {
197
194
  if (routeItem.tabId !== activedTabId.value) {
@@ -202,21 +199,21 @@ function onTabbarContextmenu(event: MouseEvent, routeItem: Tabbar.recordRaw) {
202
199
  },
203
200
  },
204
201
  {
205
- label: t('app.tabbar.closeOtherSide'),
202
+ label: '关闭其它标签页',
206
203
  disabled: !tabbar.checkCloseOtherSide(routeItem.tabId),
207
204
  onClick: () => {
208
205
  tabbar.closeOtherSide(routeItem.tabId);
209
206
  },
210
207
  },
211
208
  {
212
- label: t('app.tabbar.closeLeftSide'),
209
+ label: '关闭左侧标签页',
213
210
  disabled: !tabbar.checkCloseLeftSide(routeItem.tabId),
214
211
  onClick: () => {
215
212
  tabbar.closeLeftSide(routeItem.tabId);
216
213
  },
217
214
  },
218
215
  {
219
- label: t('app.tabbar.closeRightSide'),
216
+ label: '关闭右侧标签页',
220
217
  disabled: !tabbar.checkCloseRightSide(routeItem.tabId),
221
218
  onClick: () => {
222
219
  tabbar.closeRightSide(routeItem.tabId);
@@ -244,6 +241,46 @@ function iconName(isActive: boolean, icon: Tabbar.recordRaw['icon'], activeIcon:
244
241
  return name;
245
242
  }
246
243
 
244
+ // 解析 fullPath 的纯路径部分(去掉 query/hash)
245
+ function stripPath(fullPath: string) {
246
+ return fullPath.split(/[?#]/)[0];
247
+ }
248
+
249
+ // 查找与 tab 对应的路由记录
250
+ function findRouteRecordByElement(element: Tabbar.recordRaw) {
251
+ const pathOnly = stripPath(element.fullPath);
252
+ const all = [...routeStore.flatSystemRoutes, ...routeStore.flatRoutes] as any[];
253
+ return all.find(r => r.path === pathOnly);
254
+ }
255
+
256
+ // 解析该 tab 的 iconOptions(优先当前路由,其次面包屑链路上最近的一个)
257
+ function resolveIconOptions(element: Tabbar.recordRaw): undefined | {
258
+ boxType?: 'square' | 'prism' | 'null'
259
+ angle?: number | string
260
+ background?: any
261
+ radius?: number | string
262
+ iconColor?: string
263
+ } {
264
+ const r: any = findRouteRecordByElement(element);
265
+ let options = (r?.meta as any)?.iconOptions;
266
+ if (!options && r?.meta?.breadcrumbNeste?.length) {
267
+ const found = [...r.meta.breadcrumbNeste].reverse().find((it: any) => it?.iconOptions);
268
+ options = found?.iconOptions;
269
+ }
270
+ return options;
271
+ }
272
+
273
+ // 统一盒子类型:存在 iconOptions 时启用盒子;未指定或为 'null' 时默认 prism
274
+ function computeBoxType(options: ReturnType<typeof resolveIconOptions>) {
275
+ const bt = (options as any)?.boxType as any;
276
+ return bt && bt !== 'null' ? bt : 'prism';
277
+ }
278
+
279
+ // 仅 AntD 图标使用安全颜色;无配置则为 undefined
280
+ function getSafeIconColor(options: ReturnType<typeof resolveIconOptions>): string | undefined {
281
+ return (options as any)?.iconColor || undefined;
282
+ }
283
+
247
284
  onMounted(() => {
248
285
  hotkeys('alt+left,alt+right,alt+w,alt+1,alt+2,alt+3,alt+4,alt+5,alt+6,alt+7,alt+8,alt+9,alt+0', (e, handle) => {
249
286
  if (settingsStore.settings.tabbar.enable && settingsStore.settings.tabbar.enableHotkeys) {
@@ -319,7 +356,7 @@ onUnmounted(() => {
319
356
  'actived': element.tabId === activedTabId,
320
357
  'no-drag': element.isPermanent || element.isPin,
321
358
  }"
322
- :title="element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateI18nTitle(element.i18n, element.title)"
359
+ :title="element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateTitle(element.title)"
323
360
  @click="router.push(element.fullPath)"
324
361
  @dblclick="settingsStore.setMainPageMaximize()"
325
362
  @contextmenu="onTabbarContextmenu($event, element)"
@@ -328,12 +365,30 @@ onUnmounted(() => {
328
365
  <div class="tab-background" />
329
366
  <div class="tab-content">
330
367
  <div :key="element.tabId" class="title">
331
- <PubinfoIcon
332
- v-if="settingsStore.settings.tabbar.enableIcon && iconName(element.tabId === activedTabId, element.icon, element.activeIcon)"
333
- :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
334
- class="icon"
335
- />
336
- {{ element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateI18nTitle(element.i18n, element.title) }}
368
+ <template v-if="settingsStore.settings.tabbar.enableIcon && iconName(element.tabId === activedTabId, element.icon, element.activeIcon)">
369
+ <!-- iconOptions 时启用带边框/渐变的盒子效果,与 Menu 保持一致 -->
370
+ <PubinfoIcon
371
+ v-if="resolveIconOptions(element)"
372
+ small
373
+ :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
374
+ :box="computeBoxType(resolveIconOptions(element)) as any"
375
+ :size="16"
376
+ :angle="resolveIconOptions(element)?.angle"
377
+ :background="resolveIconOptions(element)?.background"
378
+ :radius="resolveIconOptions(element)?.radius"
379
+ :color="(iconName(element.tabId === activedTabId, element.icon, element.activeIcon) || '').startsWith('antd:') ? (getSafeIconColor(resolveIconOptions(element)) || '#ffffff') : undefined"
380
+ class="icon"
381
+ />
382
+ <!-- 无 iconOptions 时保持原样(纯图标),但若提供图标色也生效 -->
383
+ <PubinfoIcon
384
+ v-else
385
+ :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
386
+ :size="16"
387
+ :color="(iconName(element.tabId === activedTabId, element.icon, element.activeIcon) || '').startsWith('antd:') ? getSafeIconColor(resolveIconOptions(element)) : undefined"
388
+ class="icon"
389
+ />
390
+ </template>
391
+ {{ element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateTitle(element.title) }}
337
392
  </div>
338
393
  <div v-if="!element.isPermanent && element.isPin" class="action-icon" @click.stop="tabbarStore.unPin(element.tabId)" @dblclick.stop>
339
394
  <RiPushpin2Fill text="10px" />
@@ -2,7 +2,6 @@
2
2
  import { isUndefined } from 'lodash-es';
3
3
  import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
4
4
  import Sortable from 'sortablejs';
5
- import { useI18n } from 'vue-i18n';
6
5
  import FluentDelete24Filled from '~icons/fluent/delete-24-filled';
7
6
  import FluentStarAdd16Regular from '~icons/fluent/star-add-16-regular';
8
7
  import FluentStarDismiss16Regular from '~icons/fluent/star-dismiss-16-regular';
@@ -15,9 +14,7 @@ defineOptions({
15
14
 
16
15
  const route = useRoute();
17
16
  const router = useRouter();
18
- const { favoritesStore, routeStore, generateI18nTitle } = useContext();
19
- const { t } = useI18n();
20
-
17
+ const { favoritesStore, routeStore, generateTitle } = useContext();
21
18
  const favoritesContainerRef = ref<HTMLElement | null>(null);
22
19
 
23
20
  let _favoritesSortable: Sortable | null = null;
@@ -46,7 +43,7 @@ watch(favoritesContainerRef, (newSortableContainer) => {
46
43
  <div class="favorites-container w-80 dark:bg-[var(--g-container-bg)]">
47
44
  <div class="flex items-center justify-between px-4 py-3">
48
45
  <div class="text-sm font-bold">
49
- {{ t('app.tabbar.favorites.my-favorites') }}
46
+ {{ '我的收藏夾' }}
50
47
  </div>
51
48
  <template v-if="favoritesStore.canAdd(route.fullPath, routeStore.routes)">
52
49
  <FluentStarAdd16Regular
@@ -81,13 +78,13 @@ watch(favoritesContainerRef, (newSortableContainer) => {
81
78
  <div
82
79
  v-for="item in favoritesStore.list"
83
80
  :key="item.fullPath"
84
- :title="generateI18nTitle(item.i18n, item.title)"
81
+ :title="generateTitle(item.title)"
85
82
  class="draggable-item relative w-[calc(50%-0.25rem)] flex cursor-pointer items-center gap-1 rounded px-2 py-2 ring-1 ring-stone-3 ring-inset hover:bg-stone-1 dark:ring-stone-7 dark:hover:bg-dark/50"
86
83
  @click="router.push(item.fullPath)"
87
84
  >
88
85
  <PubinfoIcon v-if="item.icon" :name="item.icon" :size="18" />
89
86
  <div class="name flex-1 truncate pe-4">
90
- {{ generateI18nTitle(item.i18n, item.title) }}
87
+ {{ generateTitle(item.title) }}
91
88
  </div>
92
89
  <FluentDelete24Filled
93
90
  text-12px
@@ -17,7 +17,7 @@ defineOptions({
17
17
  const route = useRoute();
18
18
  const router = useRouter();
19
19
 
20
- const { settingsStore, menuStore, generateI18nTitle } = useContext();
20
+ const { settingsStore, menuStore, generateTitle } = useContext();
21
21
 
22
22
  /**
23
23
  * 计算属性:是否启用子菜单折叠按钮
@@ -52,7 +52,7 @@ const breadcrumbIsShow = computed(() =>
52
52
  function createHomeBreadcrumb() {
53
53
  return {
54
54
  path: '/',
55
- title: generateI18nTitle('route.home', settingsStore.settings.home.title),
55
+ title: generateTitle(settingsStore.settings.home.title),
56
56
  };
57
57
  }
58
58
 
@@ -78,7 +78,7 @@ const breadcrumbList = computed(() => {
78
78
  breadcrumbList.push({
79
79
  path: `__MAIN__${index}`,
80
80
  mainIndex: index,
81
- title: generateI18nTitle(menuStore.allMenus[index].meta?.i18n, menuStore.allMenus[index].meta?.title),
81
+ title: generateTitle(menuStore.allMenus[index].meta?.title),
82
82
  });
83
83
  }
84
84
  }
@@ -88,7 +88,7 @@ const breadcrumbList = computed(() => {
88
88
  if (item.hide === false) {
89
89
  breadcrumbList.push({
90
90
  path: item.path,
91
- title: generateI18nTitle(item.i18n, item.title),
91
+ title: generateTitle(item.title),
92
92
  });
93
93
  }
94
94
  });
@@ -155,14 +155,14 @@ function getCurrentBreadcrumb(path: string) {
155
155
  const main = menuStore.allMenus[idx];
156
156
  if (main) {
157
157
  const mapNode = (r: any): any => ({
158
- title: generateI18nTitle(r.meta?.i18n, r.meta?.title),
158
+ title: generateTitle(r.meta?.title),
159
159
  path: r.path,
160
160
  children: (r.children || [])
161
161
  .filter((c: any) => c.meta?.sidebar !== false)
162
162
  .map((c: any) => mapNode(c)),
163
163
  });
164
164
  return {
165
- title: generateI18nTitle(main.meta?.i18n, main.meta?.title),
165
+ title: generateTitle(main.meta?.title),
166
166
  path,
167
167
  children: (main.children || [])
168
168
  .filter(c => c.meta?.sidebar !== false)
@@ -22,7 +22,7 @@ interface LayoutContext {
22
22
 
23
23
  appTitle: string
24
24
  isDev: boolean
25
- generateI18nTitle: any
25
+ generateTitle: (titile?: string | (() => any)) => string
26
26
  }
27
27
 
28
28
  const layoutInjectionKey: InjectionKey<LayoutContext> = Symbol('Layout');
@@ -1,9 +1,44 @@
1
- import { ref } from 'vue';
1
+ import { ref, shallowRef } from 'vue';
2
2
 
3
3
  type SearchType = 'menu' | 'tab';
4
4
 
5
5
  const isShow = ref(false);
6
6
  const searchType = ref<SearchType>('menu');
7
+ const panelRegistry = new Map<symbol, number>();
8
+ const activePanelId = shallowRef<symbol | null>(null);
9
+
10
+ function updateActivePanel() {
11
+ let candidate: symbol | null = null;
12
+ let highestPriority = Number.NEGATIVE_INFINITY;
13
+ panelRegistry.forEach((priority, id) => {
14
+ if (priority > highestPriority) {
15
+ highestPriority = priority;
16
+ candidate = id;
17
+ }
18
+ });
19
+ activePanelId.value = candidate;
20
+ }
21
+
22
+ function registerPanel(id: symbol, priority = 0) {
23
+ panelRegistry.set(id, priority);
24
+ updateActivePanel();
25
+ }
26
+
27
+ function unregisterPanel(id: symbol) {
28
+ if (!panelRegistry.has(id)) {
29
+ return;
30
+ }
31
+ panelRegistry.delete(id);
32
+ updateActivePanel();
33
+ }
34
+
35
+ function setPanelPriority(id: symbol, priority: number) {
36
+ if (!panelRegistry.has(id)) {
37
+ return;
38
+ }
39
+ panelRegistry.set(id, priority);
40
+ updateActivePanel();
41
+ }
7
42
 
8
43
  function open(type?: SearchType) {
9
44
  if (type) {
@@ -39,9 +74,13 @@ export function useGlobalSearch() {
39
74
  return {
40
75
  isShow,
41
76
  searchType,
77
+ activePanelId,
42
78
  open,
43
79
  close,
44
80
  toggle,
81
+ registerPanel,
82
+ unregisterPanel,
83
+ setPanelPriority,
45
84
  };
46
85
  }
47
86
 
@@ -1,9 +1,8 @@
1
1
  import type { useSettingsStore } from '@/features/stores';
2
- import { useI18n } from 'vue-i18n';
3
2
 
4
3
  export function useTitle(appTitle: string, settingsStore: ReturnType<typeof useSettingsStore>) {
5
- const { t, te } = useI18n();
6
4
  const routeInfo = useRoute();
5
+
7
6
  watch(
8
7
  [
9
8
  () => settingsStore.settings.app.enableDynamicTitle,
@@ -13,11 +12,7 @@ export function useTitle(appTitle: string, settingsStore: ReturnType<typeof useS
13
12
  () => {
14
13
  if (settingsStore.settings.app.enableDynamicTitle && settingsStore.title) {
15
14
  const title = settingsStore.customTitleList.find(item => item.fullPath === routeInfo.fullPath)?.title
16
- || (
17
- routeInfo.meta.breadcrumbNeste?.at(-1)?.i18n
18
- ? generateI18nTitle(routeInfo.meta.breadcrumbNeste?.at(-1)?.i18n, settingsStore.title)
19
- : generateI18nTitle(routeInfo.meta.i18n, settingsStore.title)
20
- );
15
+ || generateTitle(settingsStore.title);
21
16
  document.title = `${title} - ${appTitle}`;
22
17
  }
23
18
  else {
@@ -27,14 +22,13 @@ export function useTitle(appTitle: string, settingsStore: ReturnType<typeof useS
27
22
  {
28
23
  deep: true,
29
24
  });
30
- function generateI18nTitle(key?: string, defaultTitle: string | (() => any) = t('route.undefined')) {
31
- return (settingsStore.settings.toolbar.enableI18n && !!key && te(key))
32
- ? t(key)
33
- : typeof defaultTitle === 'function'
34
- ? defaultTitle()
35
- : defaultTitle;
25
+
26
+ function generateTitle(defaultTitle: string | (() => any) = '[ 无标题 ]') {
27
+ return typeof defaultTitle === 'function'
28
+ ? defaultTitle()
29
+ : defaultTitle;
36
30
  }
37
31
  return {
38
- generateI18nTitle,
32
+ generateTitle,
39
33
  };
40
34
  }
@@ -38,7 +38,7 @@ export const Provider = defineComponent({
38
38
  const tabbarStore = useTabbarStore();
39
39
  const favoritesStore = useFavoritesStore();
40
40
 
41
- const { generateI18nTitle } = useTitle(props.appTitle, settingsStore);
41
+ const { generateTitle } = useTitle(props.appTitle, settingsStore);
42
42
 
43
43
  createContext({
44
44
  settingsStore,
@@ -52,7 +52,7 @@ export const Provider = defineComponent({
52
52
 
53
53
  appTitle: props.appTitle,
54
54
  isDev: props.isDev,
55
- generateI18nTitle,
55
+ generateTitle,
56
56
  });
57
57
 
58
58
  return () => h(
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import { useToggle } from '@vueuse/core';
3
+ import hotkeys from 'hotkeys-js';
4
+ import HSlideover from '@/built-in/layout-component/components/ui/HSlideover.vue';
5
+
6
+ const [visible, toggle] = useToggle(false);
7
+
8
+ const { pkg, buildTime } = __SYSTEM_INFO__;
9
+ hotkeys('alt+i', () => toggle());
10
+ </script>
11
+
12
+ <template>
13
+ <HSlideover v-model="visible" title="系统信息">
14
+ <div class="px-4">
15
+ <h2 class="m-0 text-lg font-bold">
16
+ 最后编译时间
17
+ </h2>
18
+ <div class="my-4 text-center text-lg font-sans">
19
+ {{ buildTime }}
20
+ </div>
21
+ </div>
22
+ <div class="px-4">
23
+ <h2 class="m-0 text-lg font-bold">
24
+ 生产环境依赖
25
+ </h2>
26
+ <ul class="list-none pl-0 text-sm">
27
+ <li v-for="(val, key) in (pkg.dependencies as object)" :key="key" class="flex items-center justify-between rounded px-2 py-1.5 hover:bg-stone-1 dark:hover:bg-stone-9">
28
+ <div class="font-bold">
29
+ {{ key }}
30
+ </div>
31
+ <div class="font-sans">
32
+ {{ val }}
33
+ </div>
34
+ </li>
35
+ </ul>
36
+ </div>
37
+ <div class="px-4">
38
+ <h2 class="m-0 text-lg font-bold">
39
+ 开发环境依赖
40
+ </h2>
41
+ <ul class="list-none pl-0 text-sm">
42
+ <li v-for="(val, key) in (pkg.devDependencies as object)" :key="key" class="flex items-center justify-between rounded px-2 py-1.5 hover:bg-stone-1 dark:hover:bg-stone-9">
43
+ <div class="font-bold">
44
+ {{ key }}
45
+ </div>
46
+ <div class="font-sans">
47
+ {{ val }}
48
+ </div>
49
+ </li>
50
+ </ul>
51
+ </div>
52
+ </HSlideover>
53
+ </template>
@@ -0,0 +1,16 @@
1
+ import type { ModuleOptions } from '@/core';
2
+ import SystemInfoComponent from './components/SystemInfo.vue';
3
+
4
+ /**
5
+ * 系统信息
6
+ */
7
+ export function SystemInfo(): ModuleOptions {
8
+ return {
9
+ name: 'built-in:system-info',
10
+ setup() {
11
+ const container = document.createElement('div');
12
+ document.body.appendChild(container);
13
+ createApp(SystemInfoComponent).mount(container);
14
+ },
15
+ };
16
+ }
package/src/core/ctx.ts CHANGED
@@ -1,3 +1,9 @@
1
1
  import { createHooks } from 'hookable';
2
+ import { getPubinfoNamespace } from '@/utils';
2
3
 
3
- export const hooks = createHooks();
4
+ const HOOKS_KEY = '__pubinfo_core_hooks__';
5
+ type HookStore = ReturnType<typeof createHooks>;
6
+
7
+ const namespace = getPubinfoNamespace() as Record<string, HookStore | undefined>;
8
+
9
+ export const hooks = namespace[HOOKS_KEY] || (namespace[HOOKS_KEY] = createHooks());
@@ -1,5 +1,5 @@
1
1
  import type { AdapterCreateOptions, AlovaAxiosRequestConfig } from '@alova/adapter-axios';
2
- import type { Alova, AlovaGenerics, AlovaOptions, Method } from 'alova';
2
+ import type { Alova, AlovaGenerics, AlovaOptions, Method, ResponseCompleteHandler, ResponseErrorHandler } from 'alova';
3
3
  import type { AxiosResponse, AxiosResponseHeaders } from 'axios';
4
4
  import type { Pinia } from 'pinia';
5
5
  import type { App, Component } from 'vue';
@@ -77,13 +77,26 @@ export type FalsyModule = false | null | undefined;
77
77
 
78
78
  export type ModuleOptions = Module | ModuleOptions[] | FalsyModule;
79
79
 
80
- type AG = AlovaGenerics<any, any, AlovaAxiosRequestConfig, AxiosResponse<any, any>, AxiosResponseHeaders, any, any, any>;
80
+ export type AG = AlovaGenerics<any, any, AlovaAxiosRequestConfig, AxiosResponse<any, any>, AxiosResponseHeaders, any, any, any>;
81
81
 
82
- export type RequestOptions = Omit<AlovaOptions<AG>, 'statesHook' | 'requestAdapter'> & { axios?: AdapterCreateOptions };
82
+ export type RequestOptions = Omit<AlovaOptions<AG>, 'statesHook' | 'requestAdapter' | 'beforeRequest' | 'responded'> & {
83
+ axios?: AdapterCreateOptions
84
+ beforeRequest?: Hooks['http:request']
85
+ responded?: Hooks['http:response']
86
+ };
83
87
  export type RequestInstance = Alova<AG>;
84
88
  export type RequestMethod = Method<AG>;
85
89
 
86
90
  export interface Hooks {
87
- 'http:request': AlovaOptions<AG>['beforeRequest']
88
- 'http:response': AlovaOptions<AG>['responded']
91
+ 'http:request': (method: Method<AG>) => void | Promise<void>
92
+ 'http:response': RespondedHandler<AG> | RespondedHandlerRecord<AG>
93
+ }
94
+
95
+ // overwrite alova
96
+ type Fn = () => void;
97
+ export type RespondedHandler<AG extends AlovaGenerics> = (response: AG['Response'], methodInstance: Method<AG>, stop: Fn) => any;
98
+ export interface RespondedHandlerRecord<AG extends AlovaGenerics> {
99
+ onSuccess?: RespondedHandler<AG>
100
+ onError?: ResponseErrorHandler<AG>
101
+ onComplete?: ResponseCompleteHandler<AG>
89
102
  }
@@ -1,8 +1,8 @@
1
+ import type { AxiosResponse } from 'axios';
1
2
  import type { RequestOptions } from './interface';
2
3
  import { axiosRequestAdapter } from '@alova/adapter-axios';
3
4
  import { createAlova } from 'alova';
4
5
  import vueHook from 'alova/vue';
5
- import { createTokenAuthentication } from '@/built-in';
6
6
  import { hooks } from './ctx';
7
7
 
8
8
  function normalizedResponded(handler: RequestOptions['responded']) {
@@ -12,9 +12,13 @@ function normalizedResponded(handler: RequestOptions['responded']) {
12
12
  return handler;
13
13
  }
14
14
 
15
- export function createRequest(options: RequestOptions) {
16
- const { onAuthRequired, onResponseRefreshToken } = createTokenAuthentication();
17
- const { axios, beforeRequest, responded: _responded, ...restOptions } = options;
15
+ export function createRequest(options: RequestOptions = {}) {
16
+ const {
17
+ axios,
18
+ beforeRequest,
19
+ responded: _responded,
20
+ ...restOptions
21
+ } = options;
18
22
 
19
23
  const responded = normalizedResponded(_responded);
20
24
 
@@ -25,25 +29,41 @@ export function createRequest(options: RequestOptions) {
25
29
  timeout: 60 * 1000,
26
30
  ...restOptions,
27
31
 
28
- beforeRequest: onAuthRequired(async (method) => {
32
+ beforeRequest: async (method) => {
29
33
  await hooks.callHook('http:request', method);
30
34
  await beforeRequest?.(method);
31
- }),
35
+ },
32
36
 
33
- responded: onResponseRefreshToken({
37
+ responded: {
34
38
  onSuccess: async (response, method) => {
35
- const res = await hooks.callHook('http:response:onSuccess', response, method) ?? response;
36
- return responded?.onSuccess?.(res, method) ?? res;
39
+ let stopped = false;
40
+
41
+ const res = await hooks.callHookWith(
42
+ async (hookFns, args) => {
43
+ let _response = args[0];
44
+ for (const fn of hookFns) {
45
+ if (stopped) {
46
+ break;
47
+ }
48
+ _response = await fn(_response, method, () => stopped = true);
49
+ }
50
+ return _response;
51
+ },
52
+ 'http:response:onSuccess',
53
+ response,
54
+ ) as AxiosResponse<any, any>;
55
+
56
+ return stopped ? res : (responded?.onSuccess?.(res, method, () => stopped = true) ?? res);
37
57
  },
38
- onError: (err, method) => {
39
- hooks.callHook('http:response:onError', err, method);
40
- responded?.onError?.(err, method);
58
+ onError: async (err, method) => {
59
+ await hooks.callHook('http:response:onError', err, method);
60
+ return responded?.onError?.(err, method);
41
61
  },
42
- onComplete: (method) => {
43
- hooks.callHook('http:response:onComplete', method);
62
+ onComplete: async (method) => {
63
+ await hooks.callHook('http:response:onComplete', method);
44
64
  responded?.onComplete?.(method);
45
65
  },
46
- }),
66
+ },
47
67
  });
48
68
 
49
69
  return instance;
@@ -1,11 +1,11 @@
1
1
  import type { ModuleRecord } from './resolver';
2
- import { readProjectModules } from './resolver';
2
+ import { getPubinfoNamespace } from '@/utils';
3
+ import { loadProjectModules, readProjectModules } from './resolver';
3
4
 
4
5
  // 持久化图标模块,支持 HMR 后恢复
5
- const g = globalThis as any;
6
- g.__PUBINFO__ = g.__PUBINFO__ || {};
7
- g.__PUBINFO__.iconModules = g.__PUBINFO__.iconModules || new Map<string, ModuleRecord>();
8
- const iconModuleMap = g.__PUBINFO__.iconModules as Map<string, ModuleRecord>;
6
+ const namespace = getPubinfoNamespace();
7
+ namespace.iconModules = namespace.iconModules || new Map<string, ModuleRecord>();
8
+ const iconModuleMap = namespace.iconModules as Map<string, ModuleRecord>;
9
9
 
10
10
  export function defineIconModule(id: string, map: ModuleRecord) {
11
11
  if (iconModuleMap.has(id)) {
@@ -31,6 +31,10 @@ export function getAllIconModules(): Map<string, ModuleRecord> {
31
31
  }
32
32
 
33
33
  export async function setupIcon(name: string, id?: string) {
34
+ if (!readProjectModules()) {
35
+ await loadProjectModules().catch(() => {});
36
+ }
37
+
34
38
  const modules = readProjectModules();
35
39
  const iconMap = modules?.icons ?? {};
36
40
  const moduleMap = id ? (getIconModule(id) ?? {}) : {};