PE05▸⌨️ Input · 進階輸入▸上傳
File Upload
檔案上傳
難度 ★★★★★標籤 upload · drag · progress3 min read
模式簡述
File Upload:拖放或點擊上傳。要支援 drag-over highlight、progress、cancel、retry、error。
Claude Code Prompt
參考:PE05 file-upload 組件:FileUpload.tsx File Upload:拖放或點擊上傳。要支援 drag-over highlight、progress、cancel、retry、error。 限制: - 沒驗證類型與大小 - progress 沒更新 - 錯誤訊息太籠統
完整程式碼
"use client";
import { useEffect, useState } from "react";
import { Upload, File, CheckCircle2 } from "lucide-react";
import { cn } from "@/lib/utils";
type State = "idle" | "dragover" | "uploading" | "done";
export function FileUpload({ state = "idle" }: { state?: State }) {
const [progress, setProgress] = useState(state === "uploading" ? 45 : state === "done" ? 100 : 0);
useEffect(() => {
setProgress(state === "uploading" ? 45 : state === "done" ? 100 : 0);
}, [state]);
const showFile = state === "uploading" || state === "done";
return (
<div className="w-full max-w-md">
<label
className={cn(
"grid h-40 cursor-pointer place-items-center rounded-lg border-2 border-dashed bg-background/30 transition-colors",
state === "dragover" ? "border-lab-accent bg-lab-accent/5" : "border-border",
)}
>
<input type="file" className="hidden" />
<div className="text-center">
<Upload
className={cn(
"mx-auto mb-2 h-6 w-6",
state === "dragover" ? "text-lab-accent" : "text-muted-foreground",
)}
/>
<p className="text-xs text-foreground">
{state === "dragover" ? "放開以上傳" : "點擊或拖放檔案"}
</p>
<p className="mt-1 font-mono text-[10px] text-muted-foreground">PNG / JPG / PDF · ≤ 10MB</p>
</div>
</label>
{showFile && (
<div className="mt-3 rounded-md border border-border bg-background/50 p-3">
<div className="mb-2 flex items-center gap-2 text-xs">
<File className="h-3.5 w-3.5 text-muted-foreground" />
<span className="flex-1 truncate text-foreground">design-v2.png</span>
{state === "done" ? (
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
) : (
<span className="font-mono text-[10px] text-muted-foreground">{progress}%</span>
)}
</div>
<div className="h-1 overflow-hidden rounded-full bg-muted">
<div
className="h-full bg-lab-accent transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
</div>
)}
</div>
);
}何時用
- ✅ 頭像上傳、文件附件
- ✅ 批次匯入資料
- ✅ 媒體素材上傳
何時別用
- ❌ 只接受純文字(用 textarea + paste)
- ❌ 極大檔案(要走 multipart + chunked + resumable)
- ❌ 需要從相機拍照(用原生 input + capture)
我踩過的坑
-
沒驗證類型與大小:使用者上傳 1GB 影片,整頁卡死。前端先檔大小+類型。
-
progress 沒更新:使用者以為當機。XHR upload onprogress 要綁。
-
錯誤訊息太籠統:「上傳失敗」沒用。要寫出原因:太大/格式錯/網路斷。
相關模式
- PF02
- PD07