/* novel detail page: 章节 / 角色 / 伏笔 三 tab 管理. */ import { escapeHtml, fetchJson, postJson, showError } from "../utils.js"; import { statusBadge } from "../components/status-badge.js"; const NOVEL_ID = window.NOVEL_ID; const alertsEl = document.getElementById("ndAlerts"); /* ---------------- header ---------------- */ async function loadHeader() { const el = document.getElementById("ndHeader"); try { const n = await fetchJson("/api/novels/" + NOVEL_ID); const s = n.stats || {}; const pct = n.target_words > 0 ? Math.min(100, Math.round(((n.current_words || 0) / n.target_words) * 100)) : 0; el.innerHTML = '' + '
' + '

' + escapeHtml(n.title) + '

' + '

' + ' ' + escapeHtml(n.genre || "未分类") + ' ' + statusBadge(n.status) + ' ' + escapeHtml(n.style || "") + '' + '

' + '
' + pct + '%
' + '

' + ' 章节 ' + (s.chapter_count || 0) + ' (完成 ' + (s.done_chapters || 0) + ') · ' + ' 总字数 ' + (s.total_words || 0) + ' · ' + ' 角色 ' + (s.character_count || 0) + ' · ' + ' 未回收伏笔 ' + (s.open_foreshadowing || 0) + '

' + '
'; } catch (err) { el.innerHTML = '
' + escapeHtml(err.message) + "
"; } } /* ---------------- chapters ---------------- */ async function loadChapters() { const el = document.getElementById("ndChapterList"); try { const chapters = await fetchJson("/api/novels/" + NOVEL_ID + "/chapters"); if (!chapters.length) { el.innerHTML = '还没有章节, 用上方表单添加。'; return; } const byVolume = {}; chapters.forEach((c) => { const v = c.volume || 1; (byVolume[v] = byVolume[v] || []).push(c); }); el.innerHTML = Object.keys(byVolume).sort((a, b) => a - b).map((v) => { const rows = byVolume[v].map((c) => '' + '' + '第' + c.chapter_number + '章' + '' + escapeHtml(c.title || "") + '' + '' + statusBadge(c.status) + '' + '' + (c.word_count || 0) + '' + '' + (c.content ? "续写/查看" : "写") + "" + "").join(""); return '' + '
第' + v + '卷
' + '' + '' + "" + rows + "
章节标题状态字数
"; }).join(""); } catch (err) { el.innerHTML = '
' + escapeHtml(err.message) + "
"; } } document.getElementById("ndAddChapterForm").addEventListener("submit", async (e) => { e.preventDefault(); try { await postJson("/api/novels/" + NOVEL_ID + "/chapters", { volume: parseInt(document.getElementById("ndChVolume").value, 10), chapter_number: parseInt(document.getElementById("ndChNumber").value, 10), title: document.getElementById("ndChTitle").value.trim(), outline: document.getElementById("ndChOutline").value.trim(), }); document.getElementById("ndChTitle").value = ""; document.getElementById("ndChOutline").value = ""; loadChapters(); } catch (err) { showError(alertsEl, "添加章节失败: " + err.message); } }); /* ---------------- characters ---------------- */ async function loadCharacters() { const el = document.getElementById("ndCharacterList"); try { const chars = await fetchJson("/api/novels/" + NOVEL_ID + "/characters"); if (!chars.length) { el.innerHTML = '还没有角色。'; return; } el.innerHTML = '' + "" + "" + chars.map((c) => '' + "" + "" + "" + '" + '' ).join("") + "
名字定位状态描述
" + escapeHtml(c.name) + "" + escapeHtml(c.role || "") + "" + statusBadge(c.status) + "' + escapeHtml((c.description || "").slice(0, 120)) + "
"; el.querySelectorAll(".nd-edit-char").forEach((btn) => { btn.addEventListener("click", async () => { const desc = prompt("编辑角色描述 (" + btn.dataset.name + "):"); if (desc === null) return; try { await postJson("/api/characters/" + btn.dataset.id, { description: desc }); loadCharacters(); } catch (err) { showError(alertsEl, "编辑失败: " + err.message); } }); }); } catch (err) { el.innerHTML = '
' + escapeHtml(err.message) + "
"; } } /* ---------------- foreshadowing ---------------- */ async function loadForeshadowing() { const el = document.getElementById("ndForeshadowingList"); try { const list = await fetchJson("/api/novels/" + NOVEL_ID + "/foreshadowing"); if (!list.length) { el.innerHTML = '还没有伏笔。'; return; } el.innerHTML = '' + "" + "" + list.map((f) => '' + '" + "" + "" + "" + "" ).join("") + "
描述埋设回收状态
' + escapeHtml((f.description || "").slice(0, 150)) + "" + (f.planted_chapter ? "第" + f.planted_chapter + "章" : "-") + "" + (f.resolved_chapter ? "第" + f.resolved_chapter + "章" : "-") + "" + statusBadge(f.status) + "" + (f.status !== "resolved" ? '' : "") + "
"; el.querySelectorAll(".nd-resolve-fs").forEach((btn) => { btn.addEventListener("click", async () => { const ch = prompt("回收于第几章?"); if (ch === null) return; try { await postJson("/api/foreshadowing/" + btn.dataset.id, { status: "resolved", resolved_chapter: parseInt(ch, 10) || null, }); loadForeshadowing(); } catch (err) { showError(alertsEl, "操作失败: " + err.message); } }); }); } catch (err) { el.innerHTML = '
' + escapeHtml(err.message) + "
"; } } /* ---------------- init ---------------- */ loadHeader(); loadChapters(); loadCharacters(); loadForeshadowing();