trm | a6b60ef | 2025-06-21 01:47:09 +0000 | [diff] [blame] | 1 | import React, { useState } from 'react' |
| 2 | import VideoPreview from './VideoPreview' |
| 3 | import { Play } from 'lucide-react' |
| 4 | |
| 5 | // 判断文件是否为视频 |
| 6 | const isVideoFile = (url) => { |
| 7 | if (!url) return false |
| 8 | const videoExtensions = ['.mp4', '.webm', '.ogg', '.avi', '.mov', '.wmv', '.flv', '.mkv'] |
| 9 | const lowerUrl = url.toLowerCase() |
| 10 | return videoExtensions.some(ext => lowerUrl.includes(ext)) || lowerUrl.includes('video') |
| 11 | } |
| 12 | |
| 13 | // 媒体预览组件(支持图片和视频) |
| 14 | const MediaPreview = ({ |
| 15 | url, |
| 16 | alt = '', |
| 17 | className = '', |
| 18 | style = {}, |
| 19 | onClick = null, |
| 20 | showPlayIcon = true, |
| 21 | maxWidth = 220, |
| 22 | maxHeight = 220 |
| 23 | }) => { |
| 24 | const [showVideoPreview, setShowVideoPreview] = useState(false) |
| 25 | |
| 26 | const handleMediaClick = () => { |
| 27 | if (isVideoFile(url)) { |
| 28 | setShowVideoPreview(true) |
| 29 | } else if (onClick) { |
| 30 | onClick(url) |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | const defaultStyle = { |
| 35 | maxWidth, |
| 36 | maxHeight, |
| 37 | borderRadius: 8, |
| 38 | objectFit: 'cover', |
| 39 | cursor: 'pointer', |
| 40 | ...style |
| 41 | } |
| 42 | |
| 43 | if (isVideoFile(url)) { |
| 44 | return ( |
| 45 | <> |
| 46 | <div style={{ position: 'relative', ...defaultStyle }} onClick={handleMediaClick}> |
| 47 | <video |
| 48 | src={url} |
| 49 | style={defaultStyle} |
| 50 | preload="metadata" |
| 51 | muted |
| 52 | /> |
| 53 | {showPlayIcon && ( |
| 54 | <div style={{ |
| 55 | position: 'absolute', |
| 56 | top: '50%', |
| 57 | left: '50%', |
| 58 | transform: 'translate(-50%, -50%)', |
| 59 | background: 'rgba(0,0,0,0.6)', |
| 60 | borderRadius: '50%', |
| 61 | padding: 12, |
| 62 | color: 'white' |
| 63 | }}> |
| 64 | <Play size={24} fill="white" /> |
| 65 | </div> |
| 66 | )} |
| 67 | </div> |
| 68 | |
| 69 | {/* 视频预览弹窗 */} |
| 70 | {showVideoPreview && ( |
| 71 | <div |
| 72 | style={{ |
| 73 | position: 'fixed', |
| 74 | zIndex: 9999, |
| 75 | top: 0, |
| 76 | left: 0, |
| 77 | right: 0, |
| 78 | bottom: 0, |
| 79 | background: 'rgba(0,0,0,0.8)', |
| 80 | display: 'flex', |
| 81 | alignItems: 'center', |
| 82 | justifyContent: 'center', |
| 83 | padding: 20 |
| 84 | }} |
| 85 | onClick={() => setShowVideoPreview(false)} |
| 86 | > |
| 87 | <div |
| 88 | style={{ |
| 89 | maxWidth: '90vw', |
| 90 | maxHeight: '90vh', |
| 91 | width: 'auto', |
| 92 | height: 'auto' |
| 93 | }} |
| 94 | onClick={(e) => e.stopPropagation()} |
| 95 | > |
| 96 | <VideoPreview |
| 97 | src={url} |
| 98 | onClose={() => setShowVideoPreview(false)} |
| 99 | style={{ borderRadius: 12, overflow: 'hidden' }} |
| 100 | /> |
| 101 | </div> |
| 102 | </div> |
| 103 | )} |
| 104 | </> |
| 105 | ) |
| 106 | } |
| 107 | |
| 108 | return ( |
| 109 | <img |
| 110 | src={url} |
| 111 | alt={alt} |
| 112 | className={className} |
| 113 | style={defaultStyle} |
| 114 | onClick={handleMediaClick} |
| 115 | /> |
| 116 | ) |
| 117 | } |
| 118 | |
| 119 | export default MediaPreview |