fix: 优化markdown解析器
This commit is contained in:
parent
eb9ec73889
commit
8f910c033e
|
|
@ -7,12 +7,9 @@ import {
|
|||
Clock,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
ExternalLink,
|
||||
Grid3X3,
|
||||
List
|
||||
} from 'lucide-react';
|
||||
import { EnhancedMarkdownRenderer } from './EnhancedMarkdownRenderer';
|
||||
import { ChatMaterialCard } from './ChatMaterialCard';
|
||||
import ImageCard from './ImageCard';
|
||||
import { GroundingSource } from '../types/ragGrounding';
|
||||
|
||||
/**
|
||||
|
|
@ -65,8 +62,6 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
|
|||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [expandedSources, setExpandedSources] = useState(false);
|
||||
const [materialViewMode, setMaterialViewMode] = useState<'grid' | 'list'>('grid');
|
||||
const [selectedReference, setSelectedReference] = useState<number | null>(null);
|
||||
|
||||
const isUser = message.type === 'user';
|
||||
const isAssistant = message.type === 'assistant';
|
||||
|
|
@ -84,11 +79,7 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
|
|||
}
|
||||
}, [message.content]);
|
||||
|
||||
// 处理引用点击
|
||||
const handleReferenceClick = useCallback((sources: GroundingSource[], index: number) => {
|
||||
setSelectedReference(selectedReference === index ? null : index);
|
||||
console.log('引用点击:', { index, sources });
|
||||
}, [selectedReference]);
|
||||
|
||||
|
||||
// 处理素材卡片点击
|
||||
const handleMaterialClick = useCallback((source: GroundingSource) => {
|
||||
|
|
@ -138,7 +129,6 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
|
|||
groundingMetadata={groundingMetadata}
|
||||
enableReferences={enableReferences}
|
||||
enableMarkdown={true}
|
||||
onReferenceClick={handleReferenceClick}
|
||||
/>
|
||||
) : (
|
||||
<div className="whitespace-pre-wrap leading-relaxed">
|
||||
|
|
@ -204,41 +194,17 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
|
|||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{expandedSources && (
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setMaterialViewMode('grid')}
|
||||
className={`p-1 rounded ${materialViewMode === 'grid' ? 'bg-gray-200' : 'hover:bg-gray-100'}`}
|
||||
title="网格视图"
|
||||
>
|
||||
<Grid3X3 className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMaterialViewMode('list')}
|
||||
className={`p-1 rounded ${materialViewMode === 'list' ? 'bg-gray-200' : 'hover:bg-gray-100'}`}
|
||||
title="列表视图"
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{expandedSources && (
|
||||
<div className={`
|
||||
${materialViewMode === 'grid'
|
||||
? 'material-grid compact'
|
||||
: 'space-y-2'
|
||||
}
|
||||
`}>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{sources.map((source, index) => (
|
||||
<ChatMaterialCard
|
||||
<ImageCard
|
||||
key={index}
|
||||
source={source as GroundingSource}
|
||||
size={materialViewMode === 'grid' ? 'small' : 'medium'}
|
||||
showDetails={materialViewMode === 'list'}
|
||||
onClick={handleMaterialClick}
|
||||
showDetails={true}
|
||||
className="cursor-pointer"
|
||||
onViewLarge={() => handleMaterialClick(source as GroundingSource)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ interface EnhancedMarkdownRendererProps {
|
|||
export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> = ({
|
||||
content,
|
||||
groundingMetadata,
|
||||
enableReferences = true,
|
||||
enableMarkdown = true,
|
||||
className = '',
|
||||
showStatistics = false,
|
||||
|
|
@ -47,8 +46,7 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [validation, setValidation] = useState<ValidationResult | null>(null);
|
||||
const [selectedGrounding, setSelectedGrounding] = useState<any>(null);
|
||||
const [showGroundingModal, setShowGroundingModal] = useState(false);
|
||||
const [expandedGrounding, setExpandedGrounding] = useState<any>(null);
|
||||
const [previewSource, setPreviewSource] = useState<any>(null);
|
||||
|
||||
// 解析Markdown内容
|
||||
|
|
@ -98,17 +96,16 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
|||
}
|
||||
}, [parseContent, enableRealTimeParsing]);
|
||||
|
||||
// 显示grounding详情
|
||||
const showGroundingDetails = useCallback((groundingAnalysis: any) => {
|
||||
setSelectedGrounding(groundingAnalysis);
|
||||
setShowGroundingModal(true);
|
||||
}, []);
|
||||
|
||||
// 关闭grounding详情
|
||||
const closeGroundingDetails = useCallback(() => {
|
||||
setShowGroundingModal(false);
|
||||
setSelectedGrounding(null);
|
||||
}, []);
|
||||
// 切换grounding详情展开状态
|
||||
const toggleGroundingDetails = useCallback((groundingAnalysis: any) => {
|
||||
if (expandedGrounding && expandedGrounding === groundingAnalysis) {
|
||||
// 如果当前已展开相同的内容,则收起
|
||||
setExpandedGrounding(null);
|
||||
} else {
|
||||
// 展开新的内容
|
||||
setExpandedGrounding(groundingAnalysis);
|
||||
}
|
||||
}, [expandedGrounding]);
|
||||
|
||||
// 查看大图
|
||||
const handleViewLarge = useCallback((source: any) => {
|
||||
|
|
@ -216,22 +213,36 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
|||
}, [groundingMetadata, parseResult]);
|
||||
|
||||
// 渲染单个Markdown节点
|
||||
const renderNode = useCallback((node: MarkdownNode, depth: number = 0, index: number = 0): React.ReactNode => {
|
||||
const renderNode = useCallback((node: MarkdownNode, depth: number = 0, index: number = 0, showGroundingIndicator: boolean = true): React.ReactNode => {
|
||||
const key = `${node.range?.start?.line || 0}-${node.range?.start?.column || 0}-${node.range?.start?.offset || 0}-${depth}-${index}`;
|
||||
// 分析当前节点的引用关联
|
||||
const groundingAnalysis = analyzeNodeGrounding(node);
|
||||
// 创建引用指示器组件
|
||||
const GroundingIndicator = groundingAnalysis ? (
|
||||
const isExpanded = expandedGrounding === groundingAnalysis;
|
||||
const GroundingIndicator = (groundingAnalysis && showGroundingIndicator) ? (
|
||||
<span
|
||||
className="inline-flex items-center ml-1 px-1 py-0.5 text-xs bg-blue-100 text-blue-700 rounded cursor-help"
|
||||
title={`引用了 ${groundingAnalysis.groundingInfo.sourceCount} 个来源`}
|
||||
className={`inline-flex items-center ml-1 px-2 py-1 text-xs rounded cursor-pointer transition-all duration-200 ${
|
||||
isExpanded
|
||||
? 'bg-blue-500 text-white shadow-md'
|
||||
: 'bg-blue-100 text-blue-700 hover:bg-blue-200'
|
||||
}`}
|
||||
title={`${isExpanded ? '收起' : '查看'} ${groundingAnalysis.groundingInfo.sourceCount} 个相关素材`}
|
||||
onClick={() => {
|
||||
console.log('📚 点击查看引用详情:', groundingAnalysis);
|
||||
// 显示详细的引用信息,包括图片
|
||||
showGroundingDetails(groundingAnalysis);
|
||||
// 切换详细的引用信息展示
|
||||
toggleGroundingDetails(groundingAnalysis);
|
||||
}}
|
||||
>
|
||||
{groundingAnalysis.groundingInfo.sourceCount}
|
||||
<span className="mr-1">🖼️</span>
|
||||
<span>{groundingAnalysis.groundingInfo.sourceCount}</span>
|
||||
<svg
|
||||
className={`w-3 h-3 ml-1 transition-transform duration-200 ${isExpanded ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
|
|
@ -332,7 +343,7 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
|||
case MarkdownNodeType.Strong:
|
||||
return (
|
||||
<strong key={key}>
|
||||
{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
|
||||
{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex, false))}
|
||||
</strong>
|
||||
);
|
||||
|
||||
|
|
@ -424,78 +435,46 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 显示引用来源信息(如果有的话) */}
|
||||
{enableReferences && groundingMetadata?.sources && groundingMetadata.sources.length > 0 && (
|
||||
<div className="mt-4 pt-3 border-t border-gray-200">
|
||||
<div className="text-xs text-gray-500 mb-2">
|
||||
基于 {groundingMetadata.sources.length} 个来源的信息
|
||||
{/* Grounding详情内联展开 */}
|
||||
{expandedGrounding && (
|
||||
<div className="mt-6 border-t border-gray-200 pt-4 animate-in slide-in-from-top-2 duration-300">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-base font-semibold text-gray-900 flex items-center">
|
||||
<span className="mr-2">🖼️</span>
|
||||
相关素材详情 ({expandedGrounding.groundingInfo?.sourceCount || 0})
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setExpandedGrounding(null)}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors p-2 rounded-full hover:bg-gray-100"
|
||||
title="收起"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{groundingMetadata.sources.slice(0, 5).map((source, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex items-center px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded-full cursor-pointer hover:bg-blue-100"
|
||||
title={source.title || `来源 ${index + 1}`}
|
||||
onClick={() => showGroundingDetails({
|
||||
groundingInfo: {
|
||||
sourceCount: 1,
|
||||
sources: [source]
|
||||
}
|
||||
})}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
))}
|
||||
{groundingMetadata.sources.length > 5 && (
|
||||
<span className="text-xs text-gray-400">
|
||||
+{groundingMetadata.sources.length - 5} 更多
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Grounding详情模态框 */}
|
||||
{showGroundingModal && selectedGrounding && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-4xl max-h-[90vh] overflow-hidden">
|
||||
{/* 模态框头部 */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
||||
<h3 className="text-lg font-semibold text-gray-900">相关素材详情</h3>
|
||||
<button
|
||||
onClick={closeGroundingDetails}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 模态框内容 */}
|
||||
<div className="p-4 overflow-y-auto max-h-[calc(90vh-8rem)]">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{selectedGrounding.groundingInfo?.sources?.map((source: any, index: number) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6 shadow-inner">
|
||||
{expandedGrounding.groundingInfo?.sources && expandedGrounding.groundingInfo.sources.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{expandedGrounding.groundingInfo.sources.map((source: any, index: number) => (
|
||||
<div key={index} className="relative transform hover:scale-105 transition-transform duration-200">
|
||||
<ImageCard
|
||||
source={source}
|
||||
showDetails={true}
|
||||
onViewLarge={handleViewLarge}
|
||||
className="shadow-lg border border-gray-200 hover:shadow-xl transition-shadow"
|
||||
className="shadow-lg border border-gray-200 hover:shadow-xl transition-all duration-300 bg-white"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 如果没有图片,显示提示信息 */}
|
||||
{(!selectedGrounding.groundingInfo?.sources || selectedGrounding.groundingInfo.sources.length === 0) && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<div className="text-4xl mb-2">🖼️</div>
|
||||
<p>暂无相关图片素材</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<div className="text-6xl mb-4 opacity-50">🖼️</div>
|
||||
<p className="text-base font-medium mb-2">暂无相关图片素材</p>
|
||||
<p className="text-sm text-gray-400">该内容段落没有关联的图片资源</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ import React, { useState, useCallback } from 'react';
|
|||
import {
|
||||
Download,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
X,
|
||||
Calendar,
|
||||
MapPin,
|
||||
User,
|
||||
Shirt,
|
||||
Image as ImageIcon,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
import { GroundingSource } from '../types/ragGrounding';
|
||||
|
|
@ -92,13 +90,6 @@ export const ImageCard: React.FC<ImageCardProps> = ({
|
|||
}
|
||||
}, [onDownload, source, isDownloading]);
|
||||
|
||||
// 处理查看大图
|
||||
const handleViewLarge = useCallback(() => {
|
||||
if (onViewLarge) {
|
||||
onViewLarge(source);
|
||||
}
|
||||
}, [onViewLarge, source]);
|
||||
|
||||
// 构建位置样式
|
||||
const positionStyle = position ? {
|
||||
position: 'absolute' as const,
|
||||
|
|
@ -111,58 +102,9 @@ export const ImageCard: React.FC<ImageCardProps> = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden max-w-sm ${className}`}
|
||||
className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden max-w-sm group ${className}`}
|
||||
style={positionStyle}
|
||||
>
|
||||
{/* 卡片头部 */}
|
||||
<div className="flex items-center justify-between p-3 border-b border-gray-100">
|
||||
<div className="flex items-center space-x-2">
|
||||
<ImageIcon className="w-4 h-4 text-pink-500" />
|
||||
<h3 className="text-sm font-medium text-gray-900 truncate">{title}</h3>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
{onViewLarge && (
|
||||
<button
|
||||
onClick={handleViewLarge}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
title="查看大图"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{onDownload && (
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
className="p-1 text-gray-400 hover:text-pink-500 transition-colors disabled:opacity-50"
|
||||
title="下载到本地"
|
||||
>
|
||||
<Download className={`w-4 h-4 ${isDownloading ? 'animate-pulse' : ''}`} />
|
||||
</button>
|
||||
)}
|
||||
{imageUri && (
|
||||
<a
|
||||
href={imageUri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1 text-gray-400 hover:text-blue-500 transition-colors"
|
||||
title="在新窗口打开"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
)}
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
title="关闭"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 图片展示区域 */}
|
||||
<div className="relative bg-gray-50">
|
||||
{imageUri && !imageError ? (
|
||||
|
|
@ -170,20 +112,56 @@ export const ImageCard: React.FC<ImageCardProps> = ({
|
|||
<img
|
||||
src={imageUri}
|
||||
alt={description || title}
|
||||
className={`w-full h-48 object-cover transition-opacity duration-300 ${
|
||||
className={`w-full h-48 object-cover transition-all duration-300 ${
|
||||
imageLoaded ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
} group-hover:scale-105`}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
/>
|
||||
|
||||
{/* 悬浮按钮组 */}
|
||||
<div className="absolute top-2 right-2 flex items-center space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
{onDownload && (
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
className="p-2 bg-black/50 hover:bg-black/70 text-white rounded-full backdrop-blur-sm transition-all duration-200 hover:scale-110 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="下载到本地"
|
||||
>
|
||||
<Download className={`w-4 h-4 ${isDownloading ? 'animate-pulse' : ''}`} />
|
||||
</button>
|
||||
)}
|
||||
{imageUri && (
|
||||
<a
|
||||
href={imageUri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 bg-black/50 hover:bg-black/70 text-white rounded-full backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
||||
title="在新窗口打开"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
)}
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 bg-black/50 hover:bg-black/70 text-white rounded-full backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
||||
title="关闭"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 加载指示器 */}
|
||||
{!imageLoaded && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-gray-100">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-pink-500"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-48 flex items-center justify-center text-gray-400">
|
||||
<div className="h-48 flex items-center justify-center text-gray-400 bg-gray-100">
|
||||
<div className="text-center">
|
||||
<AlertCircle className="w-8 h-8 mx-auto mb-2" />
|
||||
<p className="text-sm">图片无法加载</p>
|
||||
|
|
@ -194,71 +172,77 @@ export const ImageCard: React.FC<ImageCardProps> = ({
|
|||
|
||||
{/* 详细信息区域 */}
|
||||
{showDetails && (
|
||||
<div className="p-3 space-y-3">
|
||||
<div className="p-4 space-y-3">
|
||||
{/* 描述 */}
|
||||
{description && (
|
||||
<p className="text-sm text-gray-600 line-clamp-2">{description}</p>
|
||||
<p className="text-sm text-gray-700 line-clamp-2 leading-relaxed">{description}</p>
|
||||
)}
|
||||
|
||||
{/* 环境标签 */}
|
||||
{environmentTags.length > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<MapPin className="w-3 h-3 text-gray-400" />
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{environmentTags.slice(0, 3).map((tag: string, index: number) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-0.5 bg-blue-50 text-blue-600 text-xs rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{environmentTags.length > 3 && (
|
||||
<span className="text-xs text-gray-400">+{environmentTags.length - 3}</span>
|
||||
)}
|
||||
{/* 标签和信息 */}
|
||||
<div className="space-y-2">
|
||||
{/* 环境标签 */}
|
||||
{environmentTags.length > 0 && (
|
||||
<div className="flex items-start space-x-2">
|
||||
<MapPin className="w-3 h-3 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex flex-wrap gap-1 min-w-0">
|
||||
{environmentTags.slice(0, 2).map((tag: string, index: number) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-md font-medium"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{environmentTags.length > 2 && (
|
||||
<span className="text-xs text-gray-500 py-1">+{environmentTags.length - 2}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 服装类别 */}
|
||||
{categories.length > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<Shirt className="w-3 h-3 text-gray-400" />
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{categories.slice(0, 4).map((category: string, index: number) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-0.5 bg-pink-50 text-pink-600 text-xs rounded-full"
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
{categories.length > 4 && (
|
||||
<span className="text-xs text-gray-400">+{categories.length - 4}</span>
|
||||
)}
|
||||
{/* 服装类别 */}
|
||||
{categories.length > 0 && (
|
||||
<div className="flex items-start space-x-2">
|
||||
<Shirt className="w-3 h-3 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex flex-wrap gap-1 min-w-0">
|
||||
{categories.slice(0, 2).map((category: string, index: number) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-pink-50 text-pink-700 text-xs rounded-md font-medium"
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
{categories.length > 2 && (
|
||||
<span className="text-xs text-gray-500 py-1">+{categories.length - 2}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 模特信息 */}
|
||||
{models.length > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="w-3 h-3 text-gray-400" />
|
||||
<span className="text-xs text-gray-500">
|
||||
{models.length} 位模特
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* 底部信息行 */}
|
||||
<div className="flex items-center justify-between pt-1 border-t border-gray-100">
|
||||
{/* 模特信息 */}
|
||||
{models.length > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="w-3 h-3 text-gray-400" />
|
||||
<span className="text-xs text-gray-600 font-medium">
|
||||
{models.length} 位模特
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 发布时间 */}
|
||||
{releaseDate && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="w-3 h-3 text-gray-400" />
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(releaseDate).toLocaleDateString('zh-CN')}
|
||||
</span>
|
||||
{/* 发布时间 */}
|
||||
{releaseDate && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="w-3 h-3 text-gray-400" />
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(releaseDate).toLocaleDateString('zh-CN')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue