import { useRef, useEffect, useState } from 'react'; import { NameLocation, JobResult } from '../types'; import { IconCloudy } from './Icons'; interface CanvasAreaProps { maskFile: File | null; jobResult: JobResult | null; apiBase: string; viewMode: '2d' | '3d'; zoom: number; highlightLocation: NameLocation | null; } export default function CanvasArea({ maskFile, jobResult, apiBase, viewMode, zoom, highlightLocation }: CanvasAreaProps) { const [maskPreviewUrl, setMaskPreviewUrl] = useState(null); const wrapperRef = useRef(null); useEffect(() => { if (!maskFile) { setMaskPreviewUrl(null); return; } const url = URL.createObjectURL(maskFile); setMaskPreviewUrl(url); return () => URL.revokeObjectURL(url); }, [maskFile]); // 图片 URL 处理: // - 若已是完整 http URL 直接使用 // - 若是 /api/... 路径则拼接 apiBase // - 否则回退到 /api/jobs/{id}/files/png const resolveImageUrl = (): string | null => { if (!jobResult) return maskPreviewUrl; const raw = jobResult.image_url; if (!raw || !jobResult.job_id) return maskPreviewUrl; if (raw.startsWith('http')) return raw; return `${apiBase}${raw}`; }; const displayUrl = resolveImageUrl(); const isEmpty = !displayUrl; return (
{isEmpty ? (
导入底图或名单后可预览词云
) : viewMode === '3d' ? (
wordcloud 3D
) : (
wordcloud {highlightLocation && jobResult && ( )}
)}
); } function HighlightBox({ location, zoom }: { location: NameLocation; zoom: number }) { const x = location.box_x ?? location.x; const y = location.box_y ?? location.y; const width = location.box_width ?? location.width ?? location.font_size ?? 24; const height = location.box_height ?? location.height ?? location.font_size ?? 24; return (
); }