Files
JoyD/AutoRobot/Windows/Robot/Web/src/components/ResizeHandlers.js

396 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}