☰
MateChat Angular 版 McAttachment 附件上传组件实战:从拖拽上传、内置校验到 XMLHttpRequest 底层实现
2026/10/14 16:23:59 网站建设 项目流程
  • 前端
  • UI组件
  • AI 应用

【免费下载链接】MateChat

前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载

本文基于 MateChat(@matechat/ng)官方组件演示文档,系统讲解McAttachment附件上传组件的完整用法:通过uploadOptions配置上传地址、accept/maxSize启用内置校验、draggable开启拖拽上传、beforeUpload钩子实现自定义上传前校验。读完本文,你可以直接在 Angular 20 项目中复刻官方三个 Demo(basic-demo、drag-demo、valid-demo),并结合源码理解文件校验流程、拖拽容器的实现原理与底层XMLHttpRequest上传机制。

组件定位与按需引入

McAttachment组件专注于提供文件上传功能。它最核心的用法是与McInput和McFileList组合,构建一个功能完备的对话输入框——上传入口、附件列表展示、输入框三者各司其职。

按需引入路径(组件库要求@angular/core^20.3.0,见 package.json):

import { AttachmentModule } from "@matechat/ng";

AttachmentModule是一个极简的 NgModule,仅导出AttachmentComponent(内部同时导入了DropAreaComponent),定义见 attachment.module.ts:

@NgModule({ declarations: [], imports: [CommonModule, DropAreaComponent, AttachmentComponent], exports: [AttachmentComponent], }) export class AttachmentModule {}

组件选择器为mc-attachment,模板结构见 attachment.component.html:一个可点击的外层容器(ng-content用于承载自定义按钮样式)、一个受accept/multiple/isDisabled控制的隐藏原生<input type="file">,以及仅在draggable为true时渲染的mc-drop-area拖拽区域。

基本用法:上传地址与内置校验

官方 Demo 的用法是:通过upload-options配置上传地址,并通过accept和maxSize属性启用组件内置的文件类型和大小校验。

对应 basic-demo 的模板:

<mc-input [value]="inputValue" placeholder="点击左侧图标上传附件..."> <ng-template #extra> <div class="input-foot-left"> <mc-attachment [draggable]="false" [uploadOptions]="uploadOptions" accept="image/*" [maxSize]="0.5" (success)="handleSuccess($event)" (error)="handleError($event)" > <i class="icon-appendix"></i> </mc-attachment> </div> </ng-template> </mc-input>

配套 TypeScript 代码:

export class AttachmentBasicDemoComponent { inputValue = ''; // 核心:配置上传参数 uploadOptions = { uri: 'https://run.mocky.io/v3/132b3ea3-23ea-436b-aed4-c43ef9d116f0', }; // 监听上传成功和失败事件 handleSuccess = ({ file, response }) => { console.log(`文件 ${file.name} 上传成功,响应:`, response); }; handleError = ({ file, error }) => { console.error(`文件 ${file.name} 上传失败,错误:`, error); }; }

要点解读:

  • [draggable]="false":基本用法示例关闭了拖拽能力,只保留"点击图标 → 系统文件选择器"这一条上传路径;
  • accept="image/*":限制只接受图片类型,该值同时透传给内部原生<input>的accept属性(系统文件选择器层面过滤),还会参与组件自身的类型校验(后文详解);
  • [maxSize]="0.5":单个文件最大 0.5MB;
  • uploadOptions.uri:Demo 使用了一个 mock 上传服务地址,实际项目中替换为自有文件服务接口即可。

内置校验的实际执行顺序

从源码看(attachment.component.ts 的uploadFiles方法),组件对一批文件执行的是三级串行校验流水线:

