This commit is contained in:
zwgczx committed 2026-03-09 15:44:56 +08:00
1 parent d1dda7f9fa
commit ab310b49e9
6 files changed
+765 -327

No files matched your search

+103 -65
View File
@@ -371,6 +371,7 @@ const tabScrollAreaRef = ref<HTMLElement | null>(null)
const showTabScrollLeft = ref(false)
const showTabScrollRight = ref(false)
const isTabStripHover = ref(false)
const isTabDragging = ref(false)
const tabTitleOverflowMap = ref<Record<string, boolean>>({})
let tabStripViewportEl: HTMLElement | null = null
let tabTitleOverflowRafId: number | null = null
@@ -544,6 +545,14 @@ const canMoveTab = (event: any) => {
return true
}
const handleTabDragStart = () => {
isTabDragging.value = true
}
const handleTabDragEnd = () => {
isTabDragging.value = false
}
const setTabItemRef = (id: string, el: Element | ComponentPublicInstance | null) => {
if (el instanceof HTMLElement) {
tabItemElMap.set(id, el)
@@ -1341,9 +1350,9 @@ watch(
<template>
<TooltipProvider>
<div class="flex flex-col w-full h-screen bg-background overflow-hidden">
<div class="flex items-start gap-2 border-b bg-muted/30 px-2 pt-2 flex-none">
<div class="flex items-start gap-2 border-b bg-muted/30 px-2 pt-1 min-h-14 flex-none">
<div
class="mb-2 flex min-w-0 flex-1 items-center gap-1"
class="flex min-w-0 flex-1 items-start gap-1 h-full self-start"
@mouseenter="isTabStripHover = true"
@mouseleave="isTabStripHover = false"
>
@@ -1362,20 +1371,26 @@ watch(
v-model="tabsModel"
item-key="id"
tag="div"
class="flex w-max gap-1"
:animation="180"
:class="['tab-strip-sortable flex w-max gap-0', isTabDragging ? 'is-dragging' : '']"
:animation="260"
easing="cubic-bezier(0.22, 1, 0.36, 1)"
ghost-class="tab-drag-ghost"
chosen-class="tab-drag-chosen"
drag-class="tab-drag-active"
:move="canMoveTab"
@start="handleTabDragStart"
@end="handleTabDragEnd"
>
<template #item="{ element: tab }">
<div
:ref="el => setTabItemRef(tab.id, el)"
@click="tabStore.activeTabId = tab.id"
@mousedown.left="tabStore.activeTabId = tab.id"
@contextmenu.prevent="openTabContextMenu($event, tab.id)"
:class="[
'group relative flex items-center h-9 px-4 min-w-[120px] max-w-[220px] cursor-pointer rounded-t-md border-x border-t transition-all text-sm',
tabStore.activeTabId === tab.id
? 'bg-background border-border font-medium'
: 'border-transparent hover:bg-muted text-muted-foreground',
'tab-item group relative -mb-px -ml-px first:ml-0 flex items-center h-10 px-4 min-w-[120px] max-w-[220px] cursor-pointer rounded-t-md border border-transparent transition-[background-color,border-color,color,box-shadow,transform] duration-200 text-sm',
tabStore.activeTabId === tab.id && !isTabDragging
? 'z-10 bg-background text-foreground !border-border !border-b-0 font-medium'
: 'bg-muted/25 text-muted-foreground hover:bg-muted/40 hover:text-foreground hover:border-border/70',
tab.id !== 'XmView' ? 'cursor-move' : ''
]"
>
@@ -1415,12 +1430,12 @@ watch(
&gt;
</button>
</div>
<div ref="dataMenuRef" class="relative mb-2 shrink-0">
<div class="flex shrink-0 self-start items-start gap-1 mb-1">
<div ref="dataMenuRef" class="relative shrink-0">
<Button
variant="outline"
size="sm"
class="h-9 min-h-9 px-3 py-0 text-sm leading-none cursor-pointer"
class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
@click="dataMenuOpen = !dataMenuOpen"
>
<ChevronDown class="h-4 w-4 mr-1" />
@@ -1458,61 +1473,63 @@ watch(
/>
</div>
<Button
variant="outline"
size="sm"
class="mb-2 h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
@click="openUserGuide(0)"
>
<CircleHelp class="h-4 w-4 mr-1" />
使用引导
</Button>
<Button
variant="outline"
size="sm"
class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
@click="openUserGuide(0)"
>
<CircleHelp class="h-4 w-4 mr-1" />
使用引导
</Button>
<AlertDialogRoot>
<AlertDialogTrigger as-child>
<Button variant="destructive" size="sm" class="mb-2 h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none">
<RotateCcw class="h-4 w-4 mr-1" />
重置
</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认重置</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将清空所有项目数据,并恢复默认页面,确认继续吗?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="handleReset">确认重置</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<AlertDialogRoot>
<AlertDialogTrigger as-child>
<Button variant="destructive" size="sm" class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer">
<RotateCcw class="h-4 w-4 mr-1" />
重置
</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认重置</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将清空所有项目数据,并恢复默认页面,确认继续吗?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="handleReset">确认重置</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<AlertDialogRoot :open="importConfirmOpen" @update:open="importConfirmOpen = $event">
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认导入覆盖</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将使用“{{ pendingImportFileName || '所选文件' }}”覆盖当前本地全部数据,是否继续?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline" @click="cancelImportConfirm">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="confirmImportOverride">确认覆盖</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<AlertDialogRoot :open="importConfirmOpen" @update:open="importConfirmOpen = $event">
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认导入覆盖</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将使用“{{ pendingImportFileName || '所选文件' }}”覆盖当前本地全部数据,是否继续?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline" @click="cancelImportConfirm">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="confirmImportOverride">确认覆盖</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
</div>
</div>
<div class="flex-1 overflow-auto relative">
@@ -1612,6 +1629,27 @@ watch(
</template>
<style scoped>
.tab-strip-sortable > .tab-item {
transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}
.tab-strip-sortable.is-dragging > .tab-item {
will-change: transform;
}
.tab-drag-ghost {
opacity: 0.32;
}
.tab-drag-chosen {
transform: scale(1.015);
box-shadow: 0 10px 24px rgb(0 0 0 / 18%);
}
.tab-drag-active {
cursor: grabbing;
}
.tab-strip-scroll-area :deep([data-slot="scroll-area-viewport"]) {
scrollbar-width: none;
}