修改三栏布局为浮动灵活布局
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算中心面板边界 - 中心面板通常被所有其他面板影响
|
// 计算中心面板边界 - 中心面板通常被所有其他面板影响
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user