Spaces:
Sleeping
Sleeping
| /** | |
| * 交互式英语学习应用 - 阅读页面逻辑 | |
| * 支持点读、翻译显示和音频播放功能 | |
| * 适配新的 data 数据结构 | |
| */ | |
| class InteractiveLearningApp { | |
| constructor() { | |
| this.bookId = null; | |
| this.bookInfo = null; | |
| this.pages = []; | |
| this.catalog = []; | |
| this.currentPageIndex = 0; | |
| this.currentAudio = null; | |
| this.isPlaying = false; | |
| this.showTranslation = false; | |
| this.showInteractiveAreas = false; | |
| this.bookmarks = []; | |
| this.searchResults = []; | |
| this.debugMode = false; | |
| this.settings = { | |
| autoTranslation: false, | |
| playbackSpeed: 1, | |
| autoPlayNext: false | |
| }; | |
| this.init(); | |
| } | |
| async init() { | |
| try { | |
| // 从URL获取书籍ID | |
| const urlParams = new URLSearchParams(window.location.search); | |
| this.bookId = urlParams.get('book_id'); | |
| if (!this.bookId) { | |
| throw new Error('未指定书籍ID'); | |
| } | |
| await this.loadBookInfo(); | |
| await this.loadBookPages(); | |
| await this.loadBookCatalog(); | |
| this.setupEventListeners(); | |
| this.loadSettings(); | |
| this.loadBookmarks(); | |
| await this.renderCurrentPage(); | |
| this.updateUI(); | |
| } catch (error) { | |
| console.error('应用初始化失败:', error); | |
| this.showToast('应用初始化失败: ' + error.message, 'error'); | |
| } | |
| } | |
| /** | |
| * 加载书籍信息 | |
| */ | |
| async loadBookInfo() { | |
| try { | |
| const response = await fetch(`/api/v2/books/${this.bookId}`); | |
| if (!response.ok) { | |
| throw new Error('获取书籍信息失败'); | |
| } | |
| const result = await response.json(); | |
| if (!result.success) { | |
| throw new Error(result.error || '获取书籍信息失败'); | |
| } | |
| this.bookInfo = result.book; | |
| // 更新页面标题 | |
| document.getElementById('bookTitle').innerHTML = ` | |
| <i class="fas fa-book-open"></i> | |
| ${this.bookInfo.market_book_name} | |
| `; | |
| document.title = `${this.bookInfo.market_book_name} - 交互式英语学习`; | |
| console.log('书籍信息:', this.bookInfo); | |
| } catch (error) { | |
| console.error('加载书籍信息失败:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * 加载书籍页面列表 | |
| */ | |
| async loadBookPages() { | |
| try { | |
| const response = await fetch(`/api/v2/books/${this.bookId}/pages`); | |
| if (!response.ok) { | |
| throw new Error('获取页面列表失败'); | |
| } | |
| const result = await response.json(); | |
| if (!result.success) { | |
| throw new Error(result.error || '获取页面列表失败'); | |
| } | |
| this.pages = result.pages || []; | |
| console.log(`加载了 ${this.pages.length} 页`); | |
| } catch (error) { | |
| console.error('加载页面列表失败:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * 加载书籍目录 | |
| */ | |
| async loadBookCatalog() { | |
| try { | |
| const response = await fetch(`/api/v2/books/${this.bookId}/catalog`); | |
| if (!response.ok) { | |
| console.warn('获取目录失败'); | |
| return; | |
| } | |
| const result = await response.json(); | |
| if (result.success) { | |
| this.catalog = result.catalog || []; | |
| console.log(`加载了 ${this.catalog.length} 个目录项`); | |
| } | |
| } catch (error) { | |
| console.warn('加载目录失败:', error); | |
| } | |
| } | |
| /** | |
| * 获取资源URL(支持本地data目录) | |
| */ | |
| getResourceUrl(relativePath) { | |
| if (!relativePath) return ''; | |
| // 资源路径格式: "168_一年级上册/images/page_001.jpg" | |
| return `data/${relativePath}`; | |
| } | |
| setupEventListeners() { | |
| // 返回按钮 | |
| document.getElementById('backBtn').addEventListener('click', () => { | |
| window.location.href = '/'; | |
| }); | |
| // 目录按钮 | |
| document.getElementById('catalogBtn').addEventListener('click', () => this.showCatalog()); | |
| // 页面导航 | |
| document.getElementById('prevBtn').addEventListener('click', () => this.previousPage()); | |
| document.getElementById('nextBtn').addEventListener('click', () => this.nextPage()); | |
| // 控制按钮 | |
| document.getElementById('translationToggle').addEventListener('click', () => this.toggleTranslation()); | |
| document.getElementById('interactiveToggle').addEventListener('click', () => this.toggleInteractiveAreas()); | |
| document.getElementById('playAllBtn').addEventListener('click', () => this.playAllPieces()); | |
| document.getElementById('bookmarkBtn').addEventListener('click', () => this.toggleBookmark()); | |
| document.getElementById('searchBtn').addEventListener('click', () => this.showSearch()); | |
| document.getElementById('settingsBtn').addEventListener('click', () => this.showSettings()); | |
| // 音频控制 | |
| document.getElementById('playPauseBtn').addEventListener('click', () => this.togglePlayPause()); | |
| document.getElementById('repeatBtn').addEventListener('click', () => this.repeatAudio()); | |
| document.getElementById('audioTimeline').addEventListener('click', (e) => this.seekAudio(e)); | |
| // 关闭按钮 | |
| document.getElementById('closeCatalog')?.addEventListener('click', () => this.hideCatalog()); | |
| document.getElementById('closeSettings').addEventListener('click', () => this.hideSettings()); | |
| document.getElementById('closeSearch').addEventListener('click', () => this.hideSearch()); | |
| document.getElementById('closeBookmark')?.addEventListener('click', () => this.hideBookmarks()); | |
| // 搜索 | |
| document.getElementById('doSearch').addEventListener('click', () => this.performSearch()); | |
| document.getElementById('searchInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.performSearch(); | |
| }); | |
| // 设置 | |
| document.getElementById('autoTranslation').addEventListener('change', (e) => { | |
| this.settings.autoTranslation = e.target.checked; | |
| this.saveSettings(); | |
| if (e.target.checked) { | |
| document.getElementById('textOverlays').classList.add('show-translation'); | |
| } | |
| }); | |
| document.getElementById('playbackSpeed').addEventListener('change', (e) => { | |
| this.settings.playbackSpeed = parseFloat(e.target.value); | |
| this.saveSettings(); | |
| if (this.currentAudio) { | |
| this.currentAudio.playbackRate = this.settings.playbackSpeed; | |
| } | |
| }); | |
| document.getElementById('autoPlayNext').addEventListener('change', (e) => { | |
| this.settings.autoPlayNext = e.target.checked; | |
| this.saveSettings(); | |
| }); | |
| // 键盘快捷键 | |
| document.addEventListener('keydown', (e) => this.handleKeyPress(e)); | |
| // 音频事件 | |
| const audio = document.getElementById('audio'); | |
| audio.addEventListener('loadedmetadata', () => this.updateAudioUI()); | |
| audio.addEventListener('timeupdate', () => this.updateAudioProgress()); | |
| audio.addEventListener('ended', () => this.onAudioEnded()); | |
| audio.addEventListener('error', (e) => { | |
| console.error('音频播放失败:', e); | |
| this.showToast('音频播放失败', 'error'); | |
| }); | |
| } | |
| /** | |
| * 渲染当前页面 | |
| */ | |
| async renderCurrentPage() { | |
| if (!this.pages || this.currentPageIndex >= this.pages.length) { | |
| return; | |
| } | |
| const pageContainer = document.getElementById('pageContent'); | |
| const loading = document.getElementById('loading'); | |
| loading.style.display = 'block'; | |
| pageContainer.style.display = 'none'; | |
| try { | |
| // 获取页面信息 | |
| const pageInfo = this.pages[this.currentPageIndex]; | |
| const pageNumber = pageInfo.page_number; | |
| // 加载页面详细内容 | |
| const response = await fetch(`/api/v2/books/${this.bookId}/pages/${pageNumber}`); | |
| if (!response.ok) { | |
| throw new Error(`加载第 ${pageNumber} 页失败`); | |
| } | |
| const result = await response.json(); | |
| if (!result.success) { | |
| throw new Error(result.error || '加载页面内容失败'); | |
| } | |
| const page = result.page; | |
| // 加载页面图片 | |
| const pageImage = document.getElementById('pageImage'); | |
| pageImage.onload = () => { | |
| loading.style.display = 'none'; | |
| pageContainer.style.display = 'block'; | |
| this.renderTextPieces(page); | |
| }; | |
| pageImage.onerror = () => { | |
| loading.style.display = 'none'; | |
| this.showToast('页面图片加载失败', 'error'); | |
| }; | |
| const imageUrl = this.getResourceUrl(page.origin_img_url); | |
| pageImage.src = imageUrl; | |
| pageImage.alt = `第${pageNumber}页`; | |
| } catch (error) { | |
| loading.style.display = 'none'; | |
| console.error('渲染页面失败:', error); | |
| this.showToast('页面加载失败: ' + error.message, 'error'); | |
| } | |
| } | |
| /** | |
| * 渲染文本片段 | |
| */ | |
| renderTextPieces(page) { | |
| const textOverlays = document.getElementById('textOverlays'); | |
| const pageImage = document.getElementById('pageImage'); | |
| textOverlays.innerHTML = ''; | |
| const renderPieces = () => { | |
| const imageRect = pageImage.getBoundingClientRect(); | |
| const overlayRect = textOverlays.getBoundingClientRect(); | |
| const offsetX = imageRect.left - overlayRect.left; | |
| const offsetY = imageRect.top - overlayRect.top; | |
| const imageWidth = imageRect.width; | |
| const imageHeight = imageRect.height; | |
| if (!page.pieces || page.pieces.length === 0) { | |
| console.warn('页面没有内容片段'); | |
| return; | |
| } | |
| page.pieces.forEach((piece, index) => { | |
| const textPiece = document.createElement('div'); | |
| textPiece.className = 'text-piece'; | |
| textPiece.dataset.pieceIndex = index; | |
| if (this.debugMode) { | |
| textPiece.style.border = '2px solid red'; | |
| textPiece.style.backgroundColor = 'rgba(255, 0, 0, 0.2)'; | |
| } | |
| // 设置位置和大小 | |
| const coord = piece.coordinate; | |
| const left = offsetX + (coord.x * imageWidth); | |
| const top = offsetY + (coord.y * imageHeight); | |
| const width = coord.width * imageWidth; | |
| const height = coord.height * imageHeight; | |
| textPiece.style.left = `${left}px`; | |
| textPiece.style.top = `${top}px`; | |
| textPiece.style.width = `${width}px`; | |
| textPiece.style.height = `${height}px`; | |
| // 创建文本内容 | |
| const originalText = document.createElement('div'); | |
| originalText.className = 'piece-text'; | |
| originalText.textContent = piece.original; | |
| const translationText = document.createElement('div'); | |
| translationText.className = 'piece-translation'; | |
| translationText.textContent = piece.translation; | |
| textPiece.appendChild(originalText); | |
| textPiece.appendChild(translationText); | |
| // 点击事件 | |
| textPiece.addEventListener('click', () => this.playPiece(piece, textPiece)); | |
| textOverlays.appendChild(textPiece); | |
| }); | |
| // 根据设置显示翻译 | |
| if (this.showTranslation || this.settings.autoTranslation) { | |
| textOverlays.classList.add('show-translation'); | |
| } | |
| }; | |
| if (pageImage.complete && pageImage.naturalHeight !== 0) { | |
| renderPieces(); | |
| } else { | |
| pageImage.addEventListener('load', renderPieces, { once: true }); | |
| } | |
| // 监听窗口大小变化 | |
| let resizeTimeout; | |
| const handleResize = () => { | |
| clearTimeout(resizeTimeout); | |
| resizeTimeout = setTimeout(() => { | |
| if (pageImage.complete && pageImage.naturalHeight !== 0) { | |
| renderPieces(); | |
| } | |
| }, 100); | |
| }; | |
| if (pageImage.resizeObserver) { | |
| pageImage.resizeObserver.disconnect(); | |
| } | |
| pageImage.resizeObserver = new ResizeObserver(handleResize); | |
| pageImage.resizeObserver.observe(pageImage); | |
| window.addEventListener('resize', handleResize); | |
| } | |
| /** | |
| * 播放片段音频 | |
| */ | |
| async playPiece(piece, element) { | |
| try { | |
| document.querySelectorAll('.text-piece').forEach(el => { | |
| el.classList.remove('active', 'playing'); | |
| }); | |
| element.classList.add('active'); | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| audioPlayer.style.display = 'block'; | |
| const audioText = document.getElementById('audioText'); | |
| audioText.innerHTML = ` | |
| <div style="font-weight: 600; margin-bottom: 0.5rem;">${piece.original}</div> | |
| <div style="color: var(--text-secondary); font-size: 0.9rem;">${piece.translation}</div> | |
| `; | |
| const audio = document.getElementById('audio'); | |
| const audioUrl = piece.origin_sound_url || piece.encrypt_sound_url; | |
| if (!audioUrl) { | |
| this.showToast('该片段没有可用的音频', 'warning'); | |
| return; | |
| } | |
| if (this.currentAudio && !this.currentAudio.paused) { | |
| this.currentAudio.pause(); | |
| } | |
| const fullAudioUrl = this.getResourceUrl(audioUrl); | |
| audio.src = fullAudioUrl; | |
| audio.playbackRate = this.settings.playbackSpeed; | |
| this.currentAudio = audio; | |
| await audio.play(); | |
| this.isPlaying = true; | |
| element.classList.add('playing'); | |
| this.updatePlayButton(); | |
| } catch (error) { | |
| console.error('音频播放失败:', error); | |
| this.showToast('音频播放失败', 'error'); | |
| element.classList.remove('active', 'playing'); | |
| } | |
| } | |
| /** | |
| * 播放整页音频 | |
| */ | |
| async playAllPieces() { | |
| this.showToast('整页播放功能开发中...', 'info'); | |
| } | |
| /** | |
| * 目录相关 | |
| */ | |
| showCatalog() { | |
| const panel = document.getElementById('catalogPanel'); | |
| panel.style.display = 'flex'; | |
| this.renderCatalog(); | |
| } | |
| hideCatalog() { | |
| document.getElementById('catalogPanel').style.display = 'none'; | |
| } | |
| renderCatalog() { | |
| const catalogList = document.getElementById('catalogList'); | |
| if (!this.catalog || this.catalog.length === 0) { | |
| // 空目录时,显示页码列表 | |
| catalogList.innerHTML = ` | |
| <div class="catalog-empty"> | |
| <p>📖 此书暂无目录信息</p> | |
| <p style="margin-top: 1rem; font-size: 0.9rem; color: var(--text-secondary);"> | |
| 共 ${this.pages.length} 页,可通过页面导航按钮浏览 | |
| </p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| catalogList.innerHTML = ''; | |
| // 如果只有一个目录项,也显示提示 | |
| if (this.catalog.length === 1) { | |
| const notice = document.createElement('div'); | |
| notice.style.cssText = 'padding: 0.75rem; background: #fef3c7; border-radius: 8px; margin-bottom: 1rem; font-size: 0.9rem;'; | |
| notice.innerHTML = ` | |
| <i class="fas fa-info-circle"></i> | |
| 此书仅有 1 个目录项,建议使用页面导航浏览 | |
| `; | |
| catalogList.appendChild(notice); | |
| } | |
| this.catalog.forEach(item => { | |
| const catalogItem = document.createElement('div'); | |
| catalogItem.className = 'catalog-item'; | |
| catalogItem.innerHTML = ` | |
| <div class="catalog-name"> | |
| <div class="catalog-name-en">${item.catalog_name || ''}</div> | |
| <div class="catalog-name-cn">${item.catalog_name_cn || ''}</div> | |
| </div> | |
| <div class="catalog-pages">P${item.start_page}${item.start_page !== item.end_page ? '-' + item.end_page : ''}</div> | |
| `; | |
| catalogItem.addEventListener('click', () => { | |
| this.goToPage(item.start_page); | |
| this.hideCatalog(); | |
| }); | |
| catalogList.appendChild(catalogItem); | |
| }); | |
| } | |
| goToPage(pageNumber) { | |
| // 找到对应的页面索引 | |
| const index = this.pages.findIndex(p => p.page_number === pageNumber); | |
| if (index >= 0) { | |
| this.currentPageIndex = index; | |
| this.renderCurrentPage(); | |
| this.updateUI(); | |
| this.stopCurrentAudio(); | |
| } | |
| } | |
| /** | |
| * 页面导航 | |
| */ | |
| previousPage() { | |
| if (this.currentPageIndex > 0) { | |
| this.currentPageIndex--; | |
| this.renderCurrentPage(); | |
| this.updateUI(); | |
| this.stopCurrentAudio(); | |
| } | |
| } | |
| nextPage() { | |
| if (this.currentPageIndex < this.pages.length - 1) { | |
| this.currentPageIndex++; | |
| this.renderCurrentPage(); | |
| this.updateUI(); | |
| this.stopCurrentAudio(); | |
| } | |
| } | |
| /** | |
| * UI更新 | |
| */ | |
| updateUI() { | |
| const currentPage = this.pages[this.currentPageIndex]; | |
| document.getElementById('currentPage').textContent = currentPage ? currentPage.page_number : 0; | |
| document.getElementById('totalPages').textContent = this.pages.length || 0; | |
| const progress = this.pages.length ? ((this.currentPageIndex + 1) / this.pages.length) * 100 : 0; | |
| document.getElementById('progressFill').style.width = `${progress}%`; | |
| document.getElementById('prevBtn').disabled = this.currentPageIndex === 0; | |
| document.getElementById('nextBtn').disabled = this.currentPageIndex === this.pages.length - 1; | |
| this.updateBookmarkButton(); | |
| } | |
| /** | |
| * 翻译和交互区域切换 | |
| */ | |
| toggleTranslation() { | |
| this.showTranslation = !this.showTranslation; | |
| const textOverlays = document.getElementById('textOverlays'); | |
| const translationBtn = document.getElementById('translationToggle'); | |
| if (this.showTranslation) { | |
| textOverlays.classList.add('show-translation'); | |
| translationBtn.classList.add('active'); | |
| } else { | |
| textOverlays.classList.remove('show-translation'); | |
| translationBtn.classList.remove('active'); | |
| } | |
| } | |
| toggleInteractiveAreas() { | |
| this.showInteractiveAreas = !this.showInteractiveAreas; | |
| const textOverlays = document.getElementById('textOverlays'); | |
| const interactiveBtn = document.getElementById('interactiveToggle'); | |
| if (this.showInteractiveAreas) { | |
| textOverlays.classList.add('show-interactive-areas'); | |
| interactiveBtn.classList.add('active'); | |
| } else { | |
| textOverlays.classList.remove('show-interactive-areas'); | |
| interactiveBtn.classList.remove('active'); | |
| } | |
| } | |
| /** | |
| * 音频控制 | |
| */ | |
| togglePlayPause() { | |
| const audio = document.getElementById('audio'); | |
| if (!audio.src) return; | |
| if (this.isPlaying) { | |
| audio.pause(); | |
| this.isPlaying = false; | |
| } else { | |
| audio.play().then(() => { | |
| this.isPlaying = true; | |
| }).catch(error => { | |
| console.error('播放失败:', error); | |
| this.showToast('播放失败', 'error'); | |
| }); | |
| } | |
| this.updatePlayButton(); | |
| } | |
| repeatAudio() { | |
| const audio = document.getElementById('audio'); | |
| if (audio.src) { | |
| audio.currentTime = 0; | |
| if (!this.isPlaying) { | |
| this.togglePlayPause(); | |
| } | |
| } | |
| } | |
| seekAudio(event) { | |
| const audio = document.getElementById('audio'); | |
| if (!audio.src || !audio.duration) return; | |
| const timeline = document.getElementById('audioTimeline'); | |
| const rect = timeline.getBoundingClientRect(); | |
| const percentage = (event.clientX - rect.left) / rect.width; | |
| const newTime = percentage * audio.duration; | |
| audio.currentTime = newTime; | |
| } | |
| updatePlayButton() { | |
| const playPauseBtn = document.getElementById('playPauseBtn'); | |
| const icon = playPauseBtn.querySelector('i'); | |
| if (this.isPlaying) { | |
| icon.className = 'fas fa-pause'; | |
| } else { | |
| icon.className = 'fas fa-play'; | |
| } | |
| } | |
| updateAudioUI() { | |
| const audio = document.getElementById('audio'); | |
| const totalTime = document.getElementById('totalTime'); | |
| if (audio.duration) { | |
| totalTime.textContent = this.formatTime(audio.duration); | |
| } | |
| } | |
| updateAudioProgress() { | |
| const audio = document.getElementById('audio'); | |
| const progressBar = document.getElementById('audioProgressBar'); | |
| const currentTime = document.getElementById('currentTime'); | |
| if (audio.duration) { | |
| const percentage = (audio.currentTime / audio.duration) * 100; | |
| progressBar.style.width = `${percentage}%`; | |
| currentTime.textContent = this.formatTime(audio.currentTime); | |
| } | |
| } | |
| onAudioEnded() { | |
| this.isPlaying = false; | |
| this.updatePlayButton(); | |
| document.querySelectorAll('.text-piece').forEach(el => { | |
| el.classList.remove('playing'); | |
| }); | |
| } | |
| stopCurrentAudio() { | |
| if (this.currentAudio && !this.currentAudio.paused) { | |
| this.currentAudio.pause(); | |
| this.isPlaying = false; | |
| this.updatePlayButton(); | |
| } | |
| document.getElementById('audioPlayer').style.display = 'none'; | |
| document.querySelectorAll('.text-piece').forEach(el => { | |
| el.classList.remove('active', 'playing'); | |
| }); | |
| } | |
| /** | |
| * 搜索功能 | |
| */ | |
| showSearch() { | |
| document.getElementById('searchPanel').style.display = 'flex'; | |
| document.getElementById('searchInput').focus(); | |
| } | |
| hideSearch() { | |
| document.getElementById('searchPanel').style.display = 'none'; | |
| document.getElementById('searchInput').value = ''; | |
| } | |
| async performSearch() { | |
| const keyword = document.getElementById('searchInput').value.trim(); | |
| if (!keyword) { | |
| this.showToast('请输入搜索关键词', 'warning'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`/api/v2/books/${this.bookId}/search?keyword=${encodeURIComponent(keyword)}&limit=20`); | |
| if (!response.ok) { | |
| throw new Error('搜索失败'); | |
| } | |
| const result = await response.json(); | |
| if (!result.success) { | |
| throw new Error(result.error || '搜索失败'); | |
| } | |
| this.searchResults = result.results || []; | |
| this.displaySearchResults(keyword); | |
| } catch (error) { | |
| console.error('搜索失败:', error); | |
| this.showToast('搜索失败: ' + error.message, 'error'); | |
| } | |
| } | |
| displaySearchResults(keyword) { | |
| const resultsContainer = document.getElementById('searchResults'); | |
| if (this.searchResults.length === 0) { | |
| resultsContainer.innerHTML = '<div class="search-empty">未找到匹配的内容</div>'; | |
| return; | |
| } | |
| resultsContainer.innerHTML = ''; | |
| this.searchResults.forEach((result, index) => { | |
| const resultItem = document.createElement('div'); | |
| resultItem.className = 'search-result-item'; | |
| resultItem.innerHTML = ` | |
| <div class="search-result-page">第 ${result.page_number} 页</div> | |
| <div class="search-result-text">${this.highlightKeyword(result.original, keyword)}</div> | |
| <div class="search-result-translation">${this.highlightKeyword(result.translation, keyword)}</div> | |
| `; | |
| resultItem.addEventListener('click', () => { | |
| this.goToPage(result.page_number); | |
| this.hideSearch(); | |
| }); | |
| resultsContainer.appendChild(resultItem); | |
| }); | |
| } | |
| highlightKeyword(text, keyword) { | |
| if (!text || !keyword) return text; | |
| const regex = new RegExp(`(${keyword})`, 'gi'); | |
| return text.replace(regex, '<span class="search-highlight">$1</span>'); | |
| } | |
| /** | |
| * 设置相关 | |
| */ | |
| showSettings() { | |
| document.getElementById('settingsPanel').style.display = 'flex'; | |
| document.getElementById('autoTranslation').checked = this.settings.autoTranslation; | |
| document.getElementById('playbackSpeed').value = this.settings.playbackSpeed; | |
| document.getElementById('autoPlayNext').checked = this.settings.autoPlayNext; | |
| } | |
| hideSettings() { | |
| document.getElementById('settingsPanel').style.display = 'none'; | |
| } | |
| loadSettings() { | |
| const saved = localStorage.getItem('learningAppSettings'); | |
| if (saved) { | |
| try { | |
| this.settings = { ...this.settings, ...JSON.parse(saved) }; | |
| } catch (error) { | |
| console.error('设置加载失败:', error); | |
| } | |
| } | |
| } | |
| saveSettings() { | |
| localStorage.setItem('learningAppSettings', JSON.stringify(this.settings)); | |
| } | |
| /** | |
| * 书签功能 | |
| */ | |
| toggleBookmark() { | |
| this.showToast('书签功能开发中...', 'info'); | |
| } | |
| updateBookmarkButton() { | |
| // 实现书签按钮更新逻辑 | |
| } | |
| loadBookmarks() { | |
| // 实现书签加载逻辑 | |
| } | |
| hideBookmarks() { | |
| document.getElementById('bookmarkPanel').style.display = 'none'; | |
| } | |
| /** | |
| * 键盘快捷键 | |
| */ | |
| handleKeyPress(event) { | |
| if (document.getElementById('settingsPanel').style.display === 'flex' || | |
| document.getElementById('searchPanel').style.display === 'flex') { | |
| if (event.key === 'Escape') { | |
| this.hideSettings(); | |
| this.hideSearch(); | |
| } | |
| return; | |
| } | |
| switch (event.key) { | |
| case 'ArrowLeft': | |
| event.preventDefault(); | |
| this.previousPage(); | |
| break; | |
| case 'ArrowRight': | |
| event.preventDefault(); | |
| this.nextPage(); | |
| break; | |
| case ' ': | |
| event.preventDefault(); | |
| this.togglePlayPause(); | |
| break; | |
| case 't': | |
| case 'T': | |
| event.preventDefault(); | |
| this.toggleTranslation(); | |
| break; | |
| case 'i': | |
| case 'I': | |
| event.preventDefault(); | |
| this.toggleInteractiveAreas(); | |
| break; | |
| } | |
| } | |
| /** | |
| * 工具方法 | |
| */ | |
| formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return `${mins}:${secs.toString().padStart(2, '0')}`; | |
| } | |
| showToast(message, type = 'info') { | |
| const toast = document.getElementById('toast'); | |
| toast.textContent = message; | |
| toast.className = `toast ${type} show`; | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| } | |
| // 页面加载完成后初始化应用 | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new InteractiveLearningApp(); | |
| }); | |
| // 处理页面可见性变化 | |
| document.addEventListener('visibilitychange', () => { | |
| if (document.hidden) { | |
| const audio = document.getElementById('audio'); | |
| if (audio && !audio.paused) { | |
| audio.pause(); | |
| } | |
| } | |
| }); | |