183 lines
7.2 KiB
TypeScript
183 lines
7.2 KiB
TypeScript
|
|
"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;
|