@@ -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 }))}
+ />
@@ -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")}
- >
-
-
-
+
+
+ 卡片
+ >
+ ),
+ },
+ {
+ value: "compact",
+ label: (
+ <>
+
+ 紧凑
+ >
+ ),
+ },
+ ]}
+ />
-
+
显示 {visibleProjects.length} / {projects.length} 个作品
@@ -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: "",