大量修改UI
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user