"use client"; import Link from "next/link"; import { CSSProperties, useEffect, useMemo, useRef, useState } from "react"; import { Book } from "@/types/book"; type ReaderViewProps = { book: Book; chapterIndex: number; initialThemeKey?: string; initialFontKey?: string; initialWidthKey?: string; }; const themeOptions = [ { key: "warm", label: "暖米", page: "#f7f0e4", stage: "#d8d0c4", text: "#2b241e", catalog: "#e6ddd0" }, { key: "mist", label: "浅灰", page: "#efede7", stage: "#d6d4cf", text: "#2a2723", catalog: "#dfddd7" }, { key: "sepia", label: "茶棕", page: "#eee0cf", stage: "#d4c3b1", text: "#35281f", catalog: "#dfcdbb" } ] as const; const fontOptions = [ { key: "sm", label: "小", size: "1.02rem", lineHeight: 2.05 }, { key: "md", label: "中", size: "1.18rem", lineHeight: 2.28 }, { key: "lg", label: "大", size: "1.34rem", lineHeight: 2.55 } ] as const; const widthOptions = [ { key: "narrow", label: "窄", width: 760 }, { key: "medium", label: "中", width: 920 }, { key: "wide", label: "宽", width: 1080 } ] as const; export function ReaderView({ book, chapterIndex, initialThemeKey, initialFontKey, initialWidthKey }: ReaderViewProps) { const initialThemeIndex = Math.max(0, themeOptions.findIndex((item) => item.key === initialThemeKey)); const initialFontIndex = Math.max(0, fontOptions.findIndex((item) => item.key === initialFontKey)); const initialWidthIndex = Math.max(0, widthOptions.findIndex((item) => item.key === initialWidthKey)); const [themeIndex, setThemeIndex] = useState(initialThemeKey ? initialThemeIndex : 0); const [fontIndex, setFontIndex] = useState(initialFontKey ? initialFontIndex : 1); const [widthIndex, setWidthIndex] = useState(initialWidthKey ? initialWidthIndex : 1); const [catalogOpen, setCatalogOpen] = useState(false); const [progress, setProgress] = useState(0); const stageRef = useRef(null); useEffect(() => { const pageColor = themeOptions[themeIndex].page; document.body.classList.add("reader-body"); const previousBodyBackground = document.body.style.background; const previousHtmlBackground = document.documentElement.style.background; document.body.style.background = pageColor; document.documentElement.style.background = pageColor; return () => { document.body.classList.remove("reader-body"); document.body.style.background = previousBodyBackground; document.documentElement.style.background = previousHtmlBackground; }; }, [themeIndex]); useEffect(() => { const stage = stageRef.current; if (!stage) { return; } stage.scrollTo({ top: 0, behavior: "auto" }); setCatalogOpen(false); const updateProgress = () => { const maxScroll = stage.scrollHeight - stage.clientHeight; const nextProgress = maxScroll <= 0 ? 0 : (stage.scrollTop / maxScroll) * 100; setProgress(nextProgress); }; updateProgress(); stage.addEventListener("scroll", updateProgress); window.addEventListener("resize", updateProgress); return () => { stage.removeEventListener("scroll", updateProgress); window.removeEventListener("resize", updateProgress); }; }, [chapterIndex, widthIndex, fontIndex, themeIndex]); const chapter = book.chapters[chapterIndex]; const prevChapterIndex = chapterIndex > 0 ? chapterIndex - 1 : null; const nextChapterIndex = chapterIndex < book.chapters.length - 1 ? chapterIndex + 1 : null; const currentTheme = themeOptions[themeIndex]; const currentFont = fontOptions[fontIndex]; const currentWidth = widthOptions[widthIndex]; const chapterWords = chapter.content.join("").length; const shellWidthStyle = useMemo( () => ({ width: "var(--reader-shell-width)" }) as CSSProperties, [] ); const cycleFont = () => setFontIndex((value) => (value + 1) % fontOptions.length); const cycleTheme = () => setThemeIndex((value) => (value + 1) % themeOptions.length); const cycleWidth = () => setWidthIndex((value) => (value + 1) % widthOptions.length); const catalogContent = ( <>

目录

{book.chapters.map((item, index) => ( setCatalogOpen(false)} > 第 {index + 1} 章 {item.title} ))}
); return (
); }