优化细节
This commit is contained in:
@@ -57,6 +57,7 @@
|
||||
@tab-close="onTabClose"
|
||||
@close-other="onCloseOther"
|
||||
@close-all="onCloseAll"
|
||||
@refresh="onRefresh"
|
||||
/>
|
||||
|
||||
<!-- 内容展示区 (内部路由渲染) -->
|
||||
|
||||
@@ -1,36 +1,122 @@
|
||||
<template>
|
||||
<view class="tags">
|
||||
<scroll-view class="tags-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<view class="tags-row">
|
||||
<transition-group name="tag-list" tag="view" class="tags-row">
|
||||
<view
|
||||
v-for="t in tabs"
|
||||
:key="t.id"
|
||||
class="tag"
|
||||
:class="{ active: activeTabId === t.id }"
|
||||
@click="$emit('tab-click', t)"
|
||||
@click="onTabClick(t)"
|
||||
@contextmenu.prevent="openContextMenu($event, t)"
|
||||
>
|
||||
<text class="tag-text">{{ t.title }}</text>
|
||||
<view class="tag-close" @click.stop="$emit('tab-close', t.id)">
|
||||
<view v-if="!t.isAffix" class="tag-close" @click.stop="$emit('tab-close', t.id)">
|
||||
<text class="tag-close-text">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</transition-group>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右键菜单 (带动画) -->
|
||||
<transition name="menu-fade">
|
||||
<view
|
||||
v-if="menuVisible"
|
||||
class="context-menu"
|
||||
:style="{ top: menuY + 'px', left: menuX + 'px' }"
|
||||
@click.stop=""
|
||||
>
|
||||
<view class="menu-item" @click="handleAction('refresh')">
|
||||
<text class="menu-icon">↻</text>
|
||||
<text class="menu-text">刷新</text>
|
||||
</view>
|
||||
<view class="menu-item" v-if="!selectedTab?.isAffix" @click="handleAction('close')">
|
||||
<text class="menu-icon">×</text>
|
||||
<text class="menu-text">关闭</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="handleAction('close-other')">
|
||||
<text class="menu-icon">↸</text>
|
||||
<text class="menu-text">关闭其他</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="handleAction('close-all')">
|
||||
<text class="menu-icon">⊘</text>
|
||||
<text class="menu-text">全部关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
</transition>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import type { TabItem } from '../types.uts'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import type { TabItem } from '../store/adminNavStore.uts'
|
||||
|
||||
defineProps<{
|
||||
tabs: TabItem[]
|
||||
activeTabId: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
const emit = defineEmits<{
|
||||
(e:'tab-click', tab: TabItem): void
|
||||
(e:'tab-close', tabId: string): void
|
||||
(e:'close-other', tabId: string): void
|
||||
(e:'close-all'): void
|
||||
(e:'refresh'): void
|
||||
}>()
|
||||
|
||||
// 右键菜单状态
|
||||
const menuVisible = ref(false)
|
||||
const menuX = ref(0)
|
||||
const menuY = ref(0)
|
||||
const selectedTab = ref<TabItem | null>(null)
|
||||
|
||||
function onTabClick(tab: TabItem) {
|
||||
closeMenu()
|
||||
emit('tab-click', tab)
|
||||
}
|
||||
|
||||
function openContextMenu(e: MouseEvent, tab: TabItem) {
|
||||
selectedTab.value = tab
|
||||
menuX.value = e.clientX
|
||||
menuY.value = e.clientY
|
||||
|
||||
// 边缘检测
|
||||
if (menuX.value + 100 > window.innerWidth) {
|
||||
menuX.value = window.innerWidth - 110
|
||||
}
|
||||
|
||||
menuVisible.value = true
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menuVisible.value = false
|
||||
}
|
||||
|
||||
function handleAction(type: string) {
|
||||
if (!selectedTab.value) return
|
||||
|
||||
const id = selectedTab.value!.id
|
||||
|
||||
if (type === 'refresh') {
|
||||
emit('refresh')
|
||||
} else if (type === 'close') {
|
||||
emit('tab-close', id)
|
||||
} else if (type === 'close-other') {
|
||||
emit('close-other', id)
|
||||
} else if (type === 'close-all') {
|
||||
emit('close-all')
|
||||
}
|
||||
|
||||
closeMenu()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', closeMenu)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('click', closeMenu)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -41,6 +127,7 @@ defineEmits<{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
position: relative;
|
||||
}
|
||||
.tags-scroll{ width: 100%; height: 44px; }
|
||||
.tags-row{
|
||||
@@ -61,6 +148,11 @@ defineEmits<{
|
||||
flex-direction: row;
|
||||
align-items:center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tag:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.tag.active{
|
||||
border-color:#1677ff;
|
||||
@@ -76,5 +168,80 @@ defineEmits<{
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
.tag-close:hover {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
.tag-close-text{ font-size:14px; color:#6b7280; line-height:14px; }
|
||||
|
||||
/* 右键菜单样式 */
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
min-width: 100px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
color: #606266;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 标签列表动画 - 平滑平移和缩放 */
|
||||
.tag-list-enter-active,
|
||||
.tag-list-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.tag-list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px) scale(0.9);
|
||||
}
|
||||
|
||||
.tag-list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.8);
|
||||
}
|
||||
|
||||
/* 确保列表项在移动过程中位置平滑切换 */
|
||||
.tag-list-move {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* 右键菜单动画 - 类似 CRMEB 的缩放渐入 */
|
||||
.menu-fade-enter-active,
|
||||
.menu-fade-leave-active {
|
||||
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.menu-fade-enter-from,
|
||||
.menu-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-10px);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user