@pubinfo/core 2.0.0-rc.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AppSetting-BI-oNc4e.js → AppSetting-DqVYDIHj.js} +15 -15
- package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-BdLpkcoh.js → HCheckList.vue_vue_type_script_setup_true_lang-SrNklW3P.js} +1 -1
- package/dist/{HToggle-DxdWLgp-.js → HToggle-DGTP9jYA.js} +1 -1
- package/dist/{PreferencesContent-CCYkZeCT.js → PreferencesContent-5NtwK9RQ.js} +4 -4
- package/dist/{SettingBreadcrumb-BTyfiy4k.js → SettingBreadcrumb-BudqQsuJ.js} +3 -3
- package/dist/{SettingCopyright-g6UHi8pZ.js → SettingCopyright-VUberG4R.js} +2 -2
- package/dist/{SettingEnableTransition-Ci-5bhbR.js → SettingEnableTransition-C6NYf021.js} +2 -2
- package/dist/SettingHome-BTaeKgwN.js +46 -0
- package/dist/{SettingMenu-BYLWzA5i.js → SettingMenu-D9Aon2LP.js} +3 -3
- package/dist/{SettingMode-tRisyKtg.js → SettingMode-DaqVd9Mq.js} +1 -1
- package/dist/{SettingNavSearch-CSM6mPf8.js → SettingNavSearch-N4JIheIk.js} +2 -2
- package/dist/{SettingOther-Bj5KF_vC.js → SettingOther-tLulcors.js} +2 -2
- package/dist/{SettingPage-CFjmrVI7.js → SettingPage-CEjWB45R.js} +2 -2
- package/dist/{SettingTabbar-uFYiaZhK.js → SettingTabbar-DyeLhcCT.js} +3 -3
- package/dist/{SettingThemes-C-tMq9o5.js → SettingThemes-C2M3tsVl.js} +1 -1
- package/dist/{SettingToolbar-BfDzijNU.js → SettingToolbar-DI7de6i0.js} +24 -31
- package/dist/{SettingTopbar-DTDv4NXD.js → SettingTopbar-BgIoXeAq.js} +3 -3
- package/dist/{SettingWidthMode-PkiwrHe3.js → SettingWidthMode-DIAU4s5e.js} +1 -1
- package/dist/{TopThinMode-BrvA8pV0.js → TopThinMode-JNUHrJI2.js} +1 -1
- package/dist/built-in/index.d.ts +1 -0
- package/dist/built-in/layout-component/components/Tools/SearchPanel.vue.d.ts +7 -2
- package/dist/built-in/layout-component/composables/useGlobalSearch.d.ts +7 -0
- package/dist/built-in/system-info/components/SystemInfo.vue.d.ts +2 -0
- package/dist/built-in/system-info/index.d.ts +5 -0
- package/dist/{colors-VoaDbOhe.js → colors-DxWfHM_v.js} +1 -1
- package/dist/features/components/PubinfoIcon/PrismBox.vue.d.ts +21 -0
- package/dist/features/components/PubinfoIcon/SquareBox.vue.d.ts +17 -0
- package/dist/features/components/PubinfoIcon/index.vue.d.ts +13 -9
- package/dist/features/components/PubinfoIcon/props.d.ts +58 -0
- package/dist/features/components/index.d.ts +2 -0
- package/dist/{index-BfGqLWFB.js → index-5fRpGyLW.js} +4 -4
- package/dist/{index-ConeY38N.js → index-BFRIv97x.js} +2 -2
- package/dist/{index-Dv9ndBoi.js → index-BH-vHGvk.js} +1 -1
- package/dist/{index-BSevJVD5.js → index-C7xIGcDc.js} +2 -2
- package/dist/{index-CYoFRwvw.js → index-CNVn3Ubv.js} +2 -2
- package/dist/{index-DV3hkzKA.js → index-Cf-u1Zqh.js} +1 -1
- package/dist/{index-Ddw98rJ5.js → index-D4v4g8FJ.js} +112 -98
- package/dist/{index-DrC787X_.js → index-DQGnbEGS.js} +2 -2
- package/dist/{index-IAYhIBQH.js → index-Dv7UUFkD.js} +23446 -23082
- package/dist/index.d.ts +1 -1
- package/dist/index.js +55 -48
- package/dist/{pick-vpv9EEvu.js → pick-VFuUwFn-.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/utils/global.d.ts +33 -0
- package/dist/utils/index.d.ts +2 -1
- package/package.json +8 -5
- package/src/built-in/index.ts +1 -0
- package/src/built-in/layout-component/components/Header/TopMode/index.vue +27 -6
- package/src/built-in/layout-component/components/Menu/item.vue +41 -6
- package/src/built-in/layout-component/components/SettingBar/components/SettingHome.vue +1 -4
- package/src/built-in/layout-component/components/SettingBar/components/SettingToolbar.vue +0 -6
- package/src/built-in/layout-component/components/Tools/SearchPanel.vue +113 -37
- package/src/built-in/layout-component/components/Tools/index.vue +1 -1
- package/src/built-in/layout-component/components/Topbar/Tabbar/MoreAction.vue +58 -2
- package/src/built-in/layout-component/components/Topbar/Tabbar/index.vue +64 -6
- package/src/built-in/layout-component/composables/useGlobalSearch.ts +40 -1
- package/src/built-in/system-info/components/SystemInfo.vue +53 -0
- package/src/built-in/system-info/index.ts +16 -0
- package/src/core/ctx.ts +7 -1
- package/src/core/resolver/icon.ts +9 -5
- package/src/features/components/PubinfoIcon/PrismBox.vue +203 -0
- package/src/features/components/PubinfoIcon/SquareBox.vue +59 -0
- package/src/features/components/PubinfoIcon/index.vue +128 -37
- package/src/features/components/PubinfoIcon/props.ts +54 -0
- package/src/features/components/index.ts +4 -1
- package/src/features/context/index.ts +1 -16
- package/src/features/settings/index.ts +0 -1
- package/src/index.ts +7 -0
- package/src/utils/global.ts +161 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/proxy.ts +7 -8
- package/types/global.d.ts +7 -0
- package/types/menu.d.ts +10 -0
- package/types/settings.d.ts +0 -7
- 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 {};
|
package/dist/utils/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubinfo/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.0
|
|
4
|
+
"version": "2.0.0",
|
|
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
|
|
27
|
-
"@pubinfo/vite": "2.0.0
|
|
29
|
+
"@pubinfo/config": "2.0.0",
|
|
30
|
+
"@pubinfo/vite": "2.0.0"
|
|
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
|
|
86
|
-
"@pubinfo/vite": "2.0.0
|
|
88
|
+
"@pubinfo/config": "2.0.0",
|
|
89
|
+
"@pubinfo/vite": "2.0.0"
|
|
87
90
|
},
|
|
88
91
|
"scripts": {
|
|
89
92
|
"dev": "vite build -w -m watch",
|
package/src/built-in/index.ts
CHANGED
|
@@ -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
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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"
|
|
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(
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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>
|