1
This commit is contained in:
1 parent
f79e8e0da6
commit
2a2c0fe2d7
11 files changed
+913
-333
No files matched your search
+201
-265
@@ -344,6 +344,7 @@ const componentMap: Record<string, any> = {
|
||||
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/xmCard.vue'))),
|
||||
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/htCard.vue'))),
|
||||
ZxFwView: markRaw(defineAsyncComponent(() => import('@/components/views/ZxFwView.vue'))),
|
||||
HtFeeMethodTypeLineView: markRaw(defineAsyncComponent(() => import('@/components/views/HtFeeMethodTypeLineView.vue'))),
|
||||
}
|
||||
|
||||
const tabStore = useTabStore()
|
||||
@@ -1060,7 +1061,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||
localforage.getItem<ContractCardItem[]>('ht-card-v1')
|
||||
])
|
||||
|
||||
const projectInfo = projectInfoRaw || {}
|
||||
const projectInfo = projectInfoRaw || {}
|
||||
const projectScaleSource = projectScaleRaw || {}
|
||||
const projectScale = buildScaleRows(projectScaleSource.detailRows)
|
||||
const projectScaleCost = sumNumbers(projectScale.map(item => item.cost))
|
||||
@@ -1349,291 +1350,220 @@ 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-1 min-h-14 flex-none">
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-start gap-1 h-full self-start"
|
||||
@mouseenter="isTabStripHover = true"
|
||||
@mouseleave="isTabStripHover = false"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
'h-9 w-8 shrink-0 cursor-pointer rounded border bg-background text-sm text-muted-foreground transition hover:bg-muted',
|
||||
<div class="flex flex-col w-full h-screen bg-background overflow-hidden">
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-2 border-b bg-muted/30 px-2 pt-1 h-15 flex-none">
|
||||
<div class="flex min-w-0 items-start gap-1 h-full" @mouseenter="isTabStripHover = true"
|
||||
@mouseleave="isTabStripHover = false">
|
||||
<button type="button" :class="[
|
||||
'h-9 w-8 self-center shrink-0 cursor-pointer rounded border bg-background text-sm text-muted-foreground transition hover:bg-muted',
|
||||
isTabStripHover && showTabScrollLeft ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
]"
|
||||
@click="scrollTabStripBy(-260)"
|
||||
>
|
||||
<
|
||||
</button>
|
||||
<ScrollArea :ref="setTabScrollAreaRef" type="auto" class="tab-strip-scroll-area min-w-0 flex-1 whitespace-nowrap">
|
||||
<draggable
|
||||
v-model="tabsModel"
|
||||
item-key="id"
|
||||
tag="div"
|
||||
: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)"
|
||||
@mousedown.left="tabStore.activeTabId = tab.id"
|
||||
@contextmenu.prevent="openTabContextMenu($event, tab.id)"
|
||||
:class="[
|
||||
'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' : ''
|
||||
]"
|
||||
>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<span
|
||||
:ref="el => setTabTitleRef(tab.id, el)"
|
||||
class="truncate mr-2"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent v-if="tabTitleOverflowMap[tab.id]" side="bottom">{{ tab.title }}</TooltipContent>
|
||||
</TooltipRoot>
|
||||
]" @click="scrollTabStripBy(-260)">
|
||||
<
|
||||
</button>
|
||||
<ScrollArea :ref="setTabScrollAreaRef" type="auto"
|
||||
class="h-full tab-strip-scroll-area min-w-0 flex-1 whitespace-nowrap">
|
||||
<draggable v-model="tabsModel" item-key="id" tag="div"
|
||||
:class="['tab-strip-sortable h-[calc(3.49rem)] 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)" @mousedown.left="tabStore.activeTabId = tab.id"
|
||||
@contextmenu.prevent="openTabContextMenu($event, tab.id)" :class="[
|
||||
'tab-item group relative -mb-px -ml-px first:ml-0 flex items-center h-full 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' : ''
|
||||
]">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<span :ref="el => setTabTitleRef(tab.id, el)" class="truncate mr-2">
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent v-if="tabTitleOverflowMap[tab.id]" side="bottom">{{ tab.title }}</TooltipContent>
|
||||
</TooltipRoot>
|
||||
|
||||
<Button
|
||||
v-if="tab.id !== 'XmView'"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-4 w-4 ml-auto opacity-0 group-hover:opacity-100 hover:bg-destructive hover:text-destructive-foreground transition-opacity"
|
||||
@click.stop="tabStore.removeTab(tab.id)"
|
||||
>
|
||||
<X class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</ScrollArea>
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
'h-9 w-8 shrink-0 cursor-pointer rounded border bg-background text-sm text-muted-foreground transition hover:bg-muted',
|
||||
<Button v-if="tab.id !== 'XmView'" variant="ghost" size="icon"
|
||||
class="h-4 w-4 ml-auto opacity-0 group-hover:opacity-100 hover:bg-destructive hover:text-destructive-foreground transition-opacity"
|
||||
@click.stop="tabStore.removeTab(tab.id)">
|
||||
<X class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</ScrollArea>
|
||||
<button type="button" :class="[
|
||||
' self-center h-9 w-8 shrink-0 cursor-pointer rounded border bg-background text-sm text-muted-foreground transition hover:bg-muted ',
|
||||
isTabStripHover && showTabScrollRight ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
]"
|
||||
@click="scrollTabStripBy(260)"
|
||||
>
|
||||
>
|
||||
]" @click="scrollTabStripBy(260)">
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex shrink-0 self-center items-center gap-1">
|
||||
<div ref="dataMenuRef" class="relative shrink-0">
|
||||
<Button variant="outline" size="sm"
|
||||
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" />
|
||||
导入/导出
|
||||
</Button>
|
||||
<div v-if="dataMenuOpen"
|
||||
class="absolute right-0 top-full mt-1 z-50 rounded-md border bg-background p-1 shadow-md">
|
||||
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="triggerImport">
|
||||
导入
|
||||
</button>
|
||||
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="exportData">
|
||||
导出
|
||||
</button>
|
||||
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="exportReport">
|
||||
导出报表
|
||||
</button>
|
||||
</div>
|
||||
<input ref="importFileRef" type="file" accept=".zw" class="hidden" @change="importData" />
|
||||
</div>
|
||||
|
||||
<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="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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-auto relative">
|
||||
<div v-for="tab in tabStore.tabs" :key="tab.id" :ref="el => setTabPanelRef(tab.id, el)"
|
||||
v-show="tabStore.activeTabId === tab.id" class="h-full w-full p-4 animate-in fade-in duration-300">
|
||||
<component :is="componentMap[tab.componentName]" v-bind="tab.props || {}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="tabContextOpen" ref="tabContextRef"
|
||||
class="fixed z-[70] w-max rounded-lg border border-border/80 bg-background/95 p-1.5 shadow-xl ring-1 ring-black/5 backdrop-blur-sm"
|
||||
:style="{ left: `${tabContextX}px`, top: `${tabContextY}px` }">
|
||||
<button
|
||||
class="flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!hasClosableTabs" @click="runTabMenuAction('all')">
|
||||
删除所有
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseLeft" @click="runTabMenuAction('left')">
|
||||
删除左侧
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseRight" @click="runTabMenuAction('right')">
|
||||
删除右侧
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseOther" @click="runTabMenuAction('other')">
|
||||
删除其他
|
||||
</button>
|
||||
</div>
|
||||
<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 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
|
||||
@click="dataMenuOpen = !dataMenuOpen"
|
||||
>
|
||||
<ChevronDown class="h-4 w-4 mr-1" />
|
||||
导入/导出
|
||||
</Button>
|
||||
<div
|
||||
v-if="dataMenuOpen"
|
||||
class="absolute right-0 top-full mt-1 z-50 rounded-md border bg-background p-1 shadow-md"
|
||||
>
|
||||
<button
|
||||
class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="triggerImport"
|
||||
>
|
||||
导入
|
||||
</button>
|
||||
<button
|
||||
class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="exportData"
|
||||
>
|
||||
导出
|
||||
</button>
|
||||
<button
|
||||
class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
|
||||
@click="exportReport"
|
||||
>
|
||||
导出报表
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
ref="importFileRef"
|
||||
type="file"
|
||||
accept=".zw"
|
||||
class="hidden"
|
||||
@change="importData"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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="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" />
|
||||
重置
|
||||
<div v-if="userGuideOpen" class="fixed inset-0 z-[120] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeUserGuide(false)">
|
||||
<div class="w-full max-w-3xl rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-start justify-between border-b px-6 py-5">
|
||||
<div>
|
||||
<p class="text-xs text-muted-foreground">新用户引导 · {{ guideProgressText }}</p>
|
||||
<h3 class="mt-1 text-lg font-semibold">{{ activeGuideStep.title }}</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeUserGuide(false)">
|
||||
<X class="h-4 w-4" />
|
||||
</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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-auto relative">
|
||||
<div
|
||||
v-for="tab in tabStore.tabs"
|
||||
:key="tab.id"
|
||||
:ref="el => setTabPanelRef(tab.id, el)"
|
||||
v-show="tabStore.activeTabId === tab.id"
|
||||
class="h-full w-full p-4 animate-in fade-in duration-300"
|
||||
>
|
||||
<component :is="componentMap[tab.componentName]" v-bind="tab.props || {}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="tabContextOpen"
|
||||
ref="tabContextRef"
|
||||
class="fixed z-[70] w-max rounded-lg border border-border/80 bg-background/95 p-1.5 shadow-xl ring-1 ring-black/5 backdrop-blur-sm"
|
||||
:style="{ left: `${tabContextX}px`, top: `${tabContextY}px` }"
|
||||
>
|
||||
<button
|
||||
class="flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!hasClosableTabs"
|
||||
@click="runTabMenuAction('all')"
|
||||
>
|
||||
删除所有
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseLeft"
|
||||
@click="runTabMenuAction('left')"
|
||||
>
|
||||
删除左侧
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseRight"
|
||||
@click="runTabMenuAction('right')"
|
||||
>
|
||||
删除右侧
|
||||
</button>
|
||||
<button
|
||||
class="mt-0.5 flex cursor-pointer items-center whitespace-nowrap rounded-md px-3 py-1.5 text-left text-sm transition-colors hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="!canCloseOther"
|
||||
@click="runTabMenuAction('other')"
|
||||
>
|
||||
删除其他
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="userGuideOpen"
|
||||
class="fixed inset-0 z-[120] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeUserGuide(false)"
|
||||
>
|
||||
<div class="w-full max-w-3xl rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-start justify-between border-b px-6 py-5">
|
||||
<div>
|
||||
<p class="text-xs text-muted-foreground">新用户引导 · {{ guideProgressText }}</p>
|
||||
<h3 class="mt-1 text-lg font-semibold">{{ activeGuideStep.title }}</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeUserGuide(false)">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 px-6 py-5">
|
||||
<p class="text-sm leading-6 text-foreground">{{ activeGuideStep.description }}</p>
|
||||
<ul class="list-disc space-y-2 pl-5 text-sm text-muted-foreground">
|
||||
<li v-for="(point, index) in activeGuideStep.points" :key="`${activeGuideStep.title}-${index}`">
|
||||
{{ point }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 border-t px-6 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button
|
||||
v-for="(_step, index) in userGuideSteps"
|
||||
:key="`guide-dot-${index}`"
|
||||
class="h-2.5 w-2.5 cursor-pointer rounded-full transition-colors"
|
||||
:class="index === userGuideStepIndex ? 'bg-primary' : 'bg-muted'"
|
||||
:aria-label="`跳转到第 ${index + 1} 步`"
|
||||
@click="jumpToGuideStep(index)"
|
||||
/>
|
||||
<div class="space-y-4 px-6 py-5">
|
||||
<p class="text-sm leading-6 text-foreground">{{ activeGuideStep.description }}</p>
|
||||
<ul class="list-disc space-y-2 pl-5 text-sm text-muted-foreground">
|
||||
<li v-for="(point, index) in activeGuideStep.points" :key="`${activeGuideStep.title}-${index}`">
|
||||
{{ point }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" @click="closeUserGuide(false)">稍后再看</Button>
|
||||
<Button variant="outline" :disabled="isFirstGuideStep" @click="prevUserGuideStep">上一步</Button>
|
||||
<Button @click="nextUserGuideStep">{{ isLastGuideStep ? '完成并不再自动弹出' : '下一步' }}</Button>
|
||||
|
||||
<div class="flex flex-col gap-3 border-t px-6 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button v-for="(_step, index) in userGuideSteps" :key="`guide-dot-${index}`"
|
||||
class="h-2.5 w-2.5 cursor-pointer rounded-full transition-colors"
|
||||
:class="index === userGuideStepIndex ? 'bg-primary' : 'bg-muted'" :aria-label="`跳转到第 ${index + 1} 步`"
|
||||
@click="jumpToGuideStep(index)" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" @click="closeUserGuide(false)">稍后再看</Button>
|
||||
<Button variant="outline" :disabled="isFirstGuideStep" @click="prevUserGuideStep">上一步</Button>
|
||||
<Button @click="nextUserGuideStep">{{ isLastGuideStep ? '完成并不再自动弹出' : '下一步' }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tab-strip-sortable > .tab-item {
|
||||
.tab-strip-sortable>.tab-item {
|
||||
transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.tab-strip-sortable.is-dragging > .tab-item {
|
||||
.tab-strip-sortable.is-dragging>.tab-item {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@@ -1652,9 +1582,15 @@ watch(
|
||||
|
||||
.tab-strip-scroll-area :deep([data-slot="scroll-area-viewport"]) {
|
||||
scrollbar-width: none;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.tab-strip-scroll-area :deep([data-slot="scroll-area-viewport"]::-webkit-scrollbar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-strip-scroll-area :deep([data-slot="scroll-area-scrollbar"][data-orientation="vertical"]),
|
||||
.tab-strip-scroll-area :deep([data-slot="scroll-area-corner"]) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user