React 文件上傳新玩法:Aliyun OSS 加持的智能上傳組件

文件上傳是前端開發中的“老朋友”,但如何讓它既簡單又強大,還能無縫對接云端存儲?今天,我要帶你認識一個超酷的 React 組件 AliUploader,它不僅支持拖拽上傳、批量編輯和文件排序,還直接把文件傳到 Aliyun OSS(阿里云對象存儲服務),返回云端鏈接供你隨時調用。我們會拆解它的代碼,優化它的邏輯,再通過一個“項目文件上傳器” Demo 展示它的實力。準備好了嗎?讓我們一起把文件上傳玩出云端新高度吧!


組件的核心功能:上傳到云端

AliUploader 的任務是讓文件管理變得簡單又高效:

  1. OSS 直傳:文件上傳直接到 Aliyun OSS,返回云端鏈接。
  2. 拖拽上傳:支持單文件或批量拖拽,操作絲滑。
  3. 文件管理:按類型分組(圖片、文檔、其他),支持排序和批量編輯備注。
  4. 云端同步:初始化時加載 OSS 文件列表,刪除時同步清理云端。

它就像一個“云端快遞員”,把你的文件快速送上云端,還能隨時查收!

代碼拆解與優化

原始代碼的問題

原始代碼已經很強大,但有幾處可以改進:

  • 上傳狀態管理混亂:初始文件狀態未明確定義,進度更新不完整。
  • 錯誤處理不足:上傳和刪除的異常捕獲不夠健壯。
  • 性能優化空間:文件列表更新可以更高效。

我們將優化這些點,讓代碼更優雅、更健壯。


優化后的代碼

1. 類型定義與工具函數
// utils.ts
import OSS from 'ali-oss';
import { message } from 'antd';
import { FileData, Props } from './index';export function getFileType(fileName: string): FileData['type'] {if (/\.(png|jpg|jpeg|gif)$/i.test(fileName)) return 'image';if (/\.(doc|docx|pdf|xls|xlsx|pptx)$/i.test(fileName)) return 'document';return 'other';
}export async function uploadToOSS(file: File,ossConfig: Props['ossConfig'],onProgress: (data: Partial<FileData>) => void,
): Promise<FileData> {if (!ossConfig) throw new Error('OSS 配置未提供');const client = new OSS({region: ossConfig.region,accessKeyId: ossConfig.accessKeyId,accessKeySecret: ossConfig.accessKeySecret,bucket: ossConfig.bucket,});const fileName = `${Date.now()}-${file.name}`;const result = await client.put(fileName, file, {progress: p => onProgress({ percent: Math.round(p * 100) }),});if (result.res.status !== 200) throw new Error('OSS 上傳失敗');const url = result.url || `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${fileName}`;return {uid: `${Date.now()}-${Math.random()}`,fileId: fileName,name: file.name,thumbUrl: url,url,status: 'done',percent: 100,type: getFileType(file.name),uploadTime: Date.now(),cloudUrl: url,};
}export async function delfileFromOSS(fileName: string,ossConfig: Props['ossConfig'],
): Promise<void> {const client = new OSS({ ...ossConfig! });const result = await client.delete(fileName);if (result.res.status !== 204) throw new Error('OSS 刪除失敗');message.success(`文件 ${fileName} 已從 OSS 刪除`);
}export async function getOSSList(ossConfig: Props['ossConfig']): Promise<FileData[]> {const client = new OSS({ ...ossConfig! });const result = await client.list();if (result.res.status !== 200) throw new Error('獲取 OSS 文件列表失敗');return result.objects.map((r: any) => ({uid: `${r.name}-${Date.now()}`,fileId: r.name,name: r.name,thumbUrl: r.url,url: r.url,status: 'done',percent: 100,type: getFileType(r.name),uploadTime: new Date(r.lastModified).getTime(),}));
}export function validateFile(file: File, accept: string, maxBytes: number): boolean {const types = accept.split(',').map(t => t.trim());const isValidType = types.some(t => file.name.endsWith(t));const isValidSize = file.size / 1024 / 1024 < maxBytes;if (!isValidType) message.warning('上傳文件格式不支持');if (!isValidSize) message.warning(`文件大小不能超過${maxBytes}MB`);return isValidType && isValidSize;
}

優化亮點

  • uploadToOSS:規范化返回 FileData,支持進度更新。
  • delfileFromOSS:移除回調,簡化邏輯。
  • getOSSList:直接返回文件列表,優化數據處理。
2. 主組件:AliUploader
import React, { useState, useEffect } from 'react';
import { Upload, Button, Collapse, Select, Modal, Input, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { FileData, Props } from './index';
import { FileItem } from './FileItem';
import { uploadToOSS, delfileFromOSS, getOSSList, validateFile } from './utils';
import './index.less';const { Panel } = Collapse;
const { Option } = Select;const AliUploader: React.FC<Props> = ({accept = '.doc,.docx,.xls,.xlsx,.pdf,.pptx,.png,.jpg',uploadName = '上傳文件',listType = 'text',maxCount = 1,maxBytes = 20,multiple = false,fileList = [],ossConfig,showUploadList = true,disabled = false,extraTip,showTips = true,onChange,onLoading,onSuccess,filedIds,
}) => {const [uploadFileList, setUploadFileList] = useState<FileData[]>(fileList);const [loading, setLoading] = useState(false);const [sortBy, setSortBy] = useState<'time' | 'name'>('time');const [selectedFiles, setSelectedFiles] = useState<string[]>([]);const [batchEditVisible, setBatchEditVisible] = useState(false);const [batchNote, setBatchNote] = useState('');const handleUpload = async (file: File, files?: File[]) => {const uploadFiles = multiple && files ? files : [file];if (uploadFileList.length + uploadFiles.length > maxCount) {message.warning(`最多上傳${maxCount}個文件`);return;}setLoading(true);onLoading?.(true);const newFiles = uploadFiles.map(f => ({uid: `${Date.now()}-${Math.random()}`,name: f.name,thumbUrl: '',status: 'uploading' as const,percent: 0,type: getFileType(f.name),uploadTime: Date.now(),}));setUploadFileList(prev => (maxCount === 1 ? newFiles : [...prev, ...newFiles]));try {const results = await Promise.all(uploadFiles.map((f, i) =>uploadToOSS(f, ossConfig!, data =>setUploadFileList(prev =>prev.map(item => (item.uid === newFiles[i].uid ? { ...item, ...data } : item)),),),),);const finalList = maxCount === 1 ? results : [...uploadFileList.filter(f => !newFiles.some(n => n.uid === f.uid)), ...results];setUploadFileList(finalList);onChange?.(finalList);onSuccess?.(finalList);filedIds?.(finalList.map(f => f.fileId));message.success(`${results.length}個文件上傳成功`);} catch (error) {setUploadFileList(prev =>prev.map(f => (newFiles.some(n => n.uid === f.uid) ? { ...f, status: 'error' } : f)),);message.error('部分文件上傳失敗');} finally {setLoading(false);onLoading?.(false);}};const handleRemove = async (file: FileData) => {try {await delfileFromOSS(file.fileId, ossConfig!);const newList = uploadFileList.filter(f => f.uid !== file.uid);setUploadFileList(newList);setSelectedFiles(prev => prev.filter(uid => uid !== file.uid));onChange?.(newList);filedIds?.(newList.map(f => f.fileId));} catch (error) {message.error('刪除失敗');}};const handleEdit = (editedFile: FileData) => {const newList = uploadFileList.map(f => (f.uid === editedFile.uid ? editedFile : f));setUploadFileList(newList);onChange?.(newList);};const handleSelect = (uid: string, selected: boolean) => {setSelectedFiles(prev => (selected ? [...prev, uid] : prev.filter(id => id !== uid)));};const handleBatchEdit = () => {if (selectedFiles.length === 0) {message.warning('請先選擇文件');return;}setBatchEditVisible(true);};const applyBatchEdit = () => {const newList = uploadFileList.map(f =>selectedFiles.includes(f.uid) ? { ...f, note: batchNote } : f,);setUploadFileList(newList);onChange?.(newList);setBatchEditVisible(false);setSelectedFiles([]);setBatchNote('');};useEffect(() => {if (ossConfig) {getOSSList(ossConfig).then(files => setUploadFileList(prev => [...prev, ...files])).catch(() => message.error('獲取 OSS 文件列表失敗'));}}, [ossConfig]);const groupedFiles = {image: uploadFileList.filter(f => f.type === 'image'),document: uploadFileList.filter(f => f.type === 'document'),other: uploadFileList.filter(f => f.type === 'other'),};const sortFiles = (files: FileData[]) =>files.sort((a, b) =>sortBy === 'time' ? b.uploadTime - a.uploadTime : a.name.localeCompare(b.name),);return (<div className="fileUpload"><Upload.Draggeraccept={accept}listType={listType as any}maxCount={maxCount}multiple={multiple}beforeUpload={(file, fileList) => {if (validateFile(file, accept, maxBytes)) {handleUpload(file, fileList);}return false; // 阻止默認上傳}}fileList={[]}disabled={disabled || loading}><Button icon={<UploadOutlined />} loading={loading} disabled={disabled}>{uploadName}</Button>{showTips && (<div className="tip">{`支持${maxBytes}MB以內的${accept}文件(可拖拽上傳,直接存至 OSS)`}</div>)}</Upload.Dragger>{showUploadList && (<div><div style={{ margin: '10px 0' }}><Select value={sortBy} onChange={setSortBy} style={{ width: 120, marginRight: 10 }}><Option value="time">按時間排序</Option><Option value="name">按名稱排序</Option></Select><Button onClick={handleBatchEdit} disabled={selectedFiles.length === 0}>批量編輯 ({selectedFiles.length})</Button></div><Collapse defaultActiveKey={['image', 'document', 'other']}>{Object.entries(groupedFiles).map(([type, files]) =>files.length > 0 ? (<Panelheader={`${type === 'image' ? '圖片' : type === 'document' ? '文檔' : '其他'} (${files.length})`}key={type}>{sortFiles(files).map(file => (<FileItemkey={file.uid}file={file}onRemove={() => handleRemove(file)}onEdit={handleEdit}onSelect={handleSelect}selected={selectedFiles.includes(file.uid)}/>))}</Panel>) : null,)}</Collapse></div>)}{extraTip && <div className="extraTip">{extraTip}</div>}<Modalopen={batchEditVisible}title={`批量編輯 (${selectedFiles.length} 個文件)`}onOk={applyBatchEdit}onCancel={() => setBatchEditVisible(false)}><Inputvalue={batchNote}onChange={e => setBatchNote(e.target.value)}placeholder="輸入統一備注"/></Modal></div>);
};export default AliUploader;
.imageList {display: flex;align-items: center;justify-content: space-between;margin-top: 8px;padding: 8px;border: 1px solid #d9d9d9;border-radius: 2px;.deleteBtn {color: rgba(0, 0, 0, 0.45);}
}.fileList {display: flex;align-items: center;justify-content: space-between;margin-top: 8px;padding: 8px;border: 1px solid #d9d9d9;border-radius: 2px;.deleteBtn {color: rgba(0, 0, 0, 0.45);visibility: hidden;}&:hover {background-color: #f5f5f5;.deleteBtn {visibility: visible;}}
}.fileName {margin: 0 8px;
}

優化亮點

  1. 上傳邏輯:規范化狀態管理,修復初始狀態問題。
  2. 錯誤處理:增加 try-catch,提升健壯性。
  3. 性能優化:避免重復添加文件,提升列表更新效率。

Demo:項目文件上傳器(OSS 版)

我們用一個“項目文件上傳器”展示 AliUploader 的功能。

使用示例

如何使用這個組件?

該組件已集成到 react-nexlif 開源庫中。?具體文檔可參考詳情文檔。你可以通過以下方式引入并使用:

pnpm install?react-nexlif

import React, { useState, useRef } from 'react';
import { AliUploader } from 'react-nexlif';
import { ApartmentOutlined } from '@ant-design/icons';
// import {ossConfig} from './utils';const App: React.FC = () => {const uploadRef = useRef(null);const ossConfig = {region: 'oss-cn-hangzhou',accessKeyId: '',accessKeySecret: '',bucket: '',
};const handleChange = (list: any[]) => {// console.log('當前文件列表:', list);};const handleSuccess = (list: any[]) => {// console.log('上傳成功:', list);list.forEach((file) => {// console.log(`文件 ${file.name} 的 OSS 鏈接: ${file.url}`),});};const handleIds = (ids: string[]) => {console.log('文件ID:', ids);};return (<div style={{ padding: '20px', maxWidth: '600px' }}><h2>項目文件上傳器(OSS 版)</h2><AliUploaderaccept=".doc,.docx,.pdf,.png,.jpg"uploadName="上傳項目文件到 OSS"maxCount={5}maxBytes={10}multiple={true}listType="picture"showUploadList={true}ossConfig={ossConfig}onChange={handleChange}onSuccess={handleSuccess}filedIds={handleIds}extraTip={<p style={{ color: '#999' }}>支持拖拽批量上傳,直接存至 OSS,最多5個文件</p>}/></div>);
};export default App;

?

使用效果

  1. 初始化:組件加載時從 OSS 獲取已有文件列表。
  2. 上傳:拖入 doc.pdf 和 image.png,文件上傳至 OSS,顯示進度。
  3. 分組:分為“圖片”和“文檔”,可按時間或名稱排序。
  4. 批量編輯:選中文件,添加備注“會議資料”。
  5. 刪除:移除文件,同時清理 OSS。
  6. 鏈接返回:控制臺打印 OSS 鏈接,如 https://web-xiaoyao.oss-cn-hangzhou.aliyuncs.com/xxx.png。

組件解析:云端快遞的魔法

  1. OSS 集成
    • uploadToOSS 上傳文件,返回 OSS 鏈接。
    • getOSSList 初始化云端文件。
    • delfileFromOSS 刪除云端文件。
  2. 上傳體驗
    • 多線程上傳,進度實時更新。
    • 拖拽支持,操作直觀。
  3. 文件管理
    • 分組、排序、批量編輯,井然有序。
    • 預覽和刪除,功能齊全。

使用場景與擴展

場景

  • 項目協作:上傳文件到 OSS,共享鏈接。
  • 內容管理:批量上傳圖片或文檔。
  • 云備份:自動同步到云端。

總結:你的云端“快遞員”

AliUploader 就像一個“云端快遞員”,把文件快速送上 Aliyun OSS,返回鏈接隨時取用。通過優化,我們讓它更健壯、更高效,用“項目文件上傳器”展示了它的實力。試著拖幾個文件進去跑跑看,或者丟進你的項目玩一玩吧!有其他需求或創意?歡迎留言一起聊聊!

關鍵詞:React 文件上傳組件、AliUploader OSS、云存儲鏈接、前端文件管理。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75524.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75524.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75524.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LabVIEW多線程

在 LabVIEW 中&#xff0c;多線程編程是提升程序執行效率的關鍵手段&#xff0c;尤其是在需要并行處理數據采集、控制執行和用戶界面交互的場景下。LabVIEW 本身是基于數據流&#xff08;Dataflow&#xff09;的編程語言&#xff0c;天然支持多線程&#xff0c;但要高效利用多線…

圖解AUTOSAR_SWS_LINStateManager

AUTOSAR LIN狀態管理器(LinSM)詳細設計 文檔摘要 本文檔提供了AUTOSAR LIN狀態管理器(LinSM)模塊的詳細設計解析,包括架構、狀態機、睡眠喚醒流程以及配置結構。通過圖形化方式展現LinSM在AUTOSAR通信棧中的作用及其與其他模塊的交互關系。 目錄 AUTOSAR LIN狀態管理器(Lin…

python+form+opengl顯示動態圖形數據

說明&#xff1a; pythonformopengl顯示動態圖形數據 我希望做一款動態opengl圖形數據 1.用python腳本&#xff0c;輸入指定參數 2.生成一組數據&#xff0c; 3.將數據保持成本地文件 4.在c#中調用此文件&#xff0c;解析 5.將數據用opengl展示 效果圖: step1:添加依賴 C:\U…

Android Gradle、Android Gradle Plugin、BuildTool關系

1. Gradle 的定位&#xff1a;通用構建工具 Gradle 是一個通用的跨平臺構建工具&#xff0c;支持多種語言&#xff08;如 Java、Kotlin、C&#xff09;和項目類型 它的核心功能包括&#xff1a; ?任務自動化&#xff1a;通過 Groovy/Kotlin DSL 腳本定義編譯、測試、打包等…

DHCP之報文格式

字段說明&#xff1a; op (op code): 表示報文的類型&#xff0c;取值為 1 或 2&#xff0c;含義如下 1:客戶端請求報 2:服務器響應報文 Secs (seconds):由客戶端填充&#xff0c;表示從客戶端開始獲得 IP 地址或 IP 地址續借后所使用了的秒數&#xff0c;缺省值為 3600s。 F…

觀察者模式在Java微服務間的使用

一.、使用RabbitMQ來實現 (1) 生產者&#xff08;訂單微服務&#xff09; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Service;Service public class OrderService {private final RabbitTemplate rabbitTemplate;…

OSPF五種數據包詳解

一、OSPF頭部封裝 OSPF是跨四層封裝協議&#xff0c;直接封裝在網絡層之上&#xff0c;需要IP協議使用一個協議號來標定——89。 OSPF頭部結構&#xff1a; 版本&#xff1a;標識OSPF的版本&#xff0c;OSPFv2&#xff08;2&#xff09;、OSPFv3&#xff08;3&#xff09;&am…

NO.64十六屆藍橋杯備戰|基礎算法-簡單貪心|貨倉選址|最大子段和|紀念品分組|排座椅|矩陣消除(C++)

貪?算法是兩極分化很嚴重的算法。簡單的問題會讓你覺得理所應當&#xff0c;難?點的問題會讓你懷疑?? 什么是貪?算法&#xff1f; 貪?算法&#xff0c;或者說是貪?策略&#xff1a;企圖?局部最優找出全局最優。 把解決問題的過程分成若?步&#xff1b;解決每?步時…

Linux(十二)信號

今天我們就要來一起學習信號啦&#xff01;&#xff01;&#xff01;還記得小編在之前的文章中說過的ctrlc嗎&#xff1f;之前小編沒有詳細介紹過&#xff0c;現在我們就要來學習啦&#xff01;&#xff01;&#xff01; 一、信號的基本介紹 首先&#xff0c;小編帶領大家先一…

Dify開發實戰-自制插件 和安裝python3最新版本 記錄版本 后續會持續更新

自定義插件 Dify 插件腳手架工具Python 環境&#xff0c;版本號 ≥ 3.12 安裝Python 一 進入官網 https://www.python.org/downloads/windows/ 點擊下載 二、安裝python&#xff08;本文中有借鑒其他圖片 所以圖片展示python版本可能不一致 請忽略&#xff09; 1.雙擊打開py…

Docker安裝、配置Redis

1.如果沒有docker-compose.yml文件的話&#xff0c;先創建docker-compose.yml 配置文件一般長這個樣子 version: 3services:redis:image: redis:latestcontainer_name: redisports:- "6379:6379"command: redis-server --requirepass "123456"restart: a…

Parasoft C++Test軟件單元測試_操作指南

系列文章目錄 Parasoft C++Test軟件靜態分析:操作指南(編碼規范、質量度量)、常見問題及處理 Parasoft C++Test軟件單元測試:操作指南、實例講解、常見問題及處理 Parasoft C++Test軟件集成測試:操作指南、實例講解、常見問題及處理 進階擴展:自動生成靜態分析文檔、自動…

二級索引詳解

二級索引詳解 二級索引(Secondary Index)是數據庫系統中除主鍵索引外的附加索引結構,用于加速基于非主鍵列的查詢操作。以下是關于二級索引的全面解析: 一、核心概念 特性主鍵索引 (Primary Index)二級索引 (Secondary Index)唯一性必須唯一可以唯一或非唯一數量每表只有…

Python_level1_字符串_11

目錄 一、基本概念 二、字符串基本操作&#xff1a;【索引、切片、遍歷】 1.字符串與列表&#xff08;相同&#xff09; 1&#xff09;索引&#xff08;從0開始&#xff09;(可以獲取某一個/某幾個連續的字符) 2&#xff09;切片 [xx:xx] 與 列表 語法規則一樣 [起…

Axure數據可視化科技感大屏設計資料——賦能多領域,展示無限價值

可視化大屏如何高效、直觀地展示數據&#xff0c;并將其轉化為有價值的決策依據&#xff0c;成為了許多企業和組織面臨的共同挑戰。Axure大屏可視化模板&#xff0c;作為一款強大的數據展示工具&#xff0c;正在以其出色的交互性和可定制性&#xff0c;賦能多個領域&#xff0c…

MySQL 性能調優:數據庫的極限運動訓練

就像運動員需要不斷訓練才能突破極限&#xff0c;數據庫也需要各種調優才能跑得更快…讓我們一起給 MySQL 安排一套專業的"健身計劃"&#xff01; 什么是 MySQL 性能調優&#xff1f;&#x1f914; MySQL 性能調優是指通過各種配置優化、結構調整和查詢改進&#x…

4.5/Q1,GBD數據庫最新文章解讀

文章題目&#xff1a;Emerging trends and cross-country health inequalities in congenital birth defects: insights from the GBD 2021 study DOI&#xff1a;10.1186/s12939-025-02412-7 中文標題&#xff1a;先天性出生缺陷的新趨勢和跨國健康不平等&#xff1a;GBD 202…

基于DeepSeek、ChatGPT支持下的地質災害風險評估、易發性分析、信息化建庫及災后重建

前言&#xff1a; 地質災害是指全球地殼自然地質演化過程中&#xff0c;由于地球內動力、外動力或者人為地質動力作用下導致的自然地質和人類的自然災害突發事件。在降水、地震等自然誘因的作用下&#xff0c;地質災害在全球范圍內頻繁發生。我國不僅常見滑坡災害&#xff0c;還…

Linux | 安裝超級終端串口軟件連接i.MX6ULL開發板(8)

01 它的安裝步驟也非常簡單,安裝語言選擇中文簡體,點擊確定,如下圖所示。 點擊下一步,如下圖所示。 02

藍橋杯15屆 寶石組合

問題描述 在一個神秘的森林里&#xff0c;住著一個小精靈名叫小藍。有一天&#xff0c;他偶然發現了一個隱藏在樹洞里的寶藏&#xff0c;里面裝滿了閃爍著美麗光芒的寶石。這些寶石都有著不同的顏色和形狀&#xff0c;但最引人注目的是它們各自獨特的 “閃亮度” 屬性。每顆寶…