396 lines
14 KiB
JavaScript
396 lines
14 KiB
JavaScript
import { onMounted, onUnmounted } from 'vue';
|
||
|
||
/**
|
||
* 面板调整大小处理模块
|
||
* 负责处理面板调整大小的所有相关逻辑
|
||
*/
|
||
export class ResizeHandlers {
|
||
constructor(store, container, layoutPersistence) {
|
||
this.store = store;
|
||
this.container = container;
|
||
this.layoutPersistence = layoutPersistence;
|
||
this.isInitialized = false;
|
||
}
|
||
|
||
/**
|
||
* 开始调整大小
|
||
*/
|
||
startResize(target, event) {
|
||
this.store.isResizing = true;
|
||
this.store.resizeTarget = target;
|
||
this.store.resizeStartPos = { x: event.clientX, y: event.clientY };
|
||
|
||
// 阻止默认行为
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
}
|
||
|
||
/**
|
||
* 处理面板调整大小开始
|
||
*/
|
||
startPanelResize(position, panelId, panelIndex, event) {
|
||
this.store.isResizing = true;
|
||
this.store.resizeTarget = position;
|
||
this.store.resizeStartPos = { x: event.clientX, y: event.clientY };
|
||
|
||
// 保存调整大小的上下文信息
|
||
this.store.resizeContext = {
|
||
position: position,
|
||
panelId: panelId,
|
||
panelIndex: panelIndex,
|
||
originalSizes: null
|
||
};
|
||
|
||
// 根据不同位置获取原始尺寸
|
||
switch (position) {
|
||
case 'top':
|
||
this.store.resizeContext.originalSizes = this.store.topPanelArea.panels.map(p => ({ width: p.width || 300 }));
|
||
break;
|
||
case 'bottom':
|
||
this.store.resizeContext.originalSizes = this.store.bottomPanelArea.panels.map(p => ({ width: p.width || 300 }));
|
||
break;
|
||
case 'left':
|
||
this.store.resizeContext.originalSizes = this.store.leftPanelArea.panels.map(p => ({ height: p.height || 200 }));
|
||
break;
|
||
case 'right':
|
||
this.store.resizeContext.originalSizes = this.store.rightPanelArea.panels.map(p => ({ height: p.height || 200 }));
|
||
break;
|
||
}
|
||
|
||
// 阻止默认行为
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
}
|
||
|
||
/**
|
||
* 处理鼠标移动
|
||
*/
|
||
handleMouseMove(event) {
|
||
if (this.store.isResizing) {
|
||
// 处理分隔线调整大小
|
||
const deltaX = event.clientX - this.store.resizeStartPos.x;
|
||
const deltaY = event.clientY - this.store.resizeStartPos.y;
|
||
if(deltaX === 0 && deltaY === 0) {
|
||
return;
|
||
}
|
||
// 获取当前调整大小的上下文
|
||
const context = this.store.resizeContext;
|
||
|
||
if (context && (context.position === 'top' || context.position === 'bottom')) {
|
||
// 处理顶部和底部面板的水平调整
|
||
this.adjustPanelsHorizontal(context.position, context.panelIndex, deltaX, context.originalSizes);
|
||
|
||
// 不重置resizeStartPos,避免抖动问题
|
||
// 保持deltaX的累积计算,提供更平滑的调整体验
|
||
} else if (context && (context.position === 'left' || context.position === 'right')) {
|
||
// 处理左侧和右侧面板的垂直调整
|
||
this.adjustPanelsVertical(context.position, context.panelIndex, deltaY, context.originalSizes);
|
||
|
||
// 不重置resizeStartPos,保持与水平调整一致的累积计算策略
|
||
// 为垂直调整也提供更平滑的体验
|
||
} else {
|
||
// 处理原始的区域调整大小逻辑
|
||
this.handleRegionResize(this.store.resizeTarget, deltaX, deltaY);
|
||
|
||
// 重置起始位置,以便下一次计算
|
||
this.store.resizeStartPos = { x: event.clientX, y: event.clientY };
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通用函数:处理顶部和底部面板的水平调整
|
||
*/
|
||
adjustPanelsHorizontal(position, panelIndex, deltaX, originalSizes) {
|
||
try {
|
||
// 获取面板区域的实际宽度
|
||
if (!this.container.value) {
|
||
return;
|
||
}
|
||
|
||
const containerRect = this.container.value.getBoundingClientRect();
|
||
// 计算可用宽度 - 顶部和底部面板的宽度应该是容器宽度减去所有受影响面板的宽度
|
||
let availableWidth = containerRect.width;
|
||
|
||
// 获取当前位置面板的受影响关系
|
||
const influencedByArray = this.store.panelSizeInfluence.influencedBy[position];
|
||
if (influencedByArray && Array.isArray(influencedByArray)) {
|
||
// 根据受影响关系调整可用空间
|
||
influencedByArray.forEach(influence => {
|
||
if (influence.influence && influence.property === 'width') {
|
||
const panelArea = influence.position === 'left' ? this.store.leftPanelArea : this.store.rightPanelArea;
|
||
const occupiedWidth = panelArea.panels.reduce((sum, panel) => sum + (panel.width || 0), 0);
|
||
availableWidth -= occupiedWidth;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 获取对应的面板区对象
|
||
const panelArea = position === 'top' ? this.store.topPanelArea : this.store.bottomPanelArea;
|
||
const panels = panelArea.panels;
|
||
|
||
// 统一使用与顶部面板相同的处理逻辑
|
||
const updatedPanels = this.store.adjustAdjacentPanels(
|
||
position, panelIndex, deltaX, originalSizes, availableWidth
|
||
);
|
||
|
||
if (updatedPanels !== panels) {
|
||
// 更新面板数组
|
||
panelArea.panels = updatedPanels;
|
||
|
||
// 手动调整后更新比例 - 直接使用从DOM获取的availableWidth作为基准,避免二次计算导致的不一致
|
||
panelArea.widthRatios = updatedPanels.map(panel =>
|
||
availableWidth > 0 ? (panel.width || 300) / availableWidth : 1 / updatedPanels.length
|
||
);
|
||
|
||
// 调用影响处理函数,确保面板间影响关系正确处理
|
||
this.store.handlePanelSizeInfluence(position, this.container.value);
|
||
|
||
// 保存布局,确保调整后的比例被持久化
|
||
this.layoutPersistence.saveLayout();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error in adjustPanelsHorizontal:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通用函数:处理左侧和右侧面板的垂直调整
|
||
*/
|
||
adjustPanelsVertical(position, panelIndex, deltaY, originalSizes) {
|
||
try {
|
||
// 获取容器元素以获取实际高度
|
||
if (!this.container.value) {
|
||
return;
|
||
}
|
||
|
||
// 获取对应的面板区对象
|
||
const panelArea = position === 'left' ? this.store.leftPanelArea : this.store.rightPanelArea;
|
||
const panels = panelArea.panels;
|
||
|
||
// 计算可用高度 - 考虑顶部和底部面板的影响以及分隔条高度
|
||
let availableHeight;
|
||
|
||
// 计算左侧/右侧面板的当前总高度(包括分隔条高度)
|
||
const separatorHeight = 8; // 分隔条高度,从CSS类h-2推断(0.5rem = 8px)
|
||
const panelsHeight = panels.reduce((sum, panel) => sum + (panel.height || 0), 0);
|
||
// 分隔条数量 = 面板数量 - 1
|
||
const totalSeparatorsHeight = (panels.length > 1) ? separatorHeight * (panels.length - 1) : 0;
|
||
const currentTotalHeight = panelsHeight + totalSeparatorsHeight;
|
||
|
||
// 如果当前总高度有效,则使用它作为可用高度基准
|
||
if (currentTotalHeight > 0) {
|
||
availableHeight = currentTotalHeight;
|
||
} else {
|
||
// 否则,从容器高度开始计算,考虑顶部和底部面板的影响
|
||
const containerRect = this.container.value.getBoundingClientRect();
|
||
availableHeight = containerRect.height;
|
||
|
||
// 获取当前位置面板的受影响关系
|
||
const influencedByArray = this.store.panelSizeInfluence.influencedBy[position];
|
||
if (influencedByArray && Array.isArray(influencedByArray)) {
|
||
// 根据受影响关系调整可用空间
|
||
influencedByArray.forEach(influence => {
|
||
if (influence.influence && influence.property === 'height') {
|
||
if (influence.position === 'top') {
|
||
// 顶部面板:使用max(上面板的子面板高度)作为高度
|
||
const maxTopPanelHeight = this.store.topPanelArea.panels.length > 0
|
||
? Math.max(...this.store.topPanelArea.panels.map(panel => panel.height || 0))
|
||
: 0;
|
||
availableHeight -= maxTopPanelHeight;
|
||
} else if (influence.position === 'bottom') {
|
||
// 底部面板:使用max(下面板的子面板高度)作为高度
|
||
const maxBottomPanelHeight = this.store.bottomPanelArea.panels.length > 0
|
||
? Math.max(...this.store.bottomPanelArea.panels.map(panel => panel.height || 0))
|
||
: 0;
|
||
availableHeight -= maxBottomPanelHeight;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 统一使用与水平调整相同的处理逻辑
|
||
const updatedPanels = this.store.adjustAdjacentPanelsVertical(
|
||
position, panelIndex, deltaY, originalSizes, availableHeight
|
||
);
|
||
|
||
if (updatedPanels !== panels) {
|
||
// 更新面板数组
|
||
panelArea.panels = updatedPanels;
|
||
|
||
// 手动调整后更新比例 - 直接使用从可用高度作为基准,避免二次计算导致的不一致
|
||
panelArea.heightRatios = updatedPanels.map(panel =>
|
||
availableHeight > 0 ? (panel.height || 200) / availableHeight : 1 / updatedPanels.length
|
||
);
|
||
|
||
// 使用公共方法处理面板影响
|
||
this.store.handlePanelSizeInfluence(position, this.container.value);
|
||
|
||
// 保存布局,确保调整后的比例被持久化
|
||
this.layoutPersistence.saveLayout();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error in adjustPanelsVertical:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通用函数:处理区域调整大小
|
||
*/
|
||
handleRegionResize(target, deltaX, deltaY) {
|
||
try {
|
||
// 检查容器元素是否存在
|
||
if (!this.container.value) {
|
||
return;
|
||
}
|
||
|
||
// 获取容器高度,用于限制顶底面板总高度
|
||
const containerHeight = this.container.value.clientHeight;
|
||
|
||
// 准备面板区域信息
|
||
const panelAreas = {
|
||
top: this.store.topPanelArea,
|
||
bottom: this.store.bottomPanelArea,
|
||
left: this.store.leftPanelArea,
|
||
right: this.store.rightPanelArea,
|
||
center: this.store.centerPanelArea
|
||
};
|
||
|
||
switch (target) {
|
||
case 'left':
|
||
// 修复左侧面板拖拽速度问题:右拖应该缓慢增大宽度,进一步减小deltaX的影响
|
||
this.store.adjustRegionSize('left', deltaX);
|
||
break;
|
||
case 'right':
|
||
// 修复右侧面板拖拽方向相反的问题:向左拖应该增大宽度,所以反转deltaX
|
||
this.store.adjustRegionSize('right', deltaX);
|
||
break;
|
||
case 'top':
|
||
this.store.adjustRegionSize('top', deltaY, this.container.value);
|
||
break;
|
||
case 'bottom':
|
||
this.store.adjustRegionSize('bottom', deltaY, this.container.value);
|
||
break;
|
||
}
|
||
|
||
// 使用store方法处理面板影响
|
||
this.store.handlePanelSizeInfluence(target, this.container.value);
|
||
} catch (error) {
|
||
console.error('Error in handleRegionResize:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理鼠标松开
|
||
*/
|
||
handleMouseUp(event, hideContextMenuCallback, checkDockZoneCallback) {
|
||
if (this.store.isResizing) {
|
||
this.store.isResizing = false;
|
||
this.store.resizeTarget = '';
|
||
// 重置调整大小上下文,避免不同位置调整之间的上下文混淆
|
||
this.store.resizeContext = null;
|
||
// 确保调整结束后重置resizeStartPos,为下一次调整做好准备
|
||
// 无论之前采用哪种调整策略,此处统一重置以保持一致性
|
||
this.store.resizeStartPos = { x: 0, y: 0 };
|
||
|
||
// 打印保存前的面板宽度值
|
||
console.log('ResizeHandlers - 保存前左侧面板宽度:', this.store.leftPanelArea.width);
|
||
this.layoutPersistence.saveLayout();
|
||
console.log('ResizeHandlers - 保存后左侧面板宽度:', this.store.leftPanelArea.width);
|
||
} else if (this.store.dragState.active && checkDockZoneCallback) {
|
||
// 计算拖拽距离
|
||
const dragDistance = Math.sqrt(
|
||
Math.pow(event.clientX - this.store.dragState.startX, 2) +
|
||
Math.pow(event.clientY - this.store.dragState.startY, 2)
|
||
);
|
||
|
||
// 如果是面板拖拽且拖拽距离超过阈值,转换为浮动窗口
|
||
if (this.store.dragState.isPanelDrag && dragDistance > 20) {
|
||
// 检查是否在停靠区域内
|
||
const dockZone = checkDockZoneCallback(event.clientX, event.clientY);
|
||
if (!dockZone) {
|
||
// 转换为浮动窗口,位置设置为鼠标当前位置
|
||
if (this.container.value) {
|
||
const rect = this.container.value.getBoundingClientRect();
|
||
this.store.floatPanel(this.store.dragState.panelId);
|
||
}
|
||
|
||
// 找到刚创建的浮动窗口并设置位置
|
||
setTimeout(() => {
|
||
const floatingWindow = this.store.floatingWindows.find(w => w.id === this.store.dragState.panelId);
|
||
if (floatingWindow) {
|
||
floatingWindow.x = event.clientX - 200; // 居中显示
|
||
floatingWindow.y = event.clientY - 30; // 标题栏高度的一半
|
||
}
|
||
}, 10);
|
||
} else {
|
||
// 在停靠区域内,执行停靠
|
||
this.store.dockPanel(this.store.dragState.panelId, dockZone.position);
|
||
}
|
||
} else {
|
||
// 处理拖拽结束,执行停靠
|
||
const dockZone = checkDockZoneCallback(event.clientX, event.clientY);
|
||
if (dockZone) {
|
||
this.store.dockPanel(this.store.dragState.panelId, dockZone.position);
|
||
}
|
||
}
|
||
|
||
// 重置拖拽状态
|
||
this.store.dragState = {
|
||
active: false,
|
||
panelId: null,
|
||
startX: 0,
|
||
startY: 0,
|
||
originalPosition: null,
|
||
isPanelDrag: false
|
||
};
|
||
this.store.dockPreview.visible = false;
|
||
}
|
||
|
||
// 隐藏右键菜单
|
||
if (hideContextMenuCallback) {
|
||
hideContextMenuCallback();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化事件监听
|
||
*/
|
||
initialize() {
|
||
if (this.isInitialized) return;
|
||
|
||
// 添加事件监听
|
||
document.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
||
|
||
this.isInitialized = true;
|
||
}
|
||
|
||
/**
|
||
* 清理事件监听
|
||
*/
|
||
cleanup() {
|
||
if (!this.isInitialized) return;
|
||
|
||
document.removeEventListener('mousemove', this.handleMouseMove.bind(this));
|
||
|
||
this.isInitialized = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建ResizeHandlers的Vue组合式函数
|
||
*/
|
||
export function useResizeHandlers(store, container, layoutPersistence) {
|
||
const resizeHandlers = new ResizeHandlers(store, container, layoutPersistence);
|
||
|
||
onMounted(() => {
|
||
resizeHandlers.initialize();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
resizeHandlers.cleanup();
|
||
});
|
||
|
||
return resizeHandlers;
|
||
} |