import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Button, Empty, Image, Space, Typography, message } from 'antd'; import { CopyOutlined, LinkOutlined } from '@ant-design/icons'; import { copyToClipboard } from '../../../utils/clipboard'; const RAW_API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000'; const RESOURCE_BASE = RAW_API_BASE.replace(/\/api\/?$/i, '').replace(/\/$/, ''); export function resolveResourceUrl(url?: string | null): string { if (!url) return ''; const value = String(url).trim(); if (!value) return ''; if (/^(https?:)?\/\//i.test(value) || /^(blob|data):/i.test(value)) return value; return `${RESOURCE_BASE}${value.startsWith('/') ? value : `/${value}`}`; } interface MediaPreviewProps { url?: string | null; type: 'image' | 'video'; height?: number; emptyDescription?: string; errorDescription?: string; } const MediaPreview: React.FC = ({ url, type, height = 260, emptyDescription = '暂无资源', errorDescription = '资源加载失败,可能文件不存在、签名过期或访问权限受限。', }) => { const resolvedUrl = useMemo(() => resolveResourceUrl(url), [url]); const [loadFailed, setLoadFailed] = useState(false); useEffect(() => { setLoadFailed(false); }, [resolvedUrl]); const copyUrl = async () => { if (!resolvedUrl) return; const ok = await copyToClipboard(resolvedUrl); message.success(ok ? '资源地址已复制' : '复制失败,请手动复制'); }; const tools = resolvedUrl ? ( {resolvedUrl} ) : null; if (!resolvedUrl) { return (
); } return (
{loadFailed ? ( ) : null} {type === 'image' ? ( setLoadFailed(true)} style={{ width: '100%', maxHeight: height, objectFit: 'contain', borderRadius: 12, background: '#f8fafc', border: '1px solid #f1f5f9', }} fallback="" preview={!loadFailed} /> ) : (
); }; export default MediaPreview;