精选诗歌

演示列表 (0)
`; } function openPresentation(songs) { if (!songs.length) return; const win = window.open('', '_blank'); if (!win) return; win.document.open(); win.document.write(buildPresentationHtml(songs)); win.document.close(); } function updateLinkState(link) { const songName = link.getAttribute('data-song-name'); const songExists = presentationList.some(song => song.name === songName); if (songExists) { link.classList.add('added'); link.textContent = '✓'; link.title = '已加入'; link.setAttribute('aria-label', '已加入'); } else { link.classList.remove('added'); link.textContent = '+'; link.title = '加入演示'; link.setAttribute('aria-label', '加入演示'); } } function refreshLinkStates() { document.querySelectorAll('.add-to-presentation').forEach(updateLinkState); } function renderPanelList() { panelItems.innerHTML = ''; presentationList.forEach((song, idx) => { const li = document.createElement('li'); li.setAttribute('draggable', 'true'); li.dataset.index = String(idx); li.innerHTML = `⋮⋮${idx + 1}. ${song.name}移除`; const removeEl = li.querySelector('.remove'); if (removeEl) removeEl.setAttribute('draggable', 'false'); addDragHandlers(li); panelItems.appendChild(li); }); panelCount.textContent = `(${presentationList.length})`; } function saveList() { localStorage.setItem(storageKey, JSON.stringify(presentationList)); renderPanelList(); refreshLinkStates(); } loadUploadedSongs(); clearPresetCategories(); renderUploadedSongs(); renderPanelList(); refreshLinkStates(); const resetVersion = localStorage.getItem(resetVersionKey); if (resetVersion !== currentResetVersion) { resetAllSongsData(); localStorage.setItem(resetVersionKey, currentResetVersion); setUploadStatus('已清理历史链接,请重新上传歌谱', false); } if (uploadButton && uploadInput) { uploadButton.addEventListener('click', async () => { const files = uploadInput.files ? Array.from(uploadInput.files) : []; if (files.length === 0) { setUploadStatus('请选择要上传的图片', true); return; } uploadButton.disabled = true; setUploadStatus('上传中…', false); try { for (const file of files) { await addUploadedSong(file); } setUploadStatus('上传完成', false); if (emptyTipEl) emptyTipEl.style.display = 'none'; uploadInput.value = ''; } catch (e) { setUploadStatus(e?.message || '上传失败', true); } finally { uploadButton.disabled = false; } }); } if (exportButton) { exportButton.addEventListener('click', async function() { if (uploadedSongs.length === 0) { alert('当前没有已上传的歌谱可导出。'); return; } if (typeof JSZip === 'undefined') { alert('打包工具尚未加载完成,请稍后重试。'); return; } exportButton.disabled = true; setUploadStatus('正在生成 ZIP 包,请稍候...', false); try { const zip = new JSZip(); const pagesDir = zip.folder('pages'); const imagesDir = zip.folder('灵栖清泉曲谱'); // 生成歌曲图片及 HTML uploadedSongs.forEach(song => { // 提取 base64 数据部分 const b64Data = song.dataUrl.split(',')[1]; imagesDir.file(song.fileName, b64Data, {base64: true}); // 生成真实静态链接使用的 HTML const safeName = song.name.replace(/\s+/g, '_'); const relativeSrc = `../灵栖清泉曲谱/${song.fileName}`; const html = buildSongViewerHtml(song.name, relativeSrc); pagesDir.file(`${safeName}.html`, html); }); // 生成 presentation.html // 预置演示页逻辑使用 generate_html_index.py 生成的那套结构 // 为了简单,我们提取当前的 buildPresentationHtml 作为简化版的 presentation.html // 或者提示用户本地环境完整功能需用 python 脚本生成,这里导出基础静态页 const paramsRaw = localStorage.getItem('presentationParamsV2'); if (paramsRaw) { zip.file('presentation_params.json', paramsRaw); } // 尝试读取现有的 generate_html_index.py 的 presentation 模板作为导出(可选,当前省略,使用简化说明) const content = await zip.generateAsync({type: 'blob'}); const url = URL.createObjectURL(content); const a = document.createElement('a'); a.href = url; a.download = `songpages_export_${new Date().toISOString().slice(0,10)}.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); setUploadStatus('导出完成', false); } catch (e) { console.error(e); setUploadStatus('导出失败: ' + e.message, true); } finally { exportButton.disabled = false; } }); } if (resetButton) { resetButton.addEventListener('click', function() { resetAllSongsData(); setUploadStatus('已清空,等待重新上传', false); }); } document.body.addEventListener('click', function(e) { const addBtn = e.target.closest('.add-to-presentation'); if (addBtn) { const songName = addBtn.getAttribute('data-song-name'); const songFile = addBtn.getAttribute('data-song-file'); if (addBtn.classList.contains('added')) { presentationList = presentationList.filter(s => s.name !== songName); } else { presentationList.push({ name: songName, file: songFile }); } saveList(); return; } const link = e.target.closest('.song-item a'); if (link) { if (link.dataset.directOpen === '1') return; e.preventDefault(); const item = link.closest('.song-item'); const addEl = item ? item.querySelector('.add-to-presentation') : null; const songName = addEl?.dataset.songName || link.textContent.replace(/^\d+\.\s*/, '').trim(); const songFile = addEl?.dataset.songFile || ''; const src = resolveSongFileToUrl(songFile); openSongViewer(songName, src); } }); panelItems.addEventListener('click', function(e) { if (e.target.classList.contains('remove')) { const name = e.target.getAttribute('data-name'); presentationList = presentationList.filter(s => s.name !== name); saveList(); } }); let dragSrcIndex = null; function addDragHandlers(li) { li.addEventListener('dragstart', function(e) { dragSrcIndex = Number(li.dataset.index); li.classList.add('dragging'); try { e.dataTransfer.effectAllowed = 'move'; } catch (_) {} }); li.addEventListener('dragend', function() { li.classList.remove('dragging'); }); li.addEventListener('dragover', function(e) { e.preventDefault(); try { e.dataTransfer.dropEffect = 'move'; } catch (_) {} }); li.addEventListener('drop', function(e) { e.preventDefault(); const targetIdx = Number(li.dataset.index); if (dragSrcIndex === null || targetIdx === dragSrcIndex) return; const item = presentationList.splice(dragSrcIndex, 1)[0]; const insertIdx = dragSrcIndex < targetIdx ? targetIdx - 1 : targetIdx; presentationList.splice(insertIdx, 0, item); saveList(); dragSrcIndex = null; }); } panelClear.addEventListener('click', function() { if (presentationList.length === 0) return; if (confirm('确认清空演示列表?')) { presentationList = []; saveList(); } }); panelStart.addEventListener('click', function() { const currentList = JSON.parse(localStorage.getItem(storageKey)) || []; if (currentList.length > 0) { const prepared = currentList.map(song => ({ name: song.name, file: resolveSongFileToUrl(song.file), key: song.file })).filter(s => s.file); openPresentation(prepared); } else { alert('演示列表为空,请先添加歌曲'); } }); panelMinimize.addEventListener('click', function() { panel.classList.toggle('collapsed'); }); panelClose.addEventListener('click', function() { panel.classList.add('hidden'); openPanelButton.style.display = 'block'; }); openPanelButton.addEventListener('click', function() { panel.classList.remove('hidden'); openPanelButton.style.display = 'none'; }); (function enableDrag() { let isDragging = false; let startX = 0, startY = 0; let startLeft = 0, startTop = 0; function onMouseDown(e) { isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; panel.style.left = (startLeft + dx) + 'px'; panel.style.top = (startTop + dy) + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } panelHeader.addEventListener('mousedown', onMouseDown); })(); });