- 前端
- UI组件
- 移动开发
【免费下载链接】cube-ui
:large_orange_diamond: A fantastic mobile ui lib implement by Vue
本指南以 cube-ui 官方文档 document/components/docs/en-US/upload.md 为骨架,系统讲解cube-upload上传组件的文件对象模型、Props 配置、事件与实例方法,并逐一结合 src/components/upload/upload.vue、src/components/upload/ajax.js 与 src/components/upload/util.js 等源码说明底层实现。读完本文,你将掌握从基础上传、文件校验、图片压缩 Base64 上传到完全自定义 UI 的完整实战方案。
本文内容对应组件版本要求:
Upload组件自1.3.0起提供;文中标注<sup>1.11.0+</sup>的配置项(如action.target支持函数、checkSuccess的可选回调参数)自1.11.0起生效;file-click事件的index参数自1.12.39起提供。使用时请以实际安装版本为准。
1. 文件对象(file object)模型
文档中约定:用户选中的原始文件称为original file,经组件包装后的对象称为file object。所有事件回传、v-model双向绑定以及上传请求体中的数据,都以 file object 为载体。其完整结构如下:
| Attribute | Description | Type | | - | - | - | | v-model | 文件列表 | Array,默认[],如[{ name, size, url, status: 'success', progress: 1 }]| | name | 文件名 | String | | size | 文件大小 | Number | | url | 文件 URL,由URL.createObjectURL生成,用于预览 | String | | base64 | 文件的 base64 值,与原文件 base64 相等;默认'',可通过插件(如压缩插件)写入 | String | | status | 文件状态:ready、uploading、success、error| String | | progress | 上传进度,数值 0~1 | Number | | file | 原始文件 | File | | response | 响应数据(尝试解析为 JSON) | Object/Array/String | | responseHeaders | 全部响应头 | String |
1.1 源码中的对象构造
从源码看,file object 由 src/components/upload/util.js 的newFile函数构造:
export function newFile(name = '', size = 0, status = '', progress = 0, file = null) { const base64 = (file && file.base64) || '' const url = base64 ? '' : createURL(file) return { name, size, url, base64, status, progress, file } }createURL在浏览器环境下调用window.URL.createObjectURL(file)生成预览 URL(见 src/components/upload/util.js 对URL的兼容性处理:依次取window.URL || window.webkitURL || window.mozURL)。值得注意的细节是:当 file object 已带有base64时,url字段为空——预览将直接使用 base64 而非 ObjectURL,这正是压缩插件写入 base64 后能直接预览的原因。
状态常量同样定义在 src/components/upload/util.js:
export const STATUS_READY = 'ready' export const STATUS_UPLOADING = 'uploading' export const STATUS_ERROR = 'error' export const STATUS_SUCCESS = 'success'1.2 response 与 responseHeaders 的写入时机
response与responseHeaders并非构造时存在,而是在请求结束(onload/onerror/ontimeout)时由 src/components/upload/ajax.js 的setResponse写入:
function setResponse() { let response = xhr.responseText || xhr.response try { response = JSON.parse(response) } catch (e) {} file.response = response file.responseHeaders = xhr.getAllResponseHeaders() }可见组件会对响应文本尝试JSON.parse,解析失败则保留原始字符串,因此response的类型是 Object/Array/String 三选一,与文档描述一致。
2. 快速上手:基础用法
文档给出的最小可用示例:
<cube-upload action="//jsonplaceholder.typicode.com/photos/" :simultaneous-uploads="1" @files-added="filesAdded" />export default { methods: { filesAdded(files) { const maxSize = 1 * 1024 * 1024 // 1M for (let k in files) { const file = files[k] if (file.size > maxSize) { file.ignore = true } } } } }使用要点:
action:配置 multipart POST 请求的上传目标 URL;simultaneous-uploads:配置同时上传的最大文件数;files-added事件:用于文件校验,通过设置file.ignore = true过滤文件。
在官方示例 example/pages/upload/default.vue 中,同样的校验逻辑还会配合$createToast弹出「You selected >1M files」的警告提示,并额外展示了实例方法start()/pause()/retry()与按钮联动:
upload() { this.isUploading = true this.$refs.upload.start() }, pause() { this.isUploading = false this.$refs.upload.pause() }, retry() { this.$refs.upload.retry() }2.1 ignore 标记如何生效:addFiles 源码解析
file.ignore是在 src/components/upload/upload.vue 的addFiles方法中被消费的:
addFiles(files) { this.$emit(EVENT_ADDED, files) const filesLen = this.files.length const newFiles = [] const maxLen = this.max - filesLen let i = 0 let file = files[i] while (newFiles.length < maxLen && file) { if (!file.ignore) { newFiles.push(file) this.files.push(newFile()) } file = files[++i] } ... }从源码可以明确三个行为:
files-added事件在任何过滤之前最先触发,因此校验逻辑必须在这个事件回调里同步修改file.ignore;- 过滤受
max限制:只有newFiles.length < max的文件会被接纳,超出部分直接丢弃; - 组件会先向
files数组 push 一个newFile()占位,待processFile异步处理完成后,再通过$set用真实 file object 替换占位(见 src/components/upload/upload.vue),$set保证了数组更新的响应性。
3. 图片压缩并通过 Base64 上传
对于移动端常见的「拍照上传」场景,直接上传原图既费流量又慢,cube-ui 文档给出的方案是:先压缩原图,再以 Base64 字段提交。
3.1 示例代码
<cube-upload ref="upload" :action="action" :simultaneous-uploads="1" :process-file="processFile" @file-submitted="fileSubmitted"></cube-upload>import compress from '../../modules/image' export default { data() { return { action2: { target: '//jsonplaceholder.typicode.com/photos/', prop: 'base64Value' } } }, methods: { processFile(file, next) { compress(file, { compress: { width: 1600, height: 1600, quality: 0.5 } }, next) }, fileSubmitted(file) { file.base64Value = file.file.base64 } } }(注:文档示例中data里变量名为action2,实际模板绑定的是action,请以自己代码中的命名保持一致。)
关键点:
action为对象时包含target与prop,prop用于指定 file object 上的哪个属性作为上传字段;process-file是一个处理原始文件的函数,处理完成后必须调用next并传入处理后的文件;file-submitted事件在文件处理完成、被加入upload.files后触发,回调参数为 file object。
3.2 processFile 与 file-submitted 的调用链
在 src/components/upload/util.js 中,processFiles/processFile实现了多文件的串行处理与回调汇总:每个原始文件经processFile(file, next)处理后,用返回值构造 file object(newFile(file.name, file.size, STATUS_READY, 0, file)),再通过eachCb回传——回到 src/components/upload/upload.vue 即执行:
this.$set(this.files, filesLen + index, file) this.$emit(EVENT_SUBMITTED, file)所以file-submitted回调里拿到的file已经是一个包含url(或base64)、status: 'ready'、progress: 0的完整 file object,其file属性指向原始文件。示例中file.base64Value = file.file.base64正是把压缩后写入原始文件对象上的base64字段搬运到 file object 上,供prop: 'base64Value'作为上传字段使用。
3.3 压缩实现:example/modules/image.js 源码解读
仓库示例中的压缩插件位于 example/modules/image.js(改编自腾讯 WeUI.js 的 uploader/image),其核心compress(file, options, callback)流程如下:
FileReader.readAsDataURL读取文件为 base64;- 若
options.compress === false,不做压缩,直接把 base64 写入file.base64并回调,适用于「不压缩、直接 base64 上传」; - 启用压缩时:创建
Image加载 base64,通过detectVerticalSquash检测 iOS 拍照图片被压扁的 bug 并计算补偿比率,通过getOrientation读取 JPEG EXIF 方向信息,再用orientationHelper对 canvas 做旋转/翻转修正; - 按
compress.width/compress.height等比缩放(宽高比不变,只缩放到不超过上限),canvas.toDataURL('image/jpeg', quality)输出压缩后的 base64; - 上传方式分流:
options.type === 'file'时把 dataURL 转成 Blob 后回调;否则把 base64 写入file.base64回调。若压缩失败(;base64,null),文件方式回退到原文件,base64 方式直接调用options.onError报错。
可见该插件为移动端「拍完即传」场景补全了三项能力:方向修正、等比压缩、质量压缩。
4. 使用插槽自定义 UI
cube-upload 的默认渲染是「缩略图网格 + 加号按钮」(分别由cube-upload-file与cube-upload-btn提供)。文档演示了如何用插槽完全接管界面,实现「点击上传身份证」这类单文件业务:
<cube-upload ref="upload" v-model="files" :action="action" @files-added="addedHandler" @file-error="errHandler"> <div class="clear-fix"> <cube-upload-file v-for="(file, i) in files" :file="file" :key="i"></cube-upload-file> <cube-upload-btn :multiple="false"> <div> <i>+</i> <p>Please click to upload ID card</p> </div> </cube-upload-btn> </div> </cube-upload>export default { data() { return { action: '//jsonplaceholder.typicode.com/photos/', files: [] } }, methods: { addedHandler() { const file = this.files[0] file && this.$refs.upload.removeFile(file) }, errHandler(file) { // const msg = file.response.message this.$createToast({ type: 'warn', txt: 'Upload fail', time: 1000 }).show() } } }配套的自定义样式(stylus):
.cube-upload .cube-upload-file, .cube-upload-btn margin: 0 height: 200px .cube-upload-file margin: 0 + .cube-upload-btn margin-top: -200px opacity: 0 .cube-upload-file-def width: 100% height: 100% .cubeic-wrong display: none .cube-upload-btn display: flex align-items: center justify-content: center > div text-align: center i display: inline-flex align-items: center justify-content: center width: 50px height: 50px margin-bottom: 20px font-size: 32px line-height: 1 font-style: normal color: #fff background-color: #333 border-radius: 50%(完整示例见 example/pages/upload/custom.vue。)
4.1 插槽机制与子组件说明
从 src/components/upload/upload.vue 可以看到,cube-upload根模板是一个带默认插槽的容器,默认内容为「upload-file列表 +upload-btn」;一旦传入插槽内容,默认 UI 整体被替换。
插槽中可用的两个子组件(均由cube-upload内部注册,见 src/modules/upload/index.js):
cube-upload-file:接收fileprop 渲染单个文件。其实现 src/components/upload/file.vue 提供两个具名插槽参数img-style(背景图样式,优先取file.url,其次取file.base64)与progress(百分比文本,如42%;success/error状态直接显示100%)。文件右上角的删除角标内部触发removeFile(经$parent.removeFile调用组件方法)。点击文件会冒泡click事件,由cube-upload统一转发为file-click;cube-upload-btn:内部包含一个透明的<input type="file">(见 src/components/upload/btn.vue),其change事件把fileEle.files传给this.$parent.addFiles(files)并立即将 input 的value置空——这样重复选择同一文件也能触发change。multiple与accept属性由公共 mixin src/components/upload/btn-mixin.js 提供(multiple默认true,accept默认image/*)。
4.2 单向删除技巧
addedHandler中this.files[0]与this.$refs.upload.removeFile(file)的组合实现「只允许上传一张、新选择即替换旧文件」。removeFile内部(见 src/components/upload/upload.vue)会依次:发出file-removed事件 → 若存在file._xhr则abort()中断请求 →URL.revokeObjectURL(file.url)释放预览 URL(防止内存泄漏)→ 从files中splice删除 → 重新调用upload()推进后续任务。
5. Props 配置全表与源码印证
| Attribute | Description | Type | Accepted Values | Demo | | - | - | - | - | - | | v-model | 文件列表 | Array |[]|[{ name, size, url, status: 'success', progress: 1 }]| | action | 上传配置 | String/Object |''|{ target: '/upload' }| | max | 最大上传文件数 | Number |10| - | | auto | 是否自动开始上传 | Boolean |true| - | | simultaneousUploads | 同时上传数量 | Number |1| - | | multiple | 是否多选 | Boolean |true| - | | accept | input 的 accept | String |image/*| - | | processFile | 处理原始文件 | Function |function (file, next) { next(file) }| - |
以上默认值均可在 src/components/upload/upload.vue 的 props 定义与 src/components/upload/btn-mixin.js 中逐一核对。
- v-model 双向同步:
valueprop 在 watch 中同步到内部files;而内部files一旦变化即$emit('input', newFiles)(见 src/components/upload/upload.vue),从而形成完整双向绑定; - auto 与 paused:
data中paused: !this.auto,即auto=false时组件默认处于暂停状态,配合实例方法start()手动触发上传(这正是官方 default 示例里「先选文件、点 Upload 按钮再传」的实现基础); - isShowBtn:当
files.length >= max时自动隐藏选择按钮(v-show="isShowBtn",见 src/components/upload/upload.vue 与 L85-L87)。
5.1 action 子配置
当action是字符串时,组件会自动转换为{ target: action }(见 src/components/upload/upload.vue 的actionOptions计算属性;action为空字符串时返回null,此时upload()直接返回,不发起请求)。
| Attribute | Description | Type | Default | | - | - | - | - | | target | multipart POST 目标 URL;若为函数,则以 file object 为参数调用,返回值作为 URL | String/Function1.11.0+| - | | fileName | multipart POST 参数名 | String |'file'| | prop | 上传 file object 上的哪个属性 | String |'file'| | headers | 额外请求头;若为函数,则以 file object 为参数调用,返回值作为 headers | Object/Function1.11.0+|{}| | data | 额外表单数据;若为函数,则以 file object 为参数调用,返回值作为 data | Object/Function1.11.0+|{}| | withCredentials | 标准 CORS 请求默认不携带 cookie;设为true后随请求发送 cookie | Boolean |false| | timeout | 上传请求超时时间 | Number |0| | progressInterval | 进度上报时间间隔(单位:ms) | Number |100| | checkSuccess | 判断响应是否成功,参数为(response, file[, cb])。file与可选cb自 1.11.0 起可用:无cb时以函数返回值isSuccess作为结果;有cb时调用cb(isSuccess)。isSuccess为true时视为上传成功 | Function |function (res) { return true }|
底层实现对照(src/components/upload/ajax.js)
ajaxUpload(file, options, changeHandler)是实际发请求的函数,逐项印证上述配置:
target、headers、data均通过evalOpts求值——若配置为函数则用 file object 调用,否则原样返回(见 src/components/upload/util.js)。例如可按「每个文件带自己的签名」动态生成 headers;- 请求体为
FormData:先 appenddata中的各字段,再formData.append(fileName, file[prop])(见 ajax.js L55-L60)。prop默认为'file'即上传原始文件;改为'base64Value'即上传 base64 字符串; - 进度上报节流:
xhr.upload.onprogress中按progressInterval控制更新频率,file.progress = e.loaded / e.total(见 ajax.js L29-L53); - 超时:仅当
timeout > 0时设置xhr.timeout,ontimeout与onerror一样走失败分支(见 ajax.js L84-L87、L97-L99); withCredentials:为true时设置xhr.withCredentials = true(见 ajax.js L90-L92);checkSuccess的两种形态:源码按checkSuccess.length <= 2区分——参数个数 ≤2(无cb)时直接取返回值,否则调用cb(isSuccess)异步判定(见 ajax.js L70-L78)。这解释了文档中「无 cb 则函数返回值即结果,有 cb 则回调决定」的约定;- 状态机终点:
onload中先校验 HTTP 状态码(< 200 || >= 300直接失败),再setResponse()并依据checkSuccess结果将file.status置为success或error;setStatus会清空进度定时器、将file.progress置为1,并回调changeHandler通知组件触发file-success/file-error事件(见 ajax.js L62-L108,事件分发见 src/components/upload/upload.vue)。
5.2 processFile 子配置
processFile是一个(file, next)形式的函数:file是原始文件,处理完成后必须调用next并传入处理后的文件。若未配置,使用默认实现function (file, cb) { cb(file) }(见 src/components/upload/upload.vue),即原样放行。
6. 事件一览
| Event Name | Description | Parameters | | - | - | - | | files-added | 文件被加入时触发,通常用于文件校验 | 原始文件列表 | | file-submitted | 文件被加入upload.files时触发 | file object | | file-removed | 文件被移除时触发 | file object | | file-success | 文件上传成功时触发 | file object | | file-error | 文件上传失败时触发 | file object | | file-click | 文件被点击时触发;1.12.39 起追加index参数 | file object,index| | input | 绑定值(文件列表)变化时触发 | 更新后的文件列表 |
事件名称在源码中以常量集中定义(见 src/components/upload/upload.vue):files-added、file-submitted、file-removed、file-success、file-error、file-click与input。
几点源码层面的补充:
file-click由cube-upload-file的点击冒泡而来,index参数是v-for循环的下标(见 src/components/upload/upload.vue);file-removed在removeFile方法开头即触发(先于请求中断与 DOM 移除),可用于埋点或提示;- 单个文件结束(无论成败)都会回调
upload(retry)继续推进队列(见 src/components/upload/upload.vue),因此simultaneousUploads控制的是「在飞请求数」:uploading状态的文件会计入并发数,直到其结束才会补位下一个ready文件。
7. 实例方法
| Method name | Description | Parameter | | - | - | - | | start | 开始上传 | - | | pause | 暂停上传 | - | | retry | 重试上传 | - | | removeFile | 移除文件 | file object |
对应实现(src/components/upload/upload.vue):
start():paused = false后调用upload(),开始/继续上传;pause():置paused = true,并遍历files,对uploading状态的文件执行file._xhr.abort()且状态回退为ready;retry():生成新的retryId(Date.now()),解除暂停并以upload(true)重试;重试时仅对error状态且_retryId !== this.retryId的文件重新发起请求(见 upload.vue L146),保证同一轮重试中每个失败文件只重试一次;removeFile(file):如 4.2 节所述,负责中断请求、释放 ObjectURL、删除并推进队列。
8. 组件注册与按需引入
cube-upload及其子组件cube-upload-btn、cube-upload-file通过 src/modules/upload/index.js 统一注册:install中注册三个组件,同时把子组件挂到Upload.Btn/Upload.File上。使用方式(以全量引入为例):
import Vue from 'vue' import CubeUpload from 'cube-ui' Vue.use(CubeUpload)或在需要时按需引入该模块,与官方推荐的方式保持一致。组件的源码、样式与按需打包产物分别位于 src/components/upload/、src/components/upload/upload.vue 与 lib/upload/(含upload.min.js、upload.min.css与index.js)。
9. 进阶实践建议
结合文档与源码,汇总几条可直接落地的实践经验:
- 文件校验的最佳时机:
files-added是唯一的同步校验入口,务必在此回调中修改file.ignore(支持按file.size、file.type、文件数量等条件过滤),校验提示可使用$createToast; - 并发与顺序控制:
simultaneous-uploads从 1 到 N 可平滑调节「同时上传数」;配合auto=false+start()可实现「先选后传」;上传大图/多图时建议限制并发避免移动端卡顿; - Base64 上传的完整链路:
processFile中调用压缩插件并next(处理后的文件)→file-submitted中把file.file.base64搬运到 file object 的自定义字段 →action.prop指向该字段。三者缺一不可,任一环节遗漏都会导致服务端收到空字段或原始文件; - 内存管理:组件在
removeFile中自动URL.revokeObjectURL,日常使用无需手动清理;但若业务上长时间保留文件列表,可关注url字段的释放时机; - 自定义校验响应:服务端返回 JSON 时利用
checkSuccess依据业务字段(如res.code === 0)判定成败,并可从file.response提取错误信息展示在file-error回调中。
以上内容均可在文档 document/components/docs/en-US/upload.md 与仓库源码(src/components/upload/、src/modules/upload/index.js、示例 example/pages/upload/)中交叉验证,读者可依此深入研读或二次开发。
- 前端
- UI组件
- 移动开发
【免费下载链接】cube-ui
:large_orange_diamond: A fantastic mobile ui lib implement by Vue
相关推荐
cube-ui Upload 组件实战:文件对象模型、并发上传、图片压缩与自定义结构
cube ui Upload 组件实战:文件对象模型、并发上传、图片压缩与自定义结构 cube ui 是滴滴出行团队开源、基于 Vue.js 的移动端 UI 组
前端UI组件移动开发G-Helper终极指南:华硕游戏本性能优化与色彩恢复完整教程
G Helper终极指南:华硕游戏本性能优化与色彩恢复完整教程 你是否厌倦了Armoury Crate的臃肿和卡顿?是否遇到过ROG游戏本屏幕突然发白、色彩失真
桌面应用系统编程cube-ui Toast 组件完全指南:$createToast 非模态提示的配置、事件与源码剖析
cube ui Toast 组件完全指南:$createToast 非模态提示的配置、事件与源码剖析 cube ui 是滴滴开源的移动端 Vue 组件库,其 T
前端UI组件移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考