卡片
Report Card
A versatile report card component with customizable actions and flexible interaction patterns
候选人评估报告
更新时间:08-04 13:56"use client";
import { ReportCard } from "@/components/composed/report-card/report-card";
export function ReportCardBasicDemo() {
return (
<div className="w-full max-w-[650px]">
<ReportCard title="候选人评估报告" description="更新时间:08-04 13:56" />
</div>
);
}
Report Card 组件是一个功能丰富的报告数据卡片组件,提供灵活的交互模式和完全自定义的操作区域。适用于数据报告、文档管理、内容列表等场景。
概述
- 渐进式披露 - 三种使用模式:简单 props → 自定义 action → 完全自定义
- 灵活操作区域 - 支持预设操作或完全自定义右侧操作区域
- 多种显示模式 - hover 显示、外部始终显示、完全隐藏
- 向后兼容 - 原有 onEdit/onDelete/onDuplicate 全部保留
- 类型安全 - 完整的 TypeScript 类型定义
快速开始
使用预设的 onEdit、onDelete、onDuplicate props,快速实现常见操作。
import { ReportCard } from "@/registry/wuhan/composed/report-card/report-card";
<ReportCard
title="候选人评估报告"
description="更新时间:08-04 13:56"
onEdit={() => console.log("编辑")}
onDelete={() => console.log("删除")}
onDuplicate={() => console.log("复制")}
/>安装
代码演示
基础示例
候选人评估报告
更新时间:08-04 13:56"use client";
import { ReportCard } from "@/components/composed/report-card/report-card";
export function ReportCardSimpleDemo() {
return (
<div className="w-full max-w-[650px] space-y-3">
<ReportCard
title="候选人评估报告"
description="更新时间:08-04 13:56"
onEdit={() => console.log("编辑")}
onDelete={() => console.log("删除")}
onDuplicate={() => console.log("复制")}
/>
</div>
);
}
自定义操作区域
数据分析报告
包含本月所有数据统计信息"use client";
import { ReportCard } from "@/components/composed/report-card/report-card";
import { Star, Download } from "lucide-react";
export function ReportCardCustomActionDemo() {
return (
<div className="w-full max-w-[650px] space-y-3">
<ReportCard
title="数据分析报告"
description="包含本月所有数据统计信息"
action={
<div className="flex items-center gap-2">
<button className="p-1.5 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
<Star className="size-4 text-[var(--Text-text-warning)]" />
</button>
<button className="p-1.5 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
<Download className="size-4 text-[var(--Text-text-secondary)]" />
</button>
</div>
}
/>
</div>
);
}
隐藏操作区域
只读报告
不可进行任何操作"use client";
import { ReportCard } from "@/components/composed/report-card/report-card";
export function ReportCardHiddenActionDemo() {
return (
<div className="w-full max-w-[650px]">
<ReportCard
title="只读报告"
description="不可进行任何操作"
showAction={false}
/>
</div>
);
}
卡片列表
候选人评估报告
更新时间:08-04 13:56数据分析报告
包含本月所有数据统计信息绩效评估报告
2024年Q4绩效数据汇总"use client";
import {
ReportCardList,
type ReportCardItem,
} from "@/components/composed/report-card/report-card";
export function ReportCardListDemo() {
const cards: ReportCardItem[] = [
{
id: "1",
title: "候选人评估报告",
description: "更新时间:08-04 13:56",
},
{
id: "2",
title: "数据分析报告",
description: "包含本月所有数据统计信息",
},
{
id: "3",
title: "绩效评估报告",
description: "2024年Q4绩效数据汇总",
},
];
return (
<div className="w-full max-w-[650px]">
<ReportCardList
title="我的报告"
cards={cards}
onEdit={(id) => console.log("编辑", id)}
onDelete={(id) => console.log("删除", id)}
onDuplicate={(id) => console.log("复制", id)}
/>
</div>
);
}
列表自定义操作
候选人评估报告
更新时间:08-04 13:56数据分析报告
包含本月所有数据统计信息绩效评估报告
2024年Q4绩效数据汇总"use client";
import {
ReportCardList,
type ReportCardItem,
} from "@/components/composed/report-card/report-card";
import { MoreVertical } from "lucide-react";
export function ReportCardListCustomDemo() {
const cards: ReportCardItem[] = [
{
id: "1",
title: "候选人评估报告",
description: "更新时间:08-04 13:56",
},
{
id: "2",
title: "数据分析报告",
description: "包含本月所有数据统计信息",
},
{
id: "3",
title: "绩效评估报告",
description: "2024年Q4绩效数据汇总",
},
];
return (
<div className="w-full max-w-[650px]">
<ReportCardList
title="可管理的报告"
cards={cards}
cardAction={(card) => (
<button
className="flex items-center justify-center w-6 h-6 rounded-md cursor-pointer bg-[var(--Container-bg-neutral-light-hover)]"
onClick={() => console.log("自定义操作", card.id)}
>
<MoreVertical className="size-4 text-[var(--Text-text-secondary)]" />
</button>
)}
showCardAction={false}
/>
</div>
);
}
状态标签组合
已发布报告
该报告已发布到团队已发布
"use client";
import { ReportCard } from "@/components/composed/report-card/report-card";
import { MoreVertical } from "lucide-react";
export function ReportCardStatusDemo() {
return (
<div className="w-full max-w-[650px] space-y-3">
<ReportCard
title="已发布报告"
description="该报告已发布到团队"
action={
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-[var(--Container-bg-success-light)] text-[var(--Text-text-success)]">
已发布
</span>
<button className="p-1 rounded-md bg-[var(--Container-bg-neutral-light-hover)] cursor-pointer">
<MoreVertical className="size-4 text-[var(--Text-text-secondary)]" />
</button>
</div>
}
/>
</div>
);
}
选择功能
单个卡片选择
选中状态: 未选中
候选人评估报告
更新时间:08-04 13:56列表卡片选择
已选中 0 / 3 个卡片
候选人评估报告
更新时间:08-04 13:56数据分析报告
包含本月所有数据统计信息团队绩效报告
2024年Q4绩效数据汇总"use client";
import * as React from "react";
import {
ReportCard,
ReportCardList,
type ReportCardItem,
} from "@/components/composed/report-card/report-card";
import { FileText, BarChart3, Users } from "lucide-react";
export function ReportCardSelectionDemo() {
// 单个卡片的选中状态
const [singleSelected, setSingleSelected] = React.useState(false);
// 列表卡片的选中状态
const [listCards, setListCards] = React.useState<ReportCardItem[]>([
{
id: "1",
title: "候选人评估报告",
description: "更新时间:08-04 13:56",
icon: <FileText className="text-[var(--Text-text-brand)]" size={16} />,
selected: false,
},
{
id: "2",
title: "数据分析报告",
description: "包含本月所有数据统计信息",
icon: <BarChart3 className="text-[var(--Text-text-success)]" size={16} />,
selected: false,
},
{
id: "3",
title: "团队绩效报告",
description: "2024年Q4绩效数据汇总",
icon: <Users className="text-[var(--Text-text-warning)]" size={16} />,
selected: false,
},
]);
// 处理单个卡片选择
const handleSingleSelectChange = (selected: boolean) => {
setSingleSelected(selected);
console.log("单个卡片选中状态:", selected);
};
// 处理列表卡片选择
const handleListSelectChange = (selected: boolean, id: string) => {
setListCards((prev) =>
prev.map((card) => (card.id === id ? { ...card, selected } : card)),
);
console.log(`卡片 ${id} 选中状态:`, selected);
};
// 计算已选中的卡片数量
const selectedCount = listCards.filter((card) => card.selected).length;
return (
<div className="w-full space-y-8">
{/* 单个卡片选择 */}
<div className="space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
单个卡片选择
</h3>
<p className="text-xs text-[var(--Text-text-secondary)]">
选中状态: {singleSelected ? "已选中" : "未选中"}
</p>
</div>
<ReportCard
title="候选人评估报告"
description="更新时间:08-04 13:56"
icon={
<FileText className="text-[var(--Text-text-brand)]" size={16} />
}
showCheckbox={true}
selected={singleSelected}
onSelectChange={handleSingleSelectChange}
/>
</div>
{/* 列表卡片选择 */}
<div className="space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
列表卡片选择
</h3>
<p className="text-xs text-[var(--Text-text-secondary)]">
已选中 {selectedCount} / {listCards.length} 个卡片
</p>
</div>
<ReportCardList
title="我的报告"
cards={listCards}
showCheckbox={true}
onSelectChange={handleListSelectChange}
onEdit={(id) => console.log("编辑", id)}
onDelete={(id) => console.log("删除", id)}
/>
</div>
</div>
);
}
API
ReportCard
报告卡片主组件,提供完整的卡片功能和灵活的操作区域。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | - | 标题 |
description | React.ReactNode | - | 描述文本 |
icon | React.ReactNode | ReportCardDefaultIcon | 左侧图标 |
width | string | "280px" | 卡片宽度 |
onEdit | () => void | - | 编辑回调(简单模式) |
onDelete | () => void | - | 删除回调(简单模式) |
onDuplicate | () => void | - | 复制回调(简单模式) |
action | React.ReactNode | - | 自定义右侧操作区域 |
showAction | boolean | true | 是否显示默认操作按钮(仅 action 未提供时生效) |
className | string | - | 自定义类名 |
ReportCardItem
interface ReportCardItem {
/** 唯一标识符 */
id: string;
/** 卡片标题 */
title: string;
/** 描述文本 */
description?: string;
/** 自定义图标 */
icon?: React.ReactNode;
}ReportCardList
报告卡片列表组件,展示多个报告卡片。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | "报告列表" | 列表标题 |
cards | ReportCardItem[] | [] | 卡片列表数据 |
onEdit | (id: string) => void | - | 编辑回调 |
onDelete | (id: string) => void | - | 删除回调 |
onDuplicate | (id: string) => void | - | 复制回调 |
cardAction | (item: ReportCardItem) => React.ReactNode | - | 自定义每个卡片右侧操作区域 |
showCardAction | boolean | true | 是否显示默认操作按钮 |
className | string | - | 自定义类名 |
使用场景
- 数据报告 - 展示各类数据分析报告卡片
- 文档管理 - 展示文档、文件列表,支持编辑操作
- 内容列表 - 展示文章、帖子等内容的卡片列表
- 报表展示 - 展示统计报表、评估报告等
- 项目报告 - 展示项目进度报告、里程碑报告
最佳实践
- 选择合适的模式 - 简单需求用 props,复杂需求用 action
- 向后兼容 - 原有代码无需修改,继续使用 onEdit/onDelete/onDuplicate
- 列表展示 - 多个卡片使用 ReportCardList 统一管理
- 图标定制 - 根据报告类型自定义图标,增强辨识度
- 宽度设置 - 根据容器宽度调整卡片宽度(默认 280px)
- 描述信息 - 提供清晰的描述信息,帮助用户理解报告内容
- 自定义操作 - 使用 action prop 实现任意复杂的操作区域
注意事项
onEdit、onDelete、onDuplicate都是可选的,按需传入- 卡片宽度默认为 280px,可通过
width属性自定义 - 标题和描述文本超出时会自动截断显示省略号
- 菜单有 150ms 延迟关闭,优化 hover 体验
actionprop 会完全替换默认操作区域showAction仅在action未提供时生效- 列表模式下使用
cardAction自定义每个卡片操作
原语组件
ReportCard 基于以下原语组件构建:
ReportCardContainerPrimitive- 容器ReportCardHeaderPrimitive- 头部(图标 + 标题 + 描述)ReportCardPrimitive- 完整原语组件ReportCardDefaultIcon- 默认报告图标
如需更灵活的定制,可以直接使用这些原语组件。
样式定制
组件使用 Tailwind CSS 和 CSS 变量,可以通过以下方式定制:
// 使用原语组件自定义样式
import {
ReportCardPrimitive,
ReportCardContainerPrimitive,
ReportCardHeaderPrimitive,
} from "@/registry/wuhan/blocks/report-card/report-card-01";
<ReportCardPrimitive
title="自定义标题"
description="自定义描述"
className="bg-blue-50 hover:bg-blue-100"
/>交互说明
操作区域显示逻辑
- 有 action prop → 显示自定义 action,忽略默认操作
- 无 action + showAction=true + 有回调 → 显示默认 ellipsis 按钮(hover 时)
- 无 action + showAction=false → 不显示任何操作区域
- 无 action + showAction=true + 无回调 → 不显示任何操作区域
Hover 流程(默认模式)
- 移入卡片 → 显示 ellipsis 操作按钮
- 移入按钮 → 展开操作菜单
- 移入菜单 → 保持菜单打开
- 移出卡片区域 → 150ms 后关闭所有
防闪烁机制
组件使用 150ms 延迟关闭策略,当用户鼠标从触发元素移动到菜单内容之间有间隙时,可以有效防止菜单意外关闭。