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.mzph.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;但最引人注目的是它们各自独特的 “闪亮度” 属性。每颗宝…