  1. 数量校验:fileList.length + files.length > maxCount时整体拒绝,并触发validChange事件,结果为{ type: 'exceedCount' };
  2. 类型校验:将accept按逗号拆分后逐项匹配——image/*这类后缀为/*的规则按 MIME 前缀匹配(file.type以image/开头),.pdf这类以.开头的规则按扩展名匹配(file.name结尾),其余规则按完整 MIME 精确匹配;不匹配则记录{ type: 'unsupportedFileType', file };
  3. 大小校验:file.size / 1024 / 1024 > maxSize时记录{ type: 'exceedSizeLimit', file }(注意maxSize单位为MB)。

每个未通过校验的文件都会以IValidResult结构汇总后,通过validChange事件一次性抛出;只有通过全部校验的文件才会创建FileItem(组件内部用Date.now()递增生成uid,并用URL.createObjectURL(file)生成本地预览地址url/thumbUrl,见 getFileItem 实现),进入上传流程并触发change事件。

另外,isDisabled 计算属性 显示:除显式传入disabled外,当已有文件数达到maxCount时上传按钮也会自动禁用,这是对数量限制的第二道防线。

拖拽上传:dropPlaceholder 与 getDropContainer

draggable属性默认为true,即不传任何属性时组件天然支持拖拽上传。官方拖拽示例(drag-demo 模板)在基本用法基础上多了dropPlaceholder与getDropContainer两个配置:

<div #input> <mc-input [value]="inputValue" placeholder="拖拽文件到页面,或点击左侧图标上传附件..."> <ng-template #extra> <div class="input-foot-left"> <mc-attachment [uploadOptions]="uploadOptions" accept="image/*" [maxSize]="0.5" [multiple]="true" dropPlaceholder="请放置到此处" [getDropContainer]="dropContainer" > <i class="icon-appendix"></i> </mc-attachment> </div> </ng-template> </mc-input> </div>
export class AttachmentDragDemoComponent implements AfterViewInit { @ViewChild('input') inputEl!: ElementRef<HTMLInputElement>; inputValue = ''; uploadOptions = { uri: 'https://run.mocky.io/v3/132b3ea3-23ea-436b-aed4-c43ef9d116f0', }; // 初始值兜底,视图就绪后替换为真实输入框 DOM dropContainer = () => document.body; ngAfterViewInit(): void { this.dropContainer = () => { return this.inputEl.nativeElement; }; } }

两个配置项的语义:

  • dropPlaceholder:拖拽悬停时放置区域的展示文案。模板层面,组件还暴露了同名dropPlaceholder模板插槽,可以完全自定义展示内容,缺省时回退到国际化文案(Attachment.dragToUpload,见 attachment.component.html);
  • getDropContainer:返回拖拽时"可以释放文件"的容器元素。注意示例的写法细节——在ngAfterViewInit之前@ViewChild尚未就绪,因此先用() => document.body兜底,初始化后再切换为输入框所在的 DOM 节点,避免访问空引用。

拖拽机制的源码解析

拖拽能力由独立的DropAreaComponent实现(drop-area.component.ts),几个值得注意的设计:

  1. 遮罩 Teleport 到 body:ngAfterViewInit中通过renderer.appendChild(document.body, this.dropAreaEl.nativeElement)将拖拽遮罩直接挂到document.body下,规避父容器overflow: hidden或层级问题;
  2. 计数器解决 dragenter/dragleave 抖动:拖拽进入子元素会成对触发dragleave/dragenter,组件用dragCounter计数,只有计数归零才隐藏遮罩(handleDragEnter /handleDragLeave);
  3. 容器动态定位:dragenter时若容器是document.body则遮罩铺满视口(inset: 0);否则读取容器getBoundingClientRect(),将遮罩精确贴合到容器尺寸与位置;
  4. 禁用联动:遮罩的isDisabled与组件的isDisabled(disabled或达到maxCount)同源,禁用状态下handleDrop直接忽略释放事件。

mc-attachment收到drop事件后的处理(onDrop):

onDrop(e: File[]) { this.uploadFiles(e); // 与点击选择走完全相同的校验+上传流程 this.drop.emit(e); // 同时向外广播 drop 事件 }

也就是说,拖拽释放的文件与点击选择的文件走同一条uploadFiles校验流水线,所有accept/maxSize/maxCount/beforeUpload规则对拖拽同样生效。

自定义上传前校验:beforeUpload 与 validChange

除了组件内置的accept和size校验,还可以通过beforeUpload钩子添加额外的自定义校验逻辑。官方示例(valid-demo):

handleBeforeUpload = (file: File) => { // 除了组件内置的 accept 和 size 校验,你还可以添加自定义的校验逻辑 if (file.name.includes('test')) { return false; } return true; }; onValidChange = (e) => { console.log('valid result', e); };
<mc-attachment [draggable]="false" [uploadOptions]="uploadOptions" [beforeUpload]="handleBeforeUpload" accept="image/*" [maxSize]="0.5" [maxCount]="3" [multiple]="true" (validChange)="onValidChange($event)" > <i class="icon-appendix"></i> </mc-attachment>

钩子签名(定义见 attachment-types.ts):

type BeforeUpload = (file: File) => boolean | Promise<boolean>;

从源码实现看(2.3 上传前钩子校验),钩子的执行细节是:

  • 支持同步返回boolean,也支持返回Promise<boolean>(内部用Promise.resolve包裹后await),因此可以发起异步检查(如"文件是否已存在于云端");
  • 返回false或 Promise 被 reject(抛错),均视为校验失败,记录{ type: 'beforeUploadRejected', file }并跳过该文件;
  • 校验结论汇总后通过validChange事件抛出,IValidResult结构为{ type: ValidResultType; file?: File }。

ValidResultType的全部取值覆盖了组件的四种拒绝原因(attachment-types.ts):

type ValidResultType = | 'exceedCount' // 超出文件数量限制 | 'unsupportedFileType' // 不支持的文件类型 | 'exceedSizeLimit' // 超出文件大小限制 | 'beforeUploadRejected'; // beforeUpload 钩子校验失败

监听validChange并配合这些枚举值,你就可以在应用层给用户呈现精确的拦截提示(例如"文件名包含 test 的文件不可上传")。

参数、事件与插槽 API 速查

参数

参数名类型默认值说明
uploadOptionsUploadOptions{}核心上传配置,包含上传地址uri、请求方法、请求头等,详见下文UploadOptions类型定义
disabledbooleanfalse是否禁用上传功能
acceptstring''接受上传的文件类型,与原生input标签的accept属性相同,例如'image/*, .pdf'
maxCountnumberInfinity允许上传的最大文件数量
maxSizenumberInfinity允许上传的单个文件最大尺寸,单位为MB
multiplebooleantrue是否支持多选文件
draggablebooleantrue是否支持拖拽上传
beforeUpload(file: File) => boolean \| Promise<boolean>null上传前钩子,返回false或被reject的Promise则停止上传
dropPlaceholderstring--拖拽上传时,放置位置的展示信息
getDropContainer() => HTMLElement--拖拽上传时,可以释放文件的区域

以上默认值均可在 AttachmentComponent 的 @Input 声明 中逐一印证。

事件

事件名说明回调参数
change文件状态改变时触发,主要在文件被选择后(file: File, fileList: FileItem[]) => void
success文件上传成功时触发(file: File, response: any, fileList: FileItem[]) => void
error文件上传失败时触发(file: File, error: any, fileList: FileItem[]) => void
progress文件上传时触发(file: File, fileList: FileItem[]) => void
drop文件在可放置区域释放时触发(files: File[]) => void
validChange有校验结论时触发(e: IValidResult[]) => void

插槽

插槽名说明
default自定义上传附件按钮样式
dropPlaceholder拖拽上传时,自定义放置位置的展示信息

底层上传机制:XMLHttpRequest 与 UploadOptions

uploadOptions属性接收一个遵循UploadOptions接口的对象,用于配置组件内置的XMLHttpRequest上传行为:

export interface UploadOptions { uri: string | URL; method?: "POST" | "PUT" | "PATCH"; headers?: { [key: string]: string }; authToken?: string; authTokenHeader?: string; additionalParameter?: { [key: string]: string | Blob }; fileFieldName?: string; withCredentials?: boolean; responseType?: "arraybuffer" | "blob" | "json" | "text"; }
属性类型是否必需说明
uristring \| URL是上传接口的服务器地址
method'POST' \| 'PUT' \| 'PATCH'否HTTP 请求方法,默认为'POST'
headersobject否自定义 HTTP 请求头
authTokenstring否用于身份验证的 Token
authTokenHeaderstring否承载authToken的请求头字段名,默认为'Authorization'
additionalParameterobject否随文件一同上传的额外参数,将被添加到FormData中
fileFieldNamestring否上传文件时,在FormData中使用的字段名,默认为'file'
withCredentialsboolean否是否在跨域请求中发送凭证(如 Cookies),默认为false
responseType'arraybuffer' \| 'blob' \| 'json' \| 'text'否手动设置期望的服务器响应类型

完整实现见 uploader.ts,执行流程为:

  1. 创建XMLHttpRequest,监听xhr.upload.onprogress计算百分比(仅在lengthComputable为真时回调,对应组件的progress事件);
  2. 组装FormData:formData.append(fileFieldName, file, file.name),文件字段名默认file,随后逐个追加additionalParameter中的键值对;
  3. xhr.open(options.method || 'POST', options.uri, true)打开请求,按需设置withCredentials、responseType、自定义headers,以及认证头——authToken存在时写入authTokenHeader(默认Authorization)头;
  4. xhr.send(formData)发起请求。

回调映射到组件事件的行为(performUpload):

  • 进度:更新fileList中对应uid条目的percentage,触发progress;
  • 成功(状态码 2xx):将条目status置为success、保存response,触发success;响应解析策略是——配置了responseType为text或xhr.response为空时取responseText,否则取xhr.response(即按responseType反序列化后的对象,如json时为 JS 对象);
  • 失败(非 2xx 或网络错误):status置为uploadError、保存error(含message/status/statusText/response),触发error。

FileItem是贯穿事件回调的数据结构(attachment-types.ts):

export interface FileItem<T = unknown, E = unknown> { uid: number; // 组件内部自增 id name: string; size: number; type?: string; status?: FileStatus; // 'uploading' | 'downloading' | 'success' | 'uploadError' | 'downloadError' percentage?: number; // 上传进度 0-100 id?: string | number; response?: T; // 服务器响应信息 error?: E; thumbUrl?: string; // 本地 objectURL,可用于缩略图/预览 url?: string; }

每个事件回调的fileList参数都是当前完整文件列表的副本([...this.fileList]),可以安全地保存或传给展示组件。

与 McInput、McFileList 组合构建对话输入框

文档明确指出:McAttachment最核心的用法是与McInput和McFileList组合。三者分工如下:

  • McAttachment(mc-attachment):上传入口 + 校验 + 上传执行,负责"文件从哪来、能不能传、传给谁";
  • McFileList(mc-file-list):接收FileItem[]数组渲染附件列表,提供移除(remove)、重新上传(retryUpload)、下载、预览等事件(file-list.component.ts);
  • McInput(mc-input):对话输入框,通过#extra等插槽承载附件入口,如前文三个 Demo 所示。

一个典型的接线方式:在页面组件中维护一个fileItems: FileItem[],把mc-attachment的change/success/error/progress事件中的fileList同步进去,再绑定给mc-file-list的fileItems输入——由于FileItem.uid贯穿上传全生命周期,列表侧据此定位条目即可。

小结

McAttachment用很少的配置面覆盖了 AI 对话场景下附件上传的完整链路:

能力配置项源码依据
上传地址/方法/认证/FormDatauploadOptionsuploader.ts
类型/大小/数量校验accept/maxSize/maxCountattachment.component.ts
异步自定义校验beforeUpload同上(2.3 节钩子)
拖拽上传与放置区域draggable/dropPlaceholder/getDropContainerdrop-area.component.ts
校验与上传全状态感知change/success/error/progress/drop/validChangeattachment-types.ts

官方三个 Demo 的可运行源码位于 AttachmentDemo 目录,可直接对照本文逐行理解;组件完整 API 参考可进一步查阅 api.md。

  • 前端
  • UI组件
  • AI 应用

【免费下载链接】MateChat

前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询