优化
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { MoreHorizontal, X } from '@lucide/vue'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import { getFirstAccessiblePath } from '@/navigation/clientNavigation'
|
||||
import { useUnsavedWorkStore } from '@/navigation/workspace-tabs/unsavedWork'
|
||||
import { useWorkspaceTabsStore } from '@/navigation/workspace-tabs/workspaceTabs'
|
||||
|
||||
type CloseCommand = 'current' | 'others' | 'right' | 'all'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const workspace = useWorkspaceTabsStore()
|
||||
const unsaved = useUnsavedWorkStore()
|
||||
const tabScroller = ref<HTMLElement>()
|
||||
|
||||
async function activate(fullPath: string) {
|
||||
if (fullPath === workspace.activeTab?.fullPath) return
|
||||
await router.push(fullPath)
|
||||
}
|
||||
|
||||
async function confirmDiscard(keys: readonly string[]) {
|
||||
const dirty = unsaved.findDirty(keys)
|
||||
if (dirty.length === 0) return true
|
||||
const labels = [...new Set(dirty.map((entry) => entry.label))].join('、')
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`“${labels}”还有未保存的修改。关闭页签后这些内容将丢失。`,
|
||||
'放弃未保存的修改?',
|
||||
{ type: 'warning', confirmButtonText: '放弃并关闭', cancelButtonText: '继续编辑' },
|
||||
)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function closeTabs(keys: readonly string[]) {
|
||||
const existingKeys = keys.filter((key) => workspace.tabs.some((tab) => tab.key === key))
|
||||
if (existingKeys.length === 0 || !await confirmDiscard(existingKeys)) return
|
||||
unsaved.removeRoutes(existingKeys)
|
||||
const result = workspace.close(existingKeys)
|
||||
if (!result.closedActive) return
|
||||
await router.push(result.fallbackPath ?? getFirstAccessiblePath(auth.permissionCodes))
|
||||
}
|
||||
|
||||
function keysForCommand(command: CloseCommand) {
|
||||
const activeIndex = workspace.tabs.findIndex((tab) => tab.key === workspace.activeKey)
|
||||
if (command === 'current') return workspace.activeKey ? [workspace.activeKey] : []
|
||||
if (command === 'others') return workspace.tabs.filter((tab) => tab.key !== workspace.activeKey).map((tab) => tab.key)
|
||||
if (command === 'right') return activeIndex < 0 ? [] : workspace.tabs.slice(activeIndex + 1).map((tab) => tab.key)
|
||||
return workspace.tabs.map((tab) => tab.key)
|
||||
}
|
||||
|
||||
function handleCommand(command: CloseCommand) {
|
||||
void closeTabs(keysForCommand(command))
|
||||
}
|
||||
|
||||
function handleTabWheel(event: WheelEvent) {
|
||||
const scroller = tabScroller.value
|
||||
if (!scroller || scroller.scrollWidth <= scroller.clientWidth) return
|
||||
const delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY
|
||||
if (delta === 0) return
|
||||
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth
|
||||
const canScroll = delta < 0 ? scroller.scrollLeft > 0 : scroller.scrollLeft < maxScrollLeft
|
||||
if (!canScroll) return
|
||||
event.preventDefault()
|
||||
scroller.scrollLeft = Math.max(0, Math.min(maxScrollLeft, scroller.scrollLeft + delta))
|
||||
}
|
||||
|
||||
watch(
|
||||
() => workspace.activeKey,
|
||||
async () => {
|
||||
await nextTick()
|
||||
tabScroller.value?.querySelector<HTMLElement>('.workspace-tab.is-active')?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'nearest',
|
||||
})
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="workspace-tabs" aria-label="已打开页面">
|
||||
<div
|
||||
ref="tabScroller"
|
||||
class="workspace-tabs__scroller"
|
||||
role="tablist"
|
||||
aria-label="工作区页签"
|
||||
@wheel="handleTabWheel"
|
||||
>
|
||||
<div
|
||||
v-for="tab in workspace.tabs"
|
||||
:key="tab.key"
|
||||
class="workspace-tab"
|
||||
:class="{ 'is-active': tab.key === workspace.activeKey, 'is-dirty': unsaved.dirtyRouteKeys.has(tab.key) }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="workspace-tab__label"
|
||||
role="tab"
|
||||
:aria-selected="tab.key === workspace.activeKey"
|
||||
:aria-current="tab.key === workspace.activeKey ? 'page' : undefined"
|
||||
:title="tab.title"
|
||||
@click="activate(tab.fullPath)"
|
||||
>
|
||||
<span>{{ tab.title }}</span>
|
||||
<i v-if="unsaved.dirtyRouteKeys.has(tab.key)" aria-label="有未保存修改" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="workspace-tab__close"
|
||||
:aria-label="`关闭${tab.title}`"
|
||||
:title="`关闭${tab.title}`"
|
||||
@click="closeTabs([tab.key])"
|
||||
><X :size="14" aria-hidden="true" /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dropdown trigger="click" placement="bottom-end" @command="handleCommand">
|
||||
<button type="button" class="workspace-tabs__more" aria-label="页签管理" title="页签管理">
|
||||
<MoreHorizontal :size="18" aria-hidden="true" />
|
||||
</button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="current" :disabled="!workspace.activeKey">关闭当前</el-dropdown-item>
|
||||
<el-dropdown-item command="others" :disabled="workspace.tabs.length <= 1">关闭其他</el-dropdown-item>
|
||||
<el-dropdown-item command="right" :disabled="keysForCommand('right').length === 0">关闭右侧</el-dropdown-item>
|
||||
<el-dropdown-item command="all" :disabled="workspace.tabs.length === 0" divided>关闭全部</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user