修改三栏布局为浮动灵活布局
This commit is contained in:
@@ -1,100 +1,112 @@
|
||||
<template>
|
||||
<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"
|
||||
position="top"
|
||||
:panels="topPanels"
|
||||
:size="topPanelHeight"
|
||||
:sizeRatios="topPanelsWidthRatios"
|
||||
:store="store"
|
||||
@closePanel="closePanel"
|
||||
@floatPanel="floatPanel"
|
||||
@dockPanel="dockPanel"
|
||||
@toggleCollapse="toggleCollapse"
|
||||
@titleBarDragStart="handlePanelTitleBarDragStart"
|
||||
@panelResizeStart="startPanelResize"
|
||||
/>
|
||||
|
||||
<!-- 中间主区域和左右面板 -->
|
||||
<div class="flex-1 flex relative overflow-hidden">
|
||||
<!-- 左侧面板区域 -->
|
||||
<PanelArea
|
||||
v-if="leftPanels.length > 0"
|
||||
position="left"
|
||||
:panels="leftPanels"
|
||||
:size="leftPanelWidth"
|
||||
:sizeRatios="leftPanelsHeightRatios"
|
||||
:store="store"
|
||||
@closePanel="closePanel"
|
||||
@floatPanel="floatPanel"
|
||||
@dockPanel="dockPanel"
|
||||
@toggleCollapse="toggleCollapse"
|
||||
@titleBarDragStart="handlePanelTitleBarDragStart"
|
||||
@panelResizeStart="startPanelResize"
|
||||
/>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="flex-1 flex flex-col bg-white relative">
|
||||
<!-- 主内容标签组 -->
|
||||
<div v-if="centerPanels.length > 0" class="flex-1">
|
||||
<TabGroup
|
||||
:panels="centerPanels"
|
||||
:activeTab="activeCenterTab"
|
||||
@switchTab="handleCenterTabSwitch"
|
||||
@closeTab="closePanel"
|
||||
@floatTab="floatPanel"
|
||||
@dockTab="dockPanel"
|
||||
@dragTabStart="handleTabDragStart"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex items-center justify-center h-full text-gray-400">
|
||||
<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>
|
||||
</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"
|
||||
<!-- 绝对定位的面板区域:顶部 -->
|
||||
<PanelArea
|
||||
v-if="topPanels.length > 0"
|
||||
position="top"
|
||||
:panels="topPanels"
|
||||
:size="topPanelHeight"
|
||||
:sizeRatios="topPanelsWidthRatios"
|
||||
:store="store"
|
||||
:bounds="{ x: topPanelArea.x || 0, y: topPanelArea.y || 0, width: topPanelArea.width || 0, height: topPanelArea.height || 0 }"
|
||||
@closePanel="closePanel"
|
||||
@floatPanel="floatPanel"
|
||||
@dockPanel="dockPanel"
|
||||
@toggleCollapse="toggleCollapse"
|
||||
@titleBarDragStart="handlePanelTitleBarDragStart"
|
||||
@panelResizeStart="startPanelResize"
|
||||
/>
|
||||
|
||||
<!-- 绝对定位的面板区域:左侧 -->
|
||||
<PanelArea
|
||||
v-if="leftPanels.length > 0"
|
||||
position="left"
|
||||
:panels="leftPanels"
|
||||
:size="leftPanelWidth"
|
||||
:sizeRatios="leftPanelsHeightRatios"
|
||||
:store="store"
|
||||
:bounds="{ x: leftPanelArea.x || 0, y: leftPanelArea.y || 0, width: leftPanelArea.width || 0, height: leftPanelArea.height || 0 }"
|
||||
@closePanel="closePanel"
|
||||
@floatPanel="floatPanel"
|
||||
@dockPanel="dockPanel"
|
||||
@toggleCollapse="toggleCollapse"
|
||||
@titleBarDragStart="handlePanelTitleBarDragStart"
|
||||
@panelResizeStart="startPanelResize"
|
||||
/>
|
||||
|
||||
<!-- 绝对定位的主内容中心区域 -->
|
||||
<div
|
||||
v-if="centerPanels.length > 0"
|
||||
class="absolute bg-white"
|
||||
:style="{
|
||||
top: (centerPanelArea.y || 0) + 'px',
|
||||
left: (centerPanelArea.x || 0) + 'px',
|
||||
width: (centerPanelArea.width || 0) + 'px',
|
||||
height: (centerPanelArea.height || 0) + 'px'
|
||||
}"
|
||||
>
|
||||
<TabGroup
|
||||
:panels="centerPanels"
|
||||
:activeTab="activeCenterTab"
|
||||
@switchTab="handleCenterTabSwitch"
|
||||
@closeTab="closePanel"
|
||||
@floatTab="floatPanel"
|
||||
@dockTab="dockPanel"
|
||||
@dragTabStart="handleTabDragStart"
|
||||
/>
|
||||
</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">
|
||||
<FloatingWindow
|
||||
@@ -106,7 +118,7 @@
|
||||
@maximize="maximizeFloatingWindow"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 停靠预览阴影 -->
|
||||
<div
|
||||
v-if="dockPreview.visible"
|
||||
@@ -119,9 +131,8 @@
|
||||
backgroundColor: dockPreview.color
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- 分割条组件 - 替换原来的div元素 -->
|
||||
<!-- 左侧垂直分隔线 -->
|
||||
|
||||
<!-- 保留分割条组件(位置计算依赖现有逻辑) -->
|
||||
<PanelResizer
|
||||
v-if="leftPanels.length > 0"
|
||||
position="left"
|
||||
@@ -134,7 +145,6 @@
|
||||
@resizeStart="startResize"
|
||||
/>
|
||||
|
||||
<!-- 右侧垂直分隔线 -->
|
||||
<PanelResizer
|
||||
v-if="rightPanels.length > 0"
|
||||
position="right"
|
||||
@@ -147,7 +157,6 @@
|
||||
@resizeStart="startResize"
|
||||
/>
|
||||
|
||||
<!-- 上部分水平分隔线 -->
|
||||
<PanelResizer
|
||||
v-if="topPanels.length > 0"
|
||||
position="top"
|
||||
@@ -156,7 +165,6 @@
|
||||
@resizeStart="startResize"
|
||||
/>
|
||||
|
||||
<!-- 下部分水平分隔线 -->
|
||||
<PanelResizer
|
||||
v-if="bottomPanels.length > 0"
|
||||
position="bottom"
|
||||
@@ -164,33 +172,6 @@
|
||||
:bottom-panel-height="bottomPanelHeight"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -252,11 +252,15 @@ export class LayoutCoordinator {
|
||||
influencedBy: {
|
||||
left: [
|
||||
{ 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: [
|
||||
{ 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:[
|
||||
{ position: 'left', property: 'width', influence: true },
|
||||
@@ -346,56 +350,58 @@ export class LayoutCoordinator {
|
||||
|
||||
// 计算左侧面板边界 - 考虑被影响列表(influencedBy)
|
||||
if (panelAreas.left && panelAreas.left.panels && panelAreas.left.panels.length > 0) {
|
||||
// 计算y坐标 - 根据被影响列表,如果包含top区,则y值应该在top区的下边缘
|
||||
let leftY = 0;
|
||||
const leftInfluencedBy = influenceData.influencedBy.left || [];
|
||||
|
||||
// 检查是否被top区域影响
|
||||
if (leftInfluencedBy.some(item => item.position === 'top' && item.influence)) {
|
||||
leftY = this.panelBounds.top.height;
|
||||
let leftY = 0;
|
||||
// 若被 top.height 影响且顶部区存在有效子面板,则从 top.height 起始
|
||||
if (
|
||||
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;
|
||||
let leftHeight = containerHeight;
|
||||
|
||||
// 考虑顶部和底部面板对高度的影响
|
||||
leftHeight -= this.panelBounds.top.height;
|
||||
leftHeight -= this.panelBounds.bottom.height;
|
||||
|
||||
this.panelBounds.left = {
|
||||
x: 0,
|
||||
y: leftY,
|
||||
width: leftWidth,
|
||||
height: leftHeight
|
||||
};
|
||||
// 按被影响列表中所有属性为 height 的条目进行扣减,且仅在对应区有有效子面板时生效
|
||||
leftInfluencedBy.forEach(item => {
|
||||
if (item.influence && item.property === 'height') {
|
||||
const pos = item.position;
|
||||
const area = panelAreas[pos];
|
||||
const hasPanels = area && area.panels && area.panels.length > 0;
|
||||
if (hasPanels) {
|
||||
const h = (this.panelBounds[pos]?.height ?? area.height ?? 0);
|
||||
leftHeight -= h;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.panelBounds.left = { x: 0, y: leftY, width: leftWidth, height: leftHeight };
|
||||
}
|
||||
|
||||
|
||||
// 计算右侧面板边界 - 考虑被影响列表
|
||||
if (panelAreas.right && panelAreas.right.panels && panelAreas.right.panels.length > 0) {
|
||||
// 计算y坐标 - 根据被影响列表,如果包含top区,则y值应该在top区的下边缘
|
||||
let rightY = 0;
|
||||
const rightInfluencedBy = influenceData.influencedBy.right || [];
|
||||
|
||||
// 检查是否被top区域影响
|
||||
if (rightInfluencedBy.some(item => item.position === 'top' && item.influence)) {
|
||||
rightY = this.panelBounds.top.height;
|
||||
let rightY = 0;
|
||||
// 若被 top.height 影响且顶部区存在有效子面板,则从 top.height 起始
|
||||
if (
|
||||
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;
|
||||
let rightHeight = containerHeight;
|
||||
|
||||
// 考虑顶部和底部面板对高度的影响
|
||||
rightHeight -= this.panelBounds.top.height;
|
||||
rightHeight -= this.panelBounds.bottom.height;
|
||||
|
||||
this.panelBounds.right = {
|
||||
x: containerWidth - rightWidth,
|
||||
y: rightY,
|
||||
width: rightWidth,
|
||||
height: rightHeight
|
||||
};
|
||||
// 按被影响列表中所有属性为 height 的条目进行扣减,且仅在对应区有有效子面板时生效
|
||||
rightInfluencedBy.forEach(item => {
|
||||
if (item.influence && item.property === 'height') {
|
||||
const pos = item.position;
|
||||
const area = panelAreas[pos];
|
||||
const hasPanels = area && area.panels && area.panels.length > 0;
|
||||
if (hasPanels) {
|
||||
const h = (this.panelBounds[pos]?.height ?? area.height ?? 0);
|
||||
rightHeight -= h;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.panelBounds.right = { x: containerWidth - rightWidth, y: rightY, width: rightWidth, height: rightHeight };
|
||||
}
|
||||
|
||||
// 计算中心面板边界 - 中心面板通常被所有其他面板影响
|
||||
|
||||
@@ -57,6 +57,12 @@ const props = defineProps({
|
||||
store: {
|
||||
type: Object,
|
||||
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'
|
||||
};
|
||||
|
||||
return `${baseClasses} ${positionClasses[props.position]}`;
|
||||
const absoluteClass = props.bounds ? ' absolute' : '';
|
||||
return `${baseClasses} ${positionClasses[props.position]}${absoluteClass}`;
|
||||
});
|
||||
|
||||
// 根据位置计算容器样式
|
||||
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 styleProperty = isVertical ? 'width' : 'height';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user