feat: 添加仅阅读按钮;默认进入沉浸模式;关闭沉浸模式的出口;隐藏沉浸模式侧边栏。
This commit is contained in:
parent
b646d9ef39
commit
56cd3108ed
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -986,6 +986,7 @@ body.immersive-dragging .immersive-resize-handle {
|
|||
|
||||
#immersive-toc-area {
|
||||
min-width: 200px;
|
||||
|
||||
}
|
||||
|
||||
#immersive-chatbot-area {
|
||||
|
|
|
|||
|
|
@ -1327,7 +1327,11 @@
|
|||
<div class="flex justify-center mt-8 mb-12">
|
||||
<button id="processBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
|
||||
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
|
||||
<span>开始智能处理</span>
|
||||
<span>开始处理</span>
|
||||
</button>
|
||||
<button id="onlyReadBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
|
||||
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
|
||||
<span>仅阅读</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
103
js/app.js
103
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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// 内置提示模板获取
|
||||
// =====================
|
||||
|
|
|
|||
|
|
@ -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 = '<i class="fas fa-compress-alt"></i>';
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -86,10 +86,27 @@
|
|||
<link rel="stylesheet" href="../../css/history_detail.css" />
|
||||
|
||||
<!-- All CSS now imported via history_detail.css (Phase 9 完成) -->
|
||||
|
||||
<!-- 页面初始隐藏,等待沉浸模式初始化 -->
|
||||
<style>
|
||||
html.immersive-pending body,
|
||||
body.immersive-pending {
|
||||
opacity: 0;
|
||||
}
|
||||
html.immersive-ready body,
|
||||
body.immersive-ready {
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// 在 DOM 解析前就添加类,避免闪烁
|
||||
document.documentElement.classList.add('immersive-pending');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="immersive-pending">
|
||||
<!-- Standard Immersive Mode Toggle Button -->
|
||||
<button id="toggle-immersive-btn" class="tiny-round-btn" title="进入沉浸式布局">
|
||||
<button id="toggle-immersive-btn" class="tiny-round-btn hidden" style="display: none;" title="进入沉浸式布局">
|
||||
<i class="fas fa-expand-alt"></i>
|
||||
<!-- Default icon for entering immersive mode -->
|
||||
</button>
|
||||
|
|
@ -400,9 +417,9 @@
|
|||
<h2 id="fileName">历史详情</h2>
|
||||
<div class="meta" id="fileMeta">
|
||||
<div class="meta-info">
|
||||
<span id="fileMetaTime"></span>
|
||||
<!-- <span id="fileMetaTime"></span>
|
||||
<span class="meta-separator">|</span>
|
||||
<span id="fileMetaImages"></span>
|
||||
<span id="fileMetaImages"></span> -->
|
||||
</div>
|
||||
<button
|
||||
class="meta-export-trigger"
|
||||
|
|
|
|||
Loading…
Reference in New Issue