大量修改UI

This commit is contained in:
Azmat@qq.com
2026-08-19 17:44:56 +08:00
parent 18b3927817
commit a343f47f0c
76 changed files with 11655 additions and 6104 deletions
+134 -113
View File
@@ -1,34 +1,43 @@
import { useMemo, useState } from "react";
import type { CSSProperties } from "react";
import {
ArrowRight,
ChevronRight,
FolderKanban,
Replace,
ScanSearch,
WandSparkles,
} from "lucide-react";
import type { BillingSummary, Product, Project } from "../types";
import type { NavigateFn, Page } from "./route-config";
import { IconKitSvg } from "../components/IconKitSvg";
type CreateTone = "blue-a" | "blue-b" | "light";
type DashTab = "all" | "wip" | "done";
type EntryTone = "primary" | "subtle";
const CREATE_GROUPS: Array<{
label: string;
hint: string;
cards: Array<{
title: string;
desc: string;
icon: string;
tone: CreateTone;
tone: EntryTone;
page: Page;
icon: "wand" | "folder" | "scan" | "replace";
}>;
}> = [
{
label: "从商品开始",
hint: "围绕商品卖点生成完整带货内容",
cards: [
{ title: "极速成片", desc: "上传商品图片,自动完成整条视频", icon: "wand", tone: "blue-a", page: "projectWizard" },
{ title: "专业创作", desc: "控制脚本、资产、故事板与生成", icon: "clapperboard", tone: "blue-b", page: "projectWizard" },
{ title: "极速成片", desc: "上传商品图片,自动完成整条视频", tone: "primary", page: "projectWizard", icon: "wand" },
{ title: "专业创作", desc: "控制脚本、资产、故事板与生成", tone: "primary", page: "projectWizard", icon: "folder" },
],
},
{
label: "从参考视频开始",
hint: "复用成熟视频的镜头表达与节奏",
cards: [
{ title: "视频复刻", desc: "拆解参考视频并提炼可编辑提示词", icon: "scan", tone: "light", page: "freeCreate" },
{ title: "商品替换", desc: "保留原片表达,替换为自己的商品", icon: "swap", tone: "light", page: "freeCreate" },
{ title: "视频复刻", desc: "拆解参考视频并提炼可编辑提示词", tone: "subtle", page: "freeCreate", icon: "scan" },
{ title: "商品替换", desc: "保留原片表达,替换为自己的商品", tone: "subtle", page: "freeCreate", icon: "replace" },
],
},
];
@@ -50,7 +59,7 @@ function dashStageNo(project: Project) {
function dashProgClass(project: Project, i: number, no: number): string {
if (project.status === "completed") return i <= no ? "done" : "";
if (project.status === "failed") return i === no ? "fail" : i < no ? "done" : "";
return i < no ? "done" : i === no ? "cur" : "";
return i < no ? "done" : i === no ? "warn" : "";
}
function dashStageLabel(project: Project): string {
if (project.status === "failed") return "失败";
@@ -67,7 +76,13 @@ function dashCardMeta(project: Project, productTitle: string): string {
const shots = project.video_segment_count ?? project.video_segments?.length ?? 0;
return ["专业创作", productTitle, shots ? `${shots} 镜` : null].filter(Boolean).join(" / ");
}
const coverStyle = (url: string): CSSProperties => ({ ["--mock-media-url"]: `url(${url})` } as CSSProperties);
function EntryIcon({ name }: { name: "wand" | "folder" | "scan" | "replace" }) {
if (name === "folder") return <FolderKanban />;
if (name === "scan") return <ScanSearch />;
if (name === "replace") return <Replace />;
return <WandSparkles />;
}
export function Dashboard({
products,
@@ -109,82 +124,84 @@ export function Dashboard({
const listed = useMemo(() => {
const rows = tab === "all" ? projects : projects.filter((project) => dashBucket(project) === tab);
return rows.slice(0, 3);
return rows.slice(0, 7);
}, [projects, tab]);
return (
<section className="dashboard-page">
<header className="dash-hero">
<section className="welcome">
<h1>欢迎回来{greetName ? `,${greetName}` : ""}</h1>
<p className="dash-hero-sub">
{dateLabel} · 你有{running}个项目正在进行中
</p>
</header>
<p>{dateLabel} · 你有{running}个项目正在进行中</p>
</section>
<section className="dash-create" aria-label="开始创作">
<h2>开始创作</h2>
{CREATE_GROUPS.map((group) => (
<div className="dash-create-group" key={group.label}>
<div className="dash-create-label">{group.label}</div>
<div className="dash-create-row">
{group.cards.map((card) => (
<button
key={card.title}
className={`dash-create-card ${card.tone}`}
type="button"
onClick={() => navigate(card.page)}
>
<span className="dash-create-ic" aria-hidden="true">
<IconKitSvg name={card.icon} size={22} strokeWidth={1.7} />
</span>
<span className="dash-create-copy">
<span className="t">{card.title}</span>
<span className="d">{card.desc}</span>
</span>
<span className="dash-create-arrow" aria-hidden="true">
<IconKitSvg name="arrowRight" size={18} strokeWidth={1.8} />
</span>
</button>
))}
<section className="creation" aria-labelledby="creationTitle">
<div className="creation-head">
<span className="section-label" id="creationTitle">开始创作</span>
</div>
<div className="creation-groups">
{CREATE_GROUPS.map((group) => (
<div className="creation-group-block" key={group.label}>
<div className="creation-group-label">
<strong>{group.label}</strong>
<span>{group.hint}</span>
</div>
<div className="creation-entry-pair">
{group.cards.map((card) => (
<button
key={card.title}
className={`entry entry-${card.tone}`}
type="button"
onClick={() => navigate(card.page)}
>
<EntryIcon name={card.icon} />
<span className="entry-copy">
<strong>{card.title}</strong>
<small>{card.desc}</small>
</span>
<ArrowRight />
</button>
))}
</div>
</div>
</div>
))}
</section>
<section className="dash-overview" aria-label="项目概览">
<div className="dash-overview-h">项目概览</div>
<div className="dash-overview-bar">
<button className="dash-overview-cell" type="button" onClick={() => navigate("projects", { tab: "all" })}>
<span className="lbl">总项目</span>
<span className="v">{projCount}</span>
</button>
<button className="dash-overview-cell" type="button" onClick={() => navigate("projects", { tab: "wip" })}>
<span className="lbl">进行中</span>
<span className="v">{running}</span>
</button>
<button className="dash-overview-cell" type="button" onClick={() => navigate("projects", { tab: "done" })}>
<span className="lbl">成片</span>
<span className="v">{completed}</span>
</button>
<button className="dash-overview-cell" type="button" onClick={() => navigate("projects")}>
<span className="lbl">本月</span>
<span className="v">+{newThisMonth}</span>
</button>
))}
</div>
</section>
<section className="dash-mine" aria-label="我的项目">
<div className="dash-mine-head">
<div className="workbench-section-divider"><span>项目概览</span></div>
<section className="status-summary" aria-label="项目概览">
<button className="summary-item" type="button" onClick={() => navigate("projects", { tab: "all" })}>
<span>总项目</span>
<strong>{projCount}</strong>
</button>
<button className="summary-item" type="button" onClick={() => navigate("projects", { tab: "wip" })}>
<span>进行中</span>
<strong>{running}</strong>
</button>
<button className="summary-item" type="button" onClick={() => navigate("projects", { tab: "done" })}>
<span>成片</span>
<strong>{completed}</strong>
</button>
<button className="summary-item" type="button" onClick={() => navigate("projects")}>
<span>本月</span>
<strong className="positive">+{newThisMonth}</strong>
</button>
</section>
<section className="projects">
<div className="projects-head">
<h2>我的项目</h2>
<button className="dash-mine-all" type="button" onClick={() => navigate("projects")}>
查看全部项目 <IconKitSvg name="chevronRight" size={14} />
<button className="text-action" type="button" onClick={() => navigate("projects")}>
<span>查看全部项目</span>
<ChevronRight />
</button>
</div>
<div className="dash-mine-pills" role="tablist" aria-label="项目筛选">
<div className="filters" role="tablist" aria-label="项目筛选">
{DASH_TABS.map((item) => (
<button
key={item.filter}
className={`dash-mine-pill${tab === item.filter ? " active" : ""}`}
className={`filter${tab === item.filter ? " active" : ""}`}
type="button"
role="tab"
aria-selected={tab === item.filter}
@@ -194,49 +211,53 @@ export function Dashboard({
</button>
))}
</div>
<div className="dash-mine-list">
{listed.length === 0 ? (
<div className="dash-mine-empty">还没有项目,从上面开始创作</div>
) : listed.map((project) => {
const no = dashStageNo(project);
const cover = project.cover_preview_url || "";
const openProject = () => navigate("pipeline", { projectId: project.id });
return (
<article
key={project.id}
className="dash-proj-card"
onClick={openProject}
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openProject(); } }}
role="button"
tabIndex={0}
>
<div className={`placeholder dash-proj-thumb${cover ? " has-mock-media" : ""}`} style={cover ? coverStyle(cover) : undefined}>
<span className="ph-frame">9:16</span>
</div>
<div className="dash-proj-main">
<div className="dash-proj-title">{project.name}</div>
<div className="dash-proj-sub">{dashCardMeta(project, productTitle(project.product))}</div>
</div>
<div className="dash-proj-stage">
<div className="dash-proj-stage-lbl">当前阶段</div>
<div className="dash-proj-stage-name">{dashStageLabel(project)}</div>
<div className="dash-proj-prog">
{Array.from({ length: DASH_STAGE_TOTAL }, (_, k) => k + 1).map((i) => (
<span key={i} className={dashProgClass(project, i, no)} />
))}
</div>
</div>
<button
className="dash-proj-cta"
type="button"
onClick={(event) => { event.stopPropagation(); openProject(); }}
{listed.length === 0 ? (
<div className="empty-state">当前筛选下暂无展示项目</div>
) : (
<div className="project-list">
{listed.map((project) => {
const no = dashStageNo(project);
const cover = project.cover_preview_url || "";
const openProject = () => navigate("pipeline", { projectId: project.id });
return (
<article
key={project.id}
className="project-card"
data-status={dashBucket(project)}
onClick={openProject}
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openProject(); } }}
role="button"
tabIndex={0}
>
{project.status === "completed" ? "查看" : "继续"}
</button>
</article>
);
})}
</div>
<div className="product-thumb">
{cover ? <img src={cover} alt="" /> : <span className="ph-frame">9:16</span>}
</div>
<div className="project-info">
<h3>{project.name}</h3>
<p>{dashCardMeta(project, productTitle(project.product))}</p>
</div>
<div className="stage">
<span className="stage-label">当前阶段</span>
<strong>{dashStageLabel(project)}</strong>
<div className="progress" aria-label="项目进度">
{Array.from({ length: DASH_STAGE_TOTAL }, (_, k) => k + 1).map((i) => (
<span key={i} className={`segment ${dashProgClass(project, i, no)}`} />
))}
</div>
</div>
<button
className="continue-button"
type="button"
onClick={(event) => { event.stopPropagation(); openProject(); }}
>
{project.status === "completed" ? "查看" : "继续"}
</button>
</article>
);
})}
</div>
)}
</section>
</section>
);