Update WBBR frontend template collection
This commit is contained in:
1
app/character/CharacterDetail.module.css
Normal file
1
app/character/CharacterDetail.module.css
Normal file
File diff suppressed because one or more lines are too long
107
app/character/page.tsx
Normal file
107
app/character/page.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user