### 主要修复内容
1. 事件监听器泄漏 :修复了事件监听器泄漏问题,确保所有监听器都能被正确清理 2. 组件生命周期管理 :为所有组件添加了onUnmounted钩子,确保资源能被正确清理 3. props大小写问题 :修复了props名称大小写不匹配问题 4. 延迟初始化 :将事件管理器的初始化从立即初始化改为延迟初始化,提高性能 5. flexbox布局修复 :修复了flexbox布局问题,确保组件能正确显示 6. 代码结构优化 :简化了代码结构,提高了可维护性 这些修改解决了事件监听器泄漏、组件生命周期管理和props传递等问题,提高了代码的质量和可维护性。
This commit is contained in:
@@ -132,7 +132,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, computed, defineEmits, ref, onMounted, watch, defineExpose } from 'vue'
|
import { defineProps, computed, defineEmits, ref, onMounted, onUnmounted, watch, defineExpose } from 'vue'
|
||||||
import TabPage from './TabPage.vue'
|
import TabPage from './TabPage.vue'
|
||||||
import Panel from './Panel.vue'
|
import Panel from './Panel.vue'
|
||||||
import { zIndexManager, Z_INDEX_LAYERS } from './dockLayers.js'
|
import { zIndexManager, Z_INDEX_LAYERS } from './dockLayers.js'
|
||||||
@@ -142,7 +142,7 @@ const props = defineProps({
|
|||||||
title: { type: String, default: '面板区' },
|
title: { type: String, default: '面板区' },
|
||||||
resizable: { type: Boolean, default: true },
|
resizable: { type: Boolean, default: true },
|
||||||
// 初始状态(支持中文值)
|
// 初始状态(支持中文值)
|
||||||
WindowState: { type: String, default: '正常' },
|
windowState: { type: String, default: '正常' },
|
||||||
// 默认尺寸
|
// 默认尺寸
|
||||||
width: { type: Number, default: 300 },
|
width: { type: Number, default: 300 },
|
||||||
height: { type: Number, default: 250 },
|
height: { type: Number, default: 250 },
|
||||||
@@ -155,7 +155,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
const localState = ref(props.WindowState)
|
const localState = ref(props.windowState)
|
||||||
// 保存原始位置和大小信息
|
// 保存原始位置和大小信息
|
||||||
const originalPosition = ref({
|
const originalPosition = ref({
|
||||||
width: props.width,
|
width: props.width,
|
||||||
@@ -200,8 +200,8 @@ watch(() => props.top, (newTop) => {
|
|||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 监听WindowState变化,同步更新localState
|
// 监听windowState变化,同步更新localState
|
||||||
watch(() => props.WindowState, (newState) => {
|
watch(() => props.windowState, (newState) => {
|
||||||
if (newState !== localState.value) {
|
if (newState !== localState.value) {
|
||||||
localState.value = newState
|
localState.value = newState
|
||||||
|
|
||||||
@@ -568,6 +568,18 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理全局事件监听器
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理拖拽相关的全局事件监听器
|
||||||
|
document.removeEventListener('mousemove', onDragMove)
|
||||||
|
document.removeEventListener('mouseup', onDragEnd)
|
||||||
|
|
||||||
|
// 清理调整大小相关的全局事件监听器
|
||||||
|
document.removeEventListener('mousemove', onResizeMove)
|
||||||
|
document.removeEventListener('mouseup', onResizeEnd)
|
||||||
|
document.removeEventListener('mouseleave', onResizeEnd)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 处理Area合并内容
|
// 处理Area合并内容
|
||||||
@@ -589,17 +601,15 @@ const mergeAreaContent = (sourceArea) => {
|
|||||||
// 处理源Area的所有tabPages(支持两种模式:tabPages和children)
|
// 处理源Area的所有tabPages(支持两种模式:tabPages和children)
|
||||||
let tabPagesData = []
|
let tabPagesData = []
|
||||||
|
|
||||||
if (sourceArea.tabPages && Array.isArray(sourceArea.tabPages)) {
|
if (sourceArea.children) {
|
||||||
// 模式1:直接tabPages结构
|
// 统一处理children结构
|
||||||
tabPagesData = sourceArea.tabPages
|
const childrenArray = Array.isArray(sourceArea.children) ? sourceArea.children : [sourceArea.children]
|
||||||
} else if (sourceArea.children && Array.isArray(sourceArea.children)) {
|
for (const child of childrenArray) {
|
||||||
// 模式2:children结构,需要遍历查找TabPage类型
|
if (child.type === 'TabPage' && child.children) {
|
||||||
for (const child of sourceArea.children) {
|
|
||||||
if (child.type === 'TabPage' && child.children && child.children.type === 'Panel') {
|
|
||||||
tabPagesData.push({
|
tabPagesData.push({
|
||||||
id: child.id,
|
id: child.id,
|
||||||
title: child.title,
|
title: child.title,
|
||||||
panels: child.children.items || []
|
panels: Array.isArray(child.children) ? child.children : (child.children.type === 'Panel' ? [child.children] : [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,20 +665,18 @@ const mergeAreaContent = (sourceArea) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理源Area的所有tabPages(支持两种模式:tabPages和children)
|
// 处理源Area的所有children(统一使用children结构)
|
||||||
let tabPagesData = []
|
let tabPagesData = []
|
||||||
|
|
||||||
if (sourceArea.tabPages && Array.isArray(sourceArea.tabPages)) {
|
if (sourceArea.children) {
|
||||||
// 模式1:直接tabPages结构
|
// 统一处理children结构
|
||||||
tabPagesData = sourceArea.tabPages
|
const childrenArray = Array.isArray(sourceArea.children) ? sourceArea.children : [sourceArea.children]
|
||||||
} else if (sourceArea.children && Array.isArray(sourceArea.children)) {
|
for (const child of childrenArray) {
|
||||||
// 模式2:children结构,需要遍历查找TabPage类型
|
if (child.type === 'TabPage' && child.children) {
|
||||||
for (const child of sourceArea.children) {
|
|
||||||
if (child.type === 'TabPage' && child.children && child.children.type === 'Panel') {
|
|
||||||
tabPagesData.push({
|
tabPagesData.push({
|
||||||
id: child.id,
|
id: child.id,
|
||||||
title: child.title,
|
title: child.title,
|
||||||
panels: child.children.items || []
|
panels: Array.isArray(child.children) ? child.children : (child.children.type === 'Panel' ? [child.children] : [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative;">
|
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative; width: 100%; height: 100%;">
|
||||||
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
|
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
|
||||||
<DockIndicator
|
<DockIndicator
|
||||||
:visible="showDockIndicator"
|
:visible="showDockIndicator"
|
||||||
@@ -13,18 +13,14 @@
|
|||||||
<!-- 主区域 - 添加ref引用 -->
|
<!-- 主区域 - 添加ref引用 -->
|
||||||
<Area
|
<Area
|
||||||
ref="mainAreaRef"
|
ref="mainAreaRef"
|
||||||
:WindowState="windowState"
|
:windowState="windowState"
|
||||||
:showTitleBar="false"
|
:showTitleBar="false"
|
||||||
title="主区域"
|
title="主区域"
|
||||||
:style="{ position: 'relative', width: '100%', height: '100%', zIndex: 1 }"
|
|
||||||
@dragover="handleMainAreaDragOver"
|
@dragover="handleMainAreaDragOver"
|
||||||
@dragleave="handleMainAreaDragLeave"
|
@dragleave="handleMainAreaDragLeave"
|
||||||
@area-merged="onAreaMerged"
|
@area-merged="onAreaMerged"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- 主区域内容区 -->
|
|
||||||
<div class="main-content-container" style="position: relative; width: 100%; height: 100%;">
|
<div class="main-content-container" style="position: relative; width: 100%; height: 100%;">
|
||||||
<!-- ResizeBar组件渲染区 -->
|
|
||||||
<ResizeBar
|
<ResizeBar
|
||||||
v-for="resizeBar in mainAreaResizeBars"
|
v-for="resizeBar in mainAreaResizeBars"
|
||||||
:key="resizeBar.id"
|
:key="resizeBar.id"
|
||||||
@@ -74,7 +70,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, defineEmits } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, defineEmits } from 'vue'
|
||||||
import Area from './Area.vue';
|
import Area from './Area.vue';
|
||||||
import Panel from './Panel.vue';
|
import Panel from './Panel.vue';
|
||||||
import TabPage from './TabPage.vue';
|
import TabPage from './TabPage.vue';
|
||||||
@@ -291,6 +287,24 @@ const setupEventListeners = () => {
|
|||||||
// 清理函数
|
// 清理函数
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
// 清理事件监听器和其他资源
|
// 清理事件监听器和其他资源
|
||||||
|
console.log('🧹 开始清理DockLayout资源');
|
||||||
|
|
||||||
|
// 清理浮动区域
|
||||||
|
floatingAreas.value = [];
|
||||||
|
|
||||||
|
// 清理隐藏区域
|
||||||
|
hiddenAreas.value = [];
|
||||||
|
|
||||||
|
// 清理主区域ResizeBar
|
||||||
|
mainAreaResizeBars.value = [];
|
||||||
|
|
||||||
|
// 清理停靠指示器状态
|
||||||
|
showDockIndicator.value = false;
|
||||||
|
currentMousePosition.value = { x: 0, y: 0 };
|
||||||
|
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
|
||||||
|
activeDockZone.value = null;
|
||||||
|
|
||||||
|
console.log('✅ DockLayout资源清理完成');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 轻量级隐藏区域管理
|
// 轻量级隐藏区域管理
|
||||||
@@ -356,7 +370,17 @@ const addFloatingPanel = (panel) => {
|
|||||||
const safePanel = panel || {
|
const safePanel = panel || {
|
||||||
id: `panel-${Date.now()}`,
|
id: `panel-${Date.now()}`,
|
||||||
title: '新建面板',
|
title: '新建面板',
|
||||||
content: '默认内容'
|
content: {
|
||||||
|
color: '#435d9c',
|
||||||
|
title: '默认面板内容',
|
||||||
|
type: 'default',
|
||||||
|
timestamp: new Date().toLocaleString(),
|
||||||
|
data: [
|
||||||
|
{ id: 1, label: '示例数据1', value: '123' },
|
||||||
|
{ id: 2, label: '示例数据2', value: '456' },
|
||||||
|
{ id: 3, label: '示例数据3', value: '789' }
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const newArea = {
|
const newArea = {
|
||||||
@@ -366,11 +390,16 @@ const addFloatingPanel = (panel) => {
|
|||||||
width: 300,
|
width: 300,
|
||||||
height: 200,
|
height: 200,
|
||||||
zIndex: zIndexManager.getFloatingAreaZIndex(`area-${Date.now()}`),
|
zIndex: zIndexManager.getFloatingAreaZIndex(`area-${Date.now()}`),
|
||||||
tabPages: [{
|
// 使用children结构以兼容Render组件的渲染逻辑
|
||||||
id: `tabpage-${Date.now()}`,
|
children: {
|
||||||
title: safePanel.title || '新建面板',
|
type: 'TabPage',
|
||||||
panels: [safePanel]
|
children: [{
|
||||||
}]
|
...safePanel,
|
||||||
|
id: `panel-${Date.now()}`,
|
||||||
|
title: safePanel.title || '新建面板',
|
||||||
|
type: 'Panel'
|
||||||
|
}]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
floatingAreas.value.push(newArea)
|
floatingAreas.value.push(newArea)
|
||||||
return newArea.id
|
return newArea.id
|
||||||
@@ -382,7 +411,7 @@ const findOrCreateMainAreaTabPage = () => {
|
|||||||
return {
|
return {
|
||||||
id: 'main-area-tabpage',
|
id: 'main-area-tabpage',
|
||||||
title: '主区域',
|
title: '主区域',
|
||||||
panels: []
|
items: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +421,13 @@ onMounted(() => {
|
|||||||
console.log('DockLayout component mounted');
|
console.log('DockLayout component mounted');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理资源
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理事件监听器和其他资源
|
||||||
|
console.log('DockLayout component unmounted');
|
||||||
|
cleanup();
|
||||||
|
})
|
||||||
|
|
||||||
// 暴露轻量级接口给父组件
|
// 暴露轻量级接口给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
// 基础数据
|
// 基础数据
|
||||||
|
|||||||
@@ -5,72 +5,14 @@
|
|||||||
v-bind="componentProps"
|
v-bind="componentProps"
|
||||||
v-on="componentListeners"
|
v-on="componentListeners"
|
||||||
>
|
>
|
||||||
<!-- 对于有children配置的情况,需要手动渲染子组件 -->
|
<!-- 统一处理children属性,不区分组件类型,只要有children就渲染 -->
|
||||||
<template v-if="type === 'Area' && config.children">
|
<template v-if="config.children">
|
||||||
<!-- 如果children是数组 -->
|
<!-- 统一处理children为数组或单个对象的情况 -->
|
||||||
<template v-if="Array.isArray(config.children)">
|
<div v-for="child in Array.isArray(config.children) ? config.children : [config.children]" :key="child.id" style="width: 100%; height: 100%;">
|
||||||
<div v-for="child in config.children" :key="child.id" style="width: 100%; height: 100%;">
|
|
||||||
<Render
|
|
||||||
v-if="child.type === 'TabPage'"
|
|
||||||
:type="'TabPage'"
|
|
||||||
:config="child"
|
|
||||||
:debug="debug"
|
|
||||||
@tab-change="$emit('tab-change', $event)"
|
|
||||||
@tab-close="$emit('tab-close', $event)"
|
|
||||||
@tab-add="$emit('tab-add', $event)"
|
|
||||||
@tabDragStart="$emit('tabDragStart', $event)"
|
|
||||||
@tabDragMove="$emit('tabDragMove', $event)"
|
|
||||||
@tabDragEnd="$emit('tabDragEnd', $event)"
|
|
||||||
@toggleCollapse="$emit('toggleCollapse', $event)"
|
|
||||||
@maximize="$emit('maximize', $event)"
|
|
||||||
@close="$emit('close', $event)"
|
|
||||||
@toggleToolbar="$emit('toggleToolbar', $event)"
|
|
||||||
@dragStart="$emit('dragStart', $event)"
|
|
||||||
@dragMove="$emit('dragMove', $event)"
|
|
||||||
@dragEnd="$emit('dragEnd', $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!-- 如果children是对象 -->
|
|
||||||
<template v-else-if="config.children.type === 'TabPage'">
|
|
||||||
<div v-for="tabPage in (Array.isArray(config.children.items) ? config.children.items : [config.children])" :key="tabPage.id" style="width: 100%; height: 100%;">
|
|
||||||
<Render
|
|
||||||
:type="'TabPage'"
|
|
||||||
:config="tabPage"
|
|
||||||
:debug="debug"
|
|
||||||
@tab-change="$emit('tab-change', $event)"
|
|
||||||
@tab-close="$emit('tab-close', $event)"
|
|
||||||
@tab-add="$emit('tab-add', $event)"
|
|
||||||
@tabDragStart="$emit('tabDragStart', $event)"
|
|
||||||
@tabDragMove="$emit('tabDragMove', $event)"
|
|
||||||
@tabDragEnd="$emit('tabDragEnd', $event)"
|
|
||||||
@toggleCollapse="$emit('toggleCollapse', $event)"
|
|
||||||
@maximize="$emit('maximize', $event)"
|
|
||||||
@close="$emit('close', $event)"
|
|
||||||
@toggleToolbar="$emit('toggleToolbar', $event)"
|
|
||||||
@dragStart="$emit('dragStart', $event)"
|
|
||||||
@dragMove="$emit('dragMove', $event)"
|
|
||||||
@dragEnd="$emit('dragEnd', $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- TabPage组件的panels prop -->
|
|
||||||
<template v-else-if="type === 'TabPage' && config.children && config.children.type === 'Panel'">
|
|
||||||
<!-- 手动渲染Panel组件 -->
|
|
||||||
<div v-for="panel in (Array.isArray(config.children.items) ? config.children.items : [config.children])" :key="panel.id" style="width: 100%; height: 100%;">
|
|
||||||
<Render
|
<Render
|
||||||
:type="'Panel'"
|
:type="child.type"
|
||||||
:config="panel"
|
:config="child"
|
||||||
:debug="debug"
|
:debug="debug"
|
||||||
@toggleCollapse="$emit('toggleCollapse', $event)"
|
|
||||||
@maximize="$emit('maximize', $event)"
|
|
||||||
@close="$emit('close', $event)"
|
|
||||||
@toggleToolbar="$emit('toggleToolbar', $event)"
|
|
||||||
@dragStart="$emit('dragStart', $event)"
|
|
||||||
@dragMove="$emit('dragMove', $event)"
|
|
||||||
@dragEnd="$emit('dragEnd', $event)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -143,20 +85,13 @@ const componentProps = computed(() => {
|
|||||||
showTitleBar: config.showTitleBar !== false,
|
showTitleBar: config.showTitleBar !== false,
|
||||||
left: config.left,
|
left: config.left,
|
||||||
top: config.top,
|
top: config.top,
|
||||||
draggable: config.draggable !== false,
|
draggable: config.draggable !== false
|
||||||
// 如果有children配置,将其转换为TabPages格式
|
|
||||||
tabPages: config.children ? (
|
|
||||||
config.children.type === 'TabPage'
|
|
||||||
? (Array.isArray(config.children.items) ? config.children.items : [config.children])
|
|
||||||
: config.children // 如果直接是tabPages数组
|
|
||||||
) : config.tabPages || []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'TabPage':
|
case 'TabPage':
|
||||||
return {
|
return {
|
||||||
id: config.id,
|
id: config.id,
|
||||||
title: config.title || '标签页',
|
title: config.title || '标签页',
|
||||||
panels: config.panels || config.children?.items || [],
|
|
||||||
showTabs: config.showTabs !== false,
|
showTabs: config.showTabs !== false,
|
||||||
tabPosition: config.tabPosition || 'top'
|
tabPosition: config.tabPosition || 'top'
|
||||||
}
|
}
|
||||||
@@ -243,6 +178,22 @@ const componentListeners = computed(() => {
|
|||||||
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabAdd:`, event)
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabAdd:`, event)
|
||||||
emit('tabAdd', event)
|
emit('tabAdd', event)
|
||||||
}
|
}
|
||||||
|
allListeners['tabDragStart'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabDragStart:`, event)
|
||||||
|
emit('tabDragStart', event)
|
||||||
|
}
|
||||||
|
allListeners['tabDragMove'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabDragMove:`, event)
|
||||||
|
emit('tabDragMove', event)
|
||||||
|
}
|
||||||
|
allListeners['tabDragEnd'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] tabDragEnd:`, event)
|
||||||
|
emit('tabDragEnd', event)
|
||||||
|
}
|
||||||
|
allListeners['toggleCollapse'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] toggleCollapse:`, event)
|
||||||
|
emit('toggleCollapse', event)
|
||||||
|
}
|
||||||
allListeners['maximize'] = (event) => {
|
allListeners['maximize'] = (event) => {
|
||||||
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] maximize:`, event)
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] maximize:`, event)
|
||||||
emit('maximize', event)
|
emit('maximize', event)
|
||||||
@@ -251,6 +202,22 @@ const componentListeners = computed(() => {
|
|||||||
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] close:`, event)
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] close:`, event)
|
||||||
emit('close', event)
|
emit('close', event)
|
||||||
}
|
}
|
||||||
|
allListeners['toggleToolbar'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] toggleToolbar:`, event)
|
||||||
|
emit('toggleToolbar', event)
|
||||||
|
}
|
||||||
|
allListeners['dragStart'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] dragStart:`, event)
|
||||||
|
emit('dragStart', event)
|
||||||
|
}
|
||||||
|
allListeners['dragMove'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] dragMove:`, event)
|
||||||
|
emit('dragMove', event)
|
||||||
|
}
|
||||||
|
allListeners['dragEnd'] = (event) => {
|
||||||
|
// if (props.debug) console.log(`[Render-TabPage ${props.config.id}] dragEnd:`, event)
|
||||||
|
emit('dragEnd', event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.type === 'Panel') {
|
if (props.type === 'Panel') {
|
||||||
@@ -288,26 +255,13 @@ const componentListeners = computed(() => {
|
|||||||
return allListeners
|
return allListeners
|
||||||
})
|
})
|
||||||
|
|
||||||
// 规范化子组件数据(用于Area组件的slot内容)
|
|
||||||
const normalizedChildren = computed(() => {
|
|
||||||
if (props.type !== 'Area' || !props.config.children) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果children是数组,直接返回
|
|
||||||
if (Array.isArray(props.config.children)) {
|
|
||||||
return props.config.children
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果children是单个对象,包装成数组
|
|
||||||
return [props.config.children]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 暴露组件实例方法
|
// 暴露组件实例方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getComponentType: () => props.type,
|
getComponentType: () => props.type,
|
||||||
getConfig: () => props.config,
|
getConfig: () => props.config,
|
||||||
getComponentProps: componentProps.value,
|
getComponentProps: () => componentProps.value,
|
||||||
isDebugMode: () => props.debug
|
isDebugMode: () => props.debug
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tab-page" :class="[`tab-page-${tabPosition}`]" style="width: 100%; height: 100%;">
|
<div class="tab-page" :class="[`tab-page-${tabPosition}`]" style="width: 100%; height: 100%;">
|
||||||
<!-- 顶部标签栏 -->
|
<!-- 顶部标签栏 -->
|
||||||
<div v-if="tabPosition === 'top' && shouldShowTabs && panels && panels.length > 0" class="tab-header tab-header-horizontal">
|
<div v-if="tabPosition === 'top' && shouldShowTabs" class="tab-header tab-header-horizontal">
|
||||||
<div
|
<div
|
||||||
v-for="(panel, index) in panels"
|
v-for="(item, index) in slotItems"
|
||||||
:key="panel.id"
|
:key="index"
|
||||||
:class="['tab-item', { 'active': activeTabIndex === index }]"
|
:class="['tab-item', { 'active': activeTabIndex === index }]"
|
||||||
@click="setActiveTab(index)"
|
@click="setActiveTab(index)"
|
||||||
@mousedown="onTabDragStart(index, $event)"
|
@mousedown="onTabDragStart(index, $event)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between h-full px-3">
|
<div class="flex items-center justify-between h-full px-3">
|
||||||
<span class="tab-title">{{ panel.title }}</span>
|
<span class="tab-title">{{ item?.title || '未命名' }}</span>
|
||||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="activeTabIndex !== index"
|
v-if="activeTabIndex !== index"
|
||||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
|
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
|
||||||
@click.stop="closeTab(panel.id)"
|
@click.stop="closeTab(item?.id)"
|
||||||
aria-label="关闭"
|
aria-label="关闭"
|
||||||
>
|
>
|
||||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||||
@@ -29,21 +29,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 左侧标签栏 -->
|
<!-- 左侧标签栏 -->
|
||||||
<div v-if="tabPosition === 'left' && shouldShowTabs && panels && panels.length > 0" class="tab-header tab-header-vertical tab-header-left">
|
<div v-if="tabPosition === 'left' && shouldShowTabs" class="tab-header tab-header-vertical tab-header-left">
|
||||||
<div
|
<div
|
||||||
v-for="(panel, index) in panels"
|
v-for="(item, index) in slotItems"
|
||||||
:key="panel.id"
|
:key="index"
|
||||||
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
||||||
@click="setActiveTab(index)"
|
@click="setActiveTab(index)"
|
||||||
@mousedown="onTabDragStart(index, $event)"
|
@mousedown="onTabDragStart(index, $event)"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
||||||
<span class="tab-title-vertical">{{ panel.title }}</span>
|
<span class="tab-title-vertical">{{ item?.title || '未命名' }}</span>
|
||||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="activeTabIndex !== index"
|
v-if="activeTabIndex !== index"
|
||||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
||||||
@click.stop="closeTab(panel.id)"
|
@click.stop="closeTab(item?.id)"
|
||||||
aria-label="关闭"
|
aria-label="关闭"
|
||||||
>
|
>
|
||||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||||
@@ -57,21 +57,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧标签栏 -->
|
<!-- 右侧标签栏 -->
|
||||||
<div v-if="tabPosition === 'right' && shouldShowTabs && panels && panels.length > 0" class="tab-header tab-header-vertical tab-header-right">
|
<div v-if="tabPosition === 'right' && shouldShowTabs" class="tab-header tab-header-vertical tab-header-right">
|
||||||
<div
|
<div
|
||||||
v-for="(panel, index) in panels"
|
v-for="(item, index) in slotItems"
|
||||||
:key="panel.id"
|
:key="index"
|
||||||
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
||||||
@click="setActiveTab(index)"
|
@click="setActiveTab(index)"
|
||||||
@mousedown="onTabDragStart(index, $event)"
|
@mousedown="onTabDragStart(index, $event)"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
||||||
<span class="tab-title-vertical">{{ panel.title }}</span>
|
<span class="tab-title-vertical">{{ item?.title || '未命名' }}</span>
|
||||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="activeTabIndex !== index"
|
v-if="activeTabIndex !== index"
|
||||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
||||||
@click.stop="closeTab(panel.id)"
|
@click.stop="closeTab(item?.id)"
|
||||||
aria-label="关闭"
|
aria-label="关闭"
|
||||||
>
|
>
|
||||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||||
@@ -87,59 +87,30 @@
|
|||||||
|
|
||||||
<!-- Tab页内容区域 -->
|
<!-- Tab页内容区域 -->
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<!-- 渲染当前激活的Panel内容 -->
|
<!-- 直接渲染插槽内容 -->
|
||||||
<template v-if="activeTabIndex >= 0 && activeTabIndex < panels.length">
|
<slot></slot>
|
||||||
<div
|
|
||||||
v-for="(panel, index) in panels"
|
|
||||||
:key="panel.id"
|
|
||||||
v-show="index === activeTabIndex"
|
|
||||||
class="tab-panel"
|
|
||||||
:class="{ active: index === activeTabIndex }"
|
|
||||||
>
|
|
||||||
<!-- 使用Panel组件渲染面板内容 -->
|
|
||||||
<Panel
|
|
||||||
:id="panel.id"
|
|
||||||
:title="panel.title"
|
|
||||||
:x="panel.x || 0"
|
|
||||||
:y="panel.y || 0"
|
|
||||||
:width="panel.width || 300"
|
|
||||||
:height="panel.height || 200"
|
|
||||||
:collapsed="panel.collapsed || false"
|
|
||||||
:toolbar-expanded="panel.toolbarExpanded || false"
|
|
||||||
:maximized="panel.maximized || false"
|
|
||||||
:content="panel.content"
|
|
||||||
@toggle-collapse="$emit('toggleCollapse', $event)"
|
|
||||||
@maximize="(event) => { /* console.log('🔸 TabPage转发最大化事件:', event); */ $emit('maximize', event); }"
|
|
||||||
@close="$emit('close', $event)"
|
|
||||||
@toggle-toolbar="$emit('toggleToolbar', $event)"
|
|
||||||
@dragStart="$emit('dragStart', $event)"
|
|
||||||
@dragMove="$emit('dragMove', $event)"
|
|
||||||
@dragEnd="$emit('dragEnd', $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!-- 空状态提示 -->
|
<!-- 空状态提示 -->
|
||||||
<div v-else class="tab-empty">
|
<div v-if="slotItems.length === 0" class="tab-empty">
|
||||||
<span>没有可显示的内容</span>
|
<span>没有可显示的内容</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部标签栏 - 移动到最后确保在底部显示 -->
|
<!-- 底部标签栏 - 移动到最后确保在底部显示 -->
|
||||||
<div v-if="tabPosition === 'bottom' && shouldShowTabs && panels && panels.length > 0" class="tab-header tab-header-horizontal tab-header-bottom">
|
<div v-if="tabPosition === 'bottom' && shouldShowTabs" class="tab-header tab-header-horizontal tab-header-bottom">
|
||||||
<div
|
<div
|
||||||
v-for="(panel, index) in panels"
|
v-for="(item, index) in slotItems"
|
||||||
:key="panel.id"
|
:key="index"
|
||||||
:class="['tab-item', { 'active': activeTabIndex === index }]"
|
:class="['tab-item', { 'active': activeTabIndex === index }]"
|
||||||
@click="setActiveTab(index)"
|
@click="setActiveTab(index)"
|
||||||
@mousedown="onTabDragStart(index, $event)"
|
@mousedown="onTabDragStart(index, $event)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between h-full px-3">
|
<div class="flex items-center justify-between h-full px-3">
|
||||||
<span class="tab-title">{{ panel.title }}</span>
|
<span class="tab-title">{{ item?.title || '未命名' }}</span>
|
||||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="activeTabIndex !== index"
|
v-if="activeTabIndex !== index"
|
||||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
|
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
|
||||||
@click.stop="closeTab(panel.id)"
|
@click.stop="closeTab(item?.id)"
|
||||||
aria-label="关闭"
|
aria-label="关闭"
|
||||||
>
|
>
|
||||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||||
@@ -155,17 +126,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits, ref, onMounted, computed } from 'vue'
|
import { defineProps, defineEmits, ref, onMounted, onUnmounted, computed, useSlots } from 'vue'
|
||||||
import Panel from './Panel.vue'
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: { type: String, required: true },
|
id: { type: String, required: true },
|
||||||
title: { type: String, default: '标签页' },
|
title: { type: String, default: '标签页' },
|
||||||
// 从父组件传入的面板数组
|
|
||||||
panels: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
// 是否显示页标签栏
|
// 是否显示页标签栏
|
||||||
showTabs: {
|
showTabs: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -188,31 +155,32 @@ const activeTabIndex = ref(-1)
|
|||||||
let isDragging = false
|
let isDragging = false
|
||||||
let dragIndex = -1
|
let dragIndex = -1
|
||||||
|
|
||||||
|
// 计算属性:获取插槽项的props
|
||||||
|
const slotItems = computed(() => {
|
||||||
|
if (!slots.default) return []
|
||||||
|
const slotChildren = slots.default()
|
||||||
|
return slotChildren.map(child => child?.props || {})
|
||||||
|
})
|
||||||
|
|
||||||
// 计算属性:控制标签栏的显示
|
// 计算属性:控制标签栏的显示
|
||||||
const shouldShowTabs = computed(() => {
|
const shouldShowTabs = computed(() => {
|
||||||
// 未来可以优化:当只有一个Panel且不是浮动窗口时隐藏标签栏
|
// 显示标签栏的条件:showTabs为true,且有子组件
|
||||||
const result = props.showTabs && props.panels && props.panels.length > 0
|
return props.showTabs && slots.default && slots.default().length > 0
|
||||||
// console.log(`[TabPage ${props.id}] shouldShowTabs:`, {
|
|
||||||
// showTabs: result,
|
|
||||||
// panelsLength: props.panels?.length || 0,
|
|
||||||
// tabPosition: props.tabPosition,
|
|
||||||
// shouldShow: result,
|
|
||||||
// panels: props.panels
|
|
||||||
// })
|
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置激活的标签页
|
// 设置激活的标签页
|
||||||
const setActiveTab = (index) => {
|
const setActiveTab = (index) => {
|
||||||
if (index >= 0 && index < props.panels.length) {
|
const slotChildren = slots.default ? slots.default() : []
|
||||||
|
if (index >= 0 && index < slotChildren.length) {
|
||||||
activeTabIndex.value = index
|
activeTabIndex.value = index
|
||||||
emit('tabChange', { index, tab: props.panels[index] })
|
emit('tabChange', { index, tab: slotChildren[index] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件挂载后,如果有面板且没有激活的标签,默认激活第一个
|
// 组件挂载后,如果有子组件且没有激活的标签,默认激活第一个
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.panels && props.panels.length > 0 && activeTabIndex.value === -1) {
|
const slotChildren = slots.default ? slots.default() : []
|
||||||
|
if (slotChildren.length > 0 && activeTabIndex.value === -1) {
|
||||||
setActiveTab(0)
|
setActiveTab(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -234,7 +202,7 @@ const onTabDragStart = (index, event) => {
|
|||||||
clientX: event.clientX,
|
clientX: event.clientX,
|
||||||
clientY: event.clientY,
|
clientY: event.clientY,
|
||||||
tabIndex: index,
|
tabIndex: index,
|
||||||
tabId: props.panels[index].id
|
tabId: $slots.default()[index]?.props?.id
|
||||||
})
|
})
|
||||||
|
|
||||||
// 防止文本选择和默认行为
|
// 防止文本选择和默认行为
|
||||||
@@ -275,6 +243,14 @@ const onTabDragEnd = () => {
|
|||||||
document.removeEventListener('mouseleave', onTabDragEnd)
|
document.removeEventListener('mouseleave', onTabDragEnd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 组件卸载时清理全局事件监听器
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 确保清理所有可能存在的全局事件监听器
|
||||||
|
document.removeEventListener('mousemove', onTabDragMove)
|
||||||
|
document.removeEventListener('mouseup', onTabDragEnd)
|
||||||
|
document.removeEventListener('mouseleave', onTabDragEnd)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -295,6 +271,12 @@ const onTabDragEnd = () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-page-top .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部位置:内容区 -> 工具栏 -> 标签栏 */
|
/* 底部位置:内容区 -> 工具栏 -> 标签栏 */
|
||||||
.tab-page-bottom {
|
.tab-page-bottom {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
@@ -305,11 +287,23 @@ const onTabDragEnd = () => {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-page-left .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 右侧位置:标签栏 -> 工具栏 -> 内容区(垂直排列) */
|
/* 右侧位置:标签栏 -> 工具栏 -> 内容区(垂直排列) */
|
||||||
.tab-page-right {
|
.tab-page-right {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-page-right .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--vs-blue-top: #4f72b3;
|
--vs-blue-top: #4f72b3;
|
||||||
--vs-blue-bottom: #3c5a99;
|
--vs-blue-bottom: #3c5a99;
|
||||||
@@ -536,6 +530,30 @@ const onTabDragEnd = () => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0; /* 重要:允许flex子项收缩 */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部位置:标签栏 -> 内容区 */
|
||||||
|
.tab-page-top .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0; /* 重要:允许flex子项收缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧位置:标签栏 -> 内容区 */
|
||||||
|
.tab-page-left .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0; /* 重要:允许flex子项收缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧位置:标签栏 -> 内容区 */
|
||||||
|
.tab-page-right .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0; /* 重要:允许flex子项收缩 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-panel {
|
.tab-panel {
|
||||||
|
|||||||
@@ -682,13 +682,43 @@ EnhancedEventBus.prototype.on = function(eventType, callback, options = {}) {
|
|||||||
return unsubscribe
|
return unsubscribe
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扩展clear方法,清理定时器
|
// 清理所有监听器和资源
|
||||||
EnhancedEventBus.prototype.clear = function() {
|
EnhancedEventBus.prototype.clear = function() {
|
||||||
originalClear.call(this)
|
// 清理mitt事件总线的所有监听器
|
||||||
|
if (this.bus && this.bus.all) {
|
||||||
|
this.bus.all.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理事件处理器注册表
|
||||||
|
if (typeof handlerRegistry !== 'undefined' && handlerRegistry.destroyAll) {
|
||||||
|
handlerRegistry.destroyAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理去重器
|
||||||
|
if (this.deduplicator) {
|
||||||
|
this.deduplicator.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理优先级队列
|
||||||
|
if (this.priorityQueue) {
|
||||||
|
this.priorityQueue.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理性能指标
|
||||||
|
if (this.performanceMetrics) {
|
||||||
|
this.performanceMetrics.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理事件历史
|
||||||
|
this.eventHistory = []
|
||||||
|
|
||||||
|
// 清理定时器
|
||||||
if (this.cleanupInterval) {
|
if (this.cleanupInterval) {
|
||||||
clearInterval(this.cleanupInterval)
|
clearInterval(this.cleanupInterval)
|
||||||
this.cleanupInterval = null
|
this.cleanupInterval = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[EventBus:${this.instanceId}] 已清理所有监听器和资源`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加定期清理定时器
|
// 添加定期清理定时器
|
||||||
|
|||||||
@@ -1614,8 +1614,18 @@ class DragStateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建单例实例
|
// 延迟创建单例实例
|
||||||
const dragStateManager = new DragStateManager();
|
let dragStateManager = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保单例实例存在
|
||||||
|
*/
|
||||||
|
function ensureDragStateManagerInstance() {
|
||||||
|
if (!dragStateManager) {
|
||||||
|
dragStateManager = new DragStateManager();
|
||||||
|
}
|
||||||
|
return dragStateManager;
|
||||||
|
}
|
||||||
|
|
||||||
// 便捷操作函数
|
// 便捷操作函数
|
||||||
export const dragStateActions = {
|
export const dragStateActions = {
|
||||||
@@ -1624,87 +1634,150 @@ export const dragStateActions = {
|
|||||||
* @param {string} dragId - 拖拽ID
|
* @param {string} dragId - 拖拽ID
|
||||||
* @returns {Object} 拖拽状态
|
* @returns {Object} 拖拽状态
|
||||||
*/
|
*/
|
||||||
getDragState: (dragId) => dragStateManager.getDragState(dragId),
|
getDragState: (dragId) => ensureDragStateManagerInstance().getDragState(dragId),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有活跃拖拽
|
* 获取所有活跃拖拽
|
||||||
* @returns {Array} 活跃拖拽列表
|
* @returns {Array} 活跃拖拽列表
|
||||||
*/
|
*/
|
||||||
getActiveDrags: () => dragStateManager.getActiveDrags(),
|
getActiveDrags: () => ensureDragStateManagerInstance().getActiveDrags(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取拖拽历史
|
* 获取拖拽历史
|
||||||
* @param {number} limit - 限制数量
|
* @param {number} limit - 限制数量
|
||||||
* @returns {Array} 历史记录
|
* @returns {Array} 历史记录
|
||||||
*/
|
*/
|
||||||
getHistory: (limit = 100) => dragStateManager.getDragHistory(limit),
|
getHistory: (limit = 100) => ensureDragStateManagerInstance().getDragHistory(limit),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取统计信息
|
* 获取统计信息
|
||||||
* @returns {Object} 统计信息
|
* @returns {Object} 统计信息
|
||||||
*/
|
*/
|
||||||
getStats: () => dragStateManager.getDragStats(),
|
getStats: () => ensureDragStateManagerInstance().getDragStats(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置调试模式
|
* 设置调试模式
|
||||||
* @param {boolean} enabled - 是否启用
|
* @param {boolean} enabled - 是否启用
|
||||||
*/
|
*/
|
||||||
setDebugMode: (enabled) => dragStateManager.setDebugMode(enabled),
|
setDebugMode: (enabled) => ensureDragStateManagerInstance().setDebugMode(enabled),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用/禁用管理器
|
* 启用/禁用管理器
|
||||||
* @param {boolean} enabled - 是否启用
|
* @param {boolean} enabled - 是否启用
|
||||||
*/
|
*/
|
||||||
setEnabled: (enabled) => dragStateManager.setEnabled(enabled),
|
setEnabled: (enabled) => ensureDragStateManagerInstance().setEnabled(enabled),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消所有拖拽
|
* 取消所有拖拽
|
||||||
*/
|
*/
|
||||||
cancelAll: () => dragStateManager.cancelAllDrags(),
|
cancelAll: () => ensureDragStateManagerInstance().cancelAllDrags(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 面板拖拽开始
|
* 面板拖拽开始
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {string} 拖拽ID
|
* @returns {string} 拖拽ID
|
||||||
*/
|
*/
|
||||||
onPanelDragStart: (eventData) => dragStateManager.onPanelDragStart(eventData),
|
onPanelDragStart: (eventData) => ensureDragStateManagerInstance().onPanelDragStart(eventData),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 面板拖拽移动
|
* 面板拖拽移动
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {boolean} 是否成功
|
* @returns {boolean} 是否成功
|
||||||
*/
|
*/
|
||||||
onPanelDragMove: (eventData) => dragStateManager.onPanelDragMove(eventData),
|
onPanelDragMove: (eventData) => ensureDragStateManagerInstance().onPanelDragMove(eventData),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 面板拖拽结束
|
* 面板拖拽结束
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {boolean} 是否成功
|
* @returns {boolean} 是否成功
|
||||||
*/
|
*/
|
||||||
onPanelDragEnd: (eventData) => dragStateManager.onPanelDragEnd(eventData),
|
onPanelDragEnd: (eventData) => ensureDragStateManagerInstance().onPanelDragEnd(eventData),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TabPage拖拽开始
|
* TabPage拖拽开始
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {string} 拖拽ID
|
* @returns {string} 拖拽ID
|
||||||
*/
|
*/
|
||||||
onPanelDragStartFromTabPage: (eventData) => dragStateManager.onPanelDragStartFromTabPage(eventData),
|
onPanelDragStartFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragStartFromTabPage(eventData),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TabPage拖拽移动
|
* TabPage拖拽移动
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {boolean} 是否成功
|
* @returns {boolean} 是否成功
|
||||||
*/
|
*/
|
||||||
onPanelDragMoveFromTabPage: (eventData) => dragStateManager.onPanelDragMoveFromTabPage(eventData),
|
onPanelDragMoveFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragMoveFromTabPage(eventData),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TabPage拖拽结束
|
* TabPage拖拽结束
|
||||||
* @param {Object} eventData - 拖拽事件数据
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
* @returns {boolean} 是否成功
|
* @returns {boolean} 是否成功
|
||||||
*/
|
*/
|
||||||
onPanelDragEndFromTabPage: (eventData) => dragStateManager.onPanelDragEndFromTabPage(eventData)
|
onPanelDragEndFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragEndFromTabPage(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area拖拽开始
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {string} 拖拽ID
|
||||||
|
*/
|
||||||
|
onAreaDragStart: (eventData) => ensureDragStateManagerInstance().onPanelDragStart(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area拖拽移动
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {boolean} 是否成功
|
||||||
|
*/
|
||||||
|
onAreaDragMove: (eventData) => ensureDragStateManagerInstance().onPanelDragMove(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area拖拽结束
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {boolean} 是否成功
|
||||||
|
*/
|
||||||
|
onAreaDragEnd: (eventData) => ensureDragStateManagerInstance().onPanelDragEnd(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab拖拽开始
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {string} 拖拽ID
|
||||||
|
*/
|
||||||
|
onTabDragStart: (eventData) => ensureDragStateManagerInstance().onPanelDragStartFromTabPage(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab拖拽移动
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {boolean} 是否成功
|
||||||
|
*/
|
||||||
|
onTabDragMove: (eventData) => ensureDragStateManagerInstance().onPanelDragMoveFromTabPage(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab拖拽结束
|
||||||
|
* @param {Object} eventData - 拖拽事件数据
|
||||||
|
* @returns {boolean} 是否成功
|
||||||
|
*/
|
||||||
|
onTabDragEnd: (eventData) => ensureDragStateManagerInstance().onPanelDragEndFromTabPage(eventData),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化拖拽管理器
|
||||||
|
*/
|
||||||
|
initialize: () => ensureDragStateManagerInstance(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁拖拽管理器
|
||||||
|
*/
|
||||||
|
destroy: () => {
|
||||||
|
if (dragStateManager) {
|
||||||
|
// 清理拖拽管理器资源
|
||||||
|
dragStateManager.activeDrags.clear();
|
||||||
|
dragStateManager.dragHistory = [];
|
||||||
|
dragStateManager = null;
|
||||||
|
console.log('🗑️ 拖拽状态管理器已销毁,所有资源已清理');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
export default dragStateManager;
|
export default {
|
||||||
|
getInstance: ensureDragStateManagerInstance,
|
||||||
|
actions: dragStateActions
|
||||||
|
};
|
||||||
export { DragState };
|
export { DragState };
|
||||||
@@ -289,13 +289,22 @@ class EventExecutionMonitor {
|
|||||||
* 全局事件管理器类
|
* 全局事件管理器类
|
||||||
*/
|
*/
|
||||||
class GlobalEventManager {
|
class GlobalEventManager {
|
||||||
|
// 静态实例属性,用于存储单例实例
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// 单例模式实现:如果已经有实例,直接返回现有实例
|
||||||
|
if (GlobalEventManager.instance) {
|
||||||
|
return GlobalEventManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
this.eventHandlers = new Map();
|
this.eventHandlers = new Map();
|
||||||
this.eventRoutes = new Map(Object.entries(EVENT_ROUTES));
|
this.eventRoutes = new Map(Object.entries(EVENT_ROUTES));
|
||||||
this.executionMonitor = new EventExecutionMonitor();
|
this.executionMonitor = new EventExecutionMonitor();
|
||||||
this.eventChains = new Map();
|
this.eventChains = new Map();
|
||||||
this.crossComponentChannels = new Map();
|
this.crossComponentChannels = new Map();
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
this.eventListenersRegistered = false;
|
||||||
this.debugMode = false;
|
this.debugMode = false;
|
||||||
|
|
||||||
// 处理器映射
|
// 处理器映射
|
||||||
@@ -311,12 +320,21 @@ class GlobalEventManager {
|
|||||||
this._cleanupExpiredData = this._cleanupExpiredData.bind(this);
|
this._cleanupExpiredData = this._cleanupExpiredData.bind(this);
|
||||||
|
|
||||||
this._initialize();
|
this._initialize();
|
||||||
|
|
||||||
|
// 保存实例到静态属性
|
||||||
|
GlobalEventManager.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化事件管理器
|
* 初始化事件管理器
|
||||||
*/
|
*/
|
||||||
_initialize() {
|
_initialize() {
|
||||||
|
// 防止重复初始化
|
||||||
|
if (this.isInitialized) {
|
||||||
|
console.warn('⚠️ 全局事件管理器已初始化,跳过重复初始化');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 注册全局事件监听器
|
// 注册全局事件监听器
|
||||||
this._registerGlobalEventListeners();
|
this._registerGlobalEventListeners();
|
||||||
|
|
||||||
@@ -341,6 +359,12 @@ class GlobalEventManager {
|
|||||||
* 注册全局事件监听器
|
* 注册全局事件监听器
|
||||||
*/
|
*/
|
||||||
_registerGlobalEventListeners() {
|
_registerGlobalEventListeners() {
|
||||||
|
// 检查是否已经注册过事件监听器
|
||||||
|
if (this.eventListenersRegistered) {
|
||||||
|
console.warn('⚠️ 事件监听器已经注册,跳过重复注册');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const globalEvents = Object.values(GLOBAL_EVENT_TYPES);
|
const globalEvents = Object.values(GLOBAL_EVENT_TYPES);
|
||||||
globalEvents.forEach(eventType => {
|
globalEvents.forEach(eventType => {
|
||||||
eventBus.on(eventType, this._onGlobalEvent, {
|
eventBus.on(eventType, this._onGlobalEvent, {
|
||||||
@@ -351,6 +375,9 @@ class GlobalEventManager {
|
|||||||
|
|
||||||
// 注册所有组件事件监听器
|
// 注册所有组件事件监听器
|
||||||
this._registerComponentEventListeners();
|
this._registerComponentEventListeners();
|
||||||
|
|
||||||
|
// 标记为已注册
|
||||||
|
this.eventListenersRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -995,11 +1022,8 @@ class GlobalEventManager {
|
|||||||
* 销毁事件管理器
|
* 销毁事件管理器
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
// 清理所有事件监听器
|
// 清理所有事件监听器,包括全局事件和组件事件
|
||||||
const globalEvents = Object.values(GLOBAL_EVENT_TYPES);
|
eventBus.clear();
|
||||||
globalEvents.forEach(eventType => {
|
|
||||||
eventBus.off(eventType, this._onGlobalEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 销毁各个处理器
|
// 销毁各个处理器
|
||||||
Object.values(this.handlerMap).forEach(handler => {
|
Object.values(this.handlerMap).forEach(handler => {
|
||||||
@@ -1012,14 +1036,17 @@ class GlobalEventManager {
|
|||||||
this.eventHandlers.clear();
|
this.eventHandlers.clear();
|
||||||
this.eventChains.clear();
|
this.eventChains.clear();
|
||||||
this.crossComponentChannels.clear();
|
this.crossComponentChannels.clear();
|
||||||
|
this.handlerMap = {};
|
||||||
|
this.eventRoutes.clear();
|
||||||
|
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
console.log('🗑️ 全局事件管理器已销毁');
|
this.eventListenersRegistered = false;
|
||||||
|
console.log('🗑️ 全局事件管理器已销毁,所有监听器已清理');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建单例实例
|
// 延迟创建单例实例
|
||||||
const globalEventManager = new GlobalEventManager();
|
let globalEventManager = null;
|
||||||
|
|
||||||
// 全局便捷API
|
// 全局便捷API
|
||||||
export const globalEventActions = {
|
export const globalEventActions = {
|
||||||
@@ -1029,6 +1056,7 @@ export const globalEventActions = {
|
|||||||
* @returns {string} 监控ID
|
* @returns {string} 监控ID
|
||||||
*/
|
*/
|
||||||
startMonitor: (operation) => {
|
startMonitor: (operation) => {
|
||||||
|
ensureInstance();
|
||||||
return globalEventManager.startPerformanceMonitor(operation);
|
return globalEventManager.startPerformanceMonitor(operation);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1038,6 +1066,7 @@ export const globalEventActions = {
|
|||||||
* @param {Object} metadata - 元数据
|
* @param {Object} metadata - 元数据
|
||||||
*/
|
*/
|
||||||
endMonitor: (monitorId, metadata = {}) => {
|
endMonitor: (monitorId, metadata = {}) => {
|
||||||
|
ensureInstance();
|
||||||
globalEventManager.endPerformanceMonitor(monitorId, metadata);
|
globalEventManager.endPerformanceMonitor(monitorId, metadata);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1048,6 +1077,7 @@ export const globalEventActions = {
|
|||||||
* @returns {string} 链ID
|
* @returns {string} 链ID
|
||||||
*/
|
*/
|
||||||
createChain: (chainName, events) => {
|
createChain: (chainName, events) => {
|
||||||
|
ensureInstance();
|
||||||
return globalEventManager.createEventChain(chainName, events);
|
return globalEventManager.createEventChain(chainName, events);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1058,6 +1088,7 @@ export const globalEventActions = {
|
|||||||
* @param {Array} targets - 目标组件
|
* @param {Array} targets - 目标组件
|
||||||
*/
|
*/
|
||||||
broadcast: (message, data = {}, targets = null) => {
|
broadcast: (message, data = {}, targets = null) => {
|
||||||
|
ensureInstance();
|
||||||
globalEventManager.broadcast(message, data, targets);
|
globalEventManager.broadcast(message, data, targets);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1069,6 +1100,7 @@ export const globalEventActions = {
|
|||||||
* @returns {Promise} 响应
|
* @returns {Promise} 响应
|
||||||
*/
|
*/
|
||||||
request: (component, action, payload) => {
|
request: (component, action, payload) => {
|
||||||
|
ensureInstance();
|
||||||
return globalEventManager.request(component, action, payload);
|
return globalEventManager.request(component, action, payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1084,6 +1116,7 @@ export const globalEventActions = {
|
|||||||
* @returns {Object} 统计信息
|
* @returns {Object} 统计信息
|
||||||
*/
|
*/
|
||||||
getStats: () => {
|
getStats: () => {
|
||||||
|
ensureInstance();
|
||||||
return globalEventManager.getExecutionStats();
|
return globalEventManager.getExecutionStats();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1092,9 +1125,45 @@ export const globalEventActions = {
|
|||||||
* @returns {Array} 链状态
|
* @returns {Array} 链状态
|
||||||
*/
|
*/
|
||||||
getChainStatus: () => {
|
getChainStatus: () => {
|
||||||
|
ensureInstance();
|
||||||
return globalEventManager.getEventChainStatus();
|
return globalEventManager.getEventChainStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化事件管理器
|
||||||
|
*/
|
||||||
|
initialize: () => {
|
||||||
|
ensureInstance();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁事件管理器
|
||||||
|
*/
|
||||||
|
destroy: () => {
|
||||||
|
if (globalEventManager) {
|
||||||
|
globalEventManager.destroy();
|
||||||
|
globalEventManager = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保单例实例存在
|
||||||
|
*/
|
||||||
|
function ensureInstance() {
|
||||||
|
if (!globalEventManager) {
|
||||||
|
globalEventManager = new GlobalEventManager();
|
||||||
|
}
|
||||||
|
return globalEventManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例实例访问方法
|
||||||
|
export const getGlobalEventManager = () => {
|
||||||
|
return ensureInstance();
|
||||||
|
};
|
||||||
|
|
||||||
// 导出事件管理器和相关API
|
// 导出事件管理器和相关API
|
||||||
export default globalEventManager;
|
export default {
|
||||||
|
getInstance: getGlobalEventManager,
|
||||||
|
actions: globalEventActions
|
||||||
|
};
|
||||||
@@ -37,7 +37,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
fixAliyunSDKPlugin // 添加我们的修复插件
|
fixAliyunSDKPlugin // 添加我们的修复插件
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
port: 8080, // 改为不同于后端的端口
|
port: 8081, // 改为不同于后端的端口
|
||||||
proxy: {
|
proxy: {
|
||||||
// 添加代理配置,将WebSocket请求转发到后端
|
// 添加代理配置,将WebSocket请求转发到后端
|
||||||
'/': {
|
'/': {
|
||||||
|
|||||||
Reference in New Issue
Block a user