This repository has been archived on 2026-08-27. You can view files and clone it, but cannot push or open issues or pull requests.
Files
2026-08-27 20:15:24 +08:00

108 lines
8.0 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import data from "../data/content-templates.json";
import styles from "./CharacterDetail.module.css";
const character = data.character;
export default function CharacterDetailPage() {
const [favorite, setFavorite] = useState(false);
const [tags, setTags] = useState<string[]>(character.tags);
const [newTag, setNewTag] = useState("");
const [note, setNote] = useState("第 09 话需要加强她主动提出关闭穹顶照明的动机铺垫。\n与遥的冲突不能只停留在信息差需要落到选择差异。 ");
const [editingNote, setEditingNote] = useState(false);
const [activeTab, setActiveTab] = useState("overview");
const [toast, setToast] = useState("");
useEffect(() => {
try {
const saved = window.localStorage.getItem("star-character-detail");
if (saved) {
const parsed = JSON.parse(saved);
setFavorite(Boolean(parsed.favorite));
if (Array.isArray(parsed.tags)) setTags(parsed.tags);
if (typeof parsed.note === "string") setNote(parsed.note);
}
} catch { /* use JSON defaults */ }
}, []);
useEffect(() => {
window.localStorage.setItem("star-character-detail", JSON.stringify({ favorite, tags, note }));
}, [favorite, tags, note]);
const notify = (message: string) => {
setToast(message);
window.setTimeout(() => setToast(""), 2100);
};
const addTag = () => {
const value = newTag.trim();
if (!value || tags.includes(value)) return;
setTags((current) => [...current, value]);
setNewTag("");
notify(`已添加标签「${value}`);
};
return (
<div className={styles.page}>
<header className={styles.topbar}>
<Link className={styles.brand} href="/"><span>S</span><strong>STELLAR<br /><small>CHARACTER DATABASE</small></strong></Link>
<nav><Link href="/simple-list"></Link><Link className={styles.active} href="/character"></Link><Link href="/markdown">Markdown </Link></nav>
<div className={styles.headerActions}><button onClick={() => notify("搜索为模板交互占位")}></button><button onClick={() => notify("分享链接已复制(演示)")}></button><span></span></div>
</header>
<section className={styles.cover}>
<div className={styles.coverImage} />
<div className={styles.coverShade} />
<div className={styles.coverInner}>
<div className={styles.breadcrumb}><Link href="/"></Link><span>/</span><Link href="/simple-list"></Link><span>/</span><strong>{character.name}</strong></div>
<div className={styles.identity}>
<p>{character.code}</p><h1>{character.name}</h1><span>{character.nameEn}</span><blockquote>{character.quote}</blockquote>
</div>
<button className={favorite ? styles.following : styles.follow} onClick={() => { setFavorite((value) => !value); notify(favorite ? "已取消收藏" : "已收藏角色"); }}>{favorite ? "✓ 已收藏" : " 收藏角色"}</button>
</div>
</section>
<main className={styles.content}>
<aside className={styles.profileCard}>
<div className={styles.portrait} role="img" aria-label="天野澪角色形象"><span></span></div>
<div className={styles.profileBody}>
<p>{character.role}</p>
<dl>
<div><dt></dt><dd>{character.age} </dd></div><div><dt></dt><dd>{character.birthday}</dd></div><div><dt></dt><dd>{character.height}</dd></div><div><dt></dt><dd>{character.voice}</dd></div><div><dt></dt><dd>{character.affiliation}</dd></div>
</dl>
<button onClick={() => notify("角色编辑表单为模板交互占位")}></button>
</div>
</aside>
<div className={styles.mainColumn}>
<nav className={styles.tabs}>{[["overview","角色概览"],["setting","能力设定"],["timeline","剧情轨迹"]].map(([id,label]) => <button key={id} className={activeTab === id ? styles.tabActive : ""} onClick={() => setActiveTab(id)}>{label}</button>)}</nav>
{activeTab === "overview" && <>
<section className={styles.panel}><header><div><span>PROFILE</span><h2></h2></div><button onClick={() => notify("编辑器为模板交互占位")}></button></header><p className={styles.bio}>{character.bio}</p><div className={styles.keyFacts}><div><small></small><strong></strong></div><div><small></small><strong></strong></div><div><small></small><strong></strong></div></div></section>
<section className={styles.panel}><header><div><span>RELATIONSHIPS</span><h2></h2></div><button onClick={() => notify("关系图为模板交互占位")}></button></header><div className={styles.relations}>{character.relations.map((item) => <article key={item.name}><span className={styles[item.tone]}>{item.initial}</span><p><strong>{item.name}</strong><small>{item.relation}</small></p><button></button></article>)}</div></section>
</>}
{activeTab === "setting" && <>
<section className={styles.panel}><header><div><span>PERSONAL ATTRIBUTES</span><h2></h2></div><small> v2.4</small></header><div className={styles.traits}>{character.traits.map((trait) => <div key={trait.label}><p><span>{trait.label}</span><strong>{trait.value}</strong></p><i><b style={{width:`${trait.value}%`}} /></i></div>)}</div></section>
<section className={styles.panel}><header><div><span>SPECIAL SKILLS</span><h2></h2></div></header><div className={styles.skills}>{character.skills.map((skill) => <article key={skill.name}><span>{skill.level}</span><p><strong>{skill.name}</strong><small>{skill.description}</small></p></article>)}</div></section>
</>}
{activeTab === "timeline" && <section className={styles.panel}><header><div><span>STORY TIMELINE</span><h2></h2></div><button onClick={() => notify("已切换为按剧集排序")}></button></header><div className={styles.timeline}>{character.timeline.map((item,index) => <article key={item.episode}><i>{index+1}</i><div><span>{item.episode} · {item.type}</span><strong>{item.title}</strong></div><button> </button></article>)}</div></section>}
</div>
<aside className={styles.sideColumn}>
<section className={styles.tagPanel}><header><div><span>TAGS</span><h2></h2></div><small>{tags.length} </small></header><div className={styles.tags}>{tags.map((tag,index) => <span className={index < 2 ? styles.importantTag : ""} key={tag}>#{tag}<button onClick={() => setTags((current) => current.filter((item) => item !== tag))}>×</button></span>)}</div><form onSubmit={(event) => {event.preventDefault();addTag();}}><input value={newTag} onChange={(event) => setNewTag(event.target.value)} placeholder="输入新标签" /><button type="submit"></button></form><p></p></section>
<section className={styles.notePanel}><header><div><span>PRIVATE NOTE</span><h2></h2></div><button onClick={() => { setEditingNote((value) => !value); if (editingNote) notify("备注已保存"); }}>{editingNote ? "保存" : "编辑"}</button></header>{editingNote ? <textarea value={note} onChange={(event) => setNote(event.target.value)} autoFocus /> : <p>{note}</p>}<small> · </small></section>
<section className={styles.versionPanel}><span>v2.4</span><div><strong></strong><small> 14:32 · </small></div><button onClick={() => notify("版本记录为模板交互占位")}></button></section>
</aside>
</main>
{toast && <div className={styles.toast}> {toast}</div>}
</div>
);
}