diff --git a/apps/web/components/ProjectCard.tsx b/apps/web/components/ProjectCard.tsx index 1fa3fe7..f0ed7cd 100644 --- a/apps/web/components/ProjectCard.tsx +++ b/apps/web/components/ProjectCard.tsx @@ -7,7 +7,7 @@ import { formatProjectUpdatedAt, pendingReviewCount, } from "@/lib/projects/projects"; -import { cardClass } from "@/lib/ui/variants"; +import { cardClass, focusRing } from "@/lib/ui/variants"; interface ProjectCardProps { project: ProjectResponse; @@ -24,7 +24,7 @@ export function ProjectCard({ project, compact = false }: ProjectCardProps) { @@ -57,7 +57,7 @@ export function ProjectCard({ project, compact = false }: ProjectCardProps) {
@@ -85,7 +85,7 @@ export function ProjectCard({ project, compact = false }: ProjectCardProps) {
@@ -100,7 +111,7 @@ export function ProjectWizard() { )} {step === 3 && } {step === 4 && } - {step === 5 && } + {step === 5 && }
@@ -153,9 +164,14 @@ interface StepProps { } function StepBasics({ form, update }: StepProps) { + const titleMissing = form.title.trim().length === 0; return (
- + update({ title: e.target.value })} @@ -196,41 +212,37 @@ function StepStory({ /> -
- {STRUCTURES.map((s) => ( - - ))} -
+ update({ structure })} + options={STRUCTURES.map((s) => ({ value: s, label: s }))} + />
核心卖点
- {SELLING_POINT_PRESETS.map((p) => ( - - ))} + {SELLING_POINT_PRESETS.map((p) => { + const selected = form.sellingPoints.includes(p); + return ( + + ); + })}
@@ -279,10 +291,31 @@ function StepProtagonist({ form, update }: StepProps) { ); } -function StepWorld() { +// 第 5 步:确认/概览——回显已填字段供复核后提交(取代旧的空「世界观后续里程碑」说明)。 +function StepConfirm({ form }: { form: WizardForm }) { + const rows: Array<{ label: string; value: string }> = [ + { label: "书名", value: form.title.trim() || "未填写" }, + { label: "一句话故事", value: form.logline.trim() || "未填写" }, + { label: "题材", value: form.genre || "未选择" }, + { label: "故事结构", value: form.structure || "未选择" }, + { + label: "核心卖点", + value: form.sellingPoints.length > 0 ? form.sellingPoints.join("、") : "未选择", + }, + ]; return ( -
- 基础世界观将在「设定库」里建模(后续里程碑)。现在可直接完成立项,进入工作台开始写第一章。 +
+

+ 复核以下信息,确认无误后即可完成立项。其余设定可进工作台后在「设定库」继续完善。 +

+
+ {rows.map((row) => ( +
+
{row.label}
+
{row.value}
+
+ ))} +
); } diff --git a/apps/web/components/projects/ProjectLibrary.tsx b/apps/web/components/projects/ProjectLibrary.tsx index 6d61727..ff021a6 100644 --- a/apps/web/components/projects/ProjectLibrary.tsx +++ b/apps/web/components/projects/ProjectLibrary.tsx @@ -2,11 +2,12 @@ import Link from "next/link"; import { Clock3, LayoutGrid, List, Plus, Search } from "lucide-react"; -import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ProjectCard } from "@/components/ProjectCard"; import { Button } from "@/components/ui/Button"; import { EmptyState } from "@/components/ui/EmptyState"; +import { SegmentedControl } from "@/components/ui/SegmentedControl"; import { Select } from "@/components/ui/Select"; import { TextInput } from "@/components/ui/TextInput"; import type { ProjectResponse } from "@/lib/api/types"; @@ -16,7 +17,7 @@ import { type ProjectSort, type ProjectViewMode, } from "@/lib/projects/projects"; -import { buttonClass, cardClass, cn } from "@/lib/ui/variants"; +import { buttonClass, cardClass } from "@/lib/ui/variants"; interface ProjectLibraryProps { projects: ProjectResponse[]; @@ -84,29 +85,39 @@ export function ProjectLibrary({ projects }: ProjectLibraryProps) { -
- setView("cards")} - > - - setView("compact")} - > - -
+ +
-

+

@@ -174,35 +185,6 @@ export function ProjectLibrary({ projects }: ProjectLibraryProps) { ); } -function ViewButton({ - active, - label, - children, - onClick, -}: { - active: boolean; - label: string; - children: ReactNode; - onClick: () => void; -}) { - return ( - - ); -} - function NewProjectCard() { return ( { + it("provides one title per step", () => { + expect(STEP_TITLES).toHaveLength(WIZARD_STEPS); + }); + + it("ends on a confirmation step rather than an empty world step", () => { + expect(STEP_TITLES[WIZARD_STEPS - 1]).toBe("确认与提交"); + }); +}); + describe("wizard step gating", () => { it("blocks advancing past step 1 without a title", () => { expect(canAdvance(1, emptyWizardForm)).toBe(false); diff --git a/apps/web/lib/wizard/wizard.ts b/apps/web/lib/wizard/wizard.ts index e786275..1dcd275 100644 --- a/apps/web/lib/wizard/wizard.ts +++ b/apps/web/lib/wizard/wizard.ts @@ -14,6 +14,15 @@ export interface WizardForm { export const WIZARD_STEPS = 5; +// 各步标题(与 WIZARD_STEPS 一一对应)。第 5 步为「确认/概览」:回显已填字段供复核后提交。 +export const STEP_TITLES = [ + "书名 + 题材", + "一句话故事 + 卖点 + 结构", + "立意 / 总纲", + "主角 / 金手指", + "确认与提交", +] as const; + export const emptyWizardForm: WizardForm = { title: "", genre: "",