采用嵌套呈现方式处理

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 { defineProps, computed, defineEmits, ref, onMounted, watch, defineExpose } from 'vue'
import TabPage from './TabPage.vue' import TabPage from './TabPage.vue'
import Panel from './Panel.vue' import Panel from './Panel.vue'
import { zIndexManager, Z_INDEX_LAYERS } from './dockLayers.js'
const props = defineProps({ const props = defineProps({
id: { type: String, required: true }, id: { type: String, required: true },
@@ -222,14 +223,14 @@ watch(() => props.WindowState, (newState) => {
// 根据状态计算尺寸和位置样式 // 根据状态计算尺寸和位置样式
const areaStyle = computed(() => { const areaStyle = computed(() => {
if (isMaximized.value) { if (isMaximized.value) {
// 最大化时填充满父容器 // 最大化时填充满父容器使用更高的z-index确保在最顶层
return { return {
width: '100%', width: '100%',
height: '100%', height: '100%',
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
zIndex: 100, zIndex: Z_INDEX_LAYERS.CONTENT_ACTIVE, // 使用统一的z-index层级
margin: 0, margin: 0,
padding: 0 padding: 0
} }
@@ -238,7 +239,8 @@ const areaStyle = computed(() => {
// 非最大化状态:使用原始位置或默认居中 // 非最大化状态:使用原始位置或默认居中
const style = { const style = {
width: `${originalPosition.value.width}px`, 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> </div>
</Area> </Area>
<!-- 浮动区域直接渲染不使用额外的div包装 --> <!-- 浮动区域使用Render组件统一渲染 -->
<Area <Render
v-for="area in floatingAreas" v-for="area in floatingAreas"
:key="area.id" :key="area.id"
:id="area.id" :type="'area'"
:title="area.title" :config="area"
v-model:WindowState="area.WindowState" :style="{ zIndex: area.zIndex || zIndexManager.getFloatingAreaZIndex(area.id) }"
:showTitleBar="true" @close="() => onCloseFloatingArea(area.id)"
:width="area.width" @update:position="(position) => onUpdatePosition(area.id, position)"
: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)"
@panelMaximizeSync="onPanelMaximizeSync" @panelMaximizeSync="onPanelMaximizeSync"
@areaDragStart="onAreaDragStart(area.id, $event)" @areaDragStart="(event) => onAreaDragStart(area.id, event)"
@areaDragMove="onAreaDragMove(area.id, $event)" @areaDragMove="(event) => onAreaDragMove(area.id, event)"
@areaDragEnd="onAreaDragEnd(area.id, $event)" @areaDragEnd="(event) => onAreaDragEnd(area.id, event)"
> @tab-change="onTabChange"
<!-- 每个Area内渲染其包含的TabPages --> @tab-close="onTabClose"
<TabPage @tab-add="onTabAdd"
v-for="tabPage in area.tabPages" @tabDragStart="(event) => onTabDragStart(area.id, event)"
:key="tabPage.id" @tabDragMove="(event) => onTabDragMove(area.id, event)"
:id="tabPage.id" @tabDragEnd="onTabDragEnd"
:title="tabPage.title" @toggleCollapse="(panelId) => $emit('toggleCollapse', panelId)"
:panels="tabPage.panels" @maximize="(panelId) => onMaximize(panelId)"
:tabPosition="'bottom'" @closePanel="(panelId) => onClosePanel(area.id, panelId)"
@tab-change="onTabChange" @toggleToolbar="(panelId) => $emit('toggleToolbar', panelId)"
@tab-close="onTabClose" @dragStart="(event) => onPanelDragStartFromTabPage(area.id, event)"
@tab-add="onTabAdd" @dragMove="(event) => onPanelDragMoveFromTabPage(area.id, event)"
@tabDragStart="onTabDragStart(area.id, $event)" @dragEnd="onPanelDragEndFromTabPage"
@tabDragMove="onTabDragMove(area.id, $event)" @dragover="handleAreaDragOver"
@tabDragEnd="onTabDragEnd" @dragleave="handleAreaDragLeave"
@toggle-collapse="(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)"
@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> </div>
</template> </template>
@@ -131,6 +82,8 @@ import Panel from './Panel.vue';
import TabPage from './TabPage.vue'; import TabPage from './TabPage.vue';
import DockIndicator from './DockIndicator.vue'; import DockIndicator from './DockIndicator.vue';
import ResizeBar from './ResizeBar.vue'; import ResizeBar from './ResizeBar.vue';
import Render from './Render.vue';
import { Z_INDEX_LAYERS, zIndexManager } from './dockLayers.js';
// 定义组件可以发出的事件 // 定义组件可以发出的事件
const emit = defineEmits([ const emit = defineEmits([
@@ -295,51 +248,69 @@ const addFloatingPanel = () => {
// 如果容器已渲染,计算居中位置 // 如果容器已渲染,计算居中位置
if (dockLayoutRef.value) { if (dockLayoutRef.value) {
const containerRect = dockLayoutRef.value.getBoundingClientRect() const containerRect = dockLayoutRef.value.getBoundingClientRect()
const width = 300 const width = 280
const height = 250 const height = 200
x = Math.floor((containerRect.width - width) / 2) x = Math.floor((containerRect.width - width) / 2)
y = Math.floor((containerRect.height - height) / 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 = { const newArea = {
id: `floating-area-${areaIdCounter++}`, id: areaId,
title: `浮动区域 ${areaIdCounter - 1}`, title: `浮动区域 ${currentId}`,
x: x, x: x,
y: y, y: y,
width: 300, width: 280,
height: 250, height: 200,
WindowState: '正常', windowState: '正常',
showTitleBar: true, showTitleBar: true,
// 2. 向Area添加一个TabPage。TabPage的初始设置为填充满父容器 resizable: true,
tabPages: [ draggable: true,
zIndex: newZIndex, // 使用z-index管理器分配的层级
// 使用Render期望的children结构
children: [
{
type: 'tabpage',
id: `tabpage-${currentId}-1`,
title: `标签页 1`,
tabPosition: 'bottom',
children: {
type: 'panel',
items: [
{
type: 'panel',
id: `panel-${currentId}-1-1`,
title: `面板 ${currentId}`,
x: 0,
y: 0,
width: 280,
height: 200,
collapsed: false,
toolbarExpanded: false,
maximized: false,
content: generateRandomContent(currentId)
}
]
}
}
] ]
} }
const newTabPage = {
id: `tabpage-${areaIdCounter - 1}-1`,
title: `标签页 1`,
// TabPage填充满父容器
width: 100,
height: 100,
// 3. 向TabPage添加一个Panel。Panel的初始设置为填充满父容器
panels: [
]
}
const newPanel = {
id: `panel-${areaIdCounter - 1}-1-1`,
title: `面板 ${areaIdCounter - 1}`,
x: 0,
y: 0,
width: 100,
height: 100,
collapsed: false,
toolbarExpanded: false,
// 添加随机测试内容以便合并测试
content: generateRandomContent(areaIdCounter - 1)
}
newTabPage.panels.push(newPanel)
newArea.tabPages.push(newTabPage)
floatingAreas.value.push(newArea) 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) => { const onUpdatePosition = (id, position) => {
@@ -362,28 +333,31 @@ const onToggleCollapse = (id) => {
const onMaximize = (panelId) => { const onMaximize = (panelId) => {
// 查找包含该面板的区域 // 查找包含该面板的区域
for (const area of floatingAreas.value) { for (const area of floatingAreas.value) {
// 正确处理层级结构Area -> TabPage -> Panel if (area.children) {
if (area.tabPages) { for (const child of area.children) {
for (const tabPage of area.tabPages) { if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
if (tabPage.panels && tabPage.panels.length === 1 && tabPage.panels[0].id === panelId) { const panels = child.children.items || []
// 当区域只包含一个Panel时切换Area和Panel的最大化状态 if (panels.length === 1 && panels[0].id === panelId) {
const isCurrentlyMaximized = area.WindowState === '最大化' || area.WindowState === 'maximized' // 当区域只包含一个Panel时切换Area和Panel的最大化状态
const isCurrentlyMaximized = area.windowState === '最大化' || area.windowState === 'maximized'
// 使用Vue推荐的方式更新响应式数据
if (isCurrentlyMaximized) { if (isCurrentlyMaximized) {
// 切换为正常状态 // 切换为正常状态
area.WindowState = '正常' area.windowState = '正常'
// 确保Panel也恢复正常状态 - 使用展开运算符创建新对象确保响应式 // 确保Panel也恢复正常状态 - 使用展开运算符创建新对象确保响应式
tabPage.panels[0] = { ...tabPage.panels[0], maximized: false } child.children.items[0] = { ...panels[0], maximized: false }
} else { } else {
// 切换为最大化状态 // 切换为最大化状态
area.WindowState = '最大化' area.windowState = '最大化'
// 同时最大化Panel - 使用展开运算符创建新对象确保响应式 // 同时最大化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
} }
break
} }
} }
} }
@@ -397,6 +371,9 @@ const onCloseFloatingArea = (id) => {
// 获取要移除的Area // 获取要移除的Area
const areaToRemove = floatingAreas.value[index] const areaToRemove = floatingAreas.value[index]
// 从z-index管理器中移除该区域的层级管理
zIndexManager.removeFloatingArea(id)
// 清理Panel引用确保Panel被正确移除 // 清理Panel引用确保Panel被正确移除
if (areaToRemove.panels) { if (areaToRemove.panels) {
// 这里可以添加任何需要的Panel清理逻辑 // 这里可以添加任何需要的Panel清理逻辑
@@ -411,19 +388,19 @@ const onCloseFloatingArea = (id) => {
} }
} }
// 关闭面板 // 关闭面板 - 适配children数据结构
const onClosePanel = (areaId, panelId) => { const onClosePanel = (areaId, panelId) => {
const area = floatingAreas.value.find(a => a.id === areaId) const area = floatingAreas.value.find(a => a.id === areaId)
// 正确处理层级结构Area -> TabPage -> Panel if (area && area.children) {
if (area && area.tabPages) { for (const child of area.children) {
for (const tabPage of area.tabPages) { if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
if (tabPage.panels) { const panels = child.children.items || []
const panelIndex = tabPage.panels.findIndex(p => p.id === panelId) const panelIndex = panels.findIndex(p => p.id === panelId)
if (panelIndex !== -1) { if (panelIndex !== -1) {
tabPage.panels.splice(panelIndex, 1) panels.splice(panelIndex, 1)
// 如果区域内没有面板了,可以考虑关闭整个区域 // 如果区域内没有面板了,可以考虑关闭整个区域
if (tabPage.panels.length === 0) { if (panels.length === 0) {
onCloseFloatingArea(areaId) onCloseFloatingArea(areaId)
} }
break break
@@ -2333,8 +2310,12 @@ const adjustSourceAreaForLayout = (sourceArea, targetArea, dockZone) => {
} }
} }
// 暴露方法给父组件 // 暴露数据和方法给父组件
defineExpose({ defineExpose({
// 数据属性
floatingAreas,
hiddenAreas,
// 原有方法 // 原有方法
addFloatingPanel, addFloatingPanel,

View File

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

View File

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