面板关闭事件接入总线
This commit is contained in:
@@ -586,9 +586,27 @@ const mergeAreaContent = (sourceArea) => {
|
|||||||
// 4.2.1 如果目标Area内容区为空,将源Area内容区的子组件添加到目标Area内容区
|
// 4.2.1 如果目标Area内容区为空,将源Area内容区的子组件添加到目标Area内容区
|
||||||
// console.log('[Area] 目标Area为空,添加源Area的子组件')
|
// console.log('[Area] 目标Area为空,添加源Area的子组件')
|
||||||
|
|
||||||
// 处理源Area的所有tabPages
|
// 处理源Area的所有tabPages(支持两种模式:tabPages和children)
|
||||||
if (sourceArea.tabPages && sourceArea.tabPages.length > 0) {
|
let tabPagesData = []
|
||||||
sourceArea.tabPages.forEach((tabPage, tabIndex) => {
|
|
||||||
|
if (sourceArea.tabPages && Array.isArray(sourceArea.tabPages)) {
|
||||||
|
// 模式1:直接tabPages结构
|
||||||
|
tabPagesData = sourceArea.tabPages
|
||||||
|
} else if (sourceArea.children && Array.isArray(sourceArea.children)) {
|
||||||
|
// 模式2:children结构,需要遍历查找TabPage类型
|
||||||
|
for (const child of sourceArea.children) {
|
||||||
|
if (child.type === 'TabPage' && child.children && child.children.type === 'Panel') {
|
||||||
|
tabPagesData.push({
|
||||||
|
id: child.id,
|
||||||
|
title: child.title,
|
||||||
|
panels: child.children.items || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabPagesData.length > 0) {
|
||||||
|
tabPagesData.forEach((tabPage, tabIndex) => {
|
||||||
// 保持原有的tabPage ID,确保Vue组件状态连续性
|
// 保持原有的tabPage ID,确保Vue组件状态连续性
|
||||||
const tabPageId = `merged-tabpage-${tabPage.id}`
|
const tabPageId = `merged-tabpage-${tabPage.id}`
|
||||||
const newPanels = (tabPage.panels || []).map((panel, panelIndex) => {
|
const newPanels = (tabPage.panels || []).map((panel, panelIndex) => {
|
||||||
@@ -637,9 +655,27 @@ const mergeAreaContent = (sourceArea) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理源Area的所有tabPages
|
// 处理源Area的所有tabPages(支持两种模式:tabPages和children)
|
||||||
if (sourceArea.tabPages && sourceArea.tabPages.length > 0) {
|
let tabPagesData = []
|
||||||
sourceArea.tabPages.forEach((sourceTabPage, tabIndex) => {
|
|
||||||
|
if (sourceArea.tabPages && Array.isArray(sourceArea.tabPages)) {
|
||||||
|
// 模式1:直接tabPages结构
|
||||||
|
tabPagesData = sourceArea.tabPages
|
||||||
|
} else if (sourceArea.children && Array.isArray(sourceArea.children)) {
|
||||||
|
// 模式2:children结构,需要遍历查找TabPage类型
|
||||||
|
for (const child of sourceArea.children) {
|
||||||
|
if (child.type === 'TabPage' && child.children && child.children.type === 'Panel') {
|
||||||
|
tabPagesData.push({
|
||||||
|
id: child.id,
|
||||||
|
title: child.title,
|
||||||
|
panels: child.children.items || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabPagesData.length > 0) {
|
||||||
|
tabPagesData.forEach((sourceTabPage, tabIndex) => {
|
||||||
if (sourceTabPage && sourceTabPage.panels) {
|
if (sourceTabPage && sourceTabPage.panels) {
|
||||||
// 保持原有Panel ID不变,避免Vue组件重新创建和状态丢失
|
// 保持原有Panel ID不变,避免Vue组件重新创建和状态丢失
|
||||||
const newPanels = sourceTabPage.panels.map((panel, panelIndex) => {
|
const newPanels = sourceTabPage.panels.map((panel, panelIndex) => {
|
||||||
@@ -664,7 +700,7 @@ const mergeAreaContent = (sourceArea) => {
|
|||||||
targetAreaId: props.id,
|
targetAreaId: props.id,
|
||||||
targetAreaHasContent: true, // 目标Area已有内容
|
targetAreaHasContent: true, // 目标Area已有内容
|
||||||
operation: 'merge-tabpages',
|
operation: 'merge-tabpages',
|
||||||
sourceTabPages: sourceArea.tabPages || []
|
sourceTabPages: tabPagesData
|
||||||
})
|
})
|
||||||
// 更新完成
|
// 更新完成
|
||||||
// // console.log(`[Area] 合并完成,现有TabPage共有 ${existingTabPage.tabPage.panels.length} 个Panel`)
|
// // console.log(`[Area] 合并完成,现有TabPage共有 ${existingTabPage.tabPage.panels.length} 个Panel`)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="panel bg-white overflow-hidden"
|
<div class="panel bg-white overflow-hidden"
|
||||||
:style="{ width: '100%', height: '100%' }">
|
:style="{ width: '100%', height: '100%' }"
|
||||||
|
:data-panel-id="id">
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<!-- 标题栏 -->
|
<!-- 标题栏 -->
|
||||||
<div class="title-bar h-6 bg-[#435d9c] text-white px-2 flex items-center justify-between select-none cursor-move"
|
<div class="title-bar h-6 bg-[#435d9c] text-white px-2 flex items-center justify-between select-none cursor-move"
|
||||||
@@ -111,7 +112,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, onMounted, onUnmounted } from 'vue';
|
||||||
|
import {
|
||||||
|
eventBus,
|
||||||
|
EVENT_TYPES,
|
||||||
|
emitEvent,
|
||||||
|
onEvent
|
||||||
|
} from './eventBus.js';
|
||||||
|
|
||||||
// 定义组件属性
|
// 定义组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -154,27 +161,76 @@ const props = defineProps({
|
|||||||
content: {
|
content: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
}
|
},
|
||||||
|
// 移除areaId属性,因为面板会被拖拽到不同区域
|
||||||
|
// 改为通过DOM动态获取当前所在区域
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义事件
|
// 事件订阅管理
|
||||||
const emit = defineEmits(['toggleCollapse', 'maximize', 'close', 'toggleToolbar', 'dragStart', 'dragMove', 'dragEnd']);
|
const subscriptions = new Map();
|
||||||
|
|
||||||
// 事件处理函数
|
// 动态获取当前面板所在的Area ID
|
||||||
|
const getCurrentAreaId = () => {
|
||||||
|
// 通过DOM向上查找最近的Area容器
|
||||||
|
const panelElement = document.querySelector(`[data-panel-id="${props.id}"]`);
|
||||||
|
if (panelElement) {
|
||||||
|
const areaElement = panelElement.closest('[data-area-id]');
|
||||||
|
if (areaElement) {
|
||||||
|
return areaElement.getAttribute('data-area-id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备用方法:通过父组件查找
|
||||||
|
// 向上查找vs-area容器
|
||||||
|
const parentElement = document.querySelector(`[data-panel-id="${props.id}"]`)?.parentElement;
|
||||||
|
if (parentElement) {
|
||||||
|
const areaElement = parentElement.closest('.vs-area');
|
||||||
|
if (areaElement) {
|
||||||
|
return areaElement.getAttribute('data-area-id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(`[Panel:${props.id}] 无法找到当前所在的Area,Panel可能未正确挂载`);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 事件处理函数 - 使用事件总线
|
||||||
const onToggleCollapse = () => {
|
const onToggleCollapse = () => {
|
||||||
emit('toggleCollapse', props.id);
|
console.log(`[Panel:${props.id}] 触发折叠/展开事件`)
|
||||||
|
emitEvent(EVENT_TYPES.PANEL_TOGGLE_COLLAPSE, {
|
||||||
|
panelId: props.id,
|
||||||
|
areaId: getCurrentAreaId(),
|
||||||
|
currentState: props.collapsed
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMaximize = () => {
|
const onMaximize = () => {
|
||||||
emit('maximize', props.id);
|
console.log(`[Panel:${props.id}] 触发最大化事件`)
|
||||||
|
emitEvent(EVENT_TYPES.PANEL_MAXIMIZE, {
|
||||||
|
panelId: props.id,
|
||||||
|
areaId: getCurrentAreaId(),
|
||||||
|
currentState: props.maximized
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
emit('close', props.id);
|
// console.log(`[Panel:${props.id}] 触发关闭请求事件`)
|
||||||
|
// 首先发送关闭请求,让父组件可以处理确认逻辑
|
||||||
|
emitEvent(EVENT_TYPES.PANEL_CLOSE_REQUEST, {
|
||||||
|
panelId: props.id,
|
||||||
|
areaId: getCurrentAreaId(),
|
||||||
|
panelTitle: props.title,
|
||||||
|
requestTime: Date.now()
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleToolbar = () => {
|
const onToggleToolbar = () => {
|
||||||
emit('toggleToolbar', props.id);
|
console.log(`[Panel:${props.id}] 触发工具栏切换事件`)
|
||||||
|
emitEvent(EVENT_TYPES.PANEL_TOGGLE_TOOLBAR, {
|
||||||
|
panelId: props.id,
|
||||||
|
areaId: getCurrentAreaId(),
|
||||||
|
currentState: props.toolbarExpanded
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// 拖拽相关状态
|
// 拖拽相关状态
|
||||||
@@ -185,12 +241,18 @@ const onDragStart = (e) => {
|
|||||||
// 只有当点击的是标题栏区域(不是按钮)时才触发拖拽
|
// 只有当点击的是标题栏区域(不是按钮)时才触发拖拽
|
||||||
if (!e.target.closest('.title-bar-buttons') && !e.target.closest('button')) {
|
if (!e.target.closest('.title-bar-buttons') && !e.target.closest('button')) {
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
// 传递panelId和鼠标位置
|
console.log(`[Panel:${props.id}] 开始拖拽`)
|
||||||
emit('dragStart', {
|
|
||||||
clientX: e.clientX,
|
// 使用事件总线触发拖拽开始事件
|
||||||
clientY: e.clientY,
|
emitEvent(EVENT_TYPES.PANEL_DRAG_START, {
|
||||||
panelId: props.id
|
panelId: props.id,
|
||||||
});
|
areaId: getCurrentAreaId(),
|
||||||
|
position: { x: e.clientX, y: e.clientY },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, {
|
||||||
|
source: { component: 'Panel', panelId: props.id }
|
||||||
|
})
|
||||||
|
|
||||||
// 防止文本选择和默认行为
|
// 防止文本选择和默认行为
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -208,11 +270,16 @@ const onDragMove = (e) => {
|
|||||||
// 防止文本选择和默认行为
|
// 防止文本选择和默认行为
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
emit('dragMove', {
|
|
||||||
clientX: e.clientX,
|
// 使用事件总线触发拖拽移动事件
|
||||||
clientY: e.clientY,
|
emitEvent(EVENT_TYPES.PANEL_DRAG_MOVE, {
|
||||||
panelId: props.id
|
panelId: props.id,
|
||||||
});
|
areaId: getCurrentAreaId(),
|
||||||
|
position: { x: e.clientX, y: e.clientY },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, {
|
||||||
|
source: { component: 'Panel', panelId: props.id }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,7 +287,16 @@ const onDragMove = (e) => {
|
|||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
emit('dragEnd', { panelId: props.id });
|
console.log(`[Panel:${props.id}] 结束拖拽`)
|
||||||
|
|
||||||
|
// 使用事件总线触发拖拽结束事件
|
||||||
|
emitEvent(EVENT_TYPES.PANEL_DRAG_END, {
|
||||||
|
panelId: props.id,
|
||||||
|
areaId: getCurrentAreaId(),
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, {
|
||||||
|
source: { component: 'Panel', panelId: props.id }
|
||||||
|
})
|
||||||
|
|
||||||
// 拖拽结束后移除事件监听器
|
// 拖拽结束后移除事件监听器
|
||||||
document.removeEventListener('mousemove', onDragMove);
|
document.removeEventListener('mousemove', onDragMove);
|
||||||
@@ -228,6 +304,61 @@ const onDragEnd = () => {
|
|||||||
document.removeEventListener('mouseleave', onDragEnd);
|
document.removeEventListener('mouseleave', onDragEnd);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听面板关闭事件,更新组件状态(可选)
|
||||||
|
*/
|
||||||
|
const setupEventListeners = () => {
|
||||||
|
// 监听面板最大化同步事件
|
||||||
|
const unsubscribeMaximizeSync = onEvent(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, (data) => {
|
||||||
|
if (data.panelId === props.id) {
|
||||||
|
// 这里可以添加最大化状态同步的逻辑
|
||||||
|
console.log(`[Panel:${props.id}] 收到最大化同步事件`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
subscriptions.set('maximizeSync', unsubscribeMaximizeSync)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有事件订阅
|
||||||
|
*/
|
||||||
|
const cleanupEventListeners = () => {
|
||||||
|
subscriptions.forEach((unsubscribe) => {
|
||||||
|
if (typeof unsubscribe === 'function') {
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
subscriptions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(`[Panel:${props.id}] 组件已挂载`)
|
||||||
|
|
||||||
|
// 启用调试模式(开发环境)
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
eventBus.setDebugMode(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置事件监听器
|
||||||
|
setupEventListeners()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
console.log(`[Panel:${props.id}] 组件即将卸载`)
|
||||||
|
|
||||||
|
// 清理事件监听器
|
||||||
|
cleanupEventListeners()
|
||||||
|
|
||||||
|
// 确保拖拽状态已清理
|
||||||
|
if (isDragging) {
|
||||||
|
document.removeEventListener('mousemove', onDragMove);
|
||||||
|
document.removeEventListener('mouseup', onDragEnd);
|
||||||
|
document.removeEventListener('mouseleave', onDragEnd);
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -124,3 +124,8 @@
|
|||||||
- 不影响Panel的最大化/还原功能
|
- 不影响Panel的最大化/还原功能
|
||||||
- 不影响TabPage的切换和关闭功能
|
- 不影响TabPage的切换和关闭功能
|
||||||
|
|
||||||
|
### 浮动面板关闭逻辑
|
||||||
|
1、从floatingAreas数组中根据areaId找到目标Area
|
||||||
|
2、遍历Area的 children 数组,查找类型为 'TabPage' 的子组件
|
||||||
|
3、从TabPage的children.items中获取Panel数组
|
||||||
|
4、如果目标Area只包含一个TabPage,TabPage只有一个Panel,则关闭Area.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mitt } from 'mitt'
|
import mitt from 'mitt'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
// 事件类型常量
|
// 事件类型常量
|
||||||
|
|||||||
Reference in New Issue
Block a user