拖拽Area停靠中心区域
This commit is contained in:
@@ -99,14 +99,52 @@
|
|||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<div class="vs-content">
|
<div class="vs-content">
|
||||||
<!-- 这里是内容区域,使用slot接收子组件,并监听Panel的maximize事件 -->
|
<!-- 这里是内容区域,优先显示slot内容,如果没有slot内容则显示接收到的外部内容 -->
|
||||||
|
<template v-if="$slots.default">
|
||||||
<slot @maximize="onPanelMaximize"></slot>
|
<slot @maximize="onPanelMaximize"></slot>
|
||||||
|
</template>
|
||||||
|
<!-- 直接显示接收到的外部TabPage内容,不需要额外包装 -->
|
||||||
|
<template v-else-if="receivedContent.length > 0">
|
||||||
|
<TabPage
|
||||||
|
v-for="(item, index) in receivedContent"
|
||||||
|
:key="`received-tab-${index}`"
|
||||||
|
:id="item.tabPage.id"
|
||||||
|
:title="item.tabPage.title"
|
||||||
|
:panels="item.tabPage.panels"
|
||||||
|
:tabPosition="'bottom'"
|
||||||
|
@tabDragStart="() => {}"
|
||||||
|
@tabDragMove="() => {}"
|
||||||
|
@tabDragEnd="() => {}"
|
||||||
|
>
|
||||||
|
<!-- 在TabPage内渲染其包含的Panels -->
|
||||||
|
<Panel
|
||||||
|
v-for="panel in item.tabPage.panels"
|
||||||
|
:key="`received-panel-${index}-${panel.id}`"
|
||||||
|
:id="panel.id"
|
||||||
|
:title="panel.title"
|
||||||
|
:collapsed="panel.collapsed"
|
||||||
|
:toolbarExpanded="panel.toolbarExpanded"
|
||||||
|
:maximized="panel.maximized"
|
||||||
|
@toggleCollapse="() => {}"
|
||||||
|
@maximize="onPanelMaximize"
|
||||||
|
@close="() => {}"
|
||||||
|
@toggleToolbar="() => {}"
|
||||||
|
@dragStart="() => {}"
|
||||||
|
@dragMove="() => {}"
|
||||||
|
@dragEnd="() => {}"
|
||||||
|
@dragover="handleDragOver"
|
||||||
|
@dragleave="handleDragLeave"
|
||||||
|
/>
|
||||||
|
</TabPage>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, computed, defineEmits, ref, onMounted, watch } from 'vue'
|
import { defineProps, computed, defineEmits, ref, onMounted, watch, defineExpose } from 'vue'
|
||||||
|
import TabPage from './TabPage.vue'
|
||||||
|
import Panel from './Panel.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: { type: String, required: true },
|
id: { type: String, required: true },
|
||||||
@@ -137,6 +175,9 @@ const originalPosition = ref({
|
|||||||
// 保存最大化前的位置和大小,用于还原
|
// 保存最大化前的位置和大小,用于还原
|
||||||
const maximizedFromPosition = ref(null)
|
const maximizedFromPosition = ref(null)
|
||||||
|
|
||||||
|
// 存储接收到的外部Area内容
|
||||||
|
const receivedContent = ref([])
|
||||||
|
|
||||||
// 拖拽相关状态
|
// 拖拽相关状态
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const dragStartPos = ref({ x: 0, y: 0 })
|
const dragStartPos = ref({ x: 0, y: 0 })
|
||||||
@@ -527,6 +568,61 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 添加Area内容的方法,用于主区域的Area接收外部Area内容
|
||||||
|
const appendAreaContent = (sourceArea) => {
|
||||||
|
console.log(`[Area] ${props.id} 接收到内容移动请求:`, sourceArea)
|
||||||
|
|
||||||
|
if (!sourceArea) {
|
||||||
|
console.warn('[Area] 源Area为空,无法接收内容')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 清空之前的内容,确保只接收一个新的TabPage
|
||||||
|
receivedContent.value = []
|
||||||
|
|
||||||
|
// 处理源Area的tabPages,只接收第一个TabPage(用户要求只能接收一个TabPage)
|
||||||
|
if (sourceArea.tabPages && sourceArea.tabPages.length > 0) {
|
||||||
|
const tabPage = sourceArea.tabPages[0] // 只接收第一个TabPage
|
||||||
|
if (tabPage) {
|
||||||
|
// 确保所有Panel都处于最大化状态,显示还原按钮
|
||||||
|
const maximizedPanels = (tabPage.panels || []).map(panel => ({
|
||||||
|
...panel,
|
||||||
|
maximized: true // 设置为最大化状态
|
||||||
|
}))
|
||||||
|
|
||||||
|
receivedContent.value.push({
|
||||||
|
id: `received-${tabPage.id || Date.now() + Math.random()}`,
|
||||||
|
title: tabPage.title || '标签页',
|
||||||
|
tabPage: {
|
||||||
|
...tabPage,
|
||||||
|
panels: maximizedPanels // 使用最大化状态的Panel
|
||||||
|
},
|
||||||
|
panels: maximizedPanels
|
||||||
|
})
|
||||||
|
console.log(`[Area] 成功接收tabPage: ${tabPage.title},并设置Panel为最大化状态`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[Area] 源Area中没有tabPages数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Area] ${props.id} 当前接收内容数量: ${receivedContent.value.length}`)
|
||||||
|
return true
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Area] 接收外部内容时出错:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件调用
|
||||||
|
defineExpose({
|
||||||
|
appendAreaContent,
|
||||||
|
id: props.id,
|
||||||
|
title: props.title,
|
||||||
|
isMaximized: isMaximized.value
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -784,4 +880,55 @@ onMounted(() => {
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 接收到的外部内容样式 */
|
||||||
|
.received-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background: #f8f9ff;
|
||||||
|
border: 1px solid #e0e6f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #d0d7e2;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-item:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2c3e7a;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid #e8edf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-body {
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TabPage和Panel容器样式 */
|
||||||
|
.tab-page-container,
|
||||||
|
.area-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px dashed #c7d2ea;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #fafbff;
|
||||||
|
color: #6b7aa9;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -412,7 +412,6 @@
|
|||||||
|
|
||||||
<!-- 中心指示器:独立于中心区域容器,位于容器正中央 -->
|
<!-- 中心指示器:独立于中心区域容器,位于容器正中央 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="props.hideEdgeIndicators"
|
|
||||||
width="41"
|
width="41"
|
||||||
height="41"
|
height="41"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ const windowState = ref('最大化')
|
|||||||
// 浮动区域列表 - 每个area包含panels数组
|
// 浮动区域列表 - 每个area包含panels数组
|
||||||
const floatingAreas = ref([])
|
const floatingAreas = ref([])
|
||||||
|
|
||||||
|
// 隐藏区域列表 - 存储被隐藏的Area
|
||||||
|
const hiddenAreas = ref([])
|
||||||
|
|
||||||
// 容器引用
|
// 容器引用
|
||||||
const dockLayoutRef = ref(null)
|
const dockLayoutRef = ref(null)
|
||||||
// 主区域引用
|
// 主区域引用
|
||||||
@@ -329,6 +332,28 @@ const onToggleToolbar = (id) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 隐藏Area管理方法 - 第1步实现
|
||||||
|
// 将Area添加到隐藏列表
|
||||||
|
const addAreaToHiddenList = (area) => {
|
||||||
|
// 确保area有唯一标识符
|
||||||
|
if (!area.id) {
|
||||||
|
area.id = `hidden-area-${Date.now()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已经存在于隐藏列表中
|
||||||
|
const existingIndex = hiddenAreas.value.findIndex(h => h.id === area.id)
|
||||||
|
if (existingIndex === -1) {
|
||||||
|
// 添加到隐藏列表
|
||||||
|
hiddenAreas.value.push({
|
||||||
|
...area,
|
||||||
|
hiddenAt: new Date().toISOString()
|
||||||
|
})
|
||||||
|
console.log('Area已添加到隐藏列表:', area.id)
|
||||||
|
} else {
|
||||||
|
console.warn('Area已在隐藏列表中:', area.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Panel拖拽开始
|
// Panel拖拽开始
|
||||||
const onPanelDragStart = (areaId, event) => {
|
const onPanelDragStart = (areaId, event) => {
|
||||||
const area = floatingAreas.value.find(a => a.id === areaId)
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
||||||
@@ -436,17 +461,23 @@ const onPanelDragMove = (areaId, event) => {
|
|||||||
const onPanelDragEnd = () => {
|
const onPanelDragEnd = () => {
|
||||||
|
|
||||||
panelDragState.value.isDragging = false
|
panelDragState.value.isDragging = false
|
||||||
|
const currentAreaId = panelDragState.value.currentAreaId
|
||||||
panelDragState.value.currentAreaId = null
|
panelDragState.value.currentAreaId = null
|
||||||
|
|
||||||
|
// 3.1 在onPanelDragEnd方法中添加中心停靠检测
|
||||||
|
if (activeDockZone.value === 'center' && currentAreaId) {
|
||||||
|
// 处理中心停靠
|
||||||
|
const result = handleCenterDocking(currentAreaId)
|
||||||
|
if (!result.success) {
|
||||||
|
console.warn('中心停靠失败:', result.message)
|
||||||
|
} else {
|
||||||
|
console.log('中心停靠成功:', result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏停靠指示器
|
// 隐藏停靠指示器
|
||||||
showDockIndicator.value = false
|
showDockIndicator.value = false
|
||||||
activeDockZone.value = null
|
activeDockZone.value = null
|
||||||
|
|
||||||
// 如果有活动的停靠区域,可以在这里处理停靠逻辑
|
|
||||||
if (activeDockZone.value) {
|
|
||||||
|
|
||||||
// 这里可以实现具体的停靠逻辑
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Area拖拽开始
|
// Area拖拽开始
|
||||||
@@ -454,6 +485,24 @@ const onAreaDragStart = (areaId, event) => {
|
|||||||
const area = floatingAreas.value.find(a => a.id === areaId)
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
||||||
if (!area) return
|
if (!area) return
|
||||||
|
|
||||||
|
// 设置拖拽状态(类似TabPage拖拽)
|
||||||
|
panelDragState.value.isDragging = true
|
||||||
|
panelDragState.value.currentAreaId = areaId
|
||||||
|
panelDragState.value.startClientPos = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY
|
||||||
|
}
|
||||||
|
panelDragState.value.startAreaPos = {
|
||||||
|
x: area.x,
|
||||||
|
y: area.y
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化鼠标位置跟踪
|
||||||
|
currentMousePosition.value = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽开始时显示指示器
|
// 拖拽开始时显示指示器
|
||||||
showDockIndicator.value = true
|
showDockIndicator.value = true
|
||||||
|
|
||||||
@@ -477,12 +526,33 @@ const onAreaDragStart = (areaId, event) => {
|
|||||||
|
|
||||||
// Area拖拽移动
|
// Area拖拽移动
|
||||||
const onAreaDragMove = (areaId, event) => {
|
const onAreaDragMove = (areaId, event) => {
|
||||||
|
// 更新鼠标位置
|
||||||
|
currentMousePosition.value = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY
|
||||||
|
}
|
||||||
|
|
||||||
// 根据鼠标位置动态更新停靠区域
|
// 根据鼠标位置动态更新停靠区域
|
||||||
updateDockZoneByMousePosition(event.clientX, event.clientY)
|
updateDockZoneByMousePosition(event.clientX, event.clientY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Area拖拽结束
|
// Area拖拽结束
|
||||||
const onAreaDragEnd = (areaId, event) => {
|
const onAreaDragEnd = (areaId, event) => {
|
||||||
|
// 清理拖拽状态
|
||||||
|
panelDragState.value.isDragging = false
|
||||||
|
panelDragState.value.currentAreaId = null
|
||||||
|
|
||||||
|
// 3.1 在onAreaDragEnd方法中添加中心停靠检测
|
||||||
|
if (activeDockZone.value === 'center') {
|
||||||
|
// 处理中心停靠
|
||||||
|
const result = handleCenterDocking(areaId)
|
||||||
|
if (!result.success) {
|
||||||
|
console.warn('Area中心停靠失败:', result.message)
|
||||||
|
} else {
|
||||||
|
console.log('Area中心停靠成功:', result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏停靠指示器
|
// 隐藏停靠指示器
|
||||||
showDockIndicator.value = false
|
showDockIndicator.value = false
|
||||||
activeDockZone.value = null
|
activeDockZone.value = null
|
||||||
@@ -595,8 +665,20 @@ const onTabDragMove = (areaId, event) => {
|
|||||||
const onTabDragEnd = () => {
|
const onTabDragEnd = () => {
|
||||||
|
|
||||||
tabDragState.value.isDragging = false
|
tabDragState.value.isDragging = false
|
||||||
|
const currentAreaId = tabDragState.value.currentAreaId
|
||||||
tabDragState.value.currentAreaId = null
|
tabDragState.value.currentAreaId = null
|
||||||
|
|
||||||
|
// 3.2 在onTabDragEnd方法中添加中心停靠检测
|
||||||
|
if (activeDockZone.value === 'center' && currentAreaId) {
|
||||||
|
// 处理中心停靠
|
||||||
|
const result = handleCenterDocking(currentAreaId)
|
||||||
|
if (!result.success) {
|
||||||
|
console.warn('中心停靠失败:', result.message)
|
||||||
|
} else {
|
||||||
|
console.log('中心停靠成功:', result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏停靠指示器
|
// 隐藏停靠指示器
|
||||||
showDockIndicator.value = false
|
showDockIndicator.value = false
|
||||||
activeDockZone.value = null
|
activeDockZone.value = null
|
||||||
@@ -642,7 +724,22 @@ watch(floatingAreas, () => {
|
|||||||
|
|
||||||
// 当主区域内没有其他Area时,隐藏外部边缘指示器,只显示中心指示器
|
// 当主区域内没有其他Area时,隐藏外部边缘指示器,只显示中心指示器
|
||||||
|
|
||||||
// 根据鼠标位置动态更新停靠区域
|
// 第5步:优化UI指示器显示逻辑(当主区域为空时)
|
||||||
|
/**
|
||||||
|
* 检查主区域是否为空
|
||||||
|
* @returns {boolean} 主区域是否为空
|
||||||
|
*/
|
||||||
|
const isMainAreaEmpty = () => {
|
||||||
|
checkMainContentForAreas()
|
||||||
|
return !hasAreasInMainContent.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据鼠标位置动态更新停靠区域(优化版)
|
||||||
|
* 当主区域为空时,只显示中心指示器
|
||||||
|
* @param {number} mouseX - 鼠标X坐标
|
||||||
|
* @param {number} mouseY - 鼠标Y坐标
|
||||||
|
*/
|
||||||
const updateDockZoneByMousePosition = (mouseX, mouseY) => {
|
const updateDockZoneByMousePosition = (mouseX, mouseY) => {
|
||||||
if (!dockLayoutRef.value || !targetAreaRect.value) return
|
if (!dockLayoutRef.value || !targetAreaRect.value) return
|
||||||
|
|
||||||
@@ -658,8 +755,19 @@ const updateDockZoneByMousePosition = (mouseX, mouseY) => {
|
|||||||
|
|
||||||
let newActiveZone = null
|
let newActiveZone = null
|
||||||
|
|
||||||
|
// 检查主区域是否为空
|
||||||
|
const mainAreaEmpty = isMainAreaEmpty()
|
||||||
|
|
||||||
if (relativeX >= 0 && relativeX <= 1 && relativeY >= 0 && relativeY <= 1) {
|
if (relativeX >= 0 && relativeX <= 1 && relativeY >= 0 && relativeY <= 1) {
|
||||||
// 鼠标在目标区域内
|
// 鼠标在目标区域内
|
||||||
|
if (mainAreaEmpty) {
|
||||||
|
// 主区域为空时:只允许停靠到中心区域
|
||||||
|
if (relativeX >= 0.3 && relativeX <= 0.7 && relativeY >= 0.3 && relativeY <= 0.7) {
|
||||||
|
// 扩大的中心区域 (30%-70% 的中心区域)
|
||||||
|
newActiveZone = 'center'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 主区域不为空时:显示所有指示器选项
|
||||||
if (relativeY <= threshold) {
|
if (relativeY <= threshold) {
|
||||||
newActiveZone = 'top'
|
newActiveZone = 'top'
|
||||||
} else if (relativeY >= 1 - threshold) {
|
} else if (relativeY >= 1 - threshold) {
|
||||||
@@ -673,11 +781,17 @@ const updateDockZoneByMousePosition = (mouseX, mouseY) => {
|
|||||||
newActiveZone = 'center'
|
newActiveZone = 'center'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 只有当停靠区域改变时才更新,减少不必要的重新渲染
|
// 只有当停靠区域改变时才更新,减少不必要的重新渲染
|
||||||
if (activeDockZone.value !== newActiveZone) {
|
if (activeDockZone.value !== newActiveZone) {
|
||||||
activeDockZone.value = newActiveZone
|
activeDockZone.value = newActiveZone
|
||||||
|
// 如果激活的区域不为空,显示指示器;如果为空,隐藏指示器
|
||||||
|
if (newActiveZone) {
|
||||||
|
showDockIndicator.value = true
|
||||||
|
} else {
|
||||||
|
showDockIndicator.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,9 +904,243 @@ const onPanelMaximizeSync = ({ areaId, maximized }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 第2步:Area验证逻辑 - 目标Area内容区为空的情况
|
||||||
|
/**
|
||||||
|
* 检查Area是否可以停靠到中心区域
|
||||||
|
* @param {Object} sourceArea - 源Area对象
|
||||||
|
* @param {Object} targetArea - 目标Area对象(主区域)
|
||||||
|
* @returns {Object} 验证结果 {canDock: boolean, reason: string}
|
||||||
|
*/
|
||||||
|
const canDockToCenter = (sourceArea, targetArea) => {
|
||||||
|
// 验证1:检查源Area是否包含TabPage
|
||||||
|
if (!sourceArea) {
|
||||||
|
return {
|
||||||
|
canDock: false,
|
||||||
|
reason: '源Area不存在'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceArea.tabPages || sourceArea.tabPages.length === 0) {
|
||||||
|
return {
|
||||||
|
canDock: false,
|
||||||
|
reason: '源Area不包含TabPage,无法停靠'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证2:检查目标Area(主区域)- 支持Area级别停靠
|
||||||
|
if (targetArea === mainAreaRef.value) {
|
||||||
|
// Area级别停靠支持:将源Area的内容移动到主区域
|
||||||
|
// 不再要求主区域完全为空,允许Area停靠
|
||||||
|
console.log('Area级别停靠:源Area可停靠到主区域')
|
||||||
|
} else {
|
||||||
|
// 对于其他目标Area的验证逻辑可以后续扩展
|
||||||
|
return {
|
||||||
|
canDock: false,
|
||||||
|
reason: '暂不支持停靠到非主区域'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证3:确保包含TabPage的Area才能停靠到中心
|
||||||
|
const sourceTabPage = sourceArea.tabPages[0]
|
||||||
|
if (!sourceTabPage.panels || sourceTabPage.panels.length === 0) {
|
||||||
|
return {
|
||||||
|
canDock: false,
|
||||||
|
reason: '源Area的TabPage不包含Panel,无法停靠'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有验证通过
|
||||||
|
return {
|
||||||
|
canDock: true,
|
||||||
|
reason: '验证通过,可以停靠到中心区域'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第3步:中心停靠逻辑
|
||||||
|
/**
|
||||||
|
* 处理中心停靠的核心函数
|
||||||
|
* @param {string} areaId - 源Area的ID
|
||||||
|
* @returns {Object} 处理结果 {success: boolean, message: string}
|
||||||
|
*/
|
||||||
|
const handleCenterDocking = (areaId) => {
|
||||||
|
try {
|
||||||
|
// 3.4 查找源Area
|
||||||
|
const sourceArea = floatingAreas.value.find(a => a.id === areaId)
|
||||||
|
if (!sourceArea) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '找不到指定的Area'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.1-2.5 验证是否可以停靠到中心
|
||||||
|
const validation = canDockToCenter(sourceArea, mainAreaRef.value)
|
||||||
|
if (!validation.canDock) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `验证失败:${validation.reason}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.4 实现Area子组件移动到主区域的逻辑
|
||||||
|
// 将Area的内容移动到主区域(通过主区域Ref处理)
|
||||||
|
if (mainAreaRef.value && mainAreaRef.value.appendAreaContent) {
|
||||||
|
mainAreaRef.value.appendAreaContent(sourceArea)
|
||||||
|
} else {
|
||||||
|
console.warn('主区域Ref不可用,无法移动内容')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.5 实现Area添加到隐藏列表的逻辑
|
||||||
|
addAreaToHiddenList(sourceArea)
|
||||||
|
|
||||||
|
// 3.6 实现Area从浮动区域移除的逻辑
|
||||||
|
const index = floatingAreas.value.findIndex(a => a.id === areaId)
|
||||||
|
if (index !== -1) {
|
||||||
|
floatingAreas.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.7 更新主区域和浮动区域的响应式状态
|
||||||
|
checkMainContentForAreas()
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '成功停靠到中心区域'
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('中心停靠处理错误:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `停靠处理出错:${error.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第4步:在defineExpose中暴露隐藏列表管理方法
|
||||||
|
/**
|
||||||
|
* 获取隐藏Area列表
|
||||||
|
* @returns {Array} 隐藏Area列表
|
||||||
|
*/
|
||||||
|
const getHiddenAreas = () => {
|
||||||
|
return [...hiddenAreas.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从隐藏列表恢复Area到浮动区域
|
||||||
|
* @param {string} areaId - Area的ID
|
||||||
|
* @returns {Object} 恢复结果 {success: boolean, message: string}
|
||||||
|
*/
|
||||||
|
const restoreAreaFromHidden = (areaId) => {
|
||||||
|
try {
|
||||||
|
const hiddenIndex = hiddenAreas.value.findIndex(area => area.id === areaId)
|
||||||
|
if (hiddenIndex === -1) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '找不到指定的隐藏Area'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const areaToRestore = hiddenAreas.value[hiddenIndex]
|
||||||
|
|
||||||
|
// 检查是否已存在相同的Area(避免重复)
|
||||||
|
const existingIndex = floatingAreas.value.findIndex(area => area.id === areaId)
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Area已存在于浮动区域中'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复Area到浮动区域
|
||||||
|
floatingAreas.value.push(areaToRestore)
|
||||||
|
|
||||||
|
// 从隐藏列表移除
|
||||||
|
hiddenAreas.value.splice(hiddenIndex, 1)
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
checkMainContentForAreas()
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Area已成功恢复到浮动区域'
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('恢复Area时出错:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `恢复失败:${error.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从隐藏列表移除Area(不恢复)
|
||||||
|
* @param {string} areaId - Area的ID
|
||||||
|
* @returns {Object} 移除结果 {success: boolean, message: string}
|
||||||
|
*/
|
||||||
|
const removeFromHiddenList = (areaId) => {
|
||||||
|
try {
|
||||||
|
const hiddenIndex = hiddenAreas.value.findIndex(area => area.id === areaId)
|
||||||
|
if (hiddenIndex === -1) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '找不到指定的隐藏Area'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从隐藏列表移除
|
||||||
|
hiddenAreas.value.splice(hiddenIndex, 1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Area已从隐藏列表中移除'
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('移除Area时出错:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `移除失败:${error.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空隐藏列表
|
||||||
|
* @returns {Object} 清空结果 {success: boolean, message: string, removedCount: number}
|
||||||
|
*/
|
||||||
|
const clearHiddenList = () => {
|
||||||
|
try {
|
||||||
|
const count = hiddenAreas.value.length
|
||||||
|
hiddenAreas.value.splice(0, hiddenAreas.value.length)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `已清空隐藏列表,移除${count}个Area`,
|
||||||
|
removedCount: count
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清空隐藏列表时出错:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `清空失败:${error.message}`,
|
||||||
|
removedCount: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
addFloatingPanel
|
// 原有方法
|
||||||
|
addFloatingPanel,
|
||||||
|
|
||||||
|
// 第4步:隐藏列表管理方法
|
||||||
|
getHiddenAreas,
|
||||||
|
restoreAreaFromHidden,
|
||||||
|
removeFromHiddenList,
|
||||||
|
clearHiddenList
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,10 @@
|
|||||||
1. 当一个Panel被拖动时,显示停靠指示器。
|
1. 当一个Panel被拖动时,显示停靠指示器。
|
||||||
2. 当拖动Panel到指示器时,显示停靠区。
|
2. 当拖动Panel到指示器时,显示停靠区。
|
||||||
3. 当主区域内没有其他Area时,隐藏外部边缘指示器、中心区域容器,只显示中心指示器。
|
3. 当主区域内没有其他Area时,隐藏外部边缘指示器、中心区域容器,只显示中心指示器。
|
||||||
4. 当将Area拖动到中心指示器时
|
4. 当将源Panel拖动到中心指示器时
|
||||||
4.1. 如果Area只有一个直接子组件(TabPage),则将Area的子组件停靠到中心区域。这个Area保存到DockLayout的的隐藏列表中。
|
4.1. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区为空,则将源Area的子组件停靠到中心区域。这个源Area保存到DockLayout的的隐藏列表中。
|
||||||
|
5. 当将源Area拖动到中心指示器时
|
||||||
|
5.1. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区为空,则将源Area的子组件停靠到中心区域。这个源Area保存到DockLayout的的隐藏列表中。
|
||||||
|
5.2. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区已经包含一个TabPage,则将源Area的TabPage组件的每个标签页移动并添加到目标Area的Tabpage中。这个源Area保存到DockLayout的的隐藏列表中。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user