From 344c9bc9568db6fa0d5e38aeb08fca2213b03e5a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 31 Jan 2026 21:17:35 +0800 Subject: [PATCH] feat: add real-time video upload progress bar with stage indicators Co-authored-by: Cursor --- .../settings/admin-settings-client.tsx | 147 ++++++++++++++---- 1 file changed, 114 insertions(+), 33 deletions(-) diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 47e5275..cf9a84e 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -580,6 +580,8 @@ export function AdminSettingsClient() { // 视频上传状态 const [videoUploading, setVideoUploading] = useState(false); const [videoUploadProgress, setVideoUploadProgress] = useState(""); + const [videoUploadPercent, setVideoUploadPercent] = useState(0); + const [videoUploadStage, setVideoUploadStage] = useState<"idle" | "uploading" | "compressing" | "saving" | "done">("idle"); const [videoQuality, setVideoQuality] = useState<"low" | "medium" | "high">("medium"); // 视频教程管理状态 @@ -2718,8 +2720,18 @@ export function AdminSettingsClient() { const file = e.target.files?.[0]; if (!file) return; + // 文件大小检查 + const maxSize = 500 * 1024 * 1024; // 500MB + if (file.size > maxSize) { + toast.error("视频文件不能超过500MB"); + e.target.value = ""; + return; + } + setVideoUploading(true); - setVideoUploadProgress("上传中..."); + setVideoUploadPercent(0); + setVideoUploadStage("uploading"); + setVideoUploadProgress(`上传中 0%`); try { const formData = new FormData(); @@ -2727,55 +2739,124 @@ export function AdminSettingsClient() { formData.append("quality", videoQuality); formData.append("compress", "true"); - const response = await fetch("/api/video/upload", { - method: "POST", - body: formData, + // 使用 XMLHttpRequest 以获取真实上传进度 + const result = await new Promise<{ url: string; compression?: { compressed: boolean; ratio: string; originalSize: string; compressedSize: string } }>((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + // 上传进度 + xhr.upload.onprogress = (event) => { + if (event.lengthComputable) { + const percent = Math.round((event.loaded / event.total) * 100); + setVideoUploadPercent(percent); + if (percent < 100) { + setVideoUploadProgress(`上传中 ${percent}%`); + } else { + setVideoUploadStage("compressing"); + setVideoUploadProgress("压缩处理中..."); + } + } + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + try { + const response = JSON.parse(xhr.responseText); + if (response.error) { + reject(new Error(response.error)); + } else { + setVideoUploadStage("done"); + resolve(response); + } + } catch { + reject(new Error("解析响应失败")); + } + } else { + try { + const response = JSON.parse(xhr.responseText); + reject(new Error(response.error || `上传失败 (${xhr.status})`)); + } catch { + reject(new Error(`上传失败 (${xhr.status})`)); + } + } + }; + + xhr.onerror = () => reject(new Error("网络错误,请检查连接")); + xhr.ontimeout = () => reject(new Error("上传超时")); + + xhr.open("POST", "/api/video/upload"); + xhr.timeout = 600000; // 10分钟超时 + xhr.send(formData); }); - const result = await response.json(); - - if (!response.ok) { - throw new Error(result.error || "上传失败"); - } - setTutorialVideoForm({ ...tutorialVideoForm, videoUrl: result.url }); if (result.compression?.compressed) { - toast.success(`压缩率: ${result.compression.ratio}%`); + toast.success( + `上传成功!压缩率: ${result.compression.ratio}% (${result.compression.originalSize} → ${result.compression.compressedSize})`, + { duration: 5000 } + ); } else { - toast.success("上传成功!"); + toast.success("视频上传成功!"); } } catch (error) { toast.error(error instanceof Error ? error.message : "上传失败"); } finally { setVideoUploading(false); setVideoUploadProgress(""); + setVideoUploadPercent(0); + setVideoUploadStage("idle"); e.target.value = ""; } }} disabled={videoUploading} /> - +
+ + {/* 上传进度条 */} + {videoUploading && ( +
+
+
+
+

+ {videoUploadStage === "uploading" && `已上传 ${videoUploadPercent}%`} + {videoUploadStage === "compressing" && "服务器正在压缩视频,请耐心等待..."} + {videoUploadStage === "done" && "处理完成!"} +

+
+ )} +