- 前端
- UI组件
- 小程序
- 设计系统
- 移动开发
【免费下载链接】tdesign-miniprogram
Wechat MiniProgram and Uniapp UI components lib for TDesign
本文以 Upload 组件文档 为蓝本,系统讲解 tdesign-miniprogram 仓库中 uniapp 版本t-upload组件的能力边界与用法:通过media-type/source控制选文件来源,通过files+request-method完成受控上传,通过draggable+drop事件实现长按拖拽排序。读完后可结合 upload.vue 源码理解数量上限、文件大小校验、预览与上传流程的底层实现。
引入组件
组件文档推荐使用 easycom 模式引入,配置后无需手动 import 即可在模板中直接使用<t-upload />。手动引入方式为(见 中文文档):
import TUpload from '@tdesign/uniapp/upload/upload.vue';组件源码位于 packages/uniapp-components/upload/upload.vue,配套的属性声明在 props.ts,全部 TS 类型定义(UploadFile、UploadMpConfig、RequestMethodResponse等)集中在 type.ts。仓库中提供了 6 个可运行的示例页面,位于 upload/_example 目录:
| 示例 | 文件 | 演示内容 | | -- | -- | -- | | single | single/index.vue | 单选上传,含uni.uploadFile真实上传流程 | | multiple | multiple/index.vue | 多选上传 + 网格配置 | | drag | drag/index.vue | 长按拖拽排序,@drop回写顺序 | | status | status/index.vue |loading/reload/failed等状态展示 | | messageFile | messageFile/index.vue | 从聊天记录选择文件 | | theme | theme/index.vue | grid / list 两种布局风格 |
Upload Props 完整参数
以下为组件全部属性(与文档 API 表一致,默认值来自 props.ts 的声明):
| 名称 | 类型 | 默认值 | 描述 | 必传 | | -- | -- | -- | -- | -- | | custom-style | Object | - | 自定义样式 | N | | add-btn | Boolean | true | 添加按钮 | N | | add-content | String | - | 添加按钮内容,值为slot时使用插槽渲染 | N | | allow-upload-duplicate-file | Boolean | false | 是否允许重复上传相同文件名的文件 | N | | config | Object | - | 图片/视频上传配置,详见下文类型定义 | N | | disabled | Boolean | undefined | 是否禁用组件 | N | | draggable | Boolean / Object | - |boolean \| {vibrate?: boolean; collisionVibrate?: boolean},是否支持拖拽排序,长按/碰撞时是否振动 | N | | files | Array | - |Array<UploadFile>,已上传文件列表,支持v-model:files语法糖 | N | | default-files | Array | - |Array<UploadFile>,已上传文件列表,非受控属性 | N | | grid-config | Object | - |{column?: number; width?: number; height?: number},每行列数与图片宽高 | N | | gutter | Number | 16 | 预览窗格间距,单位 rpx | N | | image-props | Object | - |ImageProps,透传 Image 组件全部属性 | N | | max | Number | 0 | 文件数量上限,值为 0 则不限制 | N | | media-type | Array | ['image', 'video'] |Array<MediaType>,type MediaType = 'image' \| 'video' \| 'mix'| N | | preview | Boolean | true | 是否支持图片预览,文件没有预览 | N | | remove-btn | Boolean | true | 移除按钮 | N | | request-method | Function | - | 自定义上传方法,签名见下文 | N | | size-limit | Number / Object | - | 文件大小限制,默认单位 KB,可选单位'B' \| 'KB' \| 'MB' \| 'GB'| N | | source | String | media | 来源,可选项:media / messageFile | N | | theme | String | grid | 组件风格,可选项:grid / list | N | | transition | Object |{ backTransition: true, duration: 300, timingFunction: 'ease' }| 拖拽位置移动时的过渡参数,duration单位为 ms | N |
相关核心类型(摘自 type.ts):
export type UploadMpConfig = UploadImageConfig | UploadVideoConfig; export interface UploadImageConfig { count?: number; sizeType?: Array<'original' | 'compressed'>; sourceType?: Array<'album' | 'camera'>; } export interface UploadVideoConfig { sourceType?: Array<'album' | 'camera'>; compressed?: boolean; maxDuration?: number; camera?: 'back' | 'front'; } export interface UploadFile { url: string; name?: string; size?: number; type?: 'image' | 'video'; percent?: number; status: 'loading' | 'reload' | 'failed' | 'done'; } export interface RequestMethodResponse { status: 'success' | 'fail'; error?: string; response: { url?: string; files?: UploadFile[]; [key: string]: any }; } export interface SizeLimitObj { size: number; unit: 'B' | 'KB' | 'MB' | 'GB'; message?: string; }config 参数如何生效
config会原样展开到uni.chooseMedia(或uni.chooseMessageFile)的调用参数中。从 upload.vue 的chooseMedia方法可以看到:count会被取Math.min(20, customLimit)(组件内部剩余可上传数量),mediaType由media-type属性决定,其余sizeType、sourceType、maxDuration、compressed等字段通过...config透传。此外源码中还做了几处平台适配:H5 端改用原生input[type=file]选择文件并自动补全宽高、时长与视频首帧封面;支付宝端与微信 PC/企业微信端因不支持chooseMedia,自动降级为chooseImage。
size-limit 的校验逻辑
大小校验由checkFileSize调用公共工具 common/utils.js 中的isOverSize完成:数字形式默认按 KB 计算(sizeLimit * 1000),对象形式则按unit换算(B/KB/MB/GB 分别乘以 1000 的幂次)。超限时会通过uni.showToast提示,对象形式可在message中使用{sizeLimit}占位符替换为实际限制值,例如:
:size-limit="{ size: 2, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB' }"文件来源与选择流程
组件点击添加按钮后的入口是onAddTap:
source="media"(默认):调用chooseMedia(mediaType),走uni.chooseMedia;source="messageFile":调用chooseMessageFile(mediaType),走uni.chooseMessageFile,从微信聊天记录选文件,需要基础库 2.5.0+。
选择成功后组件依次触发select-change→add→startUpload(见afterSelect方法):若传入了request-method,则发起自定义上传并在 Promise resolve 后将文件percent置为 100 并触发success;reject 时触发fail;未传上传函数时直接触发success。选择失败触发fail,选择流程结束(无论成败)触发complete。
add/drop/success事件携带的MediaContext类型:
export type MediaContext = VideoContext[] | ImageContext[]; export interface VideoContext { name?: string; type?: string; url?: string; duration?: number; size?: number; width?: number; height?: number; thumb: string; progress: number; } export interface ImageContext { name: string; type: string; url: string; size: number; width: number; height: number; progress: number; }其中url为选定视频的临时文件路径(本地路径)。源码中还有一个细节:getFileType会优先使用返回的fileType;由于部分平台(如 iOS 小程序)不返回文件类型,组件会在media-type为单一非 mix 值时直接取该值,否则按文件后缀(avi、mp4、mov等视频扩展名)判断是video还是image。
Upload Events 完整事件
| 名称 | 参数 | 描述 | | -- | -- | -- | | add |(context: { files: MediaContext })| 选择后触发,仅包含本次选择的文件 | | click |(context: { index: number; file: VideoContext \| ImageContext })| 点击已选文件时触发,常用于重新上传 | | complete | - | 上传成功或失败后触发 | | drop |(context: { files: MediaContext })| 拖拽结束后触发,包含所有文件(拖拽后的顺序) | | fail | - | 上传失败后触发 | | remove |(context: { index: number; file: UploadFile })| 移除文件时触发 | | select-change |(context: { files: MediaContext[]; currentSelectedFiles: MediaContext[] })| 选择后、上传前触发。files为之前已上传完成的文件列表,currentSelectedFiles为本次选中的文件列表 | | success |(context: { files: MediaContext })| 上传成功后触发,包含所有上传的文件 |
插槽(Slots):
| 名称 | 描述 | | -- | -- | | add-content | 自定义添加按钮显示内容(add-content属性置为slot时启用) |
实战一:单选上传(含真实上传与进度)
以下代码取自 single/index.vue,展示了文档中“两种上传方式”的完整写法:方式一为选择完后统一上传(选择完即展示);方式二为每次选择都立即上传并展示每次上传的进度。
<template> <view class="wrapper"> <t-upload :disabled="false" :media-type="['video', 'image']" :max="1" :files="fileList" @add="handleAdd" @remove="handleRemove" /> </view> </template> <script> import TUpload from '@tdesign/uniapp/upload/upload.vue'; export default { components: { TUpload }, data() { return { fileList: [] }; }, methods: { handleAdd(e) { const { fileList } = this; const { files } = e; // 方法1:选择完所有图片之后统一上传,选择完直接展示 this.fileList = [...fileList, ...files]; // 方法2:每次选择图片都上传,展示每次上传图片的进度 // files.forEach((file) => this.uploadFile(file)); }, onUpload(file) { const { fileList } = this; this.fileList = [...fileList, { ...file, status: 'loading' }]; const { length } = fileList; const task = uni.uploadFile({ url: 'https://example.weixin.qq.com/upload', // 仅为示例,非真实接口地址 filePath: file.url, name: 'file', formData: { user: 'test' }, success: () => { this[`fileList[${length}].status`] = 'done'; }, }); task.onProgressUpdate((res) => { this[`fileList[${length}].percent`] = res.progress; }); }, handleRemove(e) { const { index } = e; const { fileList } = this; fileList.splice(index, 1); this.fileList = fileList; }, }, }; </script>注意max="1"的受控逻辑:handleLimit方法会把max === 0视为不限制(Number.MAX_SAFE_INTEGER),超出max的文件会被截断;customLimit = max - files.length,当customLimit <= 0时添加按钮整体不渲染(模板中v-if="addBtn && customLimit > 0"),即达到上限后“添加”入口自动消失。
实战二:多选上传与网格配置
multiple/index.vue 展示了多选场景下的核心配置:grid-config控制每行 4 列、单元格 160rpx × 160rpx,remove-btn="false"全局关闭删除按钮(单个文件仍可传file.removeBtn覆盖,模板判断逻辑为tools.isBoolean(file.removeBtn) ? file.removeBtn : removeBtn),并通过@success回写文件列表:
<t-upload :media-type="['video', 'image']" :files="originFiles" :grid-config="gridConfig" :remove-btn="false" @success="handleSuccess" @remove="handleRemove" @click="handleClick" />data() { return { originFiles: [ { url: 'https://tdesign.gtimg.com/mobile/demos/example4.png', name: 'uploaded1.png', type: 'image' }, { url: 'https://tdesign.gtimg.com/mobile/demos/example6.png', name: 'uploaded2.png', type: 'image' }, { url: 'https://tdesign.gtimg.com/mobile/demos/example5.png', name: 'uploaded3.png', type: 'image' }, ], gridConfig: { column: 4, width: 160, height: 160 }, config: { count: 1 }, }; }, methods: { handleSuccess(e) { this.originFiles = e.files; }, handleRemove(e) { const { index } = e; this.originFiles.splice(index, 1); this.originFiles = [...this.originFiles]; }, handleClick(e) { console.log(e.file); }, }从源码的updateGrid方法可以确认grid-config的默认值:column默认 4,width/height默认均为 160rpx。
实战三:加载状态
文档说明组件支持loading、reload、failed三种状态,且loading状态传入percent时会额外展示进度百分比。status/index.vue 给出了典型数据:
originFiles: [ { url: '...example4.png', name: 'uploaded1.png', type: 'image', status: 'loading' }, { url: '...example5.png', name: 'uploaded2.png', type: 'image', percent: 68, status: 'loading' }, { url: '...example6.png', name: 'uploaded3.png', type: 'image', status: 'reload' }, { url: '...example5.png', name: 'uploaded4.png', type: 'image', status: 'failed' }, ]模板中的渲染规则:loading显示旋转的 loading 图标,有percent时追加百分比文案;failed/reload显示错误图标,reload的图标为refresh(可点击重试,click事件常用于重新上传)。list 布局下,副文本区在 loading 时展示全局配置文案uploadingText+ 百分比,失败时展示failText。
实战四:长按拖拽排序
draggable开启后(或传对象控制振动反馈,如{ vibrate: true, collisionVibrate: true }),组件会把普通宫格布局切换为“拖拽布局”:
initDragLayout/initDragList将每个文件项记录realKey(真实顺序)与sortKey(当前显示顺序),并通过createSelectorQuery测量单元宽高、容器位置与滚动偏移(initDragBaseData),行高变化时同步计算容器高度dragWrapStyle;- 拖拽排序核心算法在 drag.computed.js 的
sortCore中:按起止sortKey做正序/倒序换位,并通过tranX/tranY百分比 transform 实现跨行跨列动画,fixed的“添加按钮”项始终固定在末位不参与排序; - 长按触发
uni.vibrateShort(dragVibrate方法),是否振动受draggable.vibrate/draggable.collisionVibrate控制(长按振动默认开启,碰撞振动默认关闭); - 松手后若产生了顺序变化,
triggerDropEvent会按transition.backTransition与transition.duration(默认 300ms)延迟派发drop事件,drop携带拖拽后的完整文件数组,业务侧直接回写files即可(见 drag/index.vue 的handleDrop)。
<t-upload draggable :media-type="['video', 'image']" :files="originFiles" :grid-config="gridConfig" @success="handleSuccess" @remove="handleRemove" @drop="handleDrop" />theme/index.vue 还演示了theme="list"列表布局下的拖拽:list 布局固定单列,拖拽项宽度 100%,并额外补偿列表项间 24rpx 的行间距(getDragItemGap),非图片文件在 list 下以“文件图标 + 文件名 + 大小/进度文案”形式展示,适合文件类上传场景。
实战五:从聊天记录选择文件
使用wx.chooseMessageFile实现,需要基础库 2.5.0+,即把source置为messageFile(见 messageFile/index.vue):
<t-upload :media-type="['video', 'image']" :files="originFiles" :grid-config="gridConfig" :config="config" source="messageFile" @success="handleSuccess" @remove="handleRemove" />源码中chooseMessageFile的count上限为Math.min(100, customLimit),且type在media-type为数组时统一传'all'。
预览行为说明
preview默认开启。从onPreview方法可看到预览策略:先触发click事件(便于业务拦截处理);若列表中任意文件为视频类型,则调用uni.previewMedia进行媒体混合预览(sources中每个文件携带url、type与poster,封面优先取thumb),否则走uni.previewImage纯图片预览,预览失败会 toast 提示。
布局主题与 CSS 变量
theme提供grid(宫格,默认)与list(列表)两种布局,宫格布局基于内部TGrid/TGridItem组件实现。组件提供以下 CSS 变量用于自定义样式(默认值定义见 upload.less):
| 名称 | 默认值 | 描述 | | -- | -- | -- | | --td-upload-add-bg-color | @bg-color-secondarycontainer | 添加按钮背景色 | | --td-upload-add-color | @text-color-placeholder | 添加按钮文字颜色 | | --td-upload-add-disabled-bg-color | @bg-color-component-disabled | 禁用态添加按钮背景色 | | --td-upload-add-icon-disabled-color | @text-color-disabled | 禁用态添加图标颜色 | | --td-upload-add-icon-size | 56rpx | 添加图标尺寸 | | --td-upload-disabled-mask | rgba(0, 0, 0, 0.6) | 禁用遮罩颜色 | | --td-upload-drag-transition-duration | 由transition.duration注入 | 拖拽过渡时长 | | --td-upload-drag-transition-timing-function | 由transition.timingFunction注入 | 拖拽过渡缓动函数 | | --td-upload-drag-z-index | 999 | 拖拽层 z-index | | --td-upload-radius | @radius-default | 组件圆角 |
其中拖拽相关的两个变量在getDragItemStyle中按每个拖拽项内联注入--td-upload-drag-transition-duration: ${transition.duration}ms与 timing-function,因此修改transition属性即可全局生效。
FAQ:外层 flex 布局导致样式混乱
文档 FAQ 指出:Upload基于TGrid宫格实现,当外层容器使用display: flex时,子元素默认flex-grow: 0,会造成Upload组件整体宽度不足。解决办法是给Upload组件节点加上flex-grow: 1。这一点从 upload.vue 的结构也能印证:根节点内部直接渲染t-grid,而t-grid的宽度依赖父容器撑开。
总结
t-upload以files受控列表为核心,把“选择(chooseMedia/chooseMessageFile)→ 校验(数量/大小/类型)→ 上传(request-method 或业务自管)→ 展示(状态/进度/预览)→ 排序(draggable + drop)”整条链路封装完整。落地时建议:
- 数量与大小约束用
max+size-limit表达,超限提示依赖组件内置 toast; - 上传逻辑交给
request-method返回 Promise,成功/失败由success/fail/complete事件兜底; - 需要排序时开启
draggable并在drop中回写files,过渡动画用transition调整; - 样式微调优先走
grid-config、theme与--td-upload-*CSS 变量,避免与内部 Grid 布局冲突。
- 前端
- UI组件
- 小程序
- 设计系统
- 移动开发
【免费下载链接】tdesign-miniprogram
Wechat MiniProgram and Uniapp UI components lib for TDesign
相关推荐
TDesign MiniProgram Upload 上传组件完全指南:属性、事件与拖拽排序实战
TDesign MiniProgram Upload 上传组件完全指南:属性、事件与拖拽排序实战 本篇技术指南以 tdesign miniprogram 开源仓
前端UI组件小程序设计系统移动开发ng-zorro-antd Upload 组件实战指南:文件选择、拖拽上传与自定义上传实现
ng zorro antd Upload 组件实战指南:文件选择、拖拽上传与自定义上传实现 本指南以 ng zorro antd 的 Upload(上传)组件为
UI组件前端Ant Design Vue Upload 组件完整指南:文件上传、拖拽交互与自定义上传实现
Ant Design Vue Upload 组件完整指南:文件上传、拖拽交互与自定义上传实现 文件上传是 Web 应用中最高频的数据录入场景之一。Ant Des
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考