@pubinfo/core 2.0.0-rc.4 → 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 (75) hide show
  1. package/dist/{AppSetting-BI-oNc4e.js → AppSetting-DqVYDIHj.js} +15 -15
  2. package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-BdLpkcoh.js → HCheckList.vue_vue_type_script_setup_true_lang-SrNklW3P.js} +1 -1
  3. package/dist/{HToggle-DxdWLgp-.js → HToggle-DGTP9jYA.js} +1 -1
  4. package/dist/{PreferencesContent-CCYkZeCT.js → PreferencesContent-5NtwK9RQ.js} +4 -4
  5. package/dist/{SettingBreadcrumb-BTyfiy4k.js → SettingBreadcrumb-BudqQsuJ.js} +3 -3
  6. package/dist/{SettingCopyright-g6UHi8pZ.js → SettingCopyright-VUberG4R.js} +2 -2
  7. package/dist/{SettingEnableTransition-Ci-5bhbR.js → SettingEnableTransition-C6NYf021.js} +2 -2
  8. package/dist/SettingHome-BTaeKgwN.js +46 -0
  9. package/dist/{SettingMenu-BYLWzA5i.js → SettingMenu-D9Aon2LP.js} +3 -3
  10. package/dist/{SettingMode-tRisyKtg.js → SettingMode-DaqVd9Mq.js} +1 -1
  11. package/dist/{SettingNavSearch-CSM6mPf8.js → SettingNavSearch-N4JIheIk.js} +2 -2
  12. package/dist/{SettingOther-Bj5KF_vC.js → SettingOther-tLulcors.js} +2 -2
  13. package/dist/{SettingPage-CFjmrVI7.js → SettingPage-CEjWB45R.js} +2 -2
  14. package/dist/{SettingTabbar-uFYiaZhK.js → SettingTabbar-DyeLhcCT.js} +3 -3
  15. package/dist/{SettingThemes-C-tMq9o5.js → SettingThemes-C2M3tsVl.js} +1 -1
  16. package/dist/{SettingToolbar-BfDzijNU.js → SettingToolbar-DI7de6i0.js} +24 -31
  17. package/dist/{SettingTopbar-DTDv4NXD.js → SettingTopbar-BgIoXeAq.js} +3 -3
  18. package/dist/{SettingWidthMode-PkiwrHe3.js → SettingWidthMode-DIAU4s5e.js} +1 -1
  19. package/dist/{TopThinMode-BrvA8pV0.js → TopThinMode-JNUHrJI2.js} +1 -1
  20. package/dist/built-in/index.d.ts +1 -0
  21. package/dist/built-in/layout-component/components/Tools/SearchPanel.vue.d.ts +7 -2
  22. package/dist/built-in/layout-component/composables/useGlobalSearch.d.ts +7 -0
  23. package/dist/built-in/system-info/components/SystemInfo.vue.d.ts +2 -0
  24. package/dist/built-in/system-info/index.d.ts +5 -0
  25. package/dist/{colors-VoaDbOhe.js → colors-DxWfHM_v.js} +1 -1
  26. package/dist/features/components/PubinfoIcon/PrismBox.vue.d.ts +21 -0
  27. package/dist/features/components/PubinfoIcon/SquareBox.vue.d.ts +17 -0
  28. package/dist/features/components/PubinfoIcon/index.vue.d.ts +13 -9
  29. package/dist/features/components/PubinfoIcon/props.d.ts +58 -0
  30. package/dist/features/components/index.d.ts +2 -0
  31. package/dist/{index-BfGqLWFB.js → index-5fRpGyLW.js} +4 -4
  32. package/dist/{index-ConeY38N.js → index-BFRIv97x.js} +2 -2
  33. package/dist/{index-Dv9ndBoi.js → index-BH-vHGvk.js} +1 -1
  34. package/dist/{index-BSevJVD5.js → index-C7xIGcDc.js} +2 -2
  35. package/dist/{index-CYoFRwvw.js → index-CNVn3Ubv.js} +2 -2
  36. package/dist/{index-DV3hkzKA.js → index-Cf-u1Zqh.js} +1 -1
  37. package/dist/{index-Ddw98rJ5.js → index-D4v4g8FJ.js} +112 -98
  38. package/dist/{index-DrC787X_.js → index-DQGnbEGS.js} +2 -2
  39. package/dist/{index-IAYhIBQH.js → index-Dv7UUFkD.js} +23446 -23082
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +55 -48
  42. package/dist/{pick-vpv9EEvu.js → pick-VFuUwFn-.js} +1 -1
  43. package/dist/style.css +1 -1
  44. package/dist/utils/global.d.ts +33 -0
  45. package/dist/utils/index.d.ts +2 -1
  46. package/package.json +8 -5
  47. package/src/built-in/index.ts +1 -0
  48. package/src/built-in/layout-component/components/Header/TopMode/index.vue +27 -6
  49. package/src/built-in/layout-component/components/Menu/item.vue +41 -6
  50. package/src/built-in/layout-component/components/SettingBar/components/SettingHome.vue +1 -4
  51. package/src/built-in/layout-component/components/SettingBar/components/SettingToolbar.vue +0 -6
  52. package/src/built-in/layout-component/components/Tools/SearchPanel.vue +113 -37
  53. package/src/built-in/layout-component/components/Tools/index.vue +1 -1
  54. package/src/built-in/layout-component/components/Topbar/Tabbar/MoreAction.vue +58 -2
  55. package/src/built-in/layout-component/components/Topbar/Tabbar/index.vue +64 -6
  56. package/src/built-in/layout-component/composables/useGlobalSearch.ts +40 -1
  57. package/src/built-in/system-info/components/SystemInfo.vue +53 -0
  58. package/src/built-in/system-info/index.ts +16 -0
  59. package/src/core/ctx.ts +7 -1
  60. package/src/core/resolver/icon.ts +9 -5
  61. package/src/features/components/PubinfoIcon/PrismBox.vue +203 -0
  62. package/src/features/components/PubinfoIcon/SquareBox.vue +59 -0
  63. package/src/features/components/PubinfoIcon/index.vue +128 -37
  64. package/src/features/components/PubinfoIcon/props.ts +54 -0
  65. package/src/features/components/index.ts +4 -1
  66. package/src/features/context/index.ts +1 -16
  67. package/src/features/settings/index.ts +0 -1
  68. package/src/index.ts +7 -0
  69. package/src/utils/global.ts +161 -0
  70. package/src/utils/index.ts +2 -1
  71. package/src/utils/proxy.ts +7 -8
  72. package/types/global.d.ts +7 -0
  73. package/types/menu.d.ts +10 -0
  74. package/types/settings.d.ts +0 -7
  75. package/dist/SettingHome-K4Iel0Hr.js +0 -55
