import React, { useState, useEffect, useRef } from "react"; import { Tag, Pagination, Tooltip, Empty, Popover, Spin } from "antd"; import { ClockCircleOutlined, StarFilled } from "@ant-design/icons"; import type { ItemType } from "antd/es/menu/interface"; import { formatDateTime } from "@/utils/unit"; import ActionDropdown from "./ActionDropdown"; import { Database } from "lucide-react"; interface BaseCardDataType { id: string | number; name: string; type: string; icon?: React.JSX.Element; iconColor?: string; status: { label: string; icon?: React.JSX.Element; color?: string; } | null; description: string; tags?: string[]; statistics?: { label: string; value: string | number }[]; updatedAt?: string; } interface CardViewProps { data: T[]; pagination: { [key: string]: any; current: number; pageSize: number; total: number; }; operations: | { key: string; label: string; danger?: boolean; icon?: React.JSX.Element; onClick?: (item: T) => void; }[] | ((item: T) => ItemType[]); loading?: boolean; onView?: (item: T) => void; onFavorite?: (item: T) => void; isFavorite?: (item: T) => boolean; } // 标签渲染组件 const TagsRenderer = ({ tags }: { tags?: any[] }) => { const [visibleTags, setVisibleTags] = useState([]); const [hiddenTags, setHiddenTags] = useState([]); const containerRef = useRef(null); useEffect(() => { if (!tags || tags.length === 0) return; const calculateVisibleTags = () => { if (!containerRef.current) return; const containerWidth = containerRef.current.offsetWidth; const tempDiv = document.createElement("div"); tempDiv.style.visibility = "hidden"; tempDiv.style.position = "absolute"; tempDiv.style.top = "-9999px"; tempDiv.className = "flex flex-wrap gap-1"; document.body.appendChild(tempDiv); let totalWidth = 0; let visibleCount = 0; const tagElements: HTMLElement[] = []; // 为每个tag创建临时元素来测量宽度 tags.forEach((tag, index) => { const tagElement = document.createElement("span"); tagElement.className = "ant-tag ant-tag-default"; tagElement.style.margin = "2px"; tagElement.textContent = typeof tag === "string" ? tag : tag.name; tempDiv.appendChild(tagElement); tagElements.push(tagElement); const tagWidth = tagElement.offsetWidth + 4; // 加上gap的宽度 // 如果不是最后一个标签,需要预留+n标签的空间 const plusTagWidth = index < tags.length - 1 ? 35 : 0; // +n标签大约35px宽度 if (totalWidth + tagWidth + plusTagWidth <= containerWidth) { totalWidth += tagWidth; visibleCount++; } else { // 如果当前标签放不下,且已经有可见标签,则停止 if (visibleCount > 0) return; // 如果是第一个标签就放不下,至少显示一个 if (index === 0) { totalWidth += tagWidth; visibleCount = 1; } } }); document.body.removeChild(tempDiv); setVisibleTags(tags.slice(0, visibleCount)); setHiddenTags(tags.slice(visibleCount)); }; // 延迟执行以确保DOM已渲染 const timer = setTimeout(calculateVisibleTags, 0); // 监听窗口大小变化 const handleResize = () => { calculateVisibleTags(); }; window.addEventListener("resize", handleResize); return () => { clearTimeout(timer); window.removeEventListener("resize", handleResize); }; }, [tags]); if (!tags || tags.length === 0) return null; const popoverContent = (
{hiddenTags.map((tag, index) => ( {typeof tag === "string" ? tag : tag.name} ))}
); return (
{visibleTags.map((tag, index) => ( {typeof tag === "string" ? tag : tag.name} ))} {hiddenTags.length > 0 && ( +{hiddenTags.length} )}
); }; function CardView(props: CardViewProps) { const { data, pagination, operations, loading, onView, onFavorite, isFavorite, } = props; if (data.length === 0) { return (
); } const ops = (item) => typeof operations === "function" ? operations(item) : operations; return (
{data.map((item) => (
onView?.(item)} style={{ cursor: onView ? "pointer" : "default" }} > {/* Header */}
{item?.icon && (
{item?.icon}
)}

{item?.name}

{item?.status && (
{item?.status?.icon && ( {item?.status?.icon} )} {item?.status?.label}
)}
{onFavorite && ( onFavorite?.(item)} /> )}
{/* Tags */} {/* Description */}

{item?.description}

{/* Statistics */}
{item?.statistics?.map((stat, idx) => (
{stat?.label}:
{stat?.value}
))}
{/* Actions */}
{" "} {formatDateTime(item?.updatedAt)}
{operations && ( { const operation = ops(item).find((op) => op.key === key); if (operation?.onClick) { operation.onClick(item); } }} /> )}
))}
); } export default CardView;