From 56cd3108ed2c81943b3530aabc99b5f6774f3f9b Mon Sep 17 00:00:00 2001
From: MT-Fire <798521692@qq.com>
Date: Wed, 11 Mar 2026 09:55:46 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BB=85=E9=98=85?=
=?UTF-8?q?=E8=AF=BB=E6=8C=89=E9=92=AE=EF=BC=9B=E9=BB=98=E8=AE=A4=E8=BF=9B?=
=?UTF-8?q?=E5=85=A5=E6=B2=89=E6=B5=B8=E6=A8=A1=E5=BC=8F=EF=BC=9B=E5=85=B3?=
=?UTF-8?q?=E9=97=AD=E6=B2=89=E6=B5=B8=E6=A8=A1=E5=BC=8F=E7=9A=84=E5=87=BA?=
=?UTF-8?q?=E5=8F=A3=EF=BC=9B=E9=9A=90=E8=97=8F=E6=B2=89=E6=B5=B8=E6=A8=A1?=
=?UTF-8?q?=E5=BC=8F=E4=BE=A7=E8=BE=B9=E6=A0=8F=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
css/history_detail/02-layout/immersive.css | 3 +
css/history_detail/05-utilities/buttons.css | 3 +-
.../_deprecated/immersive_layout.css | 1 +
index.html | 6 +-
js/app.js | 103 ++++++++++++++
js/ui/immersive_layout_logic.js | 128 ++++++++++++------
views/history/history_detail.html | 25 +++-
7 files changed, 225 insertions(+), 44 deletions(-)
diff --git a/css/history_detail/02-layout/immersive.css b/css/history_detail/02-layout/immersive.css
index 027c339..177d2c8 100644
--- a/css/history_detail/02-layout/immersive.css
+++ b/css/history_detail/02-layout/immersive.css
@@ -56,6 +56,8 @@
#immersive-toc-area.immersive-panel {
padding: var(--spacing-xs) var(--spacing-sm) 0 var(--spacing-sm);
border-right: 1px solid var(--color-border-light);
+ /* 隐藏沉浸模式侧边栏 */
+ display: none;
}
#immersive-chatbot-area.immersive-panel {
@@ -788,6 +790,7 @@ body.immersive-active #immersive-main-content-area .container {
#immersive-toc-area {
min-width: 200px;
+
}
#immersive-chatbot-area {
diff --git a/css/history_detail/05-utilities/buttons.css b/css/history_detail/05-utilities/buttons.css
index 5d9d84f..26e96e3 100644
--- a/css/history_detail/05-utilities/buttons.css
+++ b/css/history_detail/05-utilities/buttons.css
@@ -48,7 +48,8 @@
color: #94a3b8 !important; /* slate-400 */
border: 1px solid #e2e8f0 !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
- display: flex !important;
+ /* 隐藏沉浸模式的出口 */
+ display: flex;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
diff --git a/css/history_detail/_deprecated/immersive_layout.css b/css/history_detail/_deprecated/immersive_layout.css
index b1e9c2b..a1bf4f4 100644
--- a/css/history_detail/_deprecated/immersive_layout.css
+++ b/css/history_detail/_deprecated/immersive_layout.css
@@ -986,6 +986,7 @@ body.immersive-dragging .immersive-resize-handle {
#immersive-toc-area {
min-width: 200px;
+
}
#immersive-chatbot-area {
diff --git a/index.html b/index.html
index 7a45311..b6ef1fa 100644
--- a/index.html
+++ b/index.html
@@ -1327,7 +1327,11 @@
+
diff --git a/js/app.js b/js/app.js
index 60b91b0..4739c50 100644
--- a/js/app.js
+++ b/js/app.js
@@ -43,6 +43,24 @@ function escapeHtml(str) {
});
}
+/**
+ * 将 ArrayBuffer 转换为 Base64 字符串
+ * @param {ArrayBuffer} buffer - 需要转换的 ArrayBuffer
+ * @returns {string} Base64 编码的字符串
+ */
+function arrayBufferToBase64(buffer) {
+ if (!buffer) return null;
+ const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer.buffer || []);
+ if (!bytes.length) return null;
+ let binary = '';
+ const chunkSize = 0x8000;
+ for (let i = 0; i < bytes.length; i += chunkSize) {
+ const chunk = bytes.subarray(i, i + chunkSize);
+ binary += String.fromCharCode.apply(null, chunk);
+ }
+ return btoa(binary);
+}
+
// =====================
// 全局状态变量
// =====================
@@ -523,6 +541,7 @@ function setupEventListeners() {
const githubImportBtn = document.getElementById('githubImportBtn');
const clearBtn = document.getElementById('clearFilesBtn');
const processBtn = document.getElementById('processBtn');
+ const onlyReadBtn = document.getElementById('onlyReadBtn');
const downloadBtn = document.getElementById('downloadAllBtn');
const formatFilterContainer = document.getElementById('fileFormatFilters');
const batchToggle = document.getElementById('batchModeToggle');
@@ -752,6 +771,7 @@ function setupEventListeners() {
// 处理和下载
processBtn.addEventListener('click', handleProcessClick);
+ onlyReadBtn.addEventListener('click', handleReadClick);
downloadBtn.addEventListener('click', handleDownloadClick);
if (typeof window.updateDeeplxTargetLangHint === 'function') {
@@ -2164,6 +2184,89 @@ function handleDownloadClick() {
}
}
+// =====================
+// 仅阅读(不做处理直接跳转)
+// =====================
+/**
+ * 处理点击"仅阅读"按钮的事件。
+ * 如果已有处理结果,直接跳转历史详情。
+ * 如果没有处理结果但有上传文件,则将文件保存到历史记录后跳转。
+ */
+async function handleReadClick() {
+ // 1. 优先检查是否有已处理的结果
+ if (allResults.length > 0) {
+ const successfulResult = allResults.find(r => r && r.file && !r.error && !r.skipped);
+ if (successfulResult && successfulResult.file) {
+ const recordId = `${successfulResult.file.name}_${successfulResult.file.size}`;
+ window.location.href = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
+ return;
+ }
+ }
+
+ // 2. 检查是否有上传的文件,直接保存到历史记录
+ if (pdfFiles.length > 0) {
+ const file = pdfFiles[0]; // 只处理第一个文件
+ const recordId = `${file.name}_${file.size}`;
+
+ try {
+ // 读取文件内容为 base64
+ const arrayBuffer = await file.arrayBuffer();
+ const base64Content = arrayBufferToBase64(arrayBuffer);
+
+ // 获取文件扩展名和类型
+ const ext = file.name.split('.').pop().toLowerCase();
+ const fileType = ext;
+
+ // 创建历史记录对象
+ const record = {
+ id: recordId,
+ name: file.name,
+ size: file.size,
+ time: new Date().toISOString(),
+ ocr: '',
+ translation: '',
+ images: [],
+ ocrChunks: [],
+ translatedChunks: [],
+ fileType: fileType,
+ targetLanguage: '',
+ originalEncoding: 'binary',
+ originalBinary: base64Content,
+ originalExtension: ext,
+ ocrEngine: null,
+ ocrSource: null,
+ translationModelName: 'none',
+ batchId: null,
+ batchOrder: null,
+ batchTotal: null,
+ batchTemplate: null,
+ batchFormats: null,
+ batchStartedAt: null
+ };
+
+ // 保存到数据库
+ if (typeof window.storageAdapter !== 'undefined' && typeof window.storageAdapter.saveResultToDB === 'function') {
+ await window.storageAdapter.saveResultToDB(record);
+ } else if (typeof saveResultToDB === 'function') {
+ await saveResultToDB(record);
+ } else {
+ showNotification('存储功能不可用', 'error');
+ return;
+ }
+
+ // 跳转到历史详情页面
+ window.location.href = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
+ } catch (error) {
+ console.error('保存文件到历史记录失败:', error);
+ showNotification(`保存失败: ${error.message}`, 'error');
+ }
+ } else {
+ showNotification('请先上传文件', 'warning');
+ }
+}
+
+
+
// =====================
// 内置提示模板获取
// =====================
diff --git a/js/ui/immersive_layout_logic.js b/js/ui/immersive_layout_logic.js
index 48ef498..cd661da 100644
--- a/js/ui/immersive_layout_logic.js
+++ b/js/ui/immersive_layout_logic.js
@@ -218,10 +218,17 @@
isTocDockResizing = false; // Reset flag
}
- function enterImmersiveMode() {
+ function enterImmersiveMode(options = {}) {
+ const { silent = false } = options;
+
// 检查是否为移动端设备
if (window.innerWidth <= 700) {
console.warn('拒绝在移动端(≤700px)进入沉浸式布局');
+ // 即使不进入沉浸模式,也需要显示页面
+ document.documentElement.classList.remove('immersive-pending');
+ document.documentElement.classList.add('immersive-ready');
+ document.body.classList.remove('immersive-pending');
+ document.body.classList.add('immersive-ready');
return;
}
@@ -238,19 +245,41 @@
if (!immersiveChatbotArea) missingElements.push('immersiveChatbotArea');
if (!immersiveDockPlaceholderElement) missingElements.push('immersiveDockPlaceholderElement (logic error if this happens)');
+ console.log('[enterImmersiveMode] 元素检查:', {
+ immersiveContainer: !!immersiveContainer,
+ mainPageContainer: !!mainPageContainer,
+ tocPopupElement: !!tocPopupElement,
+ chatbotModalElement: !!chatbotModalElement,
+ dockElement: !!dockElement,
+ immersiveTocArea: !!immersiveTocArea,
+ immersiveMainArea: !!immersiveMainArea,
+ immersiveChatbotArea: !!immersiveChatbotArea
+ });
+
if (missingElements.length > 0) {
console.warn('Immersive mode elements not found:', missingElements.join(', '));
+ // 元素缺失时也需要显示页面
+ document.body.classList.remove('immersive-pending');
+ document.body.classList.add('immersive-ready');
return;
}
isImmersiveActive = true;
storeOriginalPositions();
- // 添加进入动画类
- document.body.classList.add('immersive-entering');
- immersiveContainer.style.opacity = '0';
- immersiveContainer.style.transform = 'scale(0.95)';
- immersiveContainer.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
+ // 静默模式:跳过动画,直接设置状态
+ if (silent) {
+ document.body.classList.add('immersive-active', 'no-scroll');
+ immersiveContainer.style.display = 'flex';
+ immersiveContainer.style.opacity = '1';
+ immersiveContainer.style.transform = 'scale(1)';
+ } else {
+ // 添加进入动画类
+ document.body.classList.add('immersive-entering');
+ immersiveContainer.style.opacity = '0';
+ immersiveContainer.style.transform = 'scale(0.95)';
+ immersiveContainer.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
+ }
// Append TOC content first
if (tocPopupElement && immersiveTocArea) {
@@ -305,26 +334,35 @@
}
}
- document.body.classList.add('immersive-active', 'no-scroll');
- immersiveContainer.style.display = 'flex';
-
- // 简单的强制重新计算,修复初始化时的布局问题
- setTimeout(() => {
- if (immersiveContainer) {
- immersiveContainer.offsetHeight; // 触发重新布局
- }
- }, 0);
-
- // 动画进入效果
- requestAnimationFrame(() => {
- immersiveContainer.style.opacity = '1';
- immersiveContainer.style.transform = 'scale(1)';
-
+ // 静默模式下已经设置了 display 和 opacity,跳过动画
+ if (!silent) {
+ document.body.classList.add('immersive-active', 'no-scroll');
+ immersiveContainer.style.display = 'flex';
+
+ // 简单的强制重新计算,修复初始化时的布局问题
setTimeout(() => {
- document.body.classList.remove('immersive-entering');
- immersiveContainer.style.transition = '';
- }, 400);
- });
+ if (immersiveContainer) {
+ immersiveContainer.offsetHeight; // 触发重新布局
+ }
+ }, 0);
+
+ // 动画进入效果
+ requestAnimationFrame(() => {
+ immersiveContainer.style.opacity = '1';
+ immersiveContainer.style.transform = 'scale(1)';
+
+ setTimeout(() => {
+ document.body.classList.remove('immersive-entering');
+ immersiveContainer.style.transition = '';
+ }, 400);
+ });
+ }
+
+ // 显示页面(移除 pending 状态)
+ document.documentElement.classList.remove('immersive-pending');
+ document.documentElement.classList.add('immersive-ready');
+ document.body.classList.remove('immersive-pending');
+ document.body.classList.add('immersive-ready');
if (toggleBtn) {
toggleBtn.innerHTML = '';
@@ -393,7 +431,6 @@
localStorage.setItem(LS_IMMERSIVE_KEY, 'true');
document.dispatchEvent(new CustomEvent('immersiveModeEntered'));
}
-
function exitImmersiveMode() {
reQueryDynamicElements();
isImmersiveActive = false;
@@ -758,21 +795,36 @@
// Restore immersive state from localStorage
const savedImmersiveState = localStorage.getItem(LS_IMMERSIVE_KEY);
- if (savedImmersiveState === 'true') {
+
+ // 历史详情页默认进入沉浸模式,只有用户明确退出过才不进入
+ // 注意:null 表示首次访问,'true' 表示用户之前处于沉浸模式
+ // 只有 'false' 才表示用户主动退出过沉浸模式
+ const shouldEnterImmersive = savedImmersiveState !== 'false';
+
+ console.log('[ImmersiveLayout] savedImmersiveState:', savedImmersiveState, 'shouldEnterImmersive:', shouldEnterImmersive);
+
+ if (shouldEnterImmersive) {
// 检查是否为移动端,如果是则不恢复沉浸模式
if (window.innerWidth <= 700) {
- console.log('检测到移动端设备,不恢复沉浸式布局状态');
- localStorage.setItem(LS_IMMERSIVE_KEY, 'false'); // 清除保存的状态
- return;
+ console.log('检测到移动端设备,不进入沉浸式布局');
+ localStorage.setItem(LS_IMMERSIVE_KEY, 'false');
+ // 显示页面
+ document.documentElement.classList.remove('immersive-pending');
+ document.documentElement.classList.add('immersive-ready');
+ document.body.classList.remove('immersive-pending');
+ document.body.classList.add('immersive-ready');
+ } else {
+ // 立即进入沉浸模式(静默模式,无动画)
+ console.log('[ImmersiveLayout] 准备进入沉浸模式, isImmersiveActive:', isImmersiveActive);
+ enterImmersiveMode({ silent: true });
+ console.log('[ImmersiveLayout] enterImmersiveMode 调用完成, isImmersiveActive:', isImmersiveActive);
}
-
- // Slight delay to ensure other initializations (like TOC, Chatbot) can occur first
- // especially if they also interact with elements moved by immersive mode.
- setTimeout(() => {
- if (!isImmersiveActive) { // Check again in case of race conditions or manual toggle
- enterImmersiveMode();
- }
- }, 200); // Adjust delay if needed
+ } else {
+ // 不需要沉浸模式,直接显示页面
+ document.documentElement.classList.remove('immersive-pending');
+ document.documentElement.classList.add('immersive-ready');
+ document.body.classList.remove('immersive-pending');
+ document.body.classList.add('immersive-ready');
}
// Restore simple immersive state from localStorage
diff --git a/views/history/history_detail.html b/views/history/history_detail.html
index 860108c..2f0d009 100644
--- a/views/history/history_detail.html
+++ b/views/history/history_detail.html
@@ -86,10 +86,27 @@
+
+
+
+
-
+
-