@@ -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.4",
4
+ "version": "2.0.0-rc.5",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -18,13 +18,16 @@
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
28
  "vue-router": "^4.5.1",
26
- "@pubinfo/config": "2.0.0-rc.4",
27
- "@pubinfo/vite": "2.0.0-rc.4"
29
+ "@pubinfo/config": "2.0.0-rc.5",
30
+ "@pubinfo/vite": "2.0.0-rc.5"
28
31
  },
29
32
  "dependencies": {
30
33
  "@alova/adapter-axios": "^2.0.16",
@@ -82,8 +85,8 @@
82
85
  "vite-plugin-dts": "^4.5.4",
83
86
  "vue": "^3.5.17",
84
87
  "vue-router": "^4.5.1",
85
- "@pubinfo/config": "2.0.0-rc.4",
86
- "@pubinfo/vite": "2.0.0-rc.4"
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",
@@ -5,3 +5,4 @@ export * from './n-progress';
5
5
  export * from './pinia-plugin';
6
6
  export * from './pre-access';
7
7
  export * from './settings';
8
+ export * from './system-info';
@@ -117,12 +117,33 @@ onMounted(() => {
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
148
  {{ generateTitle(item.meta?.title) }}
128
149
  </span>
@@ -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
@@ -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"
@@ -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
  全屏
@@ -6,6 +6,7 @@ import { Dialog, DialogDescription, DialogPanel, TransitionChild, TransitionRoot
6
6
  import hotkeys from 'hotkeys-js';
7
7
  import { cloneDeep } from 'lodash-es';
8
8
  import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
9
+ import { computed, onBeforeUpdate, onUnmounted, ref, watch } from 'vue';
9
10
  import AntDesignCaretDownFilled from '~icons/ant-design/caret-down-filled';
10
11
  import AntDesignCaretUpFilled from '~icons/ant-design/caret-up-filled';
11
12
  import EpSearch from '~icons/ep/search';
@@ -24,6 +25,10 @@ defineOptions({
24
25
  name: 'SearchPanel',
25
26
  });
26
27
 
28
+ const props = withDefaults(defineProps<{ priority?: number }>(), {
29
+ priority: 0,
30
+ });
31
+
27
32
  const overlayTransitionClass = ref({
28
33
  enter: 'ease-in-out duration-500',
29
34
  enterFrom: 'opacity-0',
@@ -56,7 +61,23 @@ interface listTypes {
56
61
  breadcrumb: any[]
57
62
  }
58
63
 
59
- const { isShow, searchType, toggle: globalToggle } = useGlobalSearch();
64
+ const { isShow, searchType, activePanelId, toggle: globalToggle, close: globalClose, registerPanel, unregisterPanel, setPanelPriority } = useGlobalSearch();
65
+ const panelId = Symbol('global-search-panel');
66
+ const hasRegisteredGlobalHotkeys = ref(false);
67
+ registerPanel(panelId, props.priority);
68
+ onUnmounted(() => {
69
+ unregisterPanel(panelId);
70
+ if (hasRegisteredGlobalHotkeys.value) {
71
+ hotkeys.unbind('alt+s', handleOpenHotkey);
72
+ hotkeys.unbind('esc', handleCloseHotkey);
73
+ hasRegisteredGlobalHotkeys.value = false;
74
+ }
75
+ hotkeys.unbind('up', keyUp);
76
+ hotkeys.unbind('down', keyDown);
77
+ hotkeys.unbind('enter', keyEnter);
78
+ });
79
+ watch(() => props.priority, val => setPanelPriority(panelId, val));
80
+ const isActivePanel = computed(() => activePanelId.value === panelId);
60
81
  const searchInput = ref('');
61
82
  const sourceList = ref<listTypes[]>([]);
62
83
  const actived = ref(-1);
@@ -86,44 +107,79 @@ const resultList = computed(() => {
86
107
  return result;
87
108
  });
88
109
 
89
- watch(() => isShow.value, (val) => {
90
- if (val) {
91
- searchInput.value = '';
110
+ watch(
111
+ [() => isShow.value, () => isActivePanel.value],
112
+ ([show, active]) => {
113
+ if (!active) {
114
+ hotkeys.unbind('up', keyUp);
115
+ hotkeys.unbind('down', keyDown);
116
+ hotkeys.unbind('enter', keyEnter);
117
+ return;
118
+ }
119
+ if (show) {
120
+ searchInput.value = '';
121
+ actived.value = -1;
122
+ // 当搜索显示的时候绑定上、下、回车快捷键,隐藏的时候再解绑。另外当 input 处于 focus 状态时,采用 vue 来绑定键盘事件
123
+ hotkeys('up', keyUp);
124
+ hotkeys('down', keyDown);
125
+ hotkeys('enter', keyEnter);
126
+ }
127
+ else {
128
+ hotkeys.unbind('up', keyUp);
129
+ hotkeys.unbind('down', keyDown);
130
+ hotkeys.unbind('enter', keyEnter);
131
+ }
132
+ },
133
+ { immediate: true },
134
+ );
135
+ watch(
136
+ () => resultList.value,
137
+ () => {
138
+ if (!isActivePanel.value || !isShow.value) {
139
+ return;
140
+ }
92
141
  actived.value = -1;
93
- // 当搜索显示的时候绑定上、下、回车快捷键,隐藏的时候再解绑。另外当 input 处于 focus 状态时,采用 vue 来绑定键盘事件
94
- hotkeys('up', keyUp);
95
- hotkeys('down', keyDown);
96
- hotkeys('enter', keyEnter);
142
+ handleScroll();
143
+ },
144
+ );
145
+
146
+ function handleOpenHotkey(e: KeyboardEvent) {
147
+ if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
148
+ e.preventDefault();
149
+ initSourceList();
150
+ isShow.value = true;
97
151
  }
98
- else {
99
- hotkeys.unbind('up', keyUp);
100
- hotkeys.unbind('down', keyDown);
101
- hotkeys.unbind('enter', keyEnter);
152
+ }
153
+
154
+ function handleCloseHotkey(e: KeyboardEvent) {
155
+ if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
156
+ e.preventDefault();
157
+ isShow.value = false;
102
158
  }
103
- });
104
- watch(() => resultList.value, () => {
105
- actived.value = -1;
106
- handleScroll();
107
- });
159
+ }
108
160
 
109
- onMounted(() => {
110
- hotkeys('alt+s', (e) => {
111
- if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
112
- e.preventDefault();
161
+ watch(
162
+ () => isActivePanel.value,
163
+ (active) => {
164
+ if (active && !hasRegisteredGlobalHotkeys.value) {
165
+ hotkeys('alt+s', handleOpenHotkey);
166
+ hotkeys('esc', handleCloseHotkey);
167
+ hasRegisteredGlobalHotkeys.value = true;
113
168
  initSourceList();
114
- isShow.value = true;
115
169
  }
116
- });
117
- hotkeys('esc', (e) => {
118
- if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
119
- e.preventDefault();
120
- isShow.value = false;
170
+ else if (!active && hasRegisteredGlobalHotkeys.value) {
171
+ hotkeys.unbind('alt+s', handleOpenHotkey);
172
+ hotkeys.unbind('esc', handleCloseHotkey);
173
+ hasRegisteredGlobalHotkeys.value = false;
121
174
  }
122
- });
123
- initSourceList();
124
- });
175
+ },
176
+ { immediate: true },
177
+ );
125
178
 
126
179
  function switchType(type: any) {
180
+ if (!isActivePanel.value) {
181
+ return;
182
+ }
127
183
  searchInputRef.value.focus();
128
184
  initSourceList(type);
129
185
  }
@@ -189,6 +245,9 @@ function getSourceListByTabs(arr: Tabbar.recordRaw[]) {
189
245
  }
190
246
 
191
247
  function keyUp() {
248
+ if (!isActivePanel.value) {
249
+ return;
250
+ }
192
251
  if (resultList.value.length) {
193
252
  actived.value -= 1;
194
253
  if (actived.value < 0) {
@@ -198,6 +257,9 @@ function keyUp() {
198
257
  }
199
258
  }
200
259
  function keyDown() {
260
+ if (!isActivePanel.value) {
261
+ return;
262
+ }
201
263
  if (resultList.value.length) {
202
264
  actived.value += 1;
203
265
  if (actived.value > resultList.value.length - 1) {
@@ -207,11 +269,17 @@ function keyDown() {
207
269
  }
208
270
  }
209
271
  function keyEnter() {
272
+ if (!isActivePanel.value) {
273
+ return;
274
+ }
210
275
  if (actived.value !== -1) {
211
276
  searchResultItemRef.value.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.click();
212
277
  }
213
278
  }
214
279
  function handleScroll() {
280
+ if (!isActivePanel.value) {
281
+ return;
282
+ }
215
283
  if (searchResultRef.value) {
216
284
  const contentDom = searchResultRef.value.osInstance()!.elements().content;
217
285
  let scrollTo = 0;
@@ -241,7 +309,9 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
241
309
  else {
242
310
  router.push(path);
243
311
  }
244
- isShow.value = false;
312
+ if (isActivePanel.value) {
313
+ globalClose();
314
+ }
245
315
  }
246
316
 
247
317
  function toggle(type?: 'menu' | 'tab') {
@@ -252,11 +322,17 @@ function toggle(type?: 'menu' | 'tab') {
252
322
  }
253
323
 
254
324
  // 初始化本地 searchTypeLocal 与全局 searchType 同步
255
- watch(() => searchType.value, (val) => {
256
- if (val) {
257
- initSourceList(val as searchTypes);
258
- }
259
- });
325
+ watch(
326
+ () => searchType.value,
327
+ (val) => {
328
+ if (!isActivePanel.value) {
329
+ return;
330
+ }
331
+ if (val) {
332
+ initSourceList(val as searchTypes);
333
+ }
334
+ },
335
+ );
260
336
 
261
337
  defineExpose({
262
338
  toggle,
@@ -264,7 +340,7 @@ defineExpose({
264
340
  </script>
265
341
 
266
342
  <template>
267
- <TransitionRoot as="template" :show="isShow">
343
+ <TransitionRoot v-if="isActivePanel" as="template" :show="isShow">
268
344
  <Dialog :initial-focus="searchInputRef" class="fixed inset-0 z-2000 flex" @close="isShow && toggle()">
269
345
  <TransitionChild as="template" v-bind="overlayTransitionClass">
270
346
  <div class="fixed inset-0 bg-stone-200/75 backdrop-blur-sm transition-opacity dark:bg-stone-8/75" />
@@ -143,7 +143,7 @@ watch(() => userStore.user.avatar, () => {
143
143
  </div>
144
144
 
145
145
  <HotkeysIntro ref="hotkeysIntroRef" />
146
- <SearchPanel />
146
+ <SearchPanel :priority="10" />
147
147
  <Preferences v-if="settingsStore.settings.app.enableUserPreferences" ref="preferencesRef" />
148
148
  </div>
149
149
  </template>
@@ -21,7 +21,7 @@ defineOptions({
21
21
 
22
22
  const router = useRouter();
23
23
 
24
- const { settingsStore, tabbarStore, generateTitle } = useContext();
24
+ const { settingsStore, tabbarStore, generateTitle, routeStore } = useContext();
25
25
 
26
26
  const tabbar = useTabbar();
27
27
 
@@ -85,6 +85,44 @@ function iconName(isActive: boolean, icon: Tabbar.recordRaw['icon'], activeIcon:
85
85
  }
86
86
  return name;
87
87
  }
88
+
89
+ // 解析 fullPath 的纯路径部分(去掉 query/hash)
90
+ function stripPath(fullPath: string) {
91
+ return fullPath.split(/[?#]/)[0];
92
+ }
93
+
94
+ // 查找与 tab 对应的路由记录
95
+ function findRouteRecordByElement(element: Tabbar.recordRaw) {
96
+ const pathOnly = stripPath(element.fullPath);
97
+ const all = [...routeStore.flatSystemRoutes, ...routeStore.flatRoutes] as any[];
98
+ return all.find(r => r.path === pathOnly);
99
+ }
100
+
101
+ // 解析该 tab 的 iconOptions(优先当前路由,其次面包屑链路上最近的一个)
102
+ function resolveIconOptions(element: Tabbar.recordRaw): undefined | {
103
+ boxType?: 'square' | 'prism' | 'null'
104
+ angle?: number | string
105
+ background?: any
106
+ radius?: number | string
107
+ iconColor?: string
108
+ } {
109
+ const r: any = findRouteRecordByElement(element);
110
+ let options = (r?.meta as any)?.iconOptions;
111
+ if (!options && r?.meta?.breadcrumbNeste?.length) {
112
+ const found = [...r.meta.breadcrumbNeste].reverse().find((it: any) => it?.iconOptions);
113
+ options = found?.iconOptions;
114
+ }
115
+ return options;
116
+ }
117
+
118
+ function computeBoxType(options: ReturnType<typeof resolveIconOptions>) {
119
+ const bt = (options as any)?.boxType as any;
120
+ return bt && bt !== 'null' ? bt : 'prism';
121
+ }
122
+
123
+ function getSafeIconColor(options: ReturnType<typeof resolveIconOptions>): string | undefined {
124
+ return (options as any)?.iconColor || undefined;
125
+ }
88
126
  </script>
89
127
 
90
128
  <template>
@@ -125,7 +163,25 @@ function iconName(isActive: boolean, icon: Tabbar.recordRaw['icon'], activeIcon:
125
163
  }"
126
164
  >
127
165
  <div :key="element.tabId" class="title" :title="element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateTitle(element.title)" @click="router.push(element.fullPath)">
128
- <PubinfoIcon v-if="settingsStore.settings.tabbar.enableIcon && iconName(element.tabId === activedTabId, element.icon, element.activeIcon)" :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!" />
166
+ <template v-if="settingsStore.settings.tabbar.enableIcon && iconName(element.tabId === activedTabId, element.icon, element.activeIcon)">
167
+ <PubinfoIcon
168
+ v-if="resolveIconOptions(element)"
169
+ small
170
+ :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
171
+ :box="computeBoxType(resolveIconOptions(element)) as any"
172
+ :size="16"
173
+ :angle="resolveIconOptions(element)?.angle"
174
+ :background="resolveIconOptions(element)?.background"
175
+ :radius="resolveIconOptions(element)?.radius"
176
+ :color="(iconName(element.tabId === activedTabId, element.icon, element.activeIcon) || '').startsWith('antd:') ? (getSafeIconColor(resolveIconOptions(element)) || '#ffffff') : undefined"
177
+ />
178
+ <PubinfoIcon
179
+ v-else
180
+ :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
181
+ :size="16"
182
+ :color="(iconName(element.tabId === activedTabId, element.icon, element.activeIcon) || '').startsWith('antd:') ? getSafeIconColor(resolveIconOptions(element)) : undefined"
183
+ />
184
+ </template>
129
185
  {{ element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateTitle(element.title) }}
130
186
  </div>
131
187
  <div v-if="!element.isPermanent && element.isPin" class="action-icon" @click.stop="tabbarStore.unPin(element.tabId)">
@@ -22,7 +22,7 @@ defineOptions({
22
22
  const route = useRoute();
23
23
  const router = useRouter();
24
24
 
25
- const { settingsStore, tabbarStore, generateTitle } = useContext();
25
+ const { settingsStore, tabbarStore, generateTitle, routeStore } = useContext();
26
26
 
27
27
  const tabbar = useTabbar();
28
28
  const mainPage = useMainPage();
@@ -241,6 +241,46 @@ function iconName(isActive: boolean, icon: Tabbar.recordRaw['icon'], activeIcon:
241
241
  return name;
242
242
  }
243
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
+
244
284
  onMounted(() => {
245
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) => {
246
286
  if (settingsStore.settings.tabbar.enable && settingsStore.settings.tabbar.enableHotkeys) {
@@ -325,11 +365,29 @@ onUnmounted(() => {
325
365
  <div class="tab-background" />
326
366
  <div class="tab-content">
327
367
  <div :key="element.tabId" class="title">
328
- <PubinfoIcon
329
- v-if="settingsStore.settings.tabbar.enableIcon && iconName(element.tabId === activedTabId, element.icon, element.activeIcon)"
330
- :name="iconName(element.tabId === activedTabId, element.icon, element.activeIcon)!"
331
- class="icon"
332
- />
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>
333
391
  {{ element.customTitleList.find(item => item.fullPath === element.fullPath)?.title || generateTitle(element.title) }}
334
392
  </div>
335
393
  <div v-if="!element.isPermanent && element.isPin" class="action-icon" @click.stop="tabbarStore.unPin(element.tabId)" @dblclick.stop>