React 文件上传新玩法:Aliyun OSS 加持的智能上传组件
文件上传是前端开发中的“老朋友”,但如何让它既简单又强大,还能无缝对接云端存储?今天,我要带你认识一个超酷的 React 组件 AliUploader,它不仅支持拖拽上传、批量编辑和文件排序,还直接把文件传到 Aliyun OSS(阿里云对象存储服务),返回云端链接供你随时调用。我们会拆解它的代码,优化它的逻辑,再通过一个“项目文件上传器” Demo 展示它的实力。准备好了吗?让我们一起把文件上传玩出云端新高度吧!
组件的核心功能:上传到云端
AliUploader 的任务是让文件管理变得简单又高效:
- OSS 直传:文件上传直接到 Aliyun OSS,返回云端链接。
- 拖拽上传:支持单文件或批量拖拽,操作丝滑。
- 文件管理:按类型分组(图片、文档、其他),支持排序和批量编辑备注。
- 云端同步:初始化时加载 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;
}
优化亮点:
- 上传逻辑:规范化状态管理,修复初始状态问题。
- 错误处理:增加 try-catch,提升健壮性。
- 性能优化:避免重复添加文件,提升列表更新效率。
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;

使用效果
- 初始化:组件加载时从 OSS 获取已有文件列表。
- 上传:拖入 doc.pdf 和 image.png,文件上传至 OSS,显示进度。
- 分组:分为“图片”和“文档”,可按时间或名称排序。
- 批量编辑:选中文件,添加备注“会议资料”。
- 删除:移除文件,同时清理 OSS。
- 链接返回:控制台打印 OSS 链接,如 https://web-xiaoyao.oss-cn-hangzhou.aliyuncs.com/xxx.png。
组件解析:云端快递的魔法
- OSS 集成:
- uploadToOSS 上传文件,返回 OSS 链接。
- getOSSList 初始化云端文件。
- delfileFromOSS 删除云端文件。
- 上传体验:
- 多线程上传,进度实时更新。
- 拖拽支持,操作直观。
- 文件管理:
- 分组、排序、批量编辑,井然有序。
- 预览和删除,功能齐全。
使用场景与扩展
场景:
- 项目协作:上传文件到 OSS,共享链接。
- 内容管理:批量上传图片或文档。
- 云备份:自动同步到云端。
总结:你的云端“快递员”
AliUploader 就像一个“云端快递员”,把文件快速送上 Aliyun OSS,返回链接随时取用。通过优化,我们让它更健壮、更高效,用“项目文件上传器”展示了它的实力。试着拖几个文件进去跑跑看,或者丢进你的项目玩一玩吧!有其他需求或创意?欢迎留言一起聊聊!
关键词:React 文件上传组件、AliUploader OSS、云存储链接、前端文件管理。
相关文章:
React 文件上传新玩法:Aliyun OSS 加持的智能上传组件
文件上传是前端开发中的“老朋友”,但如何让它既简单又强大,还能无缝对接云端存储?今天,我要带你认识一个超酷的 React 组件 AliUploader,它不仅支持拖拽上传、批量编辑和文件排序,还直接把文件传到 Aliyun…...
群体智能优化算法-变色龙优化算法(Chameleon Swarm Algorithm, CSA,含Matlab源代码)
摘要 变色龙优化算法(Chameleon Swarm Algorithm, CSA)是一种受变色龙行为启发的群体智能优化算法。该算法模拟了变色龙在自然界中通过变换颜色来适应环境的能力,以此为基础,设计了一个适应性强、搜索能力广泛的优化算法。CSA 通…...
使用 React 和 Konva 实现一个在线画板组件
文章目录 一、前言二、Konva.js 介绍三、创建 React 画板项目3.1 安装依赖3.2 创建 CanvasBoard 组件 四、增加画布控制功能4.1 清空画布4.2 撤销 & 重做功能 五、增加颜色和画笔大小选择5.1 选择颜色5.2 选择画笔大小 六、最终效果七、总结 一、前言 在线画板是许多应用&…...
GitHub高级筛选小白使用手册
GitHub高级筛选小白使用手册 GitHub 提供了强大的搜索功能,允许用户通过高级筛选器来精确查找仓库、Issues、Pull Requests、代码等。下面是一些常用的高级筛选用法,帮助你更高效地使用 GitHub 搜索功能。 目录 搜索仓库搜索Issues搜索Pull Requests搜…...
通过第k个最大元素深入浅出快排和堆排序
快排和堆排序在确定k个元素有着得天独厚的优势,原因是无论快排还是堆排序在每一轮排序中均可以确定一个元素 快排:每一轮排序均可以确定一个元素位置堆排序:每一轮排序都可以确定一个最小值或最大值 他们的时间复杂度都是O(nlogk)ÿ…...
NVR接入录像回放平台EasyCVR视频系统守护舌尖上的安全,打造“明厨亮灶”云监管平台
一、方案背景 近年来,餐饮行业食品安全和卫生等问题频发,比如后厨卫生脏乱差等,持续引发关注,这些事情导致连锁反应,使其收益遭受损失。同时,给消费者造成了心理和生理上的伤害。 加强餐饮行业的监管成为…...
Airflow+Spark/Flink vs. Kettle
在迁移亿级(单表超过1.3亿)结构化数据(达梦→星环)的场景下,Airflow(结合分布式计算框架)的综合效果优于Kettle,以下是详细对比与方案建议: 一、核心对比:Air…...
Cribl 导入文件来检查pipeline 的设定规则(eval 等)
Cribl 导入文件来检查pipeline 的设定规则(eval 等) 从这个页面先下载,或者copy 内容来创建pipeline: Reducing Windows XML Events | Cribl Docs...
[C++面试] new、delete相关面试点
一、入门 1、说说new与malloc的基本用途 int* p1 (int*)malloc(sizeof(int)); // C风格 int* p2 new int(10); // C风格,初始化为10 new 是 C 中的运算符,用于在堆上动态分配内存并调用对象的构造函数,会自动计算所需内存…...
一周学会Pandas2 Python数据处理与分析-Jupyter Notebook安装
锋哥原创的Pandas2 Python数据处理与分析 视频教程: 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili Jupyter (Project Jupyter | Home)项目是一个非营利性开源项目,于2014年由IPython项目中诞生…...
第30周Java分布式入门 消息队列 RabbitMQ
RabbitMQ章节介绍 一、RabbitMQ概述 RabbitMQ学习内容: 本章节将学习RabbitMQ的概念、安装启动、管理后台、代码实操、交换机工作模式以及Spring Boot整合RabbitMQ。消息队列定义: 消息队列是一种用于在分布式系统中传递消息的机制。消息队列特性: 消息队列具有异步、解耦、削…...
北斗导航 | THE GNSS AMBIGUITY RATIO-TEST REVISITED: A BETTER WAY OF USING IT【论文要点】
THE GNSS AMBIGUITY RATIO-TEST REVISITED: A BETTER WAY OF USING IT 总结该论文的核心贡献及关键方法如下:论文核心内容概述 传统比率测试的局限性 传统比率测试通过比较最优与次优模糊度解的残差平方和比值(即 R = q (...
MySQL 面试知识点详解(索引、存储引擎、事务与隔离级别、MVCC、锁机制、优化)
一、索引基础概念 1 索引是什么? 定义:索引是帮助MySQL高效获取数据的有序数据结构,类似书籍的目录。核心作用:减少磁盘I/O次数,提升查询速度(以空间换时间)。 2 索引的优缺点 优点缺点加速…...
Linux / Windows 下 Mamba / Vim / Vmamba 安装教程及安装包索引
目录 背景0. 前期环境查询/需求分析1. Linux 平台1.1 Mamba1.2 Vim1.3 Vmamba 2. Windows 平台2.1 Mamba2.1.1 Mamba 12.1.2 Mamba 2- 治标不治本- 终极版- 高算力版 2.2 Vim- 治标不治本- 终极版- 高算力版 2.3 Vmamba- 治标不治本- 终极版- 高算力版 3. Linux / Windows 双平…...
deepseek v3-0324 Markdown 编辑器 HTML
Markdown 编辑器 HTML 以下是一个美观的 Markdown 编辑器 HTML 页面,支持多种主题切换和实时预览功能: <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&q…...
视频设备轨迹回放平台EasyCVR如何搭建公共娱乐场所远程视频监控系统
一、背景介绍 由于KTV、酒吧、足疗店等服务场所人员流动频繁、环境复杂,一直是治安管理的重点区域。为有效打击 “黄赌毒”、打架斗殴、寻衅滋事等违法犯罪的活动,打造安全有序的娱乐消费环境,我国相关部门将加大对这类场所的清查与管控力度…...
网络安全基础知识总结
什么是网络安全 采取必要措施,来防范对网络的攻击,侵入,干扰,破坏和非法使用,以及防范一些意外事故,使得网络处于稳定可靠运行的状态,保障网络数据的完整性、保密性、可用性的能力(CIA)。 举例…...
Python设计模式:克隆模式
1. 什么是克隆模式 克隆模式的核心思想是通过复制一个已有的对象(原型)来创建一个新的对象(克隆)。这种方式可以避免重复的初始化过程,从而提高效率。克隆模式通常涉及以下几个方面: 原型对象:…...
【工具】在 Visual Studio 中使用 Dotfuscator 对“C# 类库(DLL)或应用程序(EXE)”进行混淆
在 Visual Studio 中使用 Dotfuscator 进行混淆 Dotfuscator 是 Visual Studio 自带的混淆工具(Dotfuscator Community Edition,简称 CE)。它可以混淆 C# 类库(DLL)或应用程序(EXE),…...
积分赛——获取环境温度
设计要求 从DS18B20温度传感器上获取环境温度,并将其温度值显示到数码管上(保留两位小数)。 当“S4”定义为发送按键,按键S4按下时,串口向PC端发送当前采集的温度值; 串口发送格式: Temp:26.…...
LogicFlow获取锚点数据的自定义key并添加的连接的Edge边数据中
1、重写 PolylineEdgeModel 类(其它 EdgeModel 都可以) class CustomNetWorkNodeEdge extends PolylineEdge { } class CustomNetWorkNodeEdgeModel extends PolylineEdgeModel {getData() {const data super.getData();//获取开始锚点自定义属性添加到…...
【python中级】解压whl文件内容
【python中级】解压whl文件内容 1.背景2.解压1.背景 【python中级】关于whl文件的说明 https://blog.csdn.net/jn10010537/article/details/146979236 补充以上博客: 在 旧版 setuptools 中(< v58),如果想生成 .whl,必须先pip install 安装 wheel 三方包! pip inst…...
Xilinx系列FPGA实现HDMI2.1视频收发,支持8K@60Hz分辨率,提供2套工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我已有的4K/8K视频处理解决方案我已有的FPGA图像处理方案 3、详细设计方案设计框图硬件设计架构本HDMI2.1性能参数8K视频输入源Video PHY ControllerHDMI 2.1 Receive…...
如何把网页文章转为pdf保存
fnF12调出右边网页端的控制台 在下面输入代码 1、转CSDN上的文章 (function(){ use strict;var articleBox $("div.article_content");articleBox.removeAttr("style");var head_str ""; var foot_str ""; var olde…...
开源可视化大屏go-view前后端安装
一、后端安装 下载代码 git clone https://gitee.com/MTrun/go-view-serve修改配置 cd go-view-serve/ # 修改application-dev.yml的数据库文件地址 vi ./src/main/resources/application-dev.ymlapplication-dev.yml spring:datasource:driver-class-name: org.sqlite.JDB…...
eventEmitter实现
没有做任何异常处理,简单模拟实现 事件对象的每一个事件都对应一个数组 /*__events {"事件1":[cb1,cb2],"事件2":[cb3,cb4],"事件3":[...],"事件4":[...],};*/class E{__events {};constructor(){}//注册监听回调on(type , callbac…...
自然语言处理|如何用少样本技术提升低资源语言处理?
一、引言 在全球化的背景下,自然语言处理(NLP)技术取得了显著进展,为人们的生活和工作提供了便利。然而,大多数 NLP 研究和应用集中在少数高资源语言上,如英语和中文。据统计,全球存在超过 700…...
系统安全——文件监控-FileMonitor
namespace FileSystemWatcherDemo {public partial class Form1 : Form{ public Form1(){InitializeComponent();UsingFileSystemWatcher();} /// <summary>/// 使用FileSystemWatcher方法/// </summary>void UsingFileSystemWatcher(){//6.2//FileSystemWa…...
07-01-自考数据结构(20331)- 排序-内部排序知识点
内部排序算法是数据结构核心内容,主要包括插入类(直接插入、希尔)、交换类(冒泡、快速)、选择类(简单选择、堆)、归并和基数五大类排序方法。 知识拓扑 知识点介绍 直接插入排序 定义:将每个待排序元素插入到已排序序列的适当位置 算法步骤: 从第二个元素开始遍历…...
Unity:平滑输入(Input.GetAxis)
目录 1.为什么需要Input.GetAxis? 2. Input.GetAxis的基本功能 3. Input.GetAxis的工作原理 4. 常用参数和设置 5. 代码示例:用GetAxis控制角色移动 6. 与Input.GetAxisRaw的区别 7.如何优化GetAxis? 1.为什么需要Input.GetAxis&…...
