修改三栏布局为浮动灵活布局

This commit is contained in:
JoyD
2025-10-23 22:09:49 +08:00
parent 131946b93a
commit 88c90a3afc
3 changed files with 175 additions and 169 deletions

View File

@@ -1,99 +1,111 @@
<template> <template>
<div ref="container" class="dock-panel-container w-full h-full relative overflow-hidden bg-gray-100"> <div ref="container" class="dock-panel-container w-full h-full relative overflow-hidden bg-gray-100">
<!-- 主内容区域 --> <!-- 绝对定位的面板区域顶部 -->
<div class="flex flex-col h-full"> <PanelArea
<!-- 顶部面板区域 --> v-if="topPanels.length > 0"
<PanelArea position="top"
v-if="topPanels.length > 0" :panels="topPanels"
position="top" :size="topPanelHeight"
:panels="topPanels" :sizeRatios="topPanelsWidthRatios"
:size="topPanelHeight" :store="store"
:sizeRatios="topPanelsWidthRatios" :bounds="{ x: topPanelArea.x || 0, y: topPanelArea.y || 0, width: topPanelArea.width || 0, height: topPanelArea.height || 0 }"
:store="store" @closePanel="closePanel"
@closePanel="closePanel" @floatPanel="floatPanel"
@floatPanel="floatPanel" @dockPanel="dockPanel"
@dockPanel="dockPanel" @toggleCollapse="toggleCollapse"
@toggleCollapse="toggleCollapse" @titleBarDragStart="handlePanelTitleBarDragStart"
@titleBarDragStart="handlePanelTitleBarDragStart" @panelResizeStart="startPanelResize"
@panelResizeStart="startPanelResize" />
/>
<!-- 中间主区域和左右面板 --> <!-- 绝对定位的面板区域左侧 -->
<div class="flex-1 flex relative overflow-hidden"> <PanelArea
<!-- 左侧面板区域 --> v-if="leftPanels.length > 0"
<PanelArea position="left"
v-if="leftPanels.length > 0" :panels="leftPanels"
position="left" :size="leftPanelWidth"
:panels="leftPanels" :sizeRatios="leftPanelsHeightRatios"
:size="leftPanelWidth" :store="store"
:sizeRatios="leftPanelsHeightRatios" :bounds="{ x: leftPanelArea.x || 0, y: leftPanelArea.y || 0, width: leftPanelArea.width || 0, height: leftPanelArea.height || 0 }"
:store="store" @closePanel="closePanel"
@closePanel="closePanel" @floatPanel="floatPanel"
@floatPanel="floatPanel" @dockPanel="dockPanel"
@dockPanel="dockPanel" @toggleCollapse="toggleCollapse"
@toggleCollapse="toggleCollapse" @titleBarDragStart="handlePanelTitleBarDragStart"
@titleBarDragStart="handlePanelTitleBarDragStart" @panelResizeStart="startPanelResize"
@panelResizeStart="startPanelResize" />
/>
<!-- 主内容区域 --> <!-- 绝对定位的主内容中心区域 -->
<div class="flex-1 flex flex-col bg-white relative"> <div
<!-- 主内容标签组 --> v-if="centerPanels.length > 0"
<div v-if="centerPanels.length > 0" class="flex-1"> class="absolute bg-white"
<TabGroup :style="{
:panels="centerPanels" top: (centerPanelArea.y || 0) + 'px',
:activeTab="activeCenterTab" left: (centerPanelArea.x || 0) + 'px',
@switchTab="handleCenterTabSwitch" width: (centerPanelArea.width || 0) + 'px',
@closeTab="closePanel" height: (centerPanelArea.height || 0) + 'px'
@floatTab="floatPanel" }"
@dockTab="dockPanel" >
@dragTabStart="handleTabDragStart" <TabGroup
/> :panels="centerPanels"
</div> :activeTab="activeCenterTab"
@switchTab="handleCenterTabSwitch"
<!-- 空状态 --> @closeTab="closePanel"
<div v-else class="flex items-center justify-center h-full text-gray-400"> @floatTab="floatPanel"
<div class="text-center"> @dockTab="dockPanel"
<i class="fa-solid fa-layer-group text-4xl mb-3 opacity-30"></i> @dragTabStart="handleTabDragStart"
<p>暂无面板</p>
<p class="text-sm mt-1">您可以添加面板或从浮动窗口拖拽面板到此处</p>
</div>
</div>
</div>
<!-- 右侧面板区域 -->
<PanelArea
v-if="rightPanels.length > 0"
position="right"
:panels="rightPanels"
:size="rightPanelWidth"
:sizeRatios="rightPanelsHeightRatios"
:store="store"
@closePanel="closePanel"
@floatPanel="floatPanel"
@dockPanel="dockPanel"
@toggleCollapse="toggleCollapse"
@titleBarDragStart="handlePanelTitleBarDragStart"
@panelResizeStart="startPanelResize"
/>
</div>
<!-- 底部面板区域 -->
<PanelArea
v-if="bottomPanels.length > 0"
position="bottom"
:panels="bottomPanels"
:size="bottomPanelHeight"
:sizeRatios="bottomPanelsWidthRatios"
:store="store"
@closePanel="closePanel"
@floatPanel="floatPanel"
@dockPanel="dockPanel"
@toggleCollapse="toggleCollapse"
@titleBarDragStart="handlePanelTitleBarDragStart"
@panelResizeStart="startPanelResize"
/> />
</div> </div>
<!-- 空状态中心区无面板 -->
<div
v-else
class="absolute flex items-center justify-center text-gray-400"
:style="{
top: (centerPanelArea.y || 0) + 'px',
left: (centerPanelArea.x || 0) + 'px',
width: (centerPanelArea.width || 0) + 'px',
height: (centerPanelArea.height || 0) + 'px'
}"
>
<div class="text-center">
<i class="fa-solid fa-layer-group text-4xl mb-3 opacity-30"></i>
<p>暂无面板</p>
<p class="text-sm mt-1">您可以添加面板或从浮动窗口拖拽面板到此处</p>
</div>
</div>
<!-- 绝对定位的面板区域右侧 -->
<PanelArea
v-if="rightPanels.length > 0"
position="right"
:panels="rightPanels"
:size="rightPanelWidth"
:sizeRatios="rightPanelsHeightRatios"
:store="store"
:bounds="{ x: rightPanelArea.x || 0, y: rightPanelArea.y || 0, width: rightPanelArea.width || 0, height: rightPanelArea.height || 0 }"
@closePanel="closePanel"
@floatPanel="floatPanel"
@dockPanel="dockPanel"
@toggleCollapse="toggleCollapse"
@titleBarDragStart="handlePanelTitleBarDragStart"
@panelResizeStart="startPanelResize"
/>
<!-- 绝对定位的面板区域底部 -->
<PanelArea
v-if="bottomPanels.length > 0"
position="bottom"
:panels="bottomPanels"
:size="bottomPanelHeight"
:sizeRatios="bottomPanelsWidthRatios"
:store="store"
:bounds="{ x: bottomPanelArea.x || 0, y: bottomPanelArea.y || 0, width: bottomPanelArea.width || 0, height: bottomPanelArea.height || 0 }"
@closePanel="closePanel"
@floatPanel="floatPanel"
@dockPanel="dockPanel"
@toggleCollapse="toggleCollapse"
@titleBarDragStart="handlePanelTitleBarDragStart"
@panelResizeStart="startPanelResize"
/>
<!-- 浮动窗口区域 --> <!-- 浮动窗口区域 -->
<div v-for="window in floatingWindows" :key="window.id" class="absolute z-50"> <div v-for="window in floatingWindows" :key="window.id" class="absolute z-50">
@@ -120,8 +132,7 @@
}" }"
/> />
<!-- 分割条组件 - 替换原来的div元素 --> <!-- 保留分割条组件位置计算依赖现有逻辑 -->
<!-- 左侧垂直分隔线 -->
<PanelResizer <PanelResizer
v-if="leftPanels.length > 0" v-if="leftPanels.length > 0"
position="left" position="left"
@@ -134,7 +145,6 @@
@resizeStart="startResize" @resizeStart="startResize"
/> />
<!-- 右侧垂直分隔线 -->
<PanelResizer <PanelResizer
v-if="rightPanels.length > 0" v-if="rightPanels.length > 0"
position="right" position="right"
@@ -147,7 +157,6 @@
@resizeStart="startResize" @resizeStart="startResize"
/> />
<!-- 上部分水平分隔线 -->
<PanelResizer <PanelResizer
v-if="topPanels.length > 0" v-if="topPanels.length > 0"
position="top" position="top"
@@ -156,7 +165,6 @@
@resizeStart="startResize" @resizeStart="startResize"
/> />
<!-- 下部分水平分隔线 -->
<PanelResizer <PanelResizer
v-if="bottomPanels.length > 0" v-if="bottomPanels.length > 0"
position="bottom" position="bottom"
@@ -164,33 +172,6 @@
:bottom-panel-height="bottomPanelHeight" :bottom-panel-height="bottomPanelHeight"
@resizeStart="startResize" @resizeStart="startResize"
/> />
<!-- 任务栏 - 显示最小化的窗口 -->
<Taskbar
:minimized-windows="minimizedWindows"
@restore-window="restoreMinimizedWindow"
/>
<!-- 右键菜单 -->
<div
v-if="contextMenu.visible"
class="absolute z-50 bg-white shadow-lg rounded border border-gray-300 py-1 min-w-[150px]"
:style="{
top: contextMenu.position.y + 'px',
left: contextMenu.position.x + 'px'
}"
@mouseleave="hideContextMenu"
>
<button
v-for="item in contextMenu.items"
:key="item.id"
@click="handleContextMenuAction(item.action, item.data)"
class="w-full text-left px-4 py-2 text-sm hover:bg-gray-100"
:disabled="item.disabled"
>
{{ item.label }}
</button>
</div>
</div> </div>
</template> </template>

View File

@@ -252,11 +252,15 @@ export class LayoutCoordinator {
influencedBy: { influencedBy: {
left: [ left: [
{ position: 'center', property: 'width', influence: true }, { position: 'center', property: 'width', influence: true },
{ position: 'right', property: 'width', influence: true } { position: 'right', property: 'width', influence: true },
{ position: 'top', property: 'width', influence: true },
{ position: 'bottom', property: 'width', influence: true }
], ],
right: [ right: [
{ position: 'left', property: 'width', influence: true }, { position: 'left', property: 'width', influence: true },
{ position: 'center', property: 'width', influence: true } { position: 'center', property: 'width', influence: true },
{ position: 'top', property: 'width', influence: true },
{ position: 'bottom', property: 'width', influence: true }
], ],
top:[ top:[
{ position: 'left', property: 'width', influence: true }, { position: 'left', property: 'width', influence: true },
@@ -346,56 +350,58 @@ export class LayoutCoordinator {
// 计算左侧面板边界 - 考虑被影响列表influencedBy // 计算左侧面板边界 - 考虑被影响列表influencedBy
if (panelAreas.left && panelAreas.left.panels && panelAreas.left.panels.length > 0) { if (panelAreas.left && panelAreas.left.panels && panelAreas.left.panels.length > 0) {
// 计算y坐标 - 根据被影响列表如果包含top区则y值应该在top区的下边缘
let leftY = 0;
const leftInfluencedBy = influenceData.influencedBy.left || []; const leftInfluencedBy = influenceData.influencedBy.left || [];
let leftY = 0;
// 检查是否被top区域影响 // 若被 top.height 影响且顶部区存在有效子面板,则从 top.height 起始
if (leftInfluencedBy.some(item => item.position === 'top' && item.influence)) { if (
leftY = this.panelBounds.top.height; leftInfluencedBy.some(item => item.position === 'top' && item.property === 'height' && item.influence) &&
panelAreas.top && panelAreas.top.panels && panelAreas.top.panels.length > 0
) {
leftY = (this.panelBounds.top?.height ?? panelAreas.top.height ?? 0);
} }
// 计算宽度和高度
const leftWidth = panelAreas.left.width || 300; const leftWidth = panelAreas.left.width || 300;
let leftHeight = containerHeight; let leftHeight = containerHeight;
// 按被影响列表中所有属性为 height 的条目进行扣减,且仅在对应区有有效子面板时生效
// 考虑顶部和底部面板对高度的影响 leftInfluencedBy.forEach(item => {
leftHeight -= this.panelBounds.top.height; if (item.influence && item.property === 'height') {
leftHeight -= this.panelBounds.bottom.height; const pos = item.position;
const area = panelAreas[pos];
this.panelBounds.left = { const hasPanels = area && area.panels && area.panels.length > 0;
x: 0, if (hasPanels) {
y: leftY, const h = (this.panelBounds[pos]?.height ?? area.height ?? 0);
width: leftWidth, leftHeight -= h;
height: leftHeight }
}; }
});
this.panelBounds.left = { x: 0, y: leftY, width: leftWidth, height: leftHeight };
} }
// 计算右侧面板边界 - 考虑被影响列表 // 计算右侧面板边界 - 考虑被影响列表
if (panelAreas.right && panelAreas.right.panels && panelAreas.right.panels.length > 0) { if (panelAreas.right && panelAreas.right.panels && panelAreas.right.panels.length > 0) {
// 计算y坐标 - 根据被影响列表如果包含top区则y值应该在top区的下边缘
let rightY = 0;
const rightInfluencedBy = influenceData.influencedBy.right || []; const rightInfluencedBy = influenceData.influencedBy.right || [];
let rightY = 0;
// 检查是否被top区域影响 // 若被 top.height 影响且顶部区存在有效子面板,则从 top.height 起始
if (rightInfluencedBy.some(item => item.position === 'top' && item.influence)) { if (
rightY = this.panelBounds.top.height; rightInfluencedBy.some(item => item.position === 'top' && item.property === 'height' && item.influence) &&
panelAreas.top && panelAreas.top.panels && panelAreas.top.panels.length > 0
) {
rightY = (this.panelBounds.top?.height ?? panelAreas.top.height ?? 0);
} }
// 计算宽度和高度
const rightWidth = panelAreas.right.width || 250; const rightWidth = panelAreas.right.width || 250;
let rightHeight = containerHeight; let rightHeight = containerHeight;
// 按被影响列表中所有属性为 height 的条目进行扣减,且仅在对应区有有效子面板时生效
// 考虑顶部和底部面板对高度的影响 rightInfluencedBy.forEach(item => {
rightHeight -= this.panelBounds.top.height; if (item.influence && item.property === 'height') {
rightHeight -= this.panelBounds.bottom.height; const pos = item.position;
const area = panelAreas[pos];
this.panelBounds.right = { const hasPanels = area && area.panels && area.panels.length > 0;
x: containerWidth - rightWidth, if (hasPanels) {
y: rightY, const h = (this.panelBounds[pos]?.height ?? area.height ?? 0);
width: rightWidth, rightHeight -= h;
height: rightHeight }
}; }
});
this.panelBounds.right = { x: containerWidth - rightWidth, y: rightY, width: rightWidth, height: rightHeight };
} }
// 计算中心面板边界 - 中心面板通常被所有其他面板影响 // 计算中心面板边界 - 中心面板通常被所有其他面板影响

View File

@@ -57,6 +57,12 @@ const props = defineProps({
store: { store: {
type: Object, type: Object,
required: true required: true
},
// 绝对定位边界(可选):{ x, y, width, height }
bounds: {
type: Object,
required: false,
default: null
} }
}); });
@@ -81,11 +87,24 @@ const containerClasses = computed(() => {
bottom: 'flex border-t border-gray-300 panel-area-bottom' bottom: 'flex border-t border-gray-300 panel-area-bottom'
}; };
return `${baseClasses} ${positionClasses[props.position]}`; const absoluteClass = props.bounds ? ' absolute' : '';
return `${baseClasses} ${positionClasses[props.position]}${absoluteClass}`;
}); });
// 根据位置计算容器样式 // 根据位置计算容器样式
const containerStyle = computed(() => { const containerStyle = computed(() => {
// 若提供了绝对定位边界则使用top/left/width/height
if (props.bounds) {
const { x = 0, y = 0, width = 0, height = 0 } = props.bounds;
return {
position: 'absolute',
top: `${y}px`,
left: `${x}px`,
width: `${width}px`,
height: `${height}px`
};
}
const isVertical = ['left', 'right'].includes(props.position); const isVertical = ['left', 'right'].includes(props.position);
const styleProperty = isVertical ? 'width' : 'height'; const styleProperty = isVertical ? 'width' : 'height';