采用嵌套呈现方式处理

This commit is contained in:
zqm
2025-11-19 13:57:51 +08:00
parent 7acf65356f
commit 0e7207adce
5 changed files with 398 additions and 186 deletions

View File

@@ -135,6 +135,7 @@
import { defineProps, computed, defineEmits, ref, onMounted, watch, defineExpose } from 'vue'
import TabPage from './TabPage.vue'
import Panel from './Panel.vue'
import { zIndexManager, Z_INDEX_LAYERS } from './dockLayers.js'
const props = defineProps({
id: { type: String, required: true },
@@ -222,14 +223,14 @@ watch(() => props.WindowState, (newState) => {
// 根据状态计算尺寸和位置样式
const areaStyle = computed(() => {
if (isMaximized.value) {
// 最大化时填充满父容器
// 最大化时填充满父容器使用更高的z-index确保在最顶层
return {
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: 0,
zIndex: 100,
zIndex: Z_INDEX_LAYERS.CONTENT_ACTIVE, // 使用统一的z-index层级
margin: 0,
padding: 0
}
@@ -238,7 +239,8 @@ const areaStyle = computed(() => {
// 非最大化状态:使用原始位置或默认居中
const style = {
width: `${originalPosition.value.width}px`,
height: `${originalPosition.value.height}px`
height: `${originalPosition.value.height}px`,
zIndex: Z_INDEX_LAYERS.CONTENT // 使用统一的z-index层级
}
// 如果有明确的位置,则使用指定位置

View File

@@ -43,84 +43,35 @@
/>
</div>
</Area>
<!-- 浮动区域直接渲染不使用额外的div包装 -->
<Area
<!-- 浮动区域使用Render组件统一渲染 -->
<Render
v-for="area in floatingAreas"
:key="area.id"
:id="area.id"
:title="area.title"
v-model:WindowState="area.WindowState"
:showTitleBar="true"
:width="area.width"
:height="area.height"
:left="area.WindowState !== '最大化' ? area.x : undefined"
:top="area.WindowState !== '最大化' ? area.y : undefined"
:style="area.WindowState !== '最大化' ? {
position: 'absolute',
zIndex: 1000,
background: 'rgba(255, 255, 255, 1)',
border: '1px solid #4f72b3'
} : {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1000
}"
@close="onCloseFloatingArea(area.id)"
@update:position="onUpdatePosition(area.id, $event)"
:type="'area'"
:config="area"
:style="{ zIndex: area.zIndex || zIndexManager.getFloatingAreaZIndex(area.id) }"
@close="() => onCloseFloatingArea(area.id)"
@update:position="(position) => onUpdatePosition(area.id, position)"
@panelMaximizeSync="onPanelMaximizeSync"
@areaDragStart="onAreaDragStart(area.id, $event)"
@areaDragMove="onAreaDragMove(area.id, $event)"
@areaDragEnd="onAreaDragEnd(area.id, $event)"
>
<!-- 每个Area内渲染其包含的TabPages -->
<TabPage
v-for="tabPage in area.tabPages"
:key="tabPage.id"
:id="tabPage.id"
:title="tabPage.title"
:panels="tabPage.panels"
:tabPosition="'bottom'"
@areaDragStart="(event) => onAreaDragStart(area.id, event)"
@areaDragMove="(event) => onAreaDragMove(area.id, event)"
@areaDragEnd="(event) => onAreaDragEnd(area.id, event)"
@tab-change="onTabChange"
@tab-close="onTabClose"
@tab-add="onTabAdd"
@tabDragStart="onTabDragStart(area.id, $event)"
@tabDragMove="onTabDragMove(area.id, $event)"
@tabDragStart="(event) => onTabDragStart(area.id, event)"
@tabDragMove="(event) => onTabDragMove(area.id, event)"
@tabDragEnd="onTabDragEnd"
@toggle-collapse="(panelId) => $emit('toggleCollapse', panelId)"
@toggleCollapse="(panelId) => $emit('toggleCollapse', panelId)"
@maximize="(panelId) => onMaximize(panelId)"
@close="(panelId) => onClosePanel(area.id, panelId)"
@toggle-toolbar="(panelId) => $emit('toggleToolbar', panelId)"
@dragStart="onPanelDragStartFromTabPage(area.id, $event)"
@dragMove="onPanelDragMoveFromTabPage(area.id, $event)"
@closePanel="(panelId) => onClosePanel(area.id, panelId)"
@toggleToolbar="(panelId) => $emit('toggleToolbar', panelId)"
@dragStart="(event) => onPanelDragStartFromTabPage(area.id, event)"
@dragMove="(event) => onPanelDragMoveFromTabPage(area.id, event)"
@dragEnd="onPanelDragEndFromTabPage"
@dragover="handleAreaDragOver"
@dragleave="handleAreaDragLeave"
>
<!-- 在TabPage内渲染其包含的Panels -->
<Panel
v-for="panel in tabPage.panels"
:key="panel.id"
:id="panel.id"
:title="panel.title"
:collapsed="panel.collapsed"
:toolbarExpanded="panel.toolbarExpanded"
:maximized="panel.maximized"
:content="panel.content"
@toggleCollapse="onToggleCollapse"
@maximize="(panelId) => $emit('maximize', panelId)"
@close="onClosePanel(area.id, panel.id)"
@toggleToolbar="onToggleToolbar"
@dragStart="onPanelDragStartFromTabPage(area.id, $event)"
@dragMove="onPanelDragMove(area.id, $event)"
@dragEnd="onPanelDragEnd"
@dragover="handleAreaDragOver"
@dragleave="handleAreaDragLeave"
/>
</TabPage>
</Area>
</div>
</template>
@@ -131,6 +82,8 @@ import Panel from './Panel.vue';
import TabPage from './TabPage.vue';
import DockIndicator from './DockIndicator.vue';
import ResizeBar from './ResizeBar.vue';
import Render from './Render.vue';
import { Z_INDEX_LAYERS, zIndexManager } from './dockLayers.js';
// 定义组件可以发出的事件
const emit = defineEmits([
@@ -295,51 +248,69 @@ const addFloatingPanel = () => {
// 如果容器已渲染,计算居中位置
if (dockLayoutRef.value) {
const containerRect = dockLayoutRef.value.getBoundingClientRect()
const width = 300
const height = 250
const width = 280
const height = 200
x = Math.floor((containerRect.width - width) / 2)
y = Math.floor((containerRect.height - height) / 2)
}
// 1. 添加一个Area。使用Area的初始设置
// 获取当前ID并递增
const currentId = areaIdCounter++
const areaId = `floating-area-${currentId}`
// 使用z-index管理器为新浮动区域分配z-index
const newZIndex = zIndexManager.getFloatingAreaZIndex(areaId)
// 直接创建符合Render config的数据结构确保响应式同步
const newArea = {
id: `floating-area-${areaIdCounter++}`,
title: `浮动区域 ${areaIdCounter - 1}`,
id: areaId,
title: `浮动区域 ${currentId}`,
x: x,
y: y,
width: 300,
height: 250,
WindowState: '正常',
width: 280,
height: 200,
windowState: '正常',
showTitleBar: true,
// 2. 向Area添加一个TabPage。TabPage的初始设置为填充满父容器
tabPages: [
]
}
const newTabPage = {
id: `tabpage-${areaIdCounter - 1}-1`,
resizable: true,
draggable: true,
zIndex: newZIndex, // 使用z-index管理器分配的层级
// 使用Render期望的children结构
children: [
{
type: 'tabpage',
id: `tabpage-${currentId}-1`,
title: `标签页 1`,
// TabPage填充满父容器
width: 100,
height: 100,
// 3. 向TabPage添加一个Panel。Panel的初始设置为填充满父容器
panels: [
]
}
const newPanel = {
id: `panel-${areaIdCounter - 1}-1-1`,
title: `面板 ${areaIdCounter - 1}`,
tabPosition: 'bottom',
children: {
type: 'panel',
items: [
{
type: 'panel',
id: `panel-${currentId}-1-1`,
title: `面板 ${currentId}`,
x: 0,
y: 0,
width: 100,
height: 100,
width: 280,
height: 200,
collapsed: false,
toolbarExpanded: false,
// 添加随机测试内容以便合并测试
content: generateRandomContent(areaIdCounter - 1)
maximized: false,
content: generateRandomContent(currentId)
}
newTabPage.panels.push(newPanel)
newArea.tabPages.push(newTabPage)
]
}
}
]
}
floatingAreas.value.push(newArea)
console.log('✅ 创建浮动面板成功:', newArea.id, 'z-index:', newZIndex)
console.log('🔍 浮动面板数据结构:', JSON.stringify(newArea, null, 2))
// 使用nextTick确保DOM更新后再进行后续操作
nextTick(() => {
console.log('🔍 浮动面板已添加到DOMfloatingAreas长度:', floatingAreas.value.length)
})
}
// 更新区域位置
const onUpdatePosition = (id, position) => {
@@ -362,33 +333,36 @@ const onToggleCollapse = (id) => {
const onMaximize = (panelId) => {
// 查找包含该面板的区域
for (const area of floatingAreas.value) {
// 正确处理层级结构Area -> TabPage -> Panel
if (area.tabPages) {
for (const tabPage of area.tabPages) {
if (tabPage.panels && tabPage.panels.length === 1 && tabPage.panels[0].id === panelId) {
if (area.children) {
for (const child of area.children) {
if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
const panels = child.children.items || []
if (panels.length === 1 && panels[0].id === panelId) {
// 当区域只包含一个Panel时切换Area和Panel的最大化状态
const isCurrentlyMaximized = area.WindowState === '最大化' || area.WindowState === 'maximized'
const isCurrentlyMaximized = area.windowState === '最大化' || area.windowState === 'maximized'
// 使用Vue推荐的方式更新响应式数据
if (isCurrentlyMaximized) {
// 切换为正常状态
area.WindowState = '正常'
area.windowState = '正常'
// 确保Panel也恢复正常状态 - 使用展开运算符创建新对象确保响应式
tabPage.panels[0] = { ...tabPage.panels[0], maximized: false }
child.children.items[0] = { ...panels[0], maximized: false }
} else {
// 切换为最大化状态
area.WindowState = '最大化'
area.windowState = '最大化'
// 同时最大化Panel - 使用展开运算符创建新对象确保响应式
tabPage.panels[0] = { ...tabPage.panels[0], maximized: true }
child.children.items[0] = { ...panels[0], maximized: true }
// 激活浮动区域,将其置于最顶层
zIndexManager.activateFloatingArea(area.id)
area.zIndex = zIndexManager.getFloatingAreaZIndex(area.id, true)
}
break
}
}
}
}
}
}
// 关闭浮动区域 - 同时移除内容区的Panel
const onCloseFloatingArea = (id) => {
@@ -397,6 +371,9 @@ const onCloseFloatingArea = (id) => {
// 获取要移除的Area
const areaToRemove = floatingAreas.value[index]
// 从z-index管理器中移除该区域的层级管理
zIndexManager.removeFloatingArea(id)
// 清理Panel引用确保Panel被正确移除
if (areaToRemove.panels) {
// 这里可以添加任何需要的Panel清理逻辑
@@ -411,19 +388,19 @@ const onCloseFloatingArea = (id) => {
}
}
// 关闭面板
// 关闭面板 - 适配children数据结构
const onClosePanel = (areaId, panelId) => {
const area = floatingAreas.value.find(a => a.id === areaId)
// 正确处理层级结构Area -> TabPage -> Panel
if (area && area.tabPages) {
for (const tabPage of area.tabPages) {
if (tabPage.panels) {
const panelIndex = tabPage.panels.findIndex(p => p.id === panelId)
if (area && area.children) {
for (const child of area.children) {
if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
const panels = child.children.items || []
const panelIndex = panels.findIndex(p => p.id === panelId)
if (panelIndex !== -1) {
tabPage.panels.splice(panelIndex, 1)
panels.splice(panelIndex, 1)
// 如果区域内没有面板了,可以考虑关闭整个区域
if (tabPage.panels.length === 0) {
if (panels.length === 0) {
onCloseFloatingArea(areaId)
}
break
@@ -2333,8 +2310,12 @@ const adjustSourceAreaForLayout = (sourceArea, targetArea, dockZone) => {
}
}
// 暴露方法给父组件
// 暴露数据和方法给父组件
defineExpose({
// 数据属性
floatingAreas,
hiddenAreas,
// 原有方法
addFloatingPanel,

View File

@@ -5,22 +5,74 @@
v-bind="componentProps"
v-on="componentListeners"
>
<!-- Area组件的slot内容 -->
<template v-if="type === 'area'" #default>
<!-- 渲染Area的子组件 -->
<template v-for="child in normalizedChildren" :key="child.id">
<Renderer :type="child.type" :config="child" />
<!-- 对于有children配置的情况需要手动渲染子组件 -->
<template v-if="type === 'area' && config.children">
<!-- 如果children是数组 -->
<template v-if="Array.isArray(config.children)">
<div v-for="child in config.children" :key="child.id" style="width: 100%; height: 100%;">
<Render
v-if="child.type === 'tabpage'"
:type="'tabpage'"
:config="child"
:debug="debug"
@tab-change="$emit('tab-change', $event)"
@tab-close="$emit('tab-close', $event)"
@tab-add="$emit('tab-add', $event)"
@tabDragStart="$emit('tabDragStart', $event)"
@tabDragMove="$emit('tabDragMove', $event)"
@tabDragEnd="$emit('tabDragEnd', $event)"
@toggleCollapse="$emit('toggleCollapse', $event)"
@maximize="$emit('maximize', $event)"
@close="$emit('close', $event)"
@toggleToolbar="$emit('toggleToolbar', $event)"
@dragStart="$emit('dragStart', $event)"
@dragMove="$emit('dragMove', $event)"
@dragEnd="$emit('dragEnd', $event)"
/>
</div>
</template>
<!-- 如果children是对象 -->
<template v-else-if="config.children.type === 'tabpage'">
<div v-for="tabPage in (Array.isArray(config.children.items) ? config.children.items : [config.children])" :key="tabPage.id" style="width: 100%; height: 100%;">
<Render
:type="'tabpage'"
:config="tabPage"
:debug="debug"
@tab-change="$emit('tab-change', $event)"
@tab-close="$emit('tab-close', $event)"
@tab-add="$emit('tab-add', $event)"
@tabDragStart="$emit('tabDragStart', $event)"
@tabDragMove="$emit('tabDragMove', $event)"
@tabDragEnd="$emit('tabDragEnd', $event)"
@toggleCollapse="$emit('toggleCollapse', $event)"
@maximize="$emit('maximize', $event)"
@close="$emit('close', $event)"
@toggleToolbar="$emit('toggleToolbar', $event)"
@dragStart="$emit('dragStart', $event)"
@dragMove="$emit('dragMove', $event)"
@dragEnd="$emit('dragEnd', $event)"
/>
</div>
</template>
</template>
<!-- TabPage组件的panels prop -->
<template v-else-if="type === 'tabpage'" #panels>
<!-- TabPage的panels由config提供不需要额外的slot -->
</template>
<!-- Panel组件的默认内容 -->
<template v-else-if="type === 'panel'" #default>
<!-- Panel的内容由config.content提供 -->
<template v-else-if="type === 'tabpage' && config.children && config.children.type === 'panel'">
<!-- 手动渲染Panel组件 -->
<div v-for="panel in (Array.isArray(config.children.items) ? config.children.items : [config.children])" :key="panel.id" style="width: 100%; height: 100%;">
<Render
:type="'panel'"
:config="panel"
:debug="debug"
@toggleCollapse="$emit('toggleCollapse', $event)"
@maximize="$emit('maximize', $event)"
@close="$emit('close', $event)"
@toggleToolbar="$emit('toggleToolbar', $event)"
@dragStart="$emit('dragStart', $event)"
@dragMove="$emit('dragMove', $event)"
@dragEnd="$emit('dragEnd', $event)"
/>
</div>
</template>
</component>
</template>
@@ -91,14 +143,20 @@ const componentProps = computed(() => {
showTitleBar: config.showTitleBar !== false,
left: config.left,
top: config.top,
draggable: config.draggable !== false
draggable: config.draggable !== false,
// childrenTabPages
tabPages: config.children ? (
config.children.type === 'tabpage'
? (Array.isArray(config.children.items) ? config.children.items : [config.children])
: config.children // tabPages
) : config.tabPages || []
}
case 'tabpage':
return {
id: config.id,
title: config.title || '标签页',
panels: config.panels || [],
panels: config.panels || config.children?.items || [],
showTabs: config.showTabs !== false,
tabPosition: config.tabPosition || 'top'
}
@@ -130,43 +188,43 @@ const componentListeners = computed(() => {
if (props.type === 'area') {
// Area
allListeners['areaDragStart'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] areaDragStart:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] areaDragStart:`, event)
emit('areaDragStart', event)
}
allListeners['areaDragMove'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] areaDragMove:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] areaDragMove:`, event)
emit('areaDragMove', event)
}
allListeners['areaDragEnd'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] areaDragEnd:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] areaDragEnd:`, event)
emit('areaDragEnd', event)
}
allListeners['area-merged'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] area-merged:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] area-merged:`, event)
emit('area-merged', event)
}
allListeners['toggleCollapse'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] toggleCollapse:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] toggleCollapse:`, event)
emit('toggleCollapse', event)
}
allListeners['maximize'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] maximize:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] maximize:`, event)
emit('maximize', event)
}
allListeners['close'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] close:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] close:`, event)
emit('close', event)
}
allListeners['toggleToolbar'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] toggleToolbar:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] toggleToolbar:`, event)
emit('toggleToolbar', event)
}
allListeners['update:windowState'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] update:windowState:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] update:windowState:`, event)
emit('update:windowState', event)
}
allListeners['update:position'] = (event) => {
if (props.debug) console.log(`[Renderer-Area ${props.config.id}] update:position:`, event)
if (props.debug) console.log(`[Render-Area ${props.config.id}] update:position:`, event)
emit('update:position', event)
}
}
@@ -174,23 +232,23 @@ const componentListeners = computed(() => {
if (props.type === 'tabpage') {
// TabPage
allListeners['tabChange'] = (event) => {
if (props.debug) console.log(`[Renderer-TabPage ${props.config.id}] tabChange:`, event)
if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabChange:`, event)
emit('tabChange', event)
}
allListeners['tabClose'] = (event) => {
if (props.debug) console.log(`[Renderer-TabPage ${props.config.id}] tabClose:`, event)
if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabClose:`, event)
emit('tabClose', event)
}
allListeners['tabAdd'] = (event) => {
if (props.debug) console.log(`[Renderer-TabPage ${props.config.id}] tabAdd:`, event)
if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabAdd:`, event)
emit('tabAdd', event)
}
allListeners['maximize'] = (event) => {
if (props.debug) console.log(`[Renderer-TabPage ${props.config.id}] maximize:`, event)
if (props.debug) console.log(`[Render-TabPage ${props.config.id}] maximize:`, event)
emit('maximize', event)
}
allListeners['close'] = (event) => {
if (props.debug) console.log(`[Renderer-TabPage ${props.config.id}] close:`, event)
if (props.debug) console.log(`[Render-TabPage ${props.config.id}] close:`, event)
emit('close', event)
}
}
@@ -198,31 +256,31 @@ const componentListeners = computed(() => {
if (props.type === 'panel') {
// Panel
allListeners['toggleCollapse'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] toggleCollapse:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] toggleCollapse:`, event)
emit('panelToggleCollapse', event)
}
allListeners['maximize'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] maximize:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] maximize:`, event)
emit('panelMaximize', event)
}
allListeners['close'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] close:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] close:`, event)
emit('panelClose', event)
}
allListeners['toggleToolbar'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] toggleToolbar:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] toggleToolbar:`, event)
emit('panelToggleToolbar', event)
}
allListeners['dragStart'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] dragStart:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] dragStart:`, event)
emit('dragStart', event)
}
allListeners['dragMove'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] dragMove:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] dragMove:`, event)
emit('dragMove', event)
}
allListeners['dragEnd'] = (event) => {
if (props.debug) console.log(`[Renderer-Panel ${props.config.id}] dragEnd:`, event)
if (props.debug) console.log(`[Render-Panel ${props.config.id}] dragEnd:`, event)
emit('dragEnd', event)
}
}
@@ -255,5 +313,5 @@ defineExpose({
</script>
<style scoped>
/* Renderer组件本身不添加额外样式,由子组件控制 */
/* Render组件本身不添加额外样式由子组件控制 */
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="tab-page" :class="[`tab-page-${tabPosition}`]">
<div class="tab-page" :class="[`tab-page-${tabPosition}`]" style="width: 100%; height: 100%;">
<!-- 顶部标签栏 -->
<div v-if="tabPosition === 'top' && shouldShowTabs && panels && panels.length > 0" class="tab-header tab-header-horizontal">
<div
@@ -84,6 +84,7 @@
<div class="tab-placeholder"></div>
</div>
<!-- Tab页内容区域 -->
<div class="tab-content">
<!-- 渲染当前激活的Panel内容 -->
@@ -293,18 +294,26 @@ const onTabDragEnd = () => {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* 顶部和底部位置:水平布局 */
.tab-page-top,
.tab-page-bottom {
/* 顶部位置:标签栏 -> 工具栏 -> 内容区 */
.tab-page-top {
flex-direction: column;
}
/* 左侧和右侧位置:水平布局但标签栏垂直 */
.tab-page-left,
.tab-page-right {
/* 底部位置:内容区 -> 工具栏 -> 标签栏 */
.tab-page-bottom {
flex-direction: column-reverse;
}
/* 左侧位置:标签栏 -> 工具栏 -> 内容区(垂直排列) */
.tab-page-left {
flex-direction: row;
}
/* 右侧位置:标签栏 -> 工具栏 -> 内容区(垂直排列) */
.tab-page-right {
flex-direction: row-reverse;
}
:root {
--vs-blue-top: #4f72b3;
--vs-blue-bottom: #3c5a99;

View File

@@ -0,0 +1,162 @@
/**
* DockLayout Z-Index 全局管理器
* 统一管理所有组件的层级,确保正确的显示顺序
*/
// Z-Index 分层策略
export const Z_INDEX_LAYERS = {
// 基础层级
BACKGROUND: 1, // 背景层
// 内容层级
CONTENT: 10, // 普通内容层
CONTENT_ACTIVE: 15, // 活跃内容层
// 交互组件
RESIZE_BAR: 100, // 调整手柄
// 标题栏和工具栏
TITLE_BAR: 200, // 标题栏
TOOLBAR: 300, // 工具栏
// 浮动区域
FLOATING_AREA: 1000, // 浮动区域
FLOATING_AREA_ACTIVE: 1100, // 活跃浮动区域
// 指示器和交互反馈
DOCK_INDICATOR: 5000, // 停靠指示器
DRAG_PREVIEW: 6000, // 拖拽预览
// 最高优先级
MODAL: 8000, // 模态框
TOOLTIP: 9000, // 工具提示
NOTIFICATION: 9500, // 通知
CRITICAL: 9999 // 关键提示
}
// 动态z-index管理
class ZIndexManager {
constructor() {
this.floatingAreas = new Map() // 浮动区域层级管理
this.dockIndicators = new Set() // 停靠指示器层级
this.modalCount = 0 // 模态框计数器
}
/**
* 获取浮动区域的z-index
* @param {string} areaId - 区域ID
* @param {boolean} isActive - 是否为活跃状态
* @returns {number} z-index值
*/
getFloatingAreaZIndex(areaId, isActive = false) {
if (isActive) {
return this.floatingAreas.get(areaId) || Z_INDEX_LAYERS.FLOATING_AREA_ACTIVE
}
// 为新区域分配z-index
if (!this.floatingAreas.has(areaId)) {
const maxZIndex = Math.max(...Array.from(this.floatingAreas.values()), Z_INDEX_LAYERS.FLOATING_AREA)
const newZIndex = maxZIndex + 1
this.floatingAreas.set(areaId, newZIndex)
return newZIndex
}
return this.floatingAreas.get(areaId)
}
/**
* 激活浮动区域(将其置于最顶层)
* @param {string} areaId - 区域ID
*/
activateFloatingArea(areaId) {
// 获取当前最大z-index
const maxZIndex = Math.max(...Array.from(this.floatingAreas.values()), Z_INDEX_LAYERS.FLOATING_AREA)
this.floatingAreas.set(areaId, maxZIndex + 1)
// 触发重排以确保层级生效
this.reorderFloatingAreas()
}
/**
* 重新排序浮动区域层级
*/
reorderFloatingAreas() {
const areas = Array.from(this.floatingAreas.entries())
areas.sort((a, b) => a[1] - b[1]) // 按z-index排序
areas.forEach(([areaId], index) => {
this.floatingAreas.set(areaId, Z_INDEX_LAYERS.FLOATING_AREA + index + 1)
})
}
/**
* 移除浮动区域
* @param {string} areaId - 区域ID
*/
removeFloatingArea(areaId) {
this.floatingAreas.delete(areaId)
this.reorderFloatingAreas()
}
/**
* 获取停靠指示器的z-index
* @param {string} indicatorType - 指示器类型
* @returns {number} z-index值
*/
getDockIndicatorZIndex(indicatorType = 'default') {
return Z_INDEX_LAYERS.DOCK_INDICATOR
}
/**
* 获取拖拽预览的z-index
* @returns {number} z-index值
*/
getDragPreviewZIndex() {
return Z_INDEX_LAYERS.DRAG_PREVIEW
}
/**
* 获取模态框的z-index
* @returns {number} z-index值
*/
getModalZIndex() {
this.modalCount++
return Z_INDEX_LAYERS.MODAL + this.modalCount
}
/**
* 释放模态框z-index
*/
releaseModalZIndex() {
if (this.modalCount > 0) {
this.modalCount--
}
}
/**
* 重置所有z-index用于调试或清理
*/
resetAll() {
this.floatingAreas.clear()
this.dockIndicators.clear()
this.modalCount = 0
}
/**
* 获取当前状态信息(用于调试)
*/
getStatus() {
return {
floatingAreas: Object.fromEntries(this.floatingAreas),
dockIndicators: Array.from(this.dockIndicators),
modalCount: this.modalCount
}
}
}
// 创建全局实例
const zIndexManager = new ZIndexManager()
// 导出管理器实例和相关常量
export { zIndexManager }
export default zIndexManager