feat(*): 功能开放与页面布局 - 新增运维开关并优化后台及创作界面空间利用

This commit is contained in:
ddCat committed 2026-08-04 16:24:18 +08:00
1 parent 8f7c492b70
commit f439d600ba
36 files changed
+723 -86

No files matched your search

+13 -9
View File
@@ -27,7 +27,8 @@ export default function IndexPage() {
const canvasProjects = useCanvasStore((state) => state.projects);
const user = useUserStore((state) => state.user);
const userHydrated = useUserStore((state) => state.hydrated);
const domainProjectsQuery = useQuery({ queryKey: ["projects"], queryFn: listProjects, enabled: Boolean(user) });
const shortDramaEnabled = useUserStore((state) => state.features.shortDramaEnabled);
const domainProjectsQuery = useQuery({ queryKey: ["projects"], queryFn: listProjects, enabled: Boolean(user && shortDramaEnabled) });
const domainProjects = useMemo(
() => [...(domainProjectsQuery.data?.projects || [])].sort((left, right) => right.project.updatedAt.localeCompare(left.project.updatedAt)),
[domainProjectsQuery.data],
@@ -36,7 +37,7 @@ export default function IndexPage() {
const activeProjectQuery = useQuery({
queryKey: ["project", activeProject?.project.id],
queryFn: () => getProject(activeProject!.project.id),
enabled: Boolean(user && activeProject?.project.id),
enabled: Boolean(user && shortDramaEnabled && activeProject?.project.id),
});
const recentIndependentCanvases = useMemo(
() => canvasProjects.filter((project) => !project.projectId).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 3),
@@ -55,15 +56,15 @@ export default function IndexPage() {
});
};
const loadingUserWorkspace = !userHydrated || (Boolean(user) && domainProjectsQuery.isLoading);
const loadingUserWorkspace = !userHydrated || (Boolean(user && shortDramaEnabled) && domainProjectsQuery.isLoading);
return (
<main className="app-user-content app-workspace-canvas h-full overflow-y-auto text-foreground">
<div className="app-home-workbench mx-auto w-full max-w-[1440px] px-4 pb-12 pt-5 sm:px-6 lg:px-8">
{loadingUserWorkspace ? (
<WorkspaceLoadingState className="mt-3 max-w-[980px]" label="正在恢复工作台" detail="读取项目、章节和最近画布" rows={5} />
) : user && domainProjectsQuery.isError ? (
) : user && shortDramaEnabled && domainProjectsQuery.isError ? (
<WorkspaceErrorState title="项目工作台加载失败" description={domainProjectsQuery.error instanceof Error ? domainProjectsQuery.error.message : "暂时无法读取项目列表。"} onRetry={() => void domainProjectsQuery.refetch()} />
) : activeProject ? (
) : shortDramaEnabled && activeProject ? (
<ReturningWorkspace
summary={activeProject}
detail={activeProjectQuery.data}
@@ -79,6 +80,7 @@ export default function IndexPage() {
canvasHydrated={canvasHydrated}
recentIndependentCanvases={recentIndependentCanvases}
onCreateIndependentCanvas={createIndependentCanvas}
shortDramaEnabled={shortDramaEnabled}
/>
)}
</div>
@@ -95,9 +97,10 @@ function ReturningWorkspace({ summary, detail, detailLoading, detailError, recen
recentIndependentCanvases: ReturnType<typeof useCanvasStore.getState>["projects"];
onCreateIndependentCanvas: () => void;
}) {
const taskCenterEnabled = useUserStore((state) => state.features.taskCenterEnabled);
const stage = detail ? projectDetailStage(detail) : { label: "进行中", detail: "读取项目进度" };
const continueTarget = detail ? projectContinueTarget(detail) : { href: `/projects/${summary.project.id}/overview`, title: summary.project.name, context: "打开项目概览", updatedAt: summary.project.updatedAt };
const nextActions = detail ? projectNextActions(detail, 3) : [];
const nextActions = detail ? projectNextActions(detail, 4).filter((action) => taskCenterEnabled || !action.href.startsWith("/tasks")).slice(0, 3) : [];
const completion = projectSummaryCompletion(summary);
const attentionCount = detail ? projectAttentionCount(detail) : 0;
return (
@@ -194,11 +197,12 @@ function ReturningWorkspace({ summary, detail, detailLoading, detailError, recen
);
}
function FirstProjectWorkspace({ authenticated, canvasHydrated, recentIndependentCanvases, onCreateIndependentCanvas }: {
function FirstProjectWorkspace({ authenticated, canvasHydrated, recentIndependentCanvases, onCreateIndependentCanvas, shortDramaEnabled }: {
authenticated: boolean;
canvasHydrated: boolean;
recentIndependentCanvases: ReturnType<typeof useCanvasStore.getState>["projects"];
onCreateIndependentCanvas: () => void;
shortDramaEnabled: boolean;
}) {
const projectHref = authenticated ? "/projects?create=1" : `/login?next=${encodeURIComponent("/projects?create=1")}`;
return (
@@ -208,7 +212,7 @@ function FirstProjectWorkspace({ authenticated, canvasHydrated, recentIndependen
<h1 className="mt-5 max-w-[780px] text-3xl font-semibold leading-[1.08] sm:text-4xl lg:text-5xl">把一个故事推进到可交付的镜头</h1>
<p className="mt-5 max-w-[680px] text-sm leading-7 text-foreground/58 sm:text-base">从章节、角色和参考图开始,逐步生成分镜、视频和可复用资产。需要自由探索时,也可以先打开一张自由画布。</p>
<div className="mt-7 flex flex-wrap items-center gap-3">
<Link className="inline-flex h-10 items-center gap-2 rounded-md bg-foreground px-4 text-sm font-medium text-background transition-opacity hover:opacity-85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/25" to={projectHref}><FolderKanban className="size-4" />创建项目</Link>
{shortDramaEnabled ? <Link className="inline-flex h-10 items-center gap-2 rounded-md bg-foreground px-4 text-sm font-medium text-background transition-opacity hover:opacity-85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/25" to={projectHref}><FolderKanban className="size-4" />创建项目</Link> : null}
<Button size="large" disabled={!canvasHydrated} icon={<LayoutGrid className="size-4" />} onClick={onCreateIndependentCanvas}>打开画布</Button>
</div>
</section>
@@ -230,7 +234,7 @@ function FirstProjectWorkspace({ authenticated, canvasHydrated, recentIndependen
<div>
<h2 className="text-base font-semibold">两种开始方式</h2>
<div className="mt-3 divide-y divide-border/75 border-y border-border/75">
<StartMode icon={<Clapperboard className="size-4" />} title="项目" description="适合短剧、故事板和多章节制作。集中管理章节、资产、画布与进度。" action="创建项目" href={projectHref} />
{shortDramaEnabled ? <StartMode icon={<Clapperboard className="size-4" />} title="项目" description="适合短剧、故事板和多章节制作。集中管理章节、资产、画布与进度。" action="创建项目" href={projectHref} /> : null}
<StartMode icon={<Sparkles className="size-4" />} title="自由画布" description="适合快速试图、提示词实验和不需要章节流程的自由创作。" action="打开画布" onClick={onCreateIndependentCanvas} />
</div>
</div>