feat(projects): support pinning and unpinning of tabs

This commit is contained in:
hooke
2025-12-03 07:25:46 -07:00
committed by Soybean
parent 605173a1cc
commit b8a767d704
6 changed files with 88 additions and 4 deletions

View File

@@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
const visible = defineModel<boolean>('visible'); const visible = defineModel<boolean>('visible');
const { removeTab, clearTabs, clearLeftTabs, clearRightTabs } = useTabStore(); const { removeTab, clearTabs, clearLeftTabs, clearRightTabs, fixTab, unfixTab, isTabRetain } = useTabStore();
const { SvgIconVNode } = useSvgIcon(); const { SvgIconVNode } = useSvgIcon();
type DropdownOption = { type DropdownOption = {
@@ -64,6 +64,23 @@ const options = computed(() => {
icon: SvgIconVNode({ icon: 'ant-design:line-outlined', fontSize: 18 }) icon: SvgIconVNode({ icon: 'ant-design:line-outlined', fontSize: 18 })
} }
]; ];
if (props.tabId !== '/home') {
if (isTabRetain(props.tabId)) {
opts.push({
key: 'unpin',
label: $t('dropdown.unpin'),
icon: SvgIconVNode({ icon: 'mdi:pin-off-outline', fontSize: 18 })
});
} else {
opts.push({
key: 'pin',
label: $t('dropdown.pin'),
icon: SvgIconVNode({ icon: 'mdi:pin-outline', fontSize: 18 })
});
}
}
const { excludeKeys, disabledKeys } = props; const { excludeKeys, disabledKeys } = props;
const result = opts.filter(opt => !excludeKeys.includes(opt.key)); const result = opts.filter(opt => !excludeKeys.includes(opt.key));
@@ -98,6 +115,12 @@ const dropdownAction: Record<App.Global.DropdownKey, () => void> = {
}, },
closeAll() { closeAll() {
clearTabs(); clearTabs();
},
pin() {
fixTab(props.tabId);
},
unpin() {
unfixTab(props.tabId);
} }
}; };

View File

@@ -336,7 +336,9 @@ const local: App.I18n.Schema = {
closeOther: 'Close Other', closeOther: 'Close Other',
closeLeft: 'Close Left', closeLeft: 'Close Left',
closeRight: 'Close Right', closeRight: 'Close Right',
closeAll: 'Close All' closeAll: 'Close All',
pin: 'Pin Tab',
unpin: 'Unpin Tab'
}, },
icon: { icon: {
themeConfig: 'Theme Configuration', themeConfig: 'Theme Configuration',

View File

@@ -333,7 +333,9 @@ const local: App.I18n.Schema = {
closeOther: '关闭其它', closeOther: '关闭其它',
closeLeft: '关闭左侧', closeLeft: '关闭左侧',
closeRight: '关闭右侧', closeRight: '关闭右侧',
closeAll: '关闭所有' closeAll: '关闭所有',
pin: '固定标签',
unpin: '取消固定'
}, },
icon: { icon: {
themeConfig: '主题配置', themeConfig: '主题配置',

View File

@@ -18,6 +18,7 @@ import {
getTabByRoute, getTabByRoute,
getTabIdByRoute, getTabIdByRoute,
isTabInTabs, isTabInTabs,
reorderFixedTabs,
updateTabByI18nKey, updateTabByI18nKey,
updateTabsByI18nKey updateTabsByI18nKey
} from './shared'; } from './shared';
@@ -248,6 +249,48 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
await clearTabs(excludes); await clearTabs(excludes);
} }
/**
* Fix tab
*
* @param tabId
*/
function fixTab(tabId: string) {
const tabIndex = tabs.value.findIndex(t => t.id === tabId);
if (tabIndex === -1) return;
const tab = tabs.value[tabIndex];
const fixedCount = getFixedTabIds(tabs.value).length;
tab.fixedIndex = fixedCount;
if (tabIndex !== fixedCount) {
tabs.value.splice(tabIndex, 1);
tabs.value.splice(fixedCount, 0, tab);
}
reorderFixedTabs(tabs.value);
}
/**
* Unfix tab
*
* @param tabId
*/
function unfixTab(tabId: string) {
const tabIndex = tabs.value.findIndex(t => t.id === tabId);
if (tabIndex === -1) return;
const tab = tabs.value[tabIndex];
tab.fixedIndex = undefined;
const fixedCount = getFixedTabIds(tabs.value).length;
if (tabIndex !== fixedCount) {
tabs.value.splice(tabIndex, 1);
tabs.value.splice(fixedCount, 0, tab);
}
reorderFixedTabs(tabs.value);
}
/** /**
* Set new label of tab * Set new label of tab
* *
@@ -328,6 +371,8 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
clearTabs, clearTabs,
clearLeftTabs, clearLeftTabs,
clearRightTabs, clearRightTabs,
fixTab,
unfixTab,
switchRouteByTab, switchRouteByTab,
setTabLabel, setTabLabel,
resetTabLabel, resetTabLabel,

View File

@@ -198,6 +198,18 @@ export function getFixedTabIds(tabs: App.Global.Tab[]) {
return fixedTabs.map(tab => tab.id); return fixedTabs.map(tab => tab.id);
} }
/**
* Reorder fixed tabs fixedIndex
*
* @param tabs
*/
export function reorderFixedTabs(tabs: App.Global.Tab[]) {
const fixedTabs = getFixedTabs(tabs);
fixedTabs.forEach((t, i) => {
t.fixedIndex = i;
});
}
/** /**
* Update tabs label * Update tabs label
* *

View File

@@ -282,7 +282,7 @@ declare namespace App {
type FormRule = import('naive-ui').FormItemRule; type FormRule = import('naive-ui').FormItemRule;
/** The global dropdown key */ /** The global dropdown key */
type DropdownKey = 'closeCurrent' | 'closeOther' | 'closeLeft' | 'closeRight' | 'closeAll'; type DropdownKey = 'closeCurrent' | 'closeOther' | 'closeLeft' | 'closeRight' | 'closeAll' | 'pin' | 'unpin';
} }
/** /**