v0.8.2: 工单大清理 - 编年史分页+限时挑战+星潮深化+云排行榜+手写叙事+socket多人星潮+UI打磨
工单 #8 编年史上限+分页: - engine.ts: slice(-50)→slice(-200) - ChronicleDialog.tsx: 加分页(每页10条)+上一页/下一页+页码显示 工单 #5 限时挑战 (subagent 10-a): - beacon.ts: BeaconTimedChallenge + getTimedSlotKey(4h时段) + generateTimedChallenge - BeaconPanel.tsx: amber主题限时区块 + 倒计时 + <30min紧急状态 - gameStore: trackBeacon 加 timedJustCompleted + claimTimedBeacon action 工单 #3 星潮类型深化 (subagent 10-a): - starTide.ts: +3种星潮 surge(emerald)/eclipse(rose)/prism(fuchsia) - TideModifiers: +targetLenBonus/bossWinRateBonus/autoDecodeIntervalMult - decode.ts: generatePuzzle 加 targetLenBonus 参数 - achievements: ach_tides_all 阈值 6→9 工单 #4/P2 云排行榜 (subagent 10-b): - mini-services/leaderboard-service/ (端口3030, Hono+bun, 内存1000条) - API: GET/POST /api/leaderboard + /stats + CORS + 防刷 - beacon.ts: fetchCloudLeaderboard/submitCloudScore - BeaconPanel: 本地Top20/全球Top100 双tab + YOU徽章高亮 工单 #9 手写叙事 (subagent 10-c): - chronicle.ts: EPOCH_LORE 5纪元×3节点=15段手写叙事(80-150字/段) - buildLore 优先手写节点, fallback 模板, 11个变量替换 P3 socket 多人星潮 (subagent 10-c): - mini-services/star-tide-service/ (端口3031, socket.io) - 每10-15min广播global-tide, 60s持续, 6种类型权重 - useGlobalTide hook + triggerGlobalTide action - StarTideIndicator 加 🌐 全球星潮标记 P2 UI打磨: - page.tsx: CrystalOrb 区加装饰全息环(3层旋转) + 四角标记 + 顶部状态条 + 底部铭文 QA: lint零错误 + dev HTTP200 + VLM 8/10 + 2个mini-service运行中(3030/3031)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
// 回响星核 / Echo Nexus — 回响编年史对话框(v0.4 跨周目叙事时间轴)
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -20,7 +20,8 @@ import { getPerk } from "@/lib/game/constellation";
|
||||
import { TIDE_EVENTS } from "@/lib/game/starTide";
|
||||
import { sfx } from "@/hooks/useAudio";
|
||||
import type { ChronicleEntry } from "@/lib/game/types";
|
||||
import { BookOpen, Clock, Swords, Trophy, Sparkles, Star, Waves, Cpu, Zap } from "lucide-react";
|
||||
import { BookOpen, Clock, Swords, Trophy, Sparkles, Star, Waves, Cpu, Zap, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -59,12 +60,26 @@ export function ChronicleDialog({ open, onOpenChange }: Props) {
|
||||
const bossKills = useGameStore((s) => s.bossKills ?? 0);
|
||||
const tidesAll = useGameStore((s) => s.starTidesEncountered ?? []);
|
||||
// 倒序展示(最新的在前)
|
||||
const entries: ChronicleEntry[] = [...chronicle].reverse();
|
||||
const entries: ChronicleEntry[] = useMemo(() => [...chronicle].reverse(), [chronicle]);
|
||||
|
||||
// 打开时播放翻页音效
|
||||
// v0.8.2 分页(工单 #8):编年史上限 200 条,每页 10 条
|
||||
const PAGE_SIZE = 10;
|
||||
const totalPages = Math.max(1, Math.ceil(entries.length / PAGE_SIZE));
|
||||
const [page, setPage] = useState(1);
|
||||
// 当 entries 变化(新飞升)且当前页超出范围时,回到第 1 页(最新)
|
||||
useEffect(() => {
|
||||
if (open) sfx("chronicleOpen");
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
if (page > totalPages) setPage(1);
|
||||
}, [page, totalPages]);
|
||||
// 打开时默认回到第 1 页(最新)
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
sfx("chronicleOpen");
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setPage(1);
|
||||
}
|
||||
}, [open]);
|
||||
const pagedEntries = entries.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -108,7 +123,7 @@ export function ChronicleDialog({ open, onOpenChange }: Props) {
|
||||
</DialogHeader>
|
||||
|
||||
{/* 时间轴滚动区 */}
|
||||
<div className="flex-1 overflow-y-auto px-6 pb-6 echo-scroll">
|
||||
<div className="flex-1 overflow-y-auto px-6 pb-3 echo-scroll">
|
||||
{entries.length === 0 ? (
|
||||
<EmptyState ascensions={ascensions} />
|
||||
) : (
|
||||
@@ -117,18 +132,49 @@ export function ChronicleDialog({ open, onOpenChange }: Props) {
|
||||
<div className="absolute left-[27px] top-2 bottom-2 w-px bg-gradient-to-b from-fuchsia-400/40 via-violet-400/30 to-transparent" />
|
||||
|
||||
<div className="space-y-4">
|
||||
{entries.map((entry, idx) => (
|
||||
{pagedEntries.map((entry, idx) => (
|
||||
<ChronicleCard
|
||||
key={`${entry.ascensionNumber}-${entry.timestamp}`}
|
||||
entry={entry}
|
||||
isLatest={idx === 0}
|
||||
index={idx}
|
||||
isLatest={(page - 1) * PAGE_SIZE + idx === 0}
|
||||
index={(page - 1) * PAGE_SIZE + idx}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* v0.8.2 分页控制栏(工单 #8) */}
|
||||
{entries.length > PAGE_SIZE && (
|
||||
<div className="px-6 py-2.5 border-t border-fuchsia-400/15 bg-black/40 flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] text-muted-foreground font-mono">
|
||||
第 {page} / {totalPages} 页 · 共 {entries.length} 条飞升记录
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={page <= 1}
|
||||
onClick={() => { sfx("tabSwitch"); setPage((p) => Math.max(1, p - 1)); }}
|
||||
className="h-7 px-2 text-fuchsia-300 hover:bg-fuchsia-500/15 disabled:opacity-30"
|
||||
>
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => { sfx("tabSwitch"); setPage((p) => Math.min(totalPages, p + 1)); }}
|
||||
className="h-7 px-2 text-fuchsia-300 hover:bg-fuchsia-500/15 disabled:opacity-30"
|
||||
>
|
||||
下一页
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user