- ty-search-form 折叠默认数 2 → 3 - login/register/forgot/home/ty-header/ty-sidebar/ty-mixbar/ty-tabs/ty-footer/ty-setting-panel/icon/default 布局裸 CSS 转 @apply - 修复背景色工具类误写(bg-base→bg-bg-base、bg-page→bg-bg-page) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
130 lines
3.4 KiB
Vue
130 lines
3.4 KiB
Vue
<template>
|
|
<div v-if="tabs.length" class="ty-tabs">
|
|
<div
|
|
v-for="tab in tabs"
|
|
:key="tab.path"
|
|
class="ty-tabs__item"
|
|
:class="{ 'is-active': tab.path === activePath }"
|
|
@click="emit('click', tab.path)"
|
|
@contextmenu.prevent="openMenu($event, tab)"
|
|
>
|
|
<span class="ty-tabs__title">{{ tab.title }}</span>
|
|
<span v-if="tab.closable" class="ty-tabs__close" @click.stop="emit('remove', tab.path)">
|
|
<icon type="iconify" icon="mdi:close" />
|
|
</span>
|
|
</div>
|
|
|
|
<div
|
|
v-if="menu.visible"
|
|
class="ty-tabs__context-menu"
|
|
:style="{ left: `${menu.x}px`, top: `${menu.y}px` }"
|
|
@click.stop
|
|
>
|
|
<div class="ty-tabs__context-item" @click="onMenu('close-others')">关闭其他</div>
|
|
<div class="ty-tabs__context-item" @click="onMenu('close-left')">关闭左侧</div>
|
|
<div class="ty-tabs__context-item" @click="onMenu('close-right')">关闭右侧</div>
|
|
<div class="ty-tabs__context-item" @click="onMenu('close-all')">关闭全部</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onBeforeUnmount, onMounted, reactive } from 'vue'
|
|
import type { TabItem } from '@/stores/modules/tabs'
|
|
|
|
defineOptions({ name: 'TyTabs' })
|
|
|
|
defineProps<{
|
|
/** 打开的页签。 */
|
|
tabs: TabItem[]
|
|
/** 当前激活的路径。 */
|
|
activePath: string
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'click', path: string): void
|
|
(e: 'remove', path: string): void
|
|
(e: 'close-others', path: string): void
|
|
(e: 'close-left', path: string): void
|
|
(e: 'close-right', path: string): void
|
|
(e: 'close-all'): void
|
|
}>()
|
|
|
|
const menu = reactive({ visible: false, x: 0, y: 0, path: '' })
|
|
|
|
function openMenu(e: MouseEvent, tab: TabItem): void {
|
|
menu.x = e.clientX
|
|
menu.y = e.clientY
|
|
menu.path = tab.path
|
|
menu.visible = true
|
|
}
|
|
|
|
function onMenu(action: 'close-others' | 'close-left' | 'close-right' | 'close-all'): void {
|
|
menu.visible = false
|
|
switch (action) {
|
|
case 'close-all':
|
|
emit('close-all')
|
|
break
|
|
case 'close-others':
|
|
emit('close-others', menu.path)
|
|
break
|
|
case 'close-left':
|
|
emit('close-left', menu.path)
|
|
break
|
|
case 'close-right':
|
|
emit('close-right', menu.path)
|
|
break
|
|
}
|
|
}
|
|
|
|
function closeMenu(): void {
|
|
menu.visible = false
|
|
}
|
|
|
|
onMounted(() => document.addEventListener('click', closeMenu))
|
|
onBeforeUnmount(() => document.removeEventListener('click', closeMenu))
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.ty-tabs {
|
|
@apply flex items-center gap-1 overflow-x-auto px-2 h-9 bg-bg-base border-b border-solid;
|
|
border-color: var(--wm-border-color-light);
|
|
|
|
&__item {
|
|
@apply flex flex-shrink-0 cursor-pointer items-center gap-1 h-7 px-2.5 rounded-base text-sm text-text-regular;
|
|
|
|
&:hover {
|
|
@apply bg-bg-page;
|
|
}
|
|
|
|
&.is-active {
|
|
color: var(--el-color-primary);
|
|
background: var(--el-color-primary-light-9);
|
|
}
|
|
}
|
|
|
|
&__close {
|
|
@apply flex items-center justify-center w-4 h-4 rounded-full text-xs;
|
|
|
|
&:hover {
|
|
@apply text-white;
|
|
background: var(--wm-color-text-placeholder);
|
|
}
|
|
}
|
|
|
|
&__context-menu {
|
|
@apply fixed z-50 min-w-[110px] py-1 bg-bg-base rounded-base border border-solid;
|
|
border-color: var(--wm-border-color-light);
|
|
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
|
}
|
|
|
|
&__context-item {
|
|
@apply cursor-pointer py-1.5 px-3.5 text-sm text-text-regular;
|
|
|
|
&:hover {
|
|
@apply bg-bg-page;
|
|
}
|
|
}
|
|
}
|
|
</style>
|