feat: 收集箱右侧区域布局优化——上下两段式,操作栏固定底部始终显示
This commit is contained in:
@@ -1,22 +1,14 @@
|
|||||||
import { Card, Descriptions, Tag, Typography } from "antd";
|
import { Card, Descriptions, Tag, Typography } from "antd";
|
||||||
|
|
||||||
import type { Material, MaterialStatus, MaterialType } from "../types";
|
import type { Material, MaterialType } from "../types";
|
||||||
|
|
||||||
import { formatRelativeTime } from "../../../shared/utils/time";
|
import { formatRelativeTime } from "../../../shared/utils/time";
|
||||||
|
import { STATUS_MAP } from "./constants";
|
||||||
|
|
||||||
interface MaterialContentProps {
|
interface MaterialContentProps {
|
||||||
material: Material;
|
material: Material;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_MAP: Record<MaterialStatus, { color: string; label: string }> = {
|
|
||||||
approved: { color: "green", label: "已通过" },
|
|
||||||
discarded: { color: "red", label: "已放弃" },
|
|
||||||
failed: { color: "magenta", label: "失败" },
|
|
||||||
pending: { color: "gold", label: "待处理" },
|
|
||||||
processing: { color: "blue", label: "处理中" },
|
|
||||||
review: { color: "orange", label: "待审核" },
|
|
||||||
};
|
|
||||||
|
|
||||||
const MATERIAL_TYPE_LABELS: Record<MaterialType, string> = {
|
const MATERIAL_TYPE_LABELS: Record<MaterialType, string> = {
|
||||||
general: "通用",
|
general: "通用",
|
||||||
meeting: "会议",
|
meeting: "会议",
|
||||||
@@ -27,7 +19,7 @@ export function MaterialContent({ material }: MaterialContentProps) {
|
|||||||
const typeLabel = MATERIAL_TYPE_LABELS[material.materialType] ?? material.materialType;
|
const typeLabel = MATERIAL_TYPE_LABELS[material.materialType] ?? material.materialType;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<>
|
||||||
<Typography.Title level={4}>素材详情</Typography.Title>
|
<Typography.Title level={4}>素材详情</Typography.Title>
|
||||||
<Card>
|
<Card>
|
||||||
<Typography.Paragraph>{material.description}</Typography.Paragraph>
|
<Typography.Paragraph>{material.description}</Typography.Paragraph>
|
||||||
@@ -52,6 +44,6 @@ export function MaterialContent({ material }: MaterialContentProps) {
|
|||||||
<Descriptions.Item label="创建时间">{formatRelativeTime(material.createdAt)}</Descriptions.Item>
|
<Descriptions.Item label="创建时间">{formatRelativeTime(material.createdAt)}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { CheckOutlined, CloseOutlined, RedoOutlined } from "@ant-design/icons";
|
import { CheckOutlined, CloseOutlined, RedoOutlined } from "@ant-design/icons";
|
||||||
import { App as AntApp, Button, Empty, Result, Space, Spin } from "antd";
|
import { App as AntApp, Button, Empty, Result, Space, Spin, Tag } from "antd";
|
||||||
|
|
||||||
import type { Material } from "../types";
|
|
||||||
|
|
||||||
import { useMaterial } from "../../../shared/hooks/use-materials";
|
import { useMaterial } from "../../../shared/hooks/use-materials";
|
||||||
|
import { STATUS_MAP } from "./constants";
|
||||||
import { MaterialContent } from "./MaterialContent";
|
import { MaterialContent } from "./MaterialContent";
|
||||||
|
|
||||||
interface MaterialDetailPanelProps {
|
interface MaterialDetailPanelProps {
|
||||||
@@ -23,8 +22,10 @@ export function MaterialDetailPanel({
|
|||||||
}: MaterialDetailPanelProps) {
|
}: MaterialDetailPanelProps) {
|
||||||
if (!materialId) {
|
if (!materialId) {
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<div className="app-inbox-panel">
|
||||||
<Empty description="请在左侧选择素材" />
|
<div className="app-inbox-content">
|
||||||
|
<Empty description="请在左侧选择素材" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,24 +47,30 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<div className="app-inbox-panel">
|
||||||
<Spin />
|
<div className="app-inbox-content">
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<div className="app-inbox-panel">
|
||||||
<Result subTitle="加载素材详情失败" />
|
<div className="app-inbox-content">
|
||||||
|
<Result subTitle="加载素材详情失败" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || !materialId) {
|
if (!data || !materialId) {
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<div className="app-inbox-panel">
|
||||||
<Empty description="请在左侧选择素材" />
|
<div className="app-inbox-content">
|
||||||
|
<Empty description="请在左侧选择素材" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -97,44 +104,32 @@ function MaterialDetailPanelInner({ materialId, onApprove, onDiscard, onRetry, p
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const statusInfo = STATUS_MAP[data.status] ?? { color: "default", label: data.status };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-inbox-content">
|
<div className="app-inbox-panel">
|
||||||
<MaterialContent material={data} />
|
<div className="app-inbox-content">
|
||||||
<ActionButtons material={data} onApprove={handleApprove} onDiscard={handleDiscard} onRetry={handleRetry} />
|
<MaterialContent material={data} />
|
||||||
|
</div>
|
||||||
|
<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}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionButtonsProps {
|
|
||||||
material: Material;
|
|
||||||
onApprove: () => Promise<void>;
|
|
||||||
onDiscard: () => Promise<void>;
|
|
||||||
onRetry: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ActionButtons({ material, onApprove, onDiscard, onRetry }: ActionButtonsProps) {
|
|
||||||
if (material.status === "review") {
|
|
||||||
return (
|
|
||||||
<Space style={{ marginTop: 16 }}>
|
|
||||||
<Button icon={<CheckOutlined />} onClick={() => void onApprove()} type="primary">
|
|
||||||
通过
|
|
||||||
</Button>
|
|
||||||
<Button danger icon={<CloseOutlined />} onClick={() => void onDiscard()}>
|
|
||||||
放弃
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (material.status === "failed") {
|
|
||||||
return (
|
|
||||||
<Space style={{ marginTop: 16 }}>
|
|
||||||
<Button icon={<RedoOutlined />} onClick={() => void onRetry()}>
|
|
||||||
重试
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|||||||
10
src/web/features/inbox/components/constants.ts
Normal file
10
src/web/features/inbox/components/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { MaterialStatus } from "../types";
|
||||||
|
|
||||||
|
export const STATUS_MAP: Record<MaterialStatus, { color: string; label: string }> = {
|
||||||
|
approved: { color: "green", label: "已通过" },
|
||||||
|
discarded: { color: "red", label: "已放弃" },
|
||||||
|
failed: { color: "magenta", label: "失败" },
|
||||||
|
pending: { color: "gold", label: "待处理" },
|
||||||
|
processing: { color: "blue", label: "处理中" },
|
||||||
|
review: { color: "orange", label: "待审核" },
|
||||||
|
};
|
||||||
@@ -338,16 +338,31 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-inbox-content {
|
.app-inbox-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-inbox-content {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
padding: var(--ant-padding-xl);
|
padding: var(--ant-padding-xl);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-inbox-action-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--ant-padding-sm) var(--ant-padding-xl);
|
||||||
|
border-top: 1px solid var(--ant-color-border-secondary);
|
||||||
|
background: var(--ant-color-bg-container);
|
||||||
|
}
|
||||||
|
|
||||||
.app-inbox-datepicker {
|
.app-inbox-datepicker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user