fix: 优化进度面板移动端显示和拖拽功能

- 进度面板移动端默认定位改为左上角(参考剧情优化面板)
- 修复移动端拖拽时面板消失的问题
- 使用 setProperty 设置 !important 内联样式覆盖CSS规则
- 拖拽开始时先固定当前位置防止跳变
- 移动端拖拽后保持用户自定义位置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cola-Echo
2026-01-17 01:35:20 +08:00
parent 6b80f1b755
commit 4b801c8dc7
2 changed files with 130 additions and 36 deletions

View File

@@ -3640,6 +3640,13 @@
y: clientY - rect.top, y: clientY - rect.top,
}; };
// 先将当前位置固定为内联样式(防止拖拽时位置跳变)
// 使用 setProperty 设置 !important以覆盖移动端CSS中的 !important 规则
this.container.style.setProperty('left', `${rect.left}px`, 'important');
this.container.style.setProperty('top', `${rect.top}px`, 'important');
this.container.style.setProperty('right', 'auto', 'important');
this.container.style.setProperty('transform', 'none', 'important');
// 添加拖动样式 // 添加拖动样式
this.container.classList.add("mm-dragging"); this.container.classList.add("mm-dragging");
@@ -3676,10 +3683,10 @@
newX = Math.max(0, Math.min(newX, maxX)); newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY)); newY = Math.max(0, Math.min(newY, maxY));
// 应用位置(使用 left/top 而不是 transform // 应用位置(使用 setProperty 设置 !important覆盖移动端CSS
this.container.style.left = `${newX}px`; this.container.style.setProperty('left', `${newX}px`, 'important');
this.container.style.top = `${newY}px`; this.container.style.setProperty('top', `${newY}px`, 'important');
this.container.style.transform = "none"; this.container.style.setProperty('transform', 'none', 'important');
this.position = { x: newX, y: newY }; this.position = { x: newX, y: newY };
}; };
@@ -3694,10 +3701,16 @@
document.removeEventListener("touchmove", onDragMove); document.removeEventListener("touchmove", onDragMove);
document.removeEventListener("touchend", onDragEnd); document.removeEventListener("touchend", onDragEnd);
// 保存位置 // 保存位置(如果有移动)
if (this.position) { if (this.position && dragMoved) {
// 移动端添加用户定位类让拖拽位置生效但不保存到localStorage
// 桌面端:保存位置并添加用户定位类
if (window.innerWidth >= 768) {
this.savePosition(); this.savePosition();
} }
// 移动端和桌面端都添加用户定位类,让拖拽后的位置生效
this.container.classList.add("mm-user-positioned");
}
// 判断是否为点击(短时间内没有移动) // 判断是否为点击(短时间内没有移动)
const dragDuration = Date.now() - dragStartTime; const dragDuration = Date.now() - dragStartTime;
@@ -3722,8 +3735,11 @@
}, { passive: false }); }, { passive: false });
} }
// 保存位置到 localStorage // 保存位置到 localStorage(仅桌面端)
savePosition() { savePosition() {
// 移动端不保存位置
if (window.innerWidth < 768) return;
if (this.position) { if (this.position) {
localStorage.setItem( localStorage.setItem(
"mm_progress_panel_position", "mm_progress_panel_position",
@@ -3748,6 +3764,8 @@
this.container.style.left = `${pos.x}px`; this.container.style.left = `${pos.x}px`;
this.container.style.top = `${pos.y}px`; this.container.style.top = `${pos.y}px`;
this.container.style.transform = "none"; this.container.style.transform = "none";
// 添加用户定位类,防止移动端 CSS 覆盖
this.container.classList.add("mm-user-positioned");
} }
} }
} catch (e) { } catch (e) {
@@ -3761,6 +3779,7 @@
this.container.style.left = "50%"; this.container.style.left = "50%";
this.container.style.top = "80px"; this.container.style.top = "80px";
this.container.style.transform = "translateX(-50%)"; this.container.style.transform = "translateX(-50%)";
this.container.classList.remove("mm-user-positioned");
localStorage.removeItem("mm_progress_panel_position"); localStorage.removeItem("mm_progress_panel_position");
} }
@@ -3777,13 +3796,53 @@
this.hideTimeout = null; this.hideTimeout = null;
} }
// 重置面板位置让CSS初始定位生效 // 检查是否有保存的位置
if (this.container) { if (this.container) {
// 移动端宽度小于768px不使用保存的位置始终使用CSS默认位置
const isMobile = window.innerWidth < 768;
if (isMobile) {
// 移动端清除所有内联样式使用CSS默认定位
this.container.style.left = ""; this.container.style.left = "";
this.container.style.top = ""; this.container.style.top = "";
this.container.style.right = ""; this.container.style.right = "";
this.container.style.bottom = ""; this.container.style.bottom = "";
this.container.style.transform = ""; this.container.style.transform = "";
this.container.classList.remove("mm-user-positioned");
this.position = null;
} else {
// 桌面端:尝试使用保存的位置
const saved = localStorage.getItem("mm_progress_panel_position");
if (saved) {
try {
const pos = JSON.parse(saved);
requestAnimationFrame(() => {
const rect = this.container.getBoundingClientRect();
const maxX = window.innerWidth - Math.min(rect.width, 320);
const maxY = window.innerHeight - Math.min(rect.height, 100);
if (pos.x >= 0 && pos.x <= maxX && pos.y >= 0 && pos.y <= maxY) {
this.position = pos;
this.container.style.left = `${pos.x}px`;
this.container.style.top = `${pos.y}px`;
this.container.style.transform = "none";
this.container.classList.add("mm-user-positioned");
} else {
this.resetPosition();
}
});
} catch (e) {
// 解析失败
}
} else {
this.container.style.left = "";
this.container.style.top = "";
this.container.style.right = "";
this.container.style.bottom = "";
this.container.style.transform = "";
this.container.classList.remove("mm-user-positioned");
}
}
} }
this.isVisible = true; this.isVisible = true;
@@ -6440,6 +6499,10 @@
async function callSingleSummaryBookAI(panel, book, userMessage, context) { async function callSingleSummaryBookAI(panel, book, userMessage, context) {
const bookName = book.name; const bookName = book.name;
// 创建 AbortController 用于终止任务
const taskId = `search_${bookName}`;
const abortController = new AbortController();
try { try {
panel.setBookStatus(bookName, "loading", "调用AI中..."); panel.setBookStatus(bookName, "loading", "调用AI中...");
panel.addBookAIMessage(bookName, "正在调用历史事件回忆AI..."); panel.addBookAIMessage(bookName, "正在调用历史事件回忆AI...");
@@ -6467,20 +6530,21 @@
const finalSystemPrompt = getJailbreakPrefix() + "\n\n" + baseSystemPrompt; const finalSystemPrompt = getJailbreakPrefix() + "\n\n" + baseSystemPrompt;
const finalUserMessage = buildUserPrompt(userMessage); const finalUserMessage = buildUserPrompt(userMessage);
// 添加到进度追踪 // 添加到进度追踪,并注册 AbortController
const taskId = `search_${bookName}`;
if (progressTracker) { if (progressTracker) {
progressTracker.addTask(taskId, `搜索:${bookName}`, "search"); progressTracker.addTask(taskId, `搜索:${bookName}`, "search");
progressTracker.setTaskAbortController(taskId, abortController);
} }
try { try {
// 调用AI // 调用AI(传递 signal 以支持终止)
const response = await APIAdapter.callWithRetry( const response = await APIAdapter.callWithRetry(
{ ...aiConfig, category: bookName, source: bookName, taskId: taskId }, { ...aiConfig, category: bookName, source: bookName, taskId: taskId },
finalSystemPrompt, finalSystemPrompt,
finalUserMessage, finalUserMessage,
taskId, taskId,
3 3,
abortController.signal
); );
// 完成进度 // 完成进度
@@ -6506,13 +6570,20 @@
} }
} catch (error) { } catch (error) {
// 失败时也标记进度完成 // 失败时也标记进度完成
const isAborted = error.name === "AbortError";
if (progressTracker) { if (progressTracker) {
progressTracker.completeTask(taskId, false, error.message); progressTracker.completeTask(taskId, false, isAborted ? "已终止" : error.message);
} }
if (isAborted) {
Logger.warn(`[交互式搜索] 总结世界书 "${bookName}" 已被终止`);
panel.setBookStatus(bookName, "error", "已终止");
panel.addBookSystemMessage(bookName, "搜索已被用户终止");
} else {
Logger.error(`[交互式搜索] 总结世界书 "${bookName}" AI调用失败:`, error.message); Logger.error(`[交互式搜索] 总结世界书 "${bookName}" AI调用失败:`, error.message);
panel.setBookStatus(bookName, "error", "失败"); panel.setBookStatus(bookName, "error", "失败");
panel.addBookSystemMessage(bookName, `AI调用失败: ${error.message}`); panel.addBookSystemMessage(bookName, `AI调用失败: ${error.message}`);
} }
}
} catch (error) { } catch (error) {
Logger.error(`[交互式搜索] 总结世界书 "${bookName}" 初始化失败:`, error.message); Logger.error(`[交互式搜索] 总结世界书 "${bookName}" 初始化失败:`, error.message);
panel.setBookStatus(bookName, "error", "失败"); panel.setBookStatus(bookName, "error", "失败");

View File

@@ -3124,11 +3124,13 @@
============================================================================ */ ============================================================================ */
@media (max-width: 768px) { @media (max-width: 768px) {
.mm-message-progress-panel { /* 进度面板移动端基础定位(参考剧情优化面板,固定在左上角) */
.mm-message-progress-panel:not(.mm-dragging):not(.mm-user-positioned) {
width: calc(100vw - 32px); width: calc(100vw - 32px);
max-width: 320px; max-width: 320px;
left: 16px; left: 16px;
top: 60px; top: 60px;
right: auto;
transform: none; transform: none;
} }
@@ -3159,10 +3161,13 @@
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.mm-message-progress-panel { /* 进度面板手机端基础定位(参考剧情优化面板,固定在左上角) */
.mm-message-progress-panel:not(.mm-dragging):not(.mm-user-positioned) {
width: calc(100vw - 24px); width: calc(100vw - 24px);
left: 12px; left: 8px;
top: 50px; top: 60px;
right: auto;
transform: none;
} }
.mm-msg-panel-header { .mm-msg-panel-header {
@@ -6445,13 +6450,22 @@
transform: none; transform: none;
} }
/* 进度面板 - 移动端安全定位 */ /* 进度面板 - 移动端安全定位(参考剧情优化面板,固定在左上角) */
.mm-message-progress-panel { .mm-message-progress-panel:not(.mm-dragging):not(.mm-user-positioned) {
top: 10px !important; top: 60px !important;
left: 50% !important; left: 16px !important;
transform: translateX(-50%) !important; right: auto !important;
transform: none !important;
max-height: 60vh !important; max-height: 60vh !important;
} }
/* 用户手动拖动后,允许自定义位置 */
.mm-message-progress-panel.mm-user-positioned {
top: unset !important;
left: unset !important;
right: unset !important;
transform: none !important;
}
} }
/* 手机适配 (480px) */ /* 手机适配 (480px) */
@@ -6558,16 +6572,25 @@
transform: none; transform: none;
} }
/* 进度面板 - 手机端安全定位 */ /* 进度面板 - 手机端安全定位(参考剧情优化面板,固定在左上角) */
.mm-message-progress-panel { .mm-message-progress-panel:not(.mm-dragging):not(.mm-user-positioned) {
top: 8px !important; top: 60px !important;
left: 50% !important; left: 8px !important;
transform: translateX(-50%) !important; right: auto !important;
transform: none !important;
max-height: 55vh !important; max-height: 55vh !important;
width: calc(100vw - 24px) !important; width: calc(100vw - 24px) !important;
max-width: 300px !important; max-width: 300px !important;
} }
/* 用户手动拖动后,允许自定义位置 */
.mm-message-progress-panel.mm-user-positioned {
top: unset !important;
left: unset !important;
right: unset !important;
transform: none !important;
}
/* 汇总检查弹窗(动态创建) */ /* 汇总检查弹窗(动态创建) */
.mm-modal.mm-modal-visible .mm-modal-content { .mm-modal.mm-modal-visible .mm-modal-content {
width: calc(100vw - 16px) !important; width: calc(100vw - 16px) !important;