feat(*): 用户工作台 - 统一黑白灰空间界面并整理画布入口

This commit is contained in:
ddCat
2026-07-31 15:33:37 +08:00
parent ecb5b496d4
commit ad254784e4
11 changed files with 484 additions and 59 deletions

View File

@@ -72,7 +72,7 @@ export default function ProjectDetailPage() {
return (
<WorkspacePage className="project-workbench-page !overflow-hidden" fluid>
<div className="flex h-full min-h-0 flex-col">
<header className="shrink-0 border-b border-border/65 bg-background/80 px-3 py-2 backdrop-blur-md sm:px-4 lg:px-5 lg:py-0">
<header className="project-workbench-header shrink-0 border-b border-border/65 bg-background/80 px-3 py-2 backdrop-blur-md sm:px-4 lg:px-5 lg:py-0">
<div className="flex min-w-0 flex-wrap items-center gap-x-3 lg:min-h-16 lg:flex-nowrap">
<div className="flex min-w-0 flex-1 items-center gap-2.5 lg:w-[250px] lg:flex-none xl:w-[290px]">
<button type="button" onClick={() => navigate("/projects")} className="grid size-9 shrink-0 place-items-center rounded-md text-foreground/42 transition-colors hover:bg-foreground/[.055] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" aria-label="返回项目" title="返回项目"><ArrowLeft className="size-4" /></button>

View File

@@ -20,14 +20,14 @@ export default function ProjectOverviewView({ detail }: ProjectDetailViewProps)
return (
<div className="space-y-7">
<section className="overflow-hidden rounded-lg border border-border/75 bg-background/80 shadow-sm">
<section className="project-overview-focus overflow-hidden rounded-lg border border-border/75 bg-background/80 shadow-sm">
<div className="grid lg:grid-cols-[minmax(0,1fr)_300px]">
<div className="min-w-0 p-5 sm:p-6 lg:p-7">
<div className="flex flex-wrap items-center gap-2 text-[11px]">
<span className="font-medium text-[var(--workspace-accent)]">当前任务</span>
<span className="text-foreground/20" aria-hidden>/</span>
<span className="text-foreground/45">{stage.label}</span>
{attentionCount ? <span className="rounded bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium text-amber-600 dark:text-amber-400">{attentionCount} 项待处理</span> : null}
{attentionCount ? <span className="rounded bg-foreground/[.07] px-1.5 py-0.5 text-[10px] font-medium text-foreground/70">{attentionCount} 项待处理</span> : null}
</div>
<h2 className="mt-3 max-w-[680px] text-2xl font-semibold leading-8 text-balance">{primaryAction.title}</h2>
<p className="mt-2 max-w-[680px] text-sm leading-6 text-foreground/52 text-pretty">{primaryAction.description}</p>
@@ -39,7 +39,7 @@ export default function ProjectOverviewView({ detail }: ProjectDetailViewProps)
</div>
</div>
<aside className="border-t border-border/70 bg-foreground/[.018] p-5 lg:border-l lg:border-t-0 lg:p-6" aria-label="项目进度">
<aside className="project-overview-status border-t border-border/70 bg-foreground/[.018] p-5 lg:border-l lg:border-t-0 lg:p-6" aria-label="项目进度">
<div className="flex items-end justify-between gap-3">
<div><div className="text-[10px] font-medium text-foreground/38">章节进度</div><div className="mt-1 text-lg font-semibold tabular-nums">{completedUnits}<span className="mx-1 text-sm font-normal text-foreground/28">/</span>{units.length}</div></div>
<span className="text-xs font-medium tabular-nums text-foreground/42">{completion}%</span>
@@ -72,7 +72,7 @@ export default function ProjectOverviewView({ detail }: ProjectDetailViewProps)
</div>
{unitStages.length ? (
<div className="mt-4 overflow-hidden rounded-lg border border-border/75 bg-background/70">
<div className="project-pipeline-surface mt-4 overflow-hidden rounded-lg border border-border/75 bg-background/70">
<div className="divide-y divide-border/65">
{unitStages.map((item) => (
<Link key={item.unit.id} to={`/projects/${project.id}/chapters/${item.unit.id}`} className="group grid gap-4 p-4 transition-colors hover:bg-foreground/[.025] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/20 sm:p-5 lg:grid-cols-[minmax(220px,.75fr)_minmax(480px,1.35fr)_24px] lg:items-center">
@@ -93,12 +93,12 @@ export default function ProjectOverviewView({ detail }: ProjectDetailViewProps)
}
function ProjectFact({ label, value, attention = false }: { label: string; value: string; attention?: boolean }) {
return <div className="min-w-0"><dt className="text-[10px] text-foreground/36">{label}</dt><dd className={`mt-1 truncate font-medium ${attention ? "text-amber-600 dark:text-amber-400" : "text-foreground/72"}`}>{value}</dd></div>;
return <div className="min-w-0"><dt className="text-[10px] text-foreground/36">{label}</dt><dd className={`mt-1 truncate font-medium ${attention ? "text-foreground/85" : "text-foreground/72"}`}>{value}</dd></div>;
}
function SecondaryAction({ action }: { action: ProjectWorkbenchAction }) {
const Icon = action.tone === "danger" ? CircleAlert : action.tone === "attention" ? Clock3 : CheckCircle2;
return <Link to={action.href} className="group flex min-w-0 items-center gap-2 rounded px-1 py-1.5 text-[11px] text-foreground/52 hover:bg-foreground/[.04] hover:text-foreground"><Icon className={`size-3.5 shrink-0 ${action.tone === "danger" ? "text-red-500" : action.tone === "attention" ? "text-amber-500" : "text-foreground/30"}`} /><span className="min-w-0 flex-1 truncate">{action.title}</span><ArrowRight className="size-3 shrink-0 text-foreground/25 transition group-hover:text-foreground/55" /></Link>;
return <Link to={action.href} className="group flex min-w-0 items-center gap-2 rounded px-1 py-1.5 text-[11px] text-foreground/52 hover:bg-foreground/[.04] hover:text-foreground"><Icon className={`size-3.5 shrink-0 ${action.tone === "danger" ? "text-foreground/80" : action.tone === "attention" ? "text-foreground/60" : "text-foreground/30"}`} /><span className="min-w-0 flex-1 truncate">{action.title}</span><ArrowRight className="size-3 shrink-0 text-foreground/25 transition group-hover:text-foreground/55" /></Link>;
}
function StagePipeline({ content, assets, storyboard, canvas }: { content: ProjectStageCell; assets: ProjectStageCell; storyboard: ProjectStageCell; canvas: ProjectStageCell }) {
@@ -111,7 +111,7 @@ function StagePipeline({ content, assets, storyboard, canvas }: { content: Proje
}
function StageStep({ label, cell }: { label: string; cell: ProjectStageCell }) {
const bar = cell.state === "completed" ? "bg-emerald-500" : cell.state === "attention" ? "bg-amber-500" : cell.state === "active" ? "bg-[var(--workspace-accent)]" : "bg-foreground/10";
const bar = cell.state === "completed" ? "bg-foreground/65" : cell.state === "attention" ? "bg-foreground/40" : cell.state === "active" ? "bg-[var(--workspace-accent)]" : "bg-foreground/10";
return (
<span className="min-w-0">
<span className="block text-[10px] font-medium text-foreground/34">{label}</span>

View File

@@ -57,7 +57,7 @@ export default function ProjectsPage() {
title="短剧创作"
description="按制作阶段查看故事项目,继续最近工作或处理未完成章节。"
meta={<span className="text-xs text-foreground/45">{rows.length} 个</span>}
actions={<><Button icon={<LayoutGrid className="size-3.5" />} onClick={() => navigate("/canvas")}>画布</Button><Button type="primary" icon={<Plus className="size-3.5" />} onClick={() => setCreateOpen(true)}>创建项目</Button></>}
actions={<Button type="primary" icon={<Plus className="size-3.5" />} onClick={() => setCreateOpen(true)}>创建项目</Button>}
/>
<ListToolbar active={Boolean(keyword || status !== "all" || sort !== "updated")} onReset={() => { setKeyword(""); setStatus("all"); setSort("updated"); }}>
<Input allowClear className="app-list-search" prefix={<Search className="size-4 text-foreground/40" />} value={keyword} placeholder="搜索项目、简介或画风" onChange={(event) => setKeyword(event.target.value)} />