完成导航栏功能
This commit is contained in:
@@ -1,6 +1,22 @@
|
||||
<template>
|
||||
<view class="tags">
|
||||
<scroll-view class="tags-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<!-- 左滑动按钮 -->
|
||||
<view
|
||||
class="scroll-btn scroll-btn-left"
|
||||
:class="{ disabled: !canScrollLeft }"
|
||||
@click="scrollToLeft"
|
||||
>
|
||||
<text class="scroll-btn-icon">‹</text>
|
||||
</view>
|
||||
|
||||
<!-- 标签滚动容器 -->
|
||||
<scroll-view
|
||||
ref="scrollViewRef"
|
||||
class="tags-scroll"
|
||||
scroll-x="true"
|
||||
show-scrollbar="false"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<transition-group name="tag-list" tag="view" class="tags-row">
|
||||
<view
|
||||
v-for="t in tabs"
|
||||
@@ -18,40 +34,87 @@
|
||||
</transition-group>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右键菜单 (带动画) -->
|
||||
<transition name="menu-fade">
|
||||
<!-- 右滑动按钮 -->
|
||||
<view
|
||||
class="scroll-btn scroll-btn-right"
|
||||
:class="{ disabled: !canScrollRight }"
|
||||
@click="scrollToRight"
|
||||
>
|
||||
<text class="scroll-btn-icon">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 蓝色方格功能按钮 (扩大悬停区域) -->
|
||||
<view
|
||||
class="function-btn-wrapper"
|
||||
@mouseenter="handleFunctionMenuOver"
|
||||
@mouseleave="handleFunctionMenuLeave"
|
||||
>
|
||||
<view
|
||||
v-if="menuVisible"
|
||||
class="context-menu"
|
||||
:style="{ top: menuY + 'px', left: menuX + 'px' }"
|
||||
@click.stop=""
|
||||
class="function-btn"
|
||||
@click="toggleFunctionMenu"
|
||||
>
|
||||
<view class="menu-item" @click="handleAction('refresh')">
|
||||
<text class="menu-icon">↻</text>
|
||||
<text class="menu-text">刷新</text>
|
||||
<view class="function-btn-icon">
|
||||
<text class="function-btn-text">⋯</text>
|
||||
</view>
|
||||
</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
|
||||
v-if="functionMenuVisible"
|
||||
class="function-menu"
|
||||
@click.stop=""
|
||||
@mouseenter="handleMenuOver"
|
||||
@mouseleave="handleMenuLeave"
|
||||
>
|
||||
<view
|
||||
v-for="menuItem in functionMenuItems"
|
||||
:key="menuItem.command"
|
||||
class="menu-item"
|
||||
:class="{ disabled: menuItem.disabled }"
|
||||
@click="handleTagAction(menuItem.command, activeTabId)"
|
||||
>
|
||||
<text class="menu-icon">{{ menuItem.icon }}</text>
|
||||
<text class="menu-text">{{ menuItem.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</transition>
|
||||
|
||||
<!-- 右键菜单 (移除 transition,直接使用 v-if) -->
|
||||
<view
|
||||
v-if="contextMenuVisible"
|
||||
class="context-menu"
|
||||
:style="{ top: menuY + 'px', left: menuX + 'px' }"
|
||||
@click.stop=""
|
||||
>
|
||||
<view
|
||||
v-for="menuItem in contextMenuItems"
|
||||
:key="menuItem.command"
|
||||
class="menu-item"
|
||||
:class="{ disabled: menuItem.disabled }"
|
||||
@click="handleTagAction(menuItem.command, selectedTab)"
|
||||
>
|
||||
<text class="menu-icon">{{ menuItem.icon }}</text>
|
||||
<text class="menu-text">{{ menuItem.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 临时调试面板 -->
|
||||
<!-- <view style="position: fixed; top: 100px; right: 20px; background: red; color: white; padding: 10px; z-index: 99999; font-size: 12px; max-width: 200px;">
|
||||
<text>调试信息:</text><br/>
|
||||
<text>functionMenuVisible: {{ functionMenuVisible }}</text><br/>
|
||||
<text>菜单项数量: {{ functionMenuItems.length }}</text><br/>
|
||||
<text>activeTabId: {{ activeTabId }}</text><br/>
|
||||
<text>tabs数量: {{ tabs.length }}</text><br/>
|
||||
<text>定时器ID: {{ hideFunctionMenuTimer }}</text><br/>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import type { TabItem } from '../store/adminNavStore.uts'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
tabs: TabItem[]
|
||||
activeTabId: string
|
||||
}>()
|
||||
@@ -64,127 +127,638 @@ const emit = defineEmits<{
|
||||
(e:'refresh'): void
|
||||
}>()
|
||||
|
||||
// ============================================
|
||||
// 菜单项配置接口
|
||||
// ============================================
|
||||
interface TagActionMenu {
|
||||
command: string
|
||||
label: string
|
||||
icon: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 滚动相关状态
|
||||
// ============================================
|
||||
const scrollViewRef = ref<any>(null)
|
||||
const scrollLeft = ref(0)
|
||||
const scrollWidth = ref(0)
|
||||
const clientWidth = ref(0)
|
||||
const canScrollLeft = ref(false)
|
||||
const canScrollRight = ref(true) // 初始设为true,假设有可滚动内容
|
||||
|
||||
// ============================================
|
||||
// 菜单相关状态
|
||||
// ============================================
|
||||
// 右键菜单状态
|
||||
const menuVisible = ref(false)
|
||||
const contextMenuVisible = ref(false)
|
||||
const menuX = ref(0)
|
||||
const menuY = ref(0)
|
||||
const selectedTab = ref<TabItem | null>(null)
|
||||
|
||||
// 功能按钮菜单状态
|
||||
const functionMenuVisible = ref(false)
|
||||
let hideFunctionMenuTimer: number | null = null
|
||||
|
||||
// ============================================
|
||||
// 统一菜单配置 (复用逻辑核心)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 获取标签操作菜单项
|
||||
* @param targetTab 目标标签,null 表示基于当前激活标签操作
|
||||
*/
|
||||
function getTagActionMenus(targetTab: TabItem | null): TagActionMenu[] {
|
||||
const isFixedTab = targetTab?.isAffix || false
|
||||
const hasMultipleTabs = props.tabs.length > 1
|
||||
const hasNonFixedTabs = props.tabs.some(t => !t.isAffix)
|
||||
|
||||
return [
|
||||
{
|
||||
command: 'refresh',
|
||||
label: '刷新',
|
||||
icon: '↻',
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
command: 'close',
|
||||
label: '关闭',
|
||||
icon: '×',
|
||||
disabled: isFixedTab || !targetTab // 固定标签或没有目标标签时禁用
|
||||
},
|
||||
{
|
||||
command: 'close-other',
|
||||
label: '关闭其他',
|
||||
icon: '↸',
|
||||
disabled: !hasMultipleTabs || props.tabs.length <= 1
|
||||
},
|
||||
{
|
||||
command: 'close-all',
|
||||
label: '全部关闭',
|
||||
icon: '⊘',
|
||||
disabled: !hasNonFixedTabs // 没有非固定标签时禁用
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一标签操作命令处理 (复用逻辑核心)
|
||||
* @param command 操作命令
|
||||
* @param targetTab 目标标签
|
||||
*/
|
||||
function handleTagAction(command: string, targetTab?: TabItem | null): void {
|
||||
if (command === 'refresh') {
|
||||
emit('refresh')
|
||||
} else if (command === 'close' && targetTab && !targetTab.isAffix) {
|
||||
emit('tab-close', targetTab.id)
|
||||
} else if (command === 'close-other') {
|
||||
const keepTabId = targetTab?.id || props.activeTabId
|
||||
emit('close-other', keepTabId)
|
||||
} else if (command === 'close-all') {
|
||||
emit('close-all')
|
||||
}
|
||||
|
||||
// 关闭所有菜单
|
||||
closeAllMenus()
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 计算属性 - 菜单项 (基于复用逻辑)
|
||||
// ============================================
|
||||
|
||||
// 右键菜单项
|
||||
const contextMenuItems = computed<TagActionMenu[]>(() => {
|
||||
return getTagActionMenus(selectedTab.value)
|
||||
})
|
||||
|
||||
// 功能按钮菜单项 (基于当前激活标签)
|
||||
const functionMenuItems = computed<TagActionMenu[]>(() => {
|
||||
const activeTab = getCurrentActiveTab()
|
||||
return getTagActionMenus(activeTab)
|
||||
})
|
||||
|
||||
// ============================================
|
||||
// 工具方法
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 获取当前激活的标签对象
|
||||
*/
|
||||
function getCurrentActiveTab(): TabItem | null {
|
||||
return props.tabs.find(t => t.id === props.activeTabId) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有菜单
|
||||
*/
|
||||
function closeAllMenus(): void {
|
||||
contextMenuVisible.value = false
|
||||
functionMenuVisible.value = false
|
||||
|
||||
// 清理功能菜单定时器
|
||||
if (hideFunctionMenuTimer !== null) {
|
||||
clearTimeout(hideFunctionMenuTimer as number)
|
||||
hideFunctionMenuTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 标签点击处理
|
||||
// ============================================
|
||||
|
||||
function onTabClick(tab: TabItem) {
|
||||
closeMenu()
|
||||
closeAllMenus()
|
||||
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
|
||||
if (menuX.value + 120 > window.innerWidth) {
|
||||
menuX.value = window.innerWidth - 130
|
||||
}
|
||||
if (menuY.value + 140 > window.innerHeight) {
|
||||
menuY.value = window.innerHeight - 150
|
||||
}
|
||||
|
||||
menuVisible.value = true
|
||||
// 关闭功能菜单,显示右键菜单
|
||||
functionMenuVisible.value = false
|
||||
contextMenuVisible.value = true
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menuVisible.value = false
|
||||
}
|
||||
// ============================================
|
||||
// 功能按钮菜单处理 (仿照 AdminHeader 用户菜单实现)
|
||||
// ============================================
|
||||
|
||||
function handleAction(type: string) {
|
||||
if (!selectedTab.value) return
|
||||
function handleFunctionMenuOver(e: any): void {
|
||||
// #ifdef H5
|
||||
console.log('=== 功能菜单悬停触发 (H5) ===')
|
||||
console.log('functionMenuVisible before:', functionMenuVisible.value)
|
||||
|
||||
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')
|
||||
if (hideFunctionMenuTimer !== null) {
|
||||
clearTimeout(hideFunctionMenuTimer as number)
|
||||
hideFunctionMenuTimer = null
|
||||
console.log('清理定时器')
|
||||
}
|
||||
|
||||
closeMenu()
|
||||
contextMenuVisible.value = false // 关闭右键菜单
|
||||
functionMenuVisible.value = true
|
||||
console.log('functionMenuVisible after:', functionMenuVisible.value)
|
||||
// #endif
|
||||
}
|
||||
|
||||
function handleFunctionMenuLeave(e: any): void {
|
||||
// #ifdef H5
|
||||
console.log('=== 功能菜单离开触发 (H5) ===')
|
||||
|
||||
if (hideFunctionMenuTimer !== null) {
|
||||
clearTimeout(hideFunctionMenuTimer as number)
|
||||
}
|
||||
hideFunctionMenuTimer = setTimeout(() => {
|
||||
functionMenuVisible.value = false
|
||||
console.log('定时器触发,隐藏菜单')
|
||||
hideFunctionMenuTimer = null
|
||||
}, 150) as number // 改回150ms,与AdminHeader一致
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 鼠标进入菜单区域 - 保持显示
|
||||
function handleMenuOver(e: any): void {
|
||||
// #ifdef H5
|
||||
console.log('=== 鼠标进入菜单区域 ===')
|
||||
if (hideFunctionMenuTimer !== null) {
|
||||
clearTimeout(hideFunctionMenuTimer as number)
|
||||
hideFunctionMenuTimer = null
|
||||
console.log('进入菜单,取消隐藏定时器')
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 鼠标离开菜单区域 - 开始隐藏倒计时
|
||||
function handleMenuLeave(e: any): void {
|
||||
// #ifdef H5
|
||||
console.log('=== 鼠标离开菜单区域 ===')
|
||||
hideFunctionMenuTimer = setTimeout(() => {
|
||||
functionMenuVisible.value = false
|
||||
console.log('离开菜单,定时器触发隐藏')
|
||||
hideFunctionMenuTimer = null
|
||||
}, 150) as number // 与AdminHeader保持一致
|
||||
// #endif
|
||||
}
|
||||
|
||||
function toggleFunctionMenu(e?: any): void {
|
||||
if (e && typeof e.stopPropagation === 'function') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
contextMenuVisible.value = false // 关闭右键菜单
|
||||
functionMenuVisible.value = !functionMenuVisible.value
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 滚动控制 (修复 uni-app x 兼容性)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 滚动事件处理
|
||||
*/
|
||||
function onScroll(e: any): void {
|
||||
if (e && e.detail) {
|
||||
scrollLeft.value = e.detail.scrollLeft || 0
|
||||
scrollWidth.value = e.detail.scrollWidth || 0
|
||||
clientWidth.value = e.detail.scrollWidth ? (e.detail.scrollWidth - e.detail.scrollLeft) : 0
|
||||
}
|
||||
checkScrollBounds()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查滚动边界状态 (简化逻辑)
|
||||
*/
|
||||
function checkScrollBounds(): void {
|
||||
// 基础边界检测
|
||||
canScrollLeft.value = scrollLeft.value > 5 // 允许5px误差
|
||||
|
||||
// 如果有多个标签,根据标签数量和滚动位置判断
|
||||
if (props.tabs.length > 3) {
|
||||
// 标签数量超过3个时,通常会有滚动需要
|
||||
const estimatedWidth = props.tabs.length * 120
|
||||
const estimatedContainerWidth = 600
|
||||
canScrollRight.value = scrollLeft.value < (estimatedWidth - estimatedContainerWidth - 20)
|
||||
} else {
|
||||
// 标签较少时,可能不需要滚动
|
||||
canScrollRight.value = scrollWidth.value > 0 && scrollLeft.value < (scrollWidth.value - clientWidth.value - 10)
|
||||
}
|
||||
|
||||
// 调试日志 (可根据需要开启/关闭)
|
||||
// console.log('ScrollBounds:', {
|
||||
// canScrollLeft: canScrollLeft.value,
|
||||
// canScrollRight: canScrollRight.value,
|
||||
// scrollLeft: scrollLeft.value,
|
||||
// scrollWidth: scrollWidth.value,
|
||||
// clientWidth: clientWidth.value,
|
||||
// tabsCount: props.tabs.length
|
||||
// })
|
||||
}
|
||||
|
||||
/**
|
||||
* 向左滚动 (修复 API 调用)
|
||||
*/
|
||||
function scrollToLeft(): void {
|
||||
if (!canScrollLeft.value || !scrollViewRef.value) return
|
||||
|
||||
const stepWidth = 200 // 滚动步长
|
||||
const targetScrollLeft = Math.max(0, scrollLeft.value - stepWidth)
|
||||
|
||||
// 使用 uni-app x 兼容的滚动方式
|
||||
if (scrollViewRef.value.scrollTo) {
|
||||
scrollViewRef.value.scrollTo({
|
||||
left: targetScrollLeft,
|
||||
animated: true
|
||||
})
|
||||
} else if (scrollViewRef.value.scrollLeft !== undefined) {
|
||||
scrollViewRef.value.scrollLeft = targetScrollLeft
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向右滚动 (修复 API 调用)
|
||||
*/
|
||||
function scrollToRight(): void {
|
||||
if (!canScrollRight.value || !scrollViewRef.value) return
|
||||
|
||||
const stepWidth = 200 // 滚动步长
|
||||
const targetScrollLeft = scrollLeft.value + stepWidth
|
||||
|
||||
// 使用 uni-app x 兼容的滚动方式
|
||||
if (scrollViewRef.value.scrollTo) {
|
||||
scrollViewRef.value.scrollTo({
|
||||
left: targetScrollLeft,
|
||||
animated: true
|
||||
})
|
||||
} else if (scrollViewRef.value.scrollLeft !== undefined) {
|
||||
scrollViewRef.value.scrollLeft = targetScrollLeft
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到激活标签 (修复实现)
|
||||
*/
|
||||
function scrollToActiveTab(): void {
|
||||
nextTick(() => {
|
||||
if (!scrollViewRef.value || props.tabs.length === 0) return
|
||||
|
||||
const activeIndex = props.tabs.findIndex(t => t.id === props.activeTabId)
|
||||
if (activeIndex === -1) return
|
||||
|
||||
// 简化实现:滚动到大概位置
|
||||
const avgTabWidth = 120 // 估算每个标签宽度
|
||||
const targetOffset = activeIndex * avgTabWidth
|
||||
|
||||
// 简化的滚动控制
|
||||
if (scrollViewRef.value.scrollTo) {
|
||||
scrollViewRef.value.scrollTo({
|
||||
left: Math.max(0, targetOffset - 100),
|
||||
animated: true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新滚动容器尺寸信息 (简化实现)
|
||||
*/
|
||||
function updateScrollInfo(): void {
|
||||
nextTick(() => {
|
||||
// 强制触发边界检测,使按钮可用
|
||||
setTimeout(() => {
|
||||
// 基于标签数量的简单估算
|
||||
const estimatedTagsWidth = props.tabs.length * 120
|
||||
const estimatedContainerWidth = 600 // 估算容器宽度
|
||||
|
||||
scrollWidth.value = estimatedTagsWidth
|
||||
clientWidth.value = estimatedContainerWidth
|
||||
|
||||
checkScrollBounds()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 监听器
|
||||
// ============================================
|
||||
|
||||
// 监听激活标签变化,自动滚动到可视区域
|
||||
watch(() => props.activeTabId, () => {
|
||||
scrollToActiveTab()
|
||||
})
|
||||
|
||||
// 监听标签数量变化,更新滚动信息
|
||||
watch(() => props.tabs.length, () => {
|
||||
updateScrollInfo()
|
||||
})
|
||||
|
||||
// ============================================
|
||||
// 生命周期
|
||||
// ============================================
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', closeMenu)
|
||||
// 监听全局点击关闭菜单
|
||||
window.addEventListener('click', closeAllMenus)
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.removeEventListener('resize', updateScrollInfo) // 防重复
|
||||
window.addEventListener('resize', updateScrollInfo)
|
||||
|
||||
// 初始化滚动信息 - 延迟确保DOM渲染完成
|
||||
setTimeout(() => {
|
||||
updateScrollInfo()
|
||||
scrollToActiveTab()
|
||||
}, 300)
|
||||
|
||||
// 强制激活滚动按钮(针对多标签情况)
|
||||
if (props.tabs.length > 3) {
|
||||
canScrollRight.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('click', closeMenu)
|
||||
window.removeEventListener('click', closeAllMenus)
|
||||
window.removeEventListener('resize', updateScrollInfo)
|
||||
|
||||
// 清理功能菜单定时器
|
||||
if (hideFunctionMenuTimer !== null) {
|
||||
clearTimeout(hideFunctionMenuTimer as number)
|
||||
hideFunctionMenuTimer = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tags{
|
||||
/* ============================================
|
||||
顶部标签栏整体布局 (修正 overflow 和 z-index)
|
||||
============================================ */
|
||||
.tags {
|
||||
height: 44px;
|
||||
background:#fff;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: visible; /* 确保下拉菜单不被裁切 */
|
||||
z-index: 1000; /* 提升容器层级 */
|
||||
}
|
||||
.tags-scroll{ width: 100%; height: 44px; }
|
||||
.tags-row{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
|
||||
/* ============================================
|
||||
滚动控制按钮
|
||||
============================================ */
|
||||
.scroll-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.scroll-btn:hover:not(.disabled) {
|
||||
background: #1677ff;
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
.scroll-btn:hover:not(.disabled) .scroll-btn-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.scroll-btn.disabled {
|
||||
background: #f5f7fa;
|
||||
border-color: #e4e7ed;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.scroll-btn-left {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.scroll-btn-right {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.scroll-btn-icon {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
标签滚动容器
|
||||
============================================ */
|
||||
.tags-scroll {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tags-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
height: 44px;
|
||||
min-width: 100%;
|
||||
}
|
||||
.tag{
|
||||
|
||||
/* ============================================
|
||||
标签样式 (保持原有设计)
|
||||
============================================ */
|
||||
.tag {
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background:#fff;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items:center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.tag.active{
|
||||
border-color:#1677ff;
|
||||
background:#eaf2ff;
|
||||
|
||||
.tag.active {
|
||||
border-color: #1677ff;
|
||||
background: #eaf2ff;
|
||||
}
|
||||
.tag-text{ font-size:12px; color:#374151; }
|
||||
.tag-close{
|
||||
|
||||
.tag-text {
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.tag-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.tag-close:hover {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
.tag-close-text{ font-size:14px; color:#6b7280; line-height:14px; }
|
||||
|
||||
/* 右键菜单样式 */
|
||||
.tag-close:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tag-close-text {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
蓝色方格功能按钮 (CRMEB 风格,修正层级)
|
||||
============================================ */
|
||||
|
||||
/* 功能按钮包装器 - 扩大悬停检测区域 */
|
||||
.function-btn-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 6px; /* 扩大 6px 悬停区域 */
|
||||
margin-right: 6px; /* 调整外边距以保持视觉间距 */
|
||||
height: 100%; /* 确保悬停不中断,仿照AdminHeader */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.function-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #1677ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
overflow: visible; /* 确保下拉菜单可见 */
|
||||
z-index: 1001; /* 提升按钮层级 */
|
||||
}
|
||||
|
||||
.function-btn:hover {
|
||||
background: #4096ff;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.3);
|
||||
}
|
||||
|
||||
.function-btn-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.function-btn-text {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 功能按钮菜单 - 使用固定定位像素值 */
|
||||
.function-menu {
|
||||
position: absolute;
|
||||
top: 44px; /* 使用固定像素值,44px是tags容器高度 */
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
|
||||
min-width: 160px;
|
||||
z-index: 99999;
|
||||
margin-top: 4px;
|
||||
overflow: visible; /* 改为visible,仿照AdminHeader */
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
右键菜单样式 (保持原有设计)
|
||||
============================================ */
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
min-width: 100px;
|
||||
min-width: 120px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
通用菜单项样式 (右键菜单和功能菜单共用)
|
||||
============================================ */
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -192,26 +766,43 @@ onUnmounted(() => {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
/* #ifdef H5 */
|
||||
.menu-item:hover:not(.disabled) {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.menu-item:hover:not(.disabled) .menu-text {
|
||||
color: #1890ff;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.menu-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
color: #606266;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
/* 标签列表动画 - 平滑平移和缩放 */
|
||||
/* ============================================
|
||||
标签列表动画 (保持原有设计)
|
||||
============================================ */
|
||||
.tag-list-enter-active,
|
||||
.tag-list-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -232,7 +823,9 @@ onUnmounted(() => {
|
||||
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);
|
||||
@@ -244,4 +837,50 @@ onUnmounted(() => {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-10px);
|
||||
}
|
||||
|
||||
/* 功能菜单动画 (从右上角展开) */
|
||||
.function-menu .menu-fade-enter-active,
|
||||
.function-menu .menu-fade-leave-active {
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.function-menu.menu-fade-enter-from,
|
||||
.function-menu.menu-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-10px);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
响应式适配
|
||||
============================================ */
|
||||
@media (max-width: 768px) {
|
||||
.scroll-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.scroll-btn-left {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.function-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.tags-row {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user