style: 收集箱布局深化——卡片流、OS滚动、操作区fill按钮

This commit is contained in:
2026-06-08 11:36:02 +08:00
parent b4e05a4a16
commit 74266dc5cc
4 changed files with 92 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
import { Card, Descriptions, Tag, Typography } from "antd";
import { Card, Descriptions, Flex, Tag, Typography } from "antd";
import type { Material, MaterialType } from "../types";
@@ -19,31 +19,32 @@ export function MaterialContent({ material }: MaterialContentProps) {
const typeLabel = MATERIAL_TYPE_LABELS[material.materialType] ?? material.materialType;
return (
<>
<Typography.Title level={4}></Typography.Title>
<Card>
<Typography.Paragraph>{material.description}</Typography.Paragraph>
{material.processedContent && (
<Typography.Paragraph
style={{
background: "var(--ant-color-fill-quaternary)",
padding: 12,
borderRadius: 6,
whiteSpace: "pre-wrap",
}}
>
{material.processedContent}
</Typography.Paragraph>
)}
<Descriptions column={1} size="small">
<Descriptions.Item label="状态">
<Tag color={statusInfo.color}>{statusInfo.label}</Tag>
</Descriptions.Item>
<Descriptions.Item label="素材类型">{typeLabel}</Descriptions.Item>
<Descriptions.Item label="关联时间">{material.associatedDate}</Descriptions.Item>
<Descriptions.Item label="创建时间">{formatRelativeTime(material.createdAt)}</Descriptions.Item>
</Descriptions>
<Flex gap={12} vertical>
<Card size="small" title="基本信息">
<Flex gap={12} vertical>
<Typography.Paragraph>{material.description}</Typography.Paragraph>
{material.processedContent && (
<Typography.Paragraph
style={{
background: "var(--ant-color-fill-quaternary)",
padding: 12,
borderRadius: 6,
whiteSpace: "pre-wrap",
}}
>
{material.processedContent}
</Typography.Paragraph>
)}
<Descriptions column={1} size="small">
<Descriptions.Item label="状态">
<Tag color={statusInfo.color}>{statusInfo.label}</Tag>
</Descriptions.Item>
<Descriptions.Item label="素材类型">{typeLabel}</Descriptions.Item>
<Descriptions.Item label="关联时间">{material.associatedDate}</Descriptions.Item>
<Descriptions.Item label="创建时间">{formatRelativeTime(material.createdAt)}</Descriptions.Item>
</Descriptions>
</Flex>
</Card>
</>
</Flex>
);
}

View File

@@ -1,10 +1,16 @@
import { CheckOutlined, CloseOutlined, RedoOutlined } from "@ant-design/icons";
import { App as AntApp, Button, Empty, Result, Space, Spin, Tag } from "antd";
import "overlayscrollbars/styles/overlayscrollbars.css";
import { App as AntApp, Button, Empty, Result, Space, Spin, Typography } from "antd";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { useMaterial } from "../../../shared/hooks/use-materials";
import { STATUS_MAP } from "./constants";
import { MaterialContent } from "./MaterialContent";
const OS_OPTIONS = {
overflow: { x: "hidden", y: "scroll" },
scrollbars: { autoHide: "move", theme: "os-theme-custom" },
} as const;
interface MaterialDetailPanelProps {
materialId: null | string;
onApprove: (materialId: string) => Promise<void>;
@@ -23,8 +29,13 @@ export function MaterialDetailPanel({
if (!materialId) {
return (
<div className="app-inbox-panel">
<div className="app-inbox-content">
<OverlayScrollbarsComponent className="app-inbox-content" options={OS_OPTIONS}>
<Empty description="请在左侧选择素材" />
</OverlayScrollbarsComponent>
<div className="app-inbox-action-bar">
<Typography.Text style={{ flex: 1, textAlign: "center", color: "var(--ant-color-text-tertiary)" }}>
</Typography.Text>
</div>
</div>
);
@@ -48,9 +59,9 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
if (isLoading) {
return (
<div className="app-inbox-panel">
<div className="app-inbox-content">
<OverlayScrollbarsComponent className="app-inbox-content" options={OS_OPTIONS}>
<Spin />
</div>
</OverlayScrollbarsComponent>
</div>
);
}
@@ -58,9 +69,9 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
if (error) {
return (
<div className="app-inbox-panel">
<div className="app-inbox-content">
<OverlayScrollbarsComponent className="app-inbox-content" options={OS_OPTIONS}>
<Result subTitle="加载素材详情失败" />
</div>
</OverlayScrollbarsComponent>
</div>
);
}
@@ -68,9 +79,9 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
if (!data || !materialId) {
return (
<div className="app-inbox-panel">
<div className="app-inbox-content">
<OverlayScrollbarsComponent className="app-inbox-content" options={OS_OPTIONS}>
<Empty description="请在左侧选择素材" />
</div>
</OverlayScrollbarsComponent>
</div>
);
}
@@ -104,31 +115,39 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
}
};
const statusInfo = STATUS_MAP[data.status] ?? { color: "default", label: data.status };
return (
<div className="app-inbox-panel">
<div className="app-inbox-content">
<OverlayScrollbarsComponent className="app-inbox-content" options={OS_OPTIONS}>
<MaterialContent material={data} />
</div>
</OverlayScrollbarsComponent>
<div className="app-inbox-action-bar">
<Tag color={statusInfo.color}>{statusInfo.label}</Tag>
{data.status === "review" ? (
<Space>
<Button icon={<CheckOutlined />} onClick={() => void handleApprove()} type="primary">
</Button>
<Button danger icon={<CloseOutlined />} onClick={() => void handleDiscard()}>
</Button>
</Space>
) : data.status === "failed" ? (
<Space>
<Button icon={<RedoOutlined />} onClick={() => void handleRetry()}>
</Button>
</Space>
) : null}
<Space>
<Button
type="primary"
disabled={data.status !== "review"}
icon={<CheckOutlined />}
onClick={() => void handleApprove()}
>
</Button>
<Button
type="primary"
danger
disabled={data.status !== "review"}
icon={<CloseOutlined />}
onClick={() => void handleDiscard()}
>
</Button>
<Button
type="primary"
disabled={data.status !== "failed"}
icon={<RedoOutlined />}
onClick={() => void handleRetry()}
>
</Button>
</Space>
</div>
</div>
);