Skip to content

多窗口功能的一些优化和问题修复#160

Merged
LarryZhu-dev merged 2 commits intoAuto-Plugin:mainfrom
wxfengg:main
Feb 28, 2026
Merged

多窗口功能的一些优化和问题修复#160
LarryZhu-dev merged 2 commits intoAuto-Plugin:mainfrom
wxfengg:main

Conversation

@wxfengg
Copy link
Contributor

@wxfengg wxfengg commented Feb 28, 2026

  • 修复打开文件并独立窗口出去时出现的报错
  • 优化 Tab 拖拽分离和合并预览功能,增强用户体验

Copilot AI review requested due to automatic review settings February 28, 2026 09:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 聚焦于多窗口场景下的 Tab 拖拽分离/合并预览体验与稳定性:解决跨进程传输序列化报错、改进拖拽分离的源窗口显示逻辑,并让合并预览的插入位置可随光标动态变化。

Changes:

  • 拖拽分离时对 Tab 数据做 toRaw 处理,并在分离/取消/失败时更完善地切换与恢复源 Tab。
  • TabBar 的分离触发从“离开窗口边界”改为“离开 Tab 栏边界”,并新增 ghost 隐藏/恢复逻辑。
  • 主进程新增“合并预览位置更新”事件,支持预览 Tab 在目标窗口内随光标调整插入位置;单 Tab 窗口拖拽时用透明化强化预览反馈。

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/renderer/hooks/useTab.ts toRaw 序列化修复;分离/取消恢复逻辑;新增合并预览插入位置动态更新处理
src/renderer/components/workspace/TabBar.vue 分离检测改为基于 Tab 栏边界;新增 ghost 隐藏/恢复;调整拖拽结束流程
src/main/windowManager.ts 同目标窗口下发送 merge-preview-update;单 Tab 拖拽时根据是否命中目标透明化窗口并在 stop 时恢复
src/main/ipcBridge.ts tear-off 完成后延迟聚焦新窗口以避免源窗口 RAF 刷新被限流

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +791 to +817
// 获取所有非预览 Tab 元素
const tabElements = Array.from(
document.querySelectorAll("[data-tab-id]:not(.merge-preview)")
) as HTMLElement[];

// 默认放在末尾
let targetIndex = tabs.value.length - 1;
for (let i = 0; i < tabElements.length; i++) {
const rect = tabElements[i].getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
if (clientX < centerX) {
// 找到该元素在 tabs 数组中的真实索引
const elTabId = tabElements[i].dataset.tabId;
const realIndex = tabs.value.findIndex((t) => t.id === elTabId);
if (realIndex !== -1) {
// 如果预览 Tab 在目标之前,移除后索引需 -1
targetIndex = currentIndex < realIndex ? realIndex - 1 : realIndex;
}
break;
}
}

// 仅在位置真正变化时才更新,避免不必要的 Vue 响应式开销
if (targetIndex !== currentIndex) {
const [tab] = tabs.value.splice(currentIndex, 1);
tabs.value.splice(targetIndex, 0, tab);
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tab:merge-preview-update can arrive at ~60fps (driven by the main-process drag intervals). Each call does querySelectorAll + getBoundingClientRect over all tabs, which forces repeated layout and can become janky with many tabs. Consider throttling to requestAnimationFrame (coalesce multiple updates per frame) and/or caching tab element centers while dragging, updating only when the pointer crosses a boundary.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +105
/** 恢复 SortableJS ghost 元素(取消 tear-off 时调用) */
function showGhost() {
if (_ghostEl) {
_ghostEl.style.display = "";
_ghostEl = null;
}
}

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hideGhost() sets an inline display: none on the element with .ghost. When the drag ends, SortableJS removes the ghostClass but won't restore inline styles, so the tab can remain permanently hidden if the tear-off ultimately fails (since handleDragEnd clears _ghostEl without calling showGhost()). Prefer hiding via a CSS rule keyed off body.tab-torn-off (so visibility auto-restores when the class is removed), or ensure the inline style is always reverted on drag end / tear-off failure.

Suggested change
/** 恢复 SortableJS ghost 元素(取消 tear-off 时调用) */
function showGhost() {
if (_ghostEl) {
_ghostEl.style.display = "";
_ghostEl = null;
}
}
/** 恢复 SortableJS ghost 元素(取消 tear-off 或拖拽结束时调用) */
function showGhost() {
// 尝试恢复当前 DOM 中的 ghost 元素的 display,即使 _ghostEl 已被其他逻辑清空
const container = tabContainerRef.value;
if (container) {
const ghostEl = container.querySelector(".ghost") as HTMLElement | null;
if (ghostEl && ghostEl.style.display === "none") {
ghostEl.style.display = "";
}
}
// 同时处理通过 hideGhost() 缓存过的元素,确保其样式被恢复
if (_ghostEl) {
_ghostEl.style.display = "";
_ghostEl = null;
}
}
// 全局监听拖拽结束事件,确保无论 tear-off 是否成功,ghost 样式都会被恢复
const handleGlobalDragEnd = () => {
showGhost();
};
onMounted(() => {
window.addEventListener("dragend", handleGlobalDragEnd);
});
onUnmounted(() => {
window.removeEventListener("dragend", handleGlobalDragEnd);
});

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +252
if (prev?.id === target?.id) {
// 目标窗口未变,但光标位置变了 → 发送位置更新以动态调整预览 Tab 插入位置
if (target && !target.isDestroyed()) {
target.webContents.send("tab:merge-preview-update", screenX, screenY);
}
return target;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateMergePreview now sends tab:merge-preview-update every time the cursor moves while staying over the same target window. Given the 16ms drag intervals, this can create very high-frequency IPC traffic and renderer-side layout work. Consider throttling these updates (e.g., only send at most once per animation frame or every N ms) and/or only sending when the X position actually changes enough to affect the insertion index.

Copilot uses AI. Check for mistakes.
* 动态更新合并预览 Tab 的插入位置
* 由主进程在光标移动时持续发送,实现拖拽悬停时预览 Tab 跟随光标变换顺序
*/
function handleTabMergePreviewUpdate(screenX: number, screenY: number) {
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleTabMergePreviewUpdate declares screenY but never uses it. With noUnusedParameters: true in tsconfig, this will fail type-check/build. Either remove the parameter from the function signature (extra IPC args will be ignored), or reference it explicitly if it's intentionally kept.

Suggested change
function handleTabMergePreviewUpdate(screenX: number, screenY: number) {
function handleTabMergePreviewUpdate(screenX: number) {

Copilot uses AI. Check for mistakes.
@LarryZhu-dev LarryZhu-dev merged commit b1ea50d into Auto-Plugin:main Feb 28, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants