feat: 完成短剧项目空间与结构化资产创作闭环 (#38)

* feat(canvas): 短剧项目空间 - 建立项目章节与画布关联 MVP

* feat(canvas): 短剧项目空间 - 完善项目资产、镜头与流程闭环

Refs #30

* fix(deploy): 本地部署 - 修复前端构建并忽略本地 PostgreSQL 配置

* feat(*): 短剧项目空间 - 完成项目、章节、资产、角色与画布创作闭环

Refs #30

---------

Co-authored-by: ddCat <ddcat666@88.com>
This commit is contained in:
ddcat666
2026-07-27 10:45:11 +08:00
committed by GitHub
parent 431b9e3294
commit d2fbc413b3
208 changed files with 22655 additions and 4234 deletions

View File

@@ -2,25 +2,29 @@ import { Button, Pagination } from "antd";
import { RotateCcw } from "lucide-react";
import type { ReactNode } from "react";
import { WorkspaceSignalIcon, type WorkspaceSignalIconVariant } from "@/components/ui/aceternity/workspace-signal-icon";
import { cn } from "@/lib/utils";
export function WorkspacePage({ children, className, grid = false }: { children: ReactNode; className?: string; grid?: boolean }) {
export function WorkspacePage({ children, className, grid = false, fluid = false }: { children: ReactNode; className?: string; grid?: boolean; fluid?: boolean }) {
return (
<main className={cn("app-user-content thin-scrollbar h-full overflow-y-auto text-foreground", grid && "app-workspace-grid", className)}>
<div className="mx-auto w-full max-w-[1440px] px-4 py-6 sm:px-6 lg:px-8">{children}</div>
<div className={fluid ? "h-full w-full" : "w-full px-3 py-3 sm:px-4 sm:py-4 xl:px-5"}>{children}</div>
</main>
);
}
export function PageHeader({ title, description, meta, actions }: { title: string; description?: string; meta?: ReactNode; actions?: ReactNode }) {
export function PageHeader({ title, description, meta, actions, icon }: { title: string; description?: string; meta?: ReactNode; actions?: ReactNode; icon?: WorkspaceSignalIconVariant }) {
return (
<header className="app-page-header flex flex-col gap-4 pb-6 sm:flex-row sm:items-end sm:justify-between">
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-center gap-2.5">
<h1 className="truncate text-2xl font-semibold leading-8">{title}</h1>
{meta}
<header className="app-page-header flex min-h-14 flex-col gap-3 border-b border-border/80 pb-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 items-center gap-3">
{icon ? <WorkspaceSignalIcon variant={icon} /> : null}
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-baseline gap-2.5">
<h1 className="truncate text-[22px] font-semibold leading-7">{title}</h1>
{meta}
</div>
{description ? <p className="mt-1 text-xs leading-5 text-foreground/58">{description}</p> : null}
</div>
{description ? <p className="mt-1.5 text-sm leading-5 text-foreground/62">{description}</p> : null}
</div>
{actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
</header>
@@ -29,10 +33,10 @@ export function PageHeader({ title, description, meta, actions }: { title: strin
export function ListToolbar({ children, trailing, active, onReset }: { children: ReactNode; trailing?: ReactNode; active?: boolean; onReset?: () => void }) {
return (
<div className="mt-4 flex flex-col gap-3 border-b border-border pb-4 xl:flex-row xl:items-center xl:justify-between">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">{children}</div>
<div className="mt-3 flex min-h-12 flex-col gap-2 border-b border-border/75 pb-3 lg:flex-row lg:items-center lg:justify-between">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2.5">{children}</div>
<div className="flex shrink-0 flex-wrap items-center gap-2">
{active && onReset ? <Button icon={<RotateCcw className="size-3.5" />} onClick={onReset}>重置筛选</Button> : null}
{active && onReset ? <Button type="text" icon={<RotateCcw className="size-3.5" />} onClick={onReset}>重置</Button> : null}
{trailing}
</div>
</div>
@@ -44,7 +48,7 @@ export function TableSurface({ children, className }: { children: ReactNode; cla
}
export function CollectionGrid({ children, className }: { children: ReactNode; className?: string }) {
return <div className={cn("mt-4 grid grid-cols-1 gap-3 sm:grid-cols-[repeat(auto-fill,minmax(220px,1fr))]", className)}>{children}</div>;
return <div className={cn("mt-4 grid grid-cols-1 gap-x-4 gap-y-5 sm:grid-cols-[repeat(auto-fill,minmax(248px,1fr))]", className)}>{children}</div>;
}
export function PaginationBar({ current, pageSize, total, onChange, pageSizeOptions = [20, 50, 100] }: { current: number; pageSize: number; total: number; onChange: (page: number, pageSize: number) => void; pageSizeOptions?: number[] }) {