blob: 405aeb807e51cf345ac5be2a0d1d43d9ac73a456 [file] [log] [blame]
TRM-codingd1cbf672025-06-18 15:15:08 +08001// src/components/UploadPage.jsx
2
3import React, { useState } from 'react'
4import { Image, Video } from 'lucide-react'
5import '../style/UploadPage.css'
6
7
8/**
9 * @param {Object} props
10 * @param {(files: File[]) => void} [props.onComplete] 上传完成后回调,接收 File 数组
11 */
12export default function UploadPage({ onComplete }) {
13 const [activeTab, setActiveTab] = useState('image')
14 const [isDragOver, setIsDragOver] = useState(false)
15 const [isUploading, setIsUploading] = useState(false)
16 const [uploadedFiles, setUploadedFiles] = useState([])
17 const [uploadProgress, setUploadProgress] = useState(0)
18
19 const validateFiles = files => {
20 const imgTypes = ['image/jpeg','image/jpg','image/png','image/webp']
21 const vidTypes = ['video/mp4','video/mov','video/avi']
22 const types = activeTab === 'video' ? vidTypes : imgTypes
23 const max = activeTab === 'video'
24 ? 2 * 1024 * 1024 * 1024
25 : 32 * 1024 * 1024
26
27 const invalid = files.filter(f => !types.includes(f.type) || f.size > max)
28 if (invalid.length) {
29 alert(`发现 ${invalid.length} 个无效文件,请检查文件格式和大小`)
30 return false
31 }
32 return true
33 }
34
35 const simulateUpload = files => {
36 setIsUploading(true)
37 setUploadProgress(0)
38 setUploadedFiles(files)
39
40 const iv = setInterval(() => {
41 setUploadProgress(p => {
42 if (p >= 100) {
43 clearInterval(iv)
44 setIsUploading(false)
45 alert(`成功上传了 ${files.length} 个文件`)
46 // 上传完成后回调
47 if (typeof onComplete === 'function') {
48 onComplete(files)
49 }
50 return 100
51 }
52 return p + 10
53 })
54 }, 200)
55 }
56
57 const handleFileUpload = () => {
58 if (isUploading) return
59 const input = document.createElement('input')
60 input.type = 'file'
61 input.accept = activeTab === 'video' ? 'video/*' : 'image/*'
62 input.multiple = activeTab === 'image'
63 input.onchange = e => {
64 const files = Array.from(e.target.files)
65 if (files.length > 0 && validateFiles(files)) {
66 simulateUpload(files)
67 }
68 }
69 input.click()
70 }
71
72 const handleDragOver = e => { e.preventDefault(); e.stopPropagation(); setIsDragOver(true) }
73 const handleDragLeave = e => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false) }
74 const handleDrop = e => {
75 e.preventDefault(); e.stopPropagation(); setIsDragOver(false)
76 if (isUploading) return
77 const files = Array.from(e.dataTransfer.files)
78 if (files.length > 0 && validateFiles(files)) {
79 simulateUpload(files)
80 }
81 }
82
83 const clearFiles = () => setUploadedFiles([])
84 const removeFile = idx => setUploadedFiles(prev => prev.filter((_, i) => i !== idx))
85
86 return (
87 <div className="upload-page">
88 {/* 上传类型切换 */}
89 <div className="upload-tabs">
90 <button
91 className={`upload-tab${activeTab === 'video' ? ' active' : ''}`}
92 onClick={() => setActiveTab('video')}
93 >
94 上传视频
95 </button>
96 <button
97 className={`upload-tab${activeTab === 'image' ? ' active' : ''}`}
98 onClick={() => setActiveTab('image')}
99 >
100 上传图文
101 </button>
102 </div>
103
104 {/* 拖拽/点击上传区域 */}
105 <div
106 className={`upload-area${isDragOver ? ' drag-over' : ''}`}
107 onDragOver={handleDragOver}
108 onDragLeave={handleDragLeave}
109 onDrop={handleDrop}
110 >
111 <div className="upload-icon">
112 {activeTab === 'video' ? <Video size={48} /> : <Image size={48} />}
113 </div>
114 <h2 className="upload-title">
115 {activeTab === 'video'
116 ? '拖拽视频到此处或点击上传'
117 : '拖拽图片到此处或点击上传'}
118 </h2>
119 <p className="upload-subtitle">(需支持上传格式)</p>
120 <button
121 className={`upload-btn${isUploading ? ' uploading' : ''}`}
122 onClick={handleFileUpload}
123 disabled={isUploading}
124 >
125 {isUploading
126 ? `上传中... ${uploadProgress}%`
127 : activeTab === 'video'
128 ? '上传视频'
129 : '上传图片'}
130 </button>
131
132 {isUploading && (
133 <div className="progress-container">
134 <div className="progress-bar">
135 <div
136 className="progress-fill"
137 style={{ width: `${uploadProgress}%` }}
138 />
139 </div>
140 <div className="progress-text">{uploadProgress}%</div>
141 </div>
142 )}
143 </div>
144
145 {/* 已上传文件预览 */}
146 {uploadedFiles.length > 0 && (
147 <div className="file-preview-area">
148 <div className="preview-header">
149 <h3 className="preview-title">
150 已上传文件 ({uploadedFiles.length})
151 </h3>
152 <button
153 className="clear-files-btn"
154 onClick={clearFiles}
155 >
156 清除所有
157 </button>
158 </div>
159 <div className="file-grid">
160 {uploadedFiles.map((file, i) => (
161 <div key={i} className="file-item">
162 <button
163 className="remove-file-btn"
164 onClick={() => removeFile(i)}
165 title="删除文件"
166 >
167 ×
168 </button>
169 {file.type.startsWith('image/') ? (
170 <div className="file-thumbnail">
171 <img src={URL.createObjectURL(file)} alt={file.name} />
172 </div>
trma6b60ef2025-06-21 01:47:09 +0000173 ) : file.type.startsWith('video/') ? (
174 <div className="file-thumbnail video-thumbnail">
175 <video
176 src={URL.createObjectURL(file)}
177 muted
178 style={{ width: '100%', height: '100%', objectFit: 'cover' }}
179 />
180 <div className="video-overlay">
181 <Video size={24} />
182 </div>
183 </div>
TRM-codingd1cbf672025-06-18 15:15:08 +0800184 ) : (
185 <div className="file-thumbnail video-thumbnail">
186 <Video size={24} />
187 </div>
188 )}
189 <div className="file-info">
190 <div className="file-name" title={file.name}>
191 {file.name.length > 20
192 ? file.name.slice(0, 17) + '...'
193 : file.name}
194 </div>
195 <div className="file-size">
196 {(file.size / 1024 / 1024).toFixed(2)} MB
197 </div>
198 </div>
199 </div>
200 ))}
201 </div>
202 </div>
203 )}
204
205 {/* 上传说明信息 */}
206 <div className="upload-info fade-in">
207 {activeTab === 'image' ? (
208 <>
209 <div className="info-item">
210 <h3 className="info-title">图片大小</h3>
211 <p className="info-desc">最大32MB</p>
212 </div>
213 <div className="info-item">
214 <h3 className="info-title">图片格式</h3>
215 <p className="info-desc">png/jpg/jpeg/webp</p>
216 </div>
217 <div className="info-item">
218 <h3 className="info-title">分辨率</h3>
219 <p className="info-desc">建议720×960及以上</p>
220 </div>
221 </>
222 ) : (
223 <>
224 <div className="info-item">
225 <h3 className="info-title">视频大小</h3>
226 <p className="info-desc">最大2GB,时长≤5分钟</p>
227 </div>
228 <div className="info-item">
229 <h3 className="info-title">视频格式</h3>
230 <p className="info-desc">mp4/mov</p>
231 </div>
232 <div className="info-item">
233 <h3 className="info-title">分辨率</h3>
234 <p className="info-desc">建议720P及以上</p>
235 </div>
236 </>
237 )}
238 </div>
239 </div>
240 )
241}