Quellcode durchsuchen

feat(story-simulation): 实现框架确认面板 UI

Mochocyang vor 3 Monaten
Ursprung
Commit
482a58e9e9
1 geänderte Dateien mit 115 neuen und 0 gelöschten Zeilen
  1. 115 0
      src/components/novel/story-simulation/framework-confirm-panel.tsx

+ 115 - 0
src/components/novel/story-simulation/framework-confirm-panel.tsx

@@ -0,0 +1,115 @@
+import { useTranslation } from "react-i18next"
+
+import { Button } from "@/components/ui/button"
+import { useStorySimulationStore } from "@/stores/story-simulation-store"
+import type { StoryNode } from "@/lib/novel/story-simulation/types"
+import { cn } from "@/lib/utils"
+
+interface FrameworkConfirmPanelProps {
+  onConfirm: () => void
+  onRegenerate: () => void
+}
+
+// 起/承/转/合 阶段对应的标签配色
+const PHASE_STYLES: Record<StoryNode["phase"], string> = {
+  起: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
+  承: "bg-blue-500/15 text-blue-600 dark:text-blue-400",
+  转: "bg-amber-500/15 text-amber-600 dark:text-amber-400",
+  合: "bg-purple-500/15 text-purple-600 dark:text-purple-400",
+}
+
+export function FrameworkConfirmPanel({
+  onConfirm,
+  onRegenerate,
+}: FrameworkConfirmPanelProps) {
+  const { t } = useTranslation()
+  const currentFramework = useStorySimulationStore((s) => s.currentFramework)
+
+  if (!currentFramework) return null
+
+  return (
+    <div className="flex flex-col gap-4">
+      {/* 顶部:标题 + 操作按钮 */}
+      <div className="flex items-center justify-between gap-2">
+        <h3 className="text-lg font-semibold">
+          {t("storySimulation.frameworkTitle")}
+        </h3>
+        <div className="flex gap-2">
+          <Button variant="outline" onClick={onRegenerate}>
+            {t("storySimulation.regenerateFramework")}
+          </Button>
+          <Button onClick={onConfirm}>
+            {t("storySimulation.confirmFramework")}
+          </Button>
+        </div>
+      </div>
+
+      {/* 前提区 */}
+      <div className="rounded-lg bg-muted p-4">
+        <div className="text-sm font-medium text-muted-foreground">
+          {t("storySimulation.frameworkPremise")}
+        </div>
+        <p className="mt-1 text-sm leading-relaxed">{currentFramework.premise}</p>
+      </div>
+
+      {/* 节点列表 */}
+      <div className="flex flex-col gap-3">
+        <div className="text-sm font-medium text-muted-foreground">
+          {t("storySimulation.frameworkNodes")}
+        </div>
+        {currentFramework.nodes.map((node) => (
+          <FrameworkNodeCard key={node.index} node={node} />
+        ))}
+      </div>
+    </div>
+  )
+}
+
+function FrameworkNodeCard({ node }: { node: StoryNode }) {
+  const { t } = useTranslation()
+
+  return (
+    <div className="rounded-lg border p-4">
+      {/* 阶段标签 + 节点标题 */}
+      <div className="mb-3 flex items-center gap-2">
+        <span
+          className={cn(
+            "rounded px-1.5 py-0.5 text-xs font-medium",
+            PHASE_STYLES[node.phase]
+          )}
+        >
+          {node.phase}
+        </span>
+        <span className="font-medium">{node.title}</span>
+      </div>
+
+      {/* 节点字段:dl/dt/dd 网格布局 */}
+      <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm">
+        <dt className="whitespace-nowrap text-muted-foreground">
+          {t("storySimulation.coreConflict")}
+        </dt>
+        <dd className="leading-relaxed">{node.coreConflict}</dd>
+
+        <dt className="whitespace-nowrap text-muted-foreground">
+          {t("storySimulation.involvedCharacters")}
+        </dt>
+        <dd className="leading-relaxed">{node.involvedCharacters.join("、")}</dd>
+
+        <dt className="whitespace-nowrap text-muted-foreground">
+          {t("storySimulation.goal")}
+        </dt>
+        <dd className="leading-relaxed">{node.goal}</dd>
+
+        <dt className="whitespace-nowrap text-muted-foreground">
+          {t("storySimulation.cause")}
+        </dt>
+        <dd className="leading-relaxed">{node.causeFromPrev}</dd>
+
+        <dt className="whitespace-nowrap text-muted-foreground">
+          {t("storySimulation.expectedOutcome")}
+        </dt>
+        <dd className="leading-relaxed">{node.expectedOutcome}</dd>
+      </dl>
+    </div>
+  )
+}