跳转到内容

文件上传 file-upload

数据录入组件。三层同源:无头内核给出解剖与状态机,Vue 组件与自定义元素只是它的两层外壳,行为完全一致。

示例

基础用法

投放区自己就是一个大按钮,隐藏输入是必备部件,缺了它选不了文件

把文件拖到这里或者用下面的按钮挑一个

限制与拒收

accept / maxFiles / maxFileSize 越界的当场被拒,file-reject 逐个报出理由

只收图片,最多 3 张单张不超过 512 KB

受控

传了 files 就由宿主说了算,组件自己不再落值,只发 files-change 报告意图

选进来的文件由外部数组保管
宿主持有 0 个文件

禁用

disabled 把投放区、触发器与隐藏输入一并关停,拖拽进来也不再收

当前不接受上传

预置列表

defaultFiles 给出挂载时就在的那几份,之后列表照旧由组件自己保管,删除与清空都照常

已经带了两份进来再拖几份也收,最多 4 份
合同正文.txt54 B
交付说明.md34 B

选整个目录

directory 让隐藏输入改收目录,选中目录下的文件一次性全进来,数量上限要跟着放开

挑一个目录,里面的文件全收

缩略图墙

item-preview 是个空方框,作者往里塞什么都行;塞进去的图会被裁成方格,一行摆几张由外层网格定

把图片拖进来,最多 6 张

宿主自定的准入

组件只管 accept 与大小数量这几条通用规则,别的规矩由宿主在受控列表里再筛一道:这里同名文件只留最先来的那份

同名文件只留最先来的那份

上传生命周期

给一个 upload 实现组件就是上传器:收下即开传(auto-upload 可关成手动),进度、成败与返回地址都在每条的传输快照里,失败一键重试

拖进来或点击选择,收下即开传

试试选一个文件名带「坏」字的文件,看失败与重试。

列表项上的下载

条目里放什么由作者定:一条普通的 a[download] 就是下载口;想自己接管就换成按钮,在处理器里怎么取都行

放几份文件进来,每条后面就带上下载口

服务器附件回显

remote-files 装编辑表单里已存在的附件:与本地文件同列渲染(allFiles 远程在前)、占 max-files 名额,删除走 remote-files-change 由宿主落库

拖进来或点击选择
合同扫描件.pdf373 KB查看
报价单.xlsx50 KB查看

剩余名额与新选文件共享;删除服务器附件只改 remote-files,落库由宿主决定。

产物

自定义元素<xh-file-upload>
Vue 组件XhFileUploadClearTrigger XhFileUploadDropzone XhFileUploadHiddenInput XhFileUploadItem XhFileUploadItemDeleteTrigger XhFileUploadItemGroup XhFileUploadItemName XhFileUploadItemPreview XhFileUploadItemSizeText XhFileUploadLabel XhFileUploadRoot XhFileUploadTrigger
组合式函数useFileUpload
状态机fileUploadMachine
皮肤@xihan-ui/styles/file-upload.css

解剖

部件名即 data-part 属性值,也是皮肤的选择器。加粗的是必备部件,不渲染它组件不工作(Web Components 适配器会在诊断通道上报 wc.missing-part)。

data-scope="file-upload"root · label · dropzone · trigger · hidden-input · item-group · item · item-name · item-size-text · item-preview · item-delete-trigger · clear-trigger

Props

属性类型必填说明
acceptstring | string[]允许的类型,写法与原生 input 的 accept 一致: 'image/*' 这类通配、'.png' 这类扩展名、'application/pdf' 这类精确 MIME 都收, 逗号分隔的整串或数组两种形态都行(属性只表达得了整串,数组要走 property)。
maxFilesnumber最多留几个文件,默认 1。给 Infinity 即不限。
maxFileSizenumber单个文件的字节上限,默认不限。
minFileSizenumber单个文件的字节下限,默认 0(挡住 0 字节的空文件可以设成 1)。
disabledboolean
invalidboolean校验失败标注;只作用于样式与 data-invalid,不阻断收文件。
namestring表单字段名;给了隐藏输入才参与提交。
filesFile[]已选文件。给定即受控:cell 直读 prop,写只发 onFilesChange 不落内部值。
defaultFilesFile[]
allowDropboolean是否接受拖拽投放,默认 true。关掉后投放区不再拦默认行为,也不再出 data-dragging。
directoryboolean选目录而不是选文件(隐藏输入带 webkitdirectory)。
capture'user' | 'environment'移动端直接调用摄像头/麦克风采集。
remoteFilesFileUploadRemoteFile[]服务器已有附件(编辑表单回显)。给定即受控:cell 直读 prop,删改只发 onRemoteFilesChange 不落内部值。条目计入 maxFiles 总量,与本地文件一起渲染。
defaultRemoteFilesFileUploadRemoteFile[]
upload(request: FileUploadRequest) => Promise<FileUploadResult | undefined | void> | FileUploadResult | undefined | void每个文件的传输实现。给了它组件才是上传器:收下的文件按 autoUpload 自动开传, 进度、成败与返回地址都记进该文件的传输快照。不给则维持纯选择器。
autoUploadboolean收下即自动开传,默认 true;关掉后由 api.startUpload 逐个开。
translationsPartial<FileUploadTranslations>
onFilesChange(details: FileUploadFilesChangeDetails) => void列表变化意图回调;受控时是唯一出口,非受控随内部写入一并通知。
onFileAccept(details: FileUploadFileAcceptDetails) => void本次收下了哪些。受控与否都发——宿主要据此发起上传。
onFileReject(details: FileUploadFileRejectDetails) => void本次拒了哪些、各自为什么。
onRemoteFilesChange(details: FileUploadRemoteFilesChangeDetails) => void远程附件列表变化意图回调;受控时是唯一出口,非受控随内部写入一并通知。
onUploadComplete(details: FileUploadCompleteDetails) => void单个文件传完(upload 的 Promise 兑现)。
onUploadError(details: FileUploadErrorDetails) => void单个文件传败(upload 的 Promise 拒绝);中止不算失败不发。

状态机

状态idle · dragging

事件FILES.SET · FILES.ADD · FILE.DELETE · FILES.CLEAR · PICKER.OPEN · DRAG.OVER · DRAG.LEAVE · DROP · UPLOAD.START · REMOTE.DELETE · FORM.RESET

判据canChange · canDrop

connect API

useFileUpload 产出的对象。getXxxProps() 铺到对应部件的宿主元素上,其余是可读状态与操作入口。

成员类型说明
acceptedFilesFile[]
remoteFilesFileUploadRemoteFile[]服务器已有附件。
allFilesFileUploadFile[]渲染顺序的完整列表:远程在前、本地在后。
draggingboolean有东西正悬在投放区上方。
disabledboolean
invalidboolean
emptyboolean一个文件都没有。清空按钮据此禁用,空列表据此显示占位。
maxFilesnumber生效的数量上限(已按缺省与非法值归一)。
getFileSizeText(file: FileUploadFile) => string字节数格式化成人读的形式,供作者渲染 item-size-text;远程附件没报大小时为空串。
uploadOf(file: FileUploadFile) => FileUploadSnapshot | null该条目的传输快照:远程附件恒为 done;本地文件没配 upload 时为 null, 配了而尚未开传为 idle。
startUpload(file: File) => void手动开传(autoUpload 关着时)或失败后重试;不在列表里与传输中的调了没效果。
setFiles(files: File[]) => void
addFiles(files: File[]) => void
deleteFile(file: FileUploadFile) => void本地文件按引用剔除(传输中会中止),远程附件按 id 剔除。
clearFiles() => void
openFilePicker() => void
getRootProps() => T['element']
getLabelProps() => T['label']
getDropzoneProps() => T['element']
getTriggerProps() => T['button']
getHiddenInputProps() => T['input']
getItemGroupProps() => T['element']
getItemProps(props: FileUploadItemProps) => T['element']
getItemNameProps(props: FileUploadItemProps) => T['element']
getItemSizeTextProps(props: FileUploadItemProps) => T['element']
getItemPreviewProps(props: FileUploadItemProps) => T['element']
getItemDeleteTriggerProps(props: FileUploadItemProps) => T['button']
getClearTriggerProps() => T['button']

键盘

规格出处:W3C APG

按键生效条件行为
Tab / Shift+Tabfocus outside / inside the component投放区、选择按钮、每条的删除按钮与清空按钮各占一个 Tab 位;禁用时投放区退出 Tab 序列,几个原生按钮带 disabled 本就不可聚焦
Enter / Spacefocus on dropzone打开系统文件选择框。投放区是 div,浏览器不会替它把这两个键合成成一次点击,连接层自己接管(并拦下空格滚屏)
Enter / Spacefocus on trigger打开系统文件选择框(原生 button 的默认激活)
Enter / Spacefocus on item-delete-trigger把这一条从列表里删掉(原生 button 的默认激活)
Enter / Spacefocus on clear-trigger,且列表非空清空整份列表;列表为空时该按钮带原生 disabled,键盘根本到不了它

Released under The MIT License