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 @@ + + + + - + - @@ -400,9 +417,9 @@

历史详情

- +