crush-level-web/src/components/features/ai-standard-card.tsx

183 lines
7.2 KiB
TypeScript
Raw Normal View History

2025-11-13 08:38:25 +00:00
"use client";
import React, { useRef, useEffect, useState } from 'react';
import { GetMeetListResponse } from '@/services/home/types';
import { formatNumberToKMB } from '@/lib/utils';
import { Tag } from '@/components/ui/tag';
import { Avatar, AvatarImage } from '@/components/ui/avatar';
import Link from 'next/link';
interface AIStandardCardProps {
character: GetMeetListResponse;
disableHover?: boolean;
}
const AIStandardCard: React.FC<AIStandardCardProps> = ({ character, disableHover = false }) => {
const {
aiId,
nickname,
birthday,
characterName,
tagName,
headImg,
homeImageUrl,
introduction,
likedNum,
sex
} = character;
const introContainerRef = useRef<HTMLDivElement>(null);
const introTextRef = useRef<HTMLParagraphElement>(null);
const [maxLines, setMaxLines] = useState<number>(6);
// 动态计算可用空间的行数
useEffect(() => {
const calculateMaxLines = () => {
if (introContainerRef.current) {
const containerHeight = introContainerRef.current.offsetHeight;
const lineHeight = 20; // 对应 leading-[20px]
const calculatedLines = Math.floor(containerHeight / lineHeight);
// 确保至少显示 1 行,最多不超过合理的行数
const finalLines = Math.max(1, Math.min(calculatedLines, 12));
setMaxLines(finalLines);
}
};
calculateMaxLines();
// 监听窗口大小变化
window.addEventListener('resize', calculateMaxLines);
return () => window.removeEventListener('resize', calculateMaxLines);
}, []);
// 计算年龄
const getAge = (birthday?: string) => {
if (!birthday) return '';
const birthDate = new Date(birthday);
const today = new Date();
const age = today.getFullYear() - birthDate.getFullYear();
return age;
};
// 解析标签(假设是逗号分隔的字符串)
const tags = tagName ? tagName.split(',').filter(tag => tag.trim()) : [];
// 获取显示的背景图片
const displayImage = homeImageUrl || headImg;
const age = getAge(birthday);
const displayName = `${nickname}`;
return (
<Link href={`/chat/${aiId}`} className="w-full h-full">
<div
className={`basis-0 content-stretch flex flex-col gap-3 grow items-start justify-start relative shrink-0 cursor-pointer ${disableHover ? '' : 'group'}`}
>
<div
className="aspect-[3/4] bg-top bg-cover bg-no-repeat relative rounded-[16px] shrink-0 w-full transition-transform overflow-hidden"
style={{ backgroundImage: displayImage ? `url('${displayImage}')` : undefined }}
>
<div className="aspect-[3/4] box-border content-stretch flex flex-col items-end justify-end overflow-clip p-[12px] relative size-full">
{/* 默认渐变遮罩层 */}
<div
className={`absolute bottom-[-0.33px] left-0 right-0 flex flex-col gap-2 items-start justify-start pb-3 pt-6 px-3 transition-opacity duration-300 ${disableHover ? '' : 'group-hover:opacity-0'}`}
>
<div
className="absolute inset-0 backdrop-blur-3xl"
style={{
background: "var(--color-overlay-background, linear-gradient(180deg, rgba(33, 26, 43, 0.00) 0%, var(--sys-color-background-default, #211A2B) 100%))",
maskImage: "linear-gradient(180deg, rgba(33, 26, 43, 0.00) 0%, #211A2B 100%)"
}}
/>
<div className="relative">
{/* 角色信息区域 */}
<div className="content-stretch flex flex-col gap-2 items-start justify-start relative shrink-0 w-full">
{/* 名称 */}
<div className="txt-headline-s text-txt-primary-normal" style={{ wordBreak: 'break-word' }}>
{displayName}
</div>
<div className="flex flex-wrap gap-1">
{/* 性格标签 */}
{character.characterName && (
<Tag size="small">{character.characterName}</Tag>
)}
{tags.length > 0 && (
<div className="content-start flex flex-wrap gap-1 items-start justify-start relative shrink-0">
{tags.slice(0, 2).map((tag, index) => (
<Tag key={index} size="small" className="backdrop-blur-[8px]">
{tag}
</Tag>
))}
</div>
)}
</div>
{/*
{tags.length > 0 && (
<div className="content-start flex flex-wrap gap-1 items-start justify-start relative shrink-0 w-full">
{tags.slice(0, 2).map((tag, index) => (
<Tag key={index} size="small" className="backdrop-blur-[8px]">
{tag}
</Tag>
))}
</div>
)} */}
</div>
{/* 点赞数 */}
<div className="rounded-xs flex items-center gap-1 px-1 py-0.5">
<i className="iconfont icon-Like-fill" />
<span className="txt-label-s text-txt-primary-specialmap-normal">{formatNumberToKMB(likedNum ?? 0)}</span>
</div>
</div>
</div>
{/* Hover 时显示的简介覆盖层 */}
{!disableHover && (
<div className="absolute inset-0 backdrop-blur-[8px] bg-black/65 flex flex-col gap-2 items-start justify-end pb-3 pt-6 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
{/* 头像和名称 */}
<div className="flex gap-2 items-center w-full">
<Avatar className="size-12 sh">
<AvatarImage src={headImg} alt={nickname} />
</Avatar>
<div className="txt-title-s flex-1 min-w-0 truncate">{nickname}</div>
</div>
{/* 简介文本 */}
<div ref={introContainerRef} className="flex-1 flex gap-1 items-start justify-start min-h-0 w-full">
<div
className="txt-body-m text-txt-secondary-normal overflow-hidden w-full"
style={{
display: '-webkit-box',
WebkitLineClamp: maxLines,
WebkitBoxOrient: 'vertical'
}}
>
<p ref={introTextRef} className="leading-[20px]">{introduction || 'No introduction'}</p>
</div>
</div>
{/* 点赞数 */}
<div className="rounded-xs flex items-center gap-1 px-1 py-0.5">
<i className="iconfont icon-Like-fill" />
<span className="txt-label-s text-txt-primary-specialmap-normal">{formatNumberToKMB(likedNum ?? 0)}</span>
</div>
</div>
)}
</div>
{/* 边框 */}
<div
aria-hidden="true"
className="absolute border border-[rgba(251,222,255,0.2)] border-solid inset-0 pointer-events-none rounded-[16px]"
/>
</div>
</div>
</Link>
);
};
export default AIStandardCard;