You've already forked DataMate
- 修改 Tag 接口定义,将 id 和 color 字段改为可选类型 - 更新 onAddTag 回调函数参数类型,从对象改为字符串 - 在 AddTagPopover 组件中添加 useCallback 优化数据获取逻辑 - 调整标签去重逻辑,支持 id 或 name 任一字段匹配 - 更新 DetailHeader 组件的数据类型定义和泛型约束 - 添加 parseMetadata 工具函数用于解析元数据 - 实现 isAnnotationItem 函数判断注释类型数据 - 优化知识库详情页的标签处理和数据类型转换
158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
import React from "react";
|
|
import { Database } from "lucide-react";
|
|
import { Card, Button, Tag, Tooltip, Popconfirm } from "antd";
|
|
import type { ItemType } from "antd/es/menu/interface";
|
|
import AddTagPopover from "./AddTagPopover";
|
|
import ActionDropdown from "./ActionDropdown";
|
|
|
|
interface StatisticItem {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
value: string | number;
|
|
}
|
|
|
|
interface OperationItem {
|
|
key: string;
|
|
label: string;
|
|
icon?: React.ReactNode;
|
|
isDropdown?: boolean;
|
|
items?: ItemType[];
|
|
onMenuClick?: (key: string) => void;
|
|
onClick?: () => void;
|
|
danger?: boolean;
|
|
}
|
|
|
|
interface TagConfig {
|
|
showAdd: boolean;
|
|
tags: { id?: string | number; name: string; color?: string }[];
|
|
onFetchTags?: () => Promise<{ id?: string | number; name: string; color?: string }[]>;
|
|
onAddTag?: (tagName: string) => void;
|
|
onCreateAndTag?: (tagName: string) => void;
|
|
}
|
|
interface DetailHeaderData {
|
|
name?: string;
|
|
description?: string;
|
|
status?: { color?: string; icon?: React.ReactNode; label?: string };
|
|
tags?: { id?: string | number; name?: string }[];
|
|
icon?: React.ReactNode;
|
|
iconColor?: string;
|
|
}
|
|
|
|
interface DetailHeaderProps<T extends DetailHeaderData> {
|
|
data: T;
|
|
statistics: StatisticItem[];
|
|
operations: OperationItem[];
|
|
tagConfig?: TagConfig;
|
|
}
|
|
|
|
function DetailHeader<T extends DetailHeaderData>({
|
|
data = {} as T,
|
|
statistics,
|
|
operations,
|
|
tagConfig,
|
|
}: DetailHeaderProps<T>): React.ReactNode {
|
|
return (
|
|
<Card>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div
|
|
className={`w-16 h-16 text-white rounded-lg flex-center shadow-lg ${
|
|
data?.iconColor
|
|
? ""
|
|
: "bg-gradient-to-br from-sky-300 to-blue-500 text-white"
|
|
}`}
|
|
style={data?.iconColor ? { backgroundColor: data.iconColor } : undefined}
|
|
>
|
|
{<div className="w-[2.8rem] h-[2.8rem] text-gray-50">{data?.icon}</div> || (
|
|
<Database className="w-8 h-8 text-white" />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h1 className="text-lg font-bold text-gray-900">{data?.name}</h1>
|
|
{data?.status && (
|
|
<Tag color={data.status?.color}>
|
|
<div className="flex items-center gap-2 text-xs">
|
|
{data.status?.icon && <span>{data.status?.icon}</span>}
|
|
<span>{data.status?.label}</span>
|
|
</div>
|
|
</Tag>
|
|
)}
|
|
</div>
|
|
{data?.tags && (
|
|
<div className="flex flex-wrap mb-2">
|
|
{data?.tags?.map((tag) => (
|
|
<Tag key={tag.id} className="mr-1">
|
|
{tag.name}
|
|
</Tag>
|
|
))}
|
|
{tagConfig?.showAdd && (
|
|
<AddTagPopover
|
|
tags={tagConfig.tags}
|
|
onFetchTags={tagConfig.onFetchTags}
|
|
onAddTag={tagConfig.onAddTag}
|
|
onCreateAndTag={tagConfig.onCreateAndTag}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
<p className="text-gray-700 mb-4">{data?.description}</p>
|
|
<div className="flex items-center gap-6 text-sm">
|
|
{statistics.map((stat) => (
|
|
<div key={stat.key} className="flex items-center gap-1">
|
|
{stat.icon}
|
|
<span>{stat.value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{operations.map((op) => {
|
|
if (op.isDropdown) {
|
|
return (
|
|
<ActionDropdown
|
|
actions={op?.items}
|
|
onAction={op?.onMenuClick}
|
|
/>
|
|
);
|
|
}
|
|
if (op.confirm) {
|
|
return (
|
|
<Tooltip key={op.key} title={op.label}>
|
|
<Popconfirm
|
|
key={op.key}
|
|
{...op.confirm}
|
|
onConfirm={() => {
|
|
if (op.onClick) {
|
|
op.onClick()
|
|
} else {
|
|
op?.confirm?.onConfirm?.();
|
|
}
|
|
}}
|
|
okType={op.danger ? "danger" : "primary"}
|
|
overlayStyle={{ zIndex: 9999 }}
|
|
>
|
|
<Button icon={op.icon} danger={op.danger} />
|
|
</Popconfirm>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
return (
|
|
<Tooltip key={op.key} title={op.label}>
|
|
<Button
|
|
icon={op.icon}
|
|
danger={op.danger}
|
|
onClick={op.onClick}
|
|
/>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default DetailHeader;
|