Frontend Lab
← 回到所有模式
PE05⌨️ Input · 進階輸入上傳

File Upload

檔案上傳

難度 ★★★★★標籤 upload · drag · progress3 min read

模式簡述

File Upload:拖放或點擊上傳。要支援 drag-over highlight、progress、cancel、retry、error。

閒置|預設 dropzone

Claude Code Prompt

💬 Claude Code Prompt
參考:PE05 file-upload
組件:FileUpload.tsx

File Upload:拖放或點擊上傳。要支援 drag-over highlight、progress、cancel、retry、error。

限制:
- 沒驗證類型與大小
- progress 沒更新
- 錯誤訊息太籠統

完整程式碼

FileUpload.tsx
"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)

我踩過的坑

  1. 沒驗證類型與大小:使用者上傳 1GB 影片,整頁卡死。前端先檔大小+類型。

  2. progress 沒更新:使用者以為當機。XHR upload onprogress 要綁。

  3. 錯誤訊息太籠統:「上傳失敗」沒用。要寫出原因:太大/格式錯/網路斷。

相關模式

  • PF02
  • PD07

搜尋

按 ⌘K 隨時開啟