diff --git a/src/_common b/src/_common index 3377f3972..edf902a41 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 3377f39729f62b7ee0f03876480a78e6f2791204 +Subproject commit edf902a41627e87a6772c8200c62586566edfa46 diff --git a/src/image/image.tsx b/src/image/image.tsx index a7d40680b..5030af93e 100644 --- a/src/image/image.tsx +++ b/src/image/image.tsx @@ -1,4 +1,5 @@ import { ref, computed, defineComponent, watchEffect } from 'vue'; +import type { PropType } from 'vue'; import { useIntersectionObserver } from '@vueuse/core'; import { CloseIcon } from 'tdesign-icons-vue-next'; @@ -13,7 +14,11 @@ const { prefix } = config; export default defineComponent({ name: `${prefix}-image`, - props, + props: { + ...props, + // 非对外暴露参数 + onClick: Function as PropType<(e: MouseEvent) => void>, + }, setup(props, context) { const imageClass = usePrefixClass('image'); const renderTNodeJSX = useTNodeJSX(); @@ -101,7 +106,7 @@ export default defineComponent({ return () => { return ( -
+
{maskContent.value} {props.srcset && diff --git a/src/upload/__test__/__snapshots__/demo.test.jsx.snap b/src/upload/__test__/__snapshots__/demo.test.jsx.snap index ae8647103..0ea385d91 100644 --- a/src/upload/__test__/__snapshots__/demo.test.jsx.snap +++ b/src/upload/__test__/__snapshots__/demo.test.jsx.snap @@ -355,7 +355,8 @@ exports[`Upload > Upload mobileVue demo works fine 1`] = ` >
Upload mobileVue demo works fine 1`] = `
Upload mobileVue demo works fine 1`] = `
Upload multipleVue demo works fine 1`] = ` >
Upload multipleVue demo works fine 1`] = `
Upload multipleVue demo works fine 1`] = `
, +) { + const dragIndex = ref(-1); + let pendingTargetIndex = -1; + + // 缓存网格布局信息 + let baseData = { + itemWidth: 0, + itemHeight: 0, + columns: 0, + wrapLeft: 0, + wrapTop: 0, + }; + + // 生成文件唯一ID,用于拖拽排序识别 + const getFileId = (file: UploadFile) => { + if ((file as any).__uid) return (file as any).__uid; + const uid = `u_${Math.random().toString(36).slice(2, 9)}`; + (file as any).__uid = uid; + return uid; + }; + + const updateDragBaseData = () => { + const listEl = listRef.value; + const itemEl = listEl?.querySelector(`.${uploadClass.value}__item`); + if (listEl && itemEl) { + const rect = listEl.getBoundingClientRect(); + const itemWidth = (itemEl as HTMLElement).offsetWidth; + const itemHeight = (itemEl as HTMLElement).offsetHeight; + const style = window.getComputedStyle(listEl); + const gapX = parseInt(style.columnGap || style.gap, 10) || 8; + const gapY = parseInt(style.rowGap || style.gap, 10) || 16; + const paddingLeft = parseInt(style.paddingLeft, 10) || 0; + const paddingTop = parseInt(style.paddingTop, 10) || 0; + + baseData = { + itemWidth: itemWidth + gapX, + itemHeight: itemHeight + gapY, + columns: + Math.round( + (rect.width - paddingLeft - (parseInt(style.paddingRight, 10) || 0) + gapX) / (itemWidth + gapX), + ) || 4, + wrapLeft: rect.left + paddingLeft, + wrapTop: rect.top + paddingTop, + }; + } + }; + + const performSort = (index: number, displayFiles: UploadFile[], e: any) => { + if (index === -1 || index === dragIndex.value) { + pendingTargetIndex = -1; + return; + } + if (index === pendingTargetIndex) return; + + pendingTargetIndex = index; + const sourceIdx = dragIndex.value; + const newFiles = [...displayFiles]; + + if (sourceIdx === -1 || sourceIdx === index) return; + + const [item] = newFiles.splice(sourceIdx, 1); + newFiles.splice(index, 0, item); + + setUploadValue(newFiles, { e, trigger: 'sort', index, file: item, files: newFiles }); + if (dragIndex.value !== -1) { + dragIndex.value = index; + } + }; + + // 根据网格坐标计算目标索引 + const onTouchmove = (e: TouchEvent, displayFiles: UploadFile[]) => { + if (!props.draggable || dragIndex.value === -1) return; + if (e.cancelable) e.preventDefault(); + + const touch = e.touches[0]; + const { itemWidth, itemHeight, columns, wrapLeft, wrapTop } = baseData; + + const x = touch.clientX - wrapLeft; + const y = touch.clientY - wrapTop; + + let curX = Math.floor(x / itemWidth); + const curY = Math.floor(y / itemHeight); + + // 将 X 限制在列范围内,确保在行边界内滑动 + curX = Math.max(0, Math.min(columns - 1, curX)); + + let targetIndex = curX + columns * curY; + targetIndex = Math.max(0, Math.min(displayFiles.length - 1, targetIndex)); + + if (targetIndex !== dragIndex.value) { + const targetX = targetIndex % columns; + const targetY = Math.floor(targetIndex / columns); + const sourceX = dragIndex.value % columns; + const sourceY = Math.floor(dragIndex.value / columns); + + const xProgress = x / itemWidth - targetX; + const yProgress = y / itemHeight - targetY; + + let shouldSort = false; + if (targetY !== sourceY) { + shouldSort = targetY > sourceY ? yProgress > 0.5 : yProgress < 0.5; + } else { + shouldSort = targetX > sourceX ? xProgress > 0.5 : xProgress < 0.5; + } + + if (shouldSort) { + performSort(targetIndex, displayFiles, e); + } else if (pendingTargetIndex !== -1) { + pendingTargetIndex = -1; + } + } + }; + + const resetDrag = () => { + dragIndex.value = -1; + pendingTargetIndex = -1; + }; + + return { + onDragstart: (e: DragEvent, index: number) => { + if (!props.draggable) return; + dragIndex.value = index; + updateDragBaseData(); + }, + onDragover: (e: DragEvent, index: number, files: any) => { + if (!props.draggable || dragIndex.value === -1) return; + e.preventDefault(); + performSort(index, files, e); + }, + onDragend: resetDrag, + onTouchstart: (e: TouchEvent, index: number) => { + if (!props.draggable) return; + dragIndex.value = index; + updateDragBaseData(); + }, + onTouchmove, + dragIndex, + getFileId, + }; +} diff --git a/src/upload/hooks/useUpload.ts b/src/upload/hooks/useUpload.ts index 94db8b606..4f9abdfba 100644 --- a/src/upload/hooks/useUpload.ts +++ b/src/upload/hooks/useUpload.ts @@ -1,4 +1,4 @@ -import { computed, h, ref, toRefs, ComputedRef } from 'vue'; +import { computed, h, ref, toRefs, ComputedRef, Ref } from 'vue'; import { isFunction, isString } from 'lodash-es'; import { SizeLimitObj, TdUploadProps, UploadChangeContext, UploadFile, UploadRemoveContext } from '../type'; import useVModel from '../../hooks/useVModel'; @@ -20,12 +20,30 @@ import { getFileList, getFileUrlByFileRaw } from '../../_common/js/upload/utils' // @ts-ignore export type ValidateParams = Parameters[0]; -export default function useUpload(props: TdUploadProps) { +export interface UseUpload { + toUploadFiles: Ref; + uploadValue: Ref; + displayFiles: ComputedRef; + sizeOverLimitMessage: Ref; + uploading: Ref; + inputRef: Ref; + disabled: Ref; + xhrReq: Ref<{ files: UploadFile[]; xhrReq: XMLHttpRequest }[]>; + uploadFilePercent: (params: { file: UploadFile; percent: number }) => void; + uploadFiles: (toFiles?: UploadFile[]) => void; + onFileChange: (files: File[]) => void; + onNormalFileChange: (e: Event) => void; + onInnerRemove: (p: UploadRemoveContext) => void; + cancelUpload: (context?: { file?: UploadFile; e?: MouseEvent }) => void; + setUploadValue: (value: UploadFile[], context: UploadChangeContext) => void; +} + +export default function useUpload(props: TdUploadProps): UseUpload { const inputRef = ref(); const { disabled, autoUpload, isBatchUpload, multiple, files, modelValue, defaultFiles } = toRefs(props); // @ts-ignore const [uploadValue, setUploadValue] = useVModel(files, modelValue, defaultFiles.value, props.onChange, 'files'); - const xhrReq = ref<{ files: UploadFile[]; xhrReq: XMLHttpRequest }[]>([]); + const xhrReq: UseUpload['xhrReq'] = ref([]); const toUploadFiles = ref([]); const sizeOverLimitMessage = ref(''); @@ -385,5 +403,6 @@ export default function useUpload(props: TdUploadProps) { onNormalFileChange, onInnerRemove, cancelUpload, + setUploadValue, }; } diff --git a/src/upload/props.ts b/src/upload/props.ts index 6a3802a65..56448a94f 100644 --- a/src/upload/props.ts +++ b/src/upload/props.ts @@ -55,6 +55,11 @@ export default { type: Boolean, default: undefined, }, + /** 是否启用拖拽上传 */ + draggable: { + type: Boolean, + default: undefined, + }, /** 用于完全自定义文件列表界面内容(UI),单文件和多文件均有效 */ fileListDisplay: { type: Function as PropType, @@ -134,13 +139,13 @@ export default { sizeLimit: { type: [Number, Object] as PropType, }, + /** 是否在同一个请求中上传全部文件,默认一个请求上传一个文件。多文件上传时有效 */ + uploadAllFilesInOneRequest: Boolean, /** 是否在请求时间超过 300ms 后显示模拟进度。上传进度有模拟进度和真实进度两种。一般大小的文件上传,真实的上传进度只有 0 和 100,不利于交互呈现,因此组件内置模拟上传进度。真实上传进度一般用于大文件上传 */ useMockProgress: { type: Boolean, default: true, }, - /** 是否在同一个请求中上传全部文件,默认一个请求上传一个文件。多文件上传时有效 */ - uploadAllFilesInOneRequest: Boolean, /** 已上传文件列表,同 `files`。TS 类型:`UploadFile` */ value: { type: Array as PropType, @@ -163,6 +168,10 @@ export default { onChange: Function as PropType, /** 点击上传区域时触发 */ onClickUpload: Function as PropType, + /** 拖拽开始时触发 */ + onDrag: Function as PropType, + /** 拖拽结束后触发,返回上传的文件列表(拖拽后的文件顺序) */ + onDrop: Function as PropType, /** 上传失败后触发。`response` 指接口响应结果,`response.error` 会作为错误文本提醒。如果希望判定为上传失败,但接口响应数据不包含 `error` 字段,可以使用 `formatResponse` 格式化 `response` 数据结构。如果是多文件多请求上传场景,请到事件 `onOneFileFail` 中查看 `response` */ onFail: Function as PropType, /** 多文件/图片场景下,单个文件上传失败后触发,如果一个请求上传一个文件,则会触发多次。单文件/图片不会触发 */ diff --git a/src/upload/type.ts b/src/upload/type.ts index 084b219e4..a641dd6f1 100644 --- a/src/upload/type.ts +++ b/src/upload/type.ts @@ -37,17 +37,16 @@ export interface TdUploadProps { * @default true */ autoUpload?: boolean; - /** - * 如果是自动上传模式 `autoUpload=true`,表示单个文件上传之前的钩子函数,若函数返回值为 `false` 则表示不上传当前文件。
如果是非自动上传模式 `autoUpload=false`,函数返回值为 `false` 时表示从上传文件中剔除当前文件 - */ /** * 如果是自动上传模式 `autoUpload=true`,表示全部文件上传之前的钩子函数,函数参数为上传的文件,函数返回值决定是否继续上传,若返回值为 `false` 则终止上传。
如果是非自动上传模式 `autoUpload=false`,则函数返回值为 `false` 时表示本次选中的文件不会加入到文件列表中,即不触发 `onChange` 事件 */ beforeAllFilesUpload?: (file: UploadFile[]) => boolean | Promise; + /** + * 如果是自动上传模式 `autoUpload=true`,表示单个文件上传之前的钩子函数,若函数返回值为 `false` 则表示不上传当前文件。
如果是非自动上传模式 `autoUpload=false`,函数返回值为 `false` 时表示从上传文件中剔除当前文件 + */ beforeUpload?: (file: UploadFile) => boolean | Promise; /** * 图片选取模式,可选值为 camera (直接调起摄像头) - * @default '' */ capture?: string | boolean; /** @@ -58,6 +57,10 @@ export interface TdUploadProps { * 是否禁用组件 */ disabled?: boolean; + /** + * 是否启用拖拽上传 + */ + draggable?: boolean; /** * 用于完全自定义文件列表界面内容(UI),单文件和多文件均有效 */ @@ -92,35 +95,35 @@ export interface TdUploadProps { * 透传 Image 组件全部属性 */ imageProps?: ImageProps; - /** - * 用于控制文件上传数量,值为 0 则不限制 - * @default 0 - */ /** * 多个文件是否作为一个独立文件包,整体替换,整体删除。不允许追加文件,只允许替换文件。`theme=file-flow` 时有效 * @default false */ isBatchUpload?: boolean; + /** + * 用于控制文件上传数量,值为 0 则不限制 + * @default 0 + */ max?: number; /** * HTTP 请求类型 * @default POST */ method?: 'POST' | 'GET' | 'PUT' | 'OPTIONS' | 'PATCH' | 'post' | 'get' | 'put' | 'options' | 'patch'; + /** + * 模拟进度间隔时间,单位:毫秒,默认:300。由于原始的上传请求,小文件上传进度只有 0 和 100,故而新增模拟进度,每间隔 `mockProgressDuration` 毫秒刷新一次模拟进度。小文件设置小一点,大文件设置大一点。注意:当 `useMockProgress` 为真时,当前设置有效 + */ + mockProgressDuration?: number; /** * 支持多文件上传 * @default false */ + multiple?: boolean; /** * 文件上传时的名称 * @default file */ name?: string; - /** - * 模拟进度间隔时间,单位:毫秒,默认:300。由于原始的上传请求,小文件上传进度只有 0 和 100,故而新增模拟进度,每间隔 `mockProgressDuration` 毫秒刷新一次模拟进度。小文件设置小一点,大文件设置大一点。注意:当 `useMockProgress` 为真时,当前设置有效 - */ - mockProgressDuration?: number; - multiple?: boolean; /** * 是否支持图片预览,文件没有预览 * @default true @@ -139,16 +142,16 @@ export interface TdUploadProps { * 图片文件大小限制,默认单位 KB。可选单位有:`'B' | 'KB' | 'MB' | 'GB'`。示例一:`1000`。示例二:`{ size: 2, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB' }` */ sizeLimit?: number | SizeLimitObj; - /** - * 是否在请求时间超过 300ms 后显示模拟进度。上传进度有模拟进度和真实进度两种。一般大小的文件上传,真实的上传进度只有 0 和 100,不利于交互呈现,因此组件内置模拟上传进度。真实上传进度一般用于大文件上传 - * @default true - */ - useMockProgress?: boolean; /** * 是否在同一个请求中上传全部文件,默认一个请求上传一个文件。多文件上传时有效 * @default false */ uploadAllFilesInOneRequest?: boolean; + /** + * 是否在请求时间超过 300ms 后显示模拟进度。上传进度有模拟进度和真实进度两种。一般大小的文件上传,真实的上传进度只有 0 和 100,不利于交互呈现,因此组件内置模拟上传进度。真实上传进度一般用于大文件上传 + * @default true + */ + useMockProgress?: boolean; /** * 已上传文件列表,同 `files`。TS 类型:`UploadFile` * @default [] @@ -164,15 +167,15 @@ export interface TdUploadProps { * @default [] */ modelValue?: Array; - /** - * 点击「取消上传」时触发 - */ - onCancelUpload?: () => void; /** * 上传请求时是否携带 cookie * @default false */ withCredentials?: boolean; + /** + * 点击「取消上传」时触发 + */ + onCancelUpload?: () => void; /** * 已上传文件列表发生变化时触发,`trigger` 表示触发本次的来源 */ @@ -181,6 +184,14 @@ export interface TdUploadProps { * 点击上传区域时触发 */ onClickUpload?: (context: { e: MouseEvent }) => void; + /** + * 拖拽开始时触发 + */ + onDrag?: () => void; + /** + * 拖拽结束后触发,返回上传的文件列表(拖拽后的文件顺序) + */ + onDrop?: (value: Array) => void; /** * 上传失败后触发。`response` 指接口响应结果,`response.error` 会作为错误文本提醒。如果希望判定为上传失败,但接口响应数据不包含 `error` 字段,可以使用 `formatResponse` 格式化 `response` 数据结构。如果是多文件多请求上传场景,请到事件 `onOneFileFail` 中查看 `response` */ @@ -306,7 +317,14 @@ export interface UploadChangeContext { files?: UploadFile[]; } -export type UploadChangeTrigger = 'add' | 'remove' | 'abort' | 'progress-success' | 'progress' | 'progress-fail'; +export type UploadChangeTrigger = + | 'add' + | 'remove' + | 'abort' + | 'progress-success' + | 'progress' + | 'progress-fail' + | 'sort'; export interface UploadFailContext { e?: ProgressEvent; diff --git a/src/upload/upload.en-US.md b/src/upload/upload.en-US.md index 79fdea140..084c36933 100644 --- a/src/upload/upload.en-US.md +++ b/src/upload/upload.en-US.md @@ -12,10 +12,13 @@ addBtn | Boolean | true | `1.11.1` | N addContent | String / Slot / Function | - | Typescript: `string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N allowUploadDuplicateFile | Boolean | false | allow to upload duplicate name files | N autoUpload | Boolean | true | post upload request automatically after files being selected | N +beforeAllFilesUpload | Function | - | before all files upload, return false can stop uploading file。Typescript: `(file: UploadFile[]) => boolean \| Promise` | N beforeUpload | Function | - | stop one of files to upload。Typescript: `(file: UploadFile) => boolean \| Promise` | N -capture | String | - | \- | N +capture | String / Boolean | - | \- | N data | Object | - | extra request data of uploading. `formatRequest` can redefine all request data。Typescript: `Record \| ((files: UploadFile[]) => Record)` | N disabled | Boolean | undefined | make upload to be disabled | N +draggable | Boolean | undefined | \- | N +fileListDisplay | Slot / Function | - | used to render file list UI。Typescript: `TNode<{ files: UploadFile[]; dragEvents?: UploadDisplayDragEvents }>`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N files | Array | [] | `v-model:files` is supported。Typescript: `Array` | N defaultFiles | Array | [] | uncontrolled property。Typescript: `Array` | N format | Function | - | to redefine `UploadFile` data structure。Typescript: `(file: File) => UploadFile` | N @@ -23,40 +26,56 @@ formatRequest | Function | - | redefine request data。Typescript: `(requestData formatResponse | Function | - | redefine response data structure。Typescript: `(response: any, context: FormatResponseContext) => ResponseType` `type ResponseType = { error?: string; url?: string } & Record` ` interface FormatResponseContext { file: UploadFile; currentFiles?: UploadFile[] }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N headers | Object | - | HTTP Request Header。Typescript: `{[key: string]: string}` | N imageProps | Object | - | Typescript: `ImageProps`,[Image API Documents](./image?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N +isBatchUpload | Boolean | false | make all files to be a whole package, files can only be replaced or deleted together, can not add more files | N max | Number | 0 | max count of files limit | N method | String | POST | HTTP request method。options: POST/GET/PUT/OPTIONS/PATCH/post/get/put/options/patch | N +mockProgressDuration | Number | - | mock progress duration time. more large files more duration time | N multiple | Boolean | false | multiple files uploading | N +name | String | file | field name of files in upload request data | N preview | Boolean | true | `1.6.0` | N removeBtn | Boolean | true | `1.10.0` | N requestMethod | Function | - | custom upload request method。Typescript: `(files: UploadFile \| UploadFile[]) => Promise` `interface RequestMethodResponse { status: 'success' \| 'fail'; error?: string; response: { url?: string; files?: UploadFile[]; [key: string]: any } }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N sizeLimit | Number / Object | - | files size limit。Typescript: `number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N +uploadAllFilesInOneRequest | Boolean | false | uploading all files in one request | N useMockProgress | Boolean | true | use mock progress, instead of real progress | N value | Array | [] | file list。`v-model` and `v-model:value` is supported。Typescript: `Array` | N defaultValue | Array | [] | file list。uncontrolled property。Typescript: `Array` | N withCredentials | Boolean | false | uploading request with cookie | N -onChange | Function | | Typescript: `(value: Array, context: UploadChangeContext) => void`
trigger on uploaded files change。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail'`
| N +onCancelUpload | Function | | Typescript: `() => void`
trigger on cancel button click | N +onChange | Function | | Typescript: `(value: Array, context: UploadChangeContext) => void`
trigger on uploaded files change。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail' \| 'sort'`
| N onClickUpload | Function | | Typescript: `(context: { e: MouseEvent }) => void`
| N +onDrag | Function | | Typescript: `() => void`
| N +onDrop | Function | | Typescript: `(value: Array) => void`
| N onFail | Function | | Typescript: `(options: UploadFailContext) => void`
`response.error` used for error tips, `formatResponse` can format `response`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadFailContext { e?: ProgressEvent; failedFiles: UploadFile[]; currentFiles: UploadFile[]; response?: any; file: UploadFile; XMLHttpRequest?: XMLHttpRequest}`
| N +onOneFileFail | Function | | Typescript: `(options: UploadFailContext) => void`
trigger on one file upload failed | N +onOneFileSuccess | Function | | Typescript: `(context: Pick) => void`
trigger on file uploaded successfully | N onPreview | Function | | Typescript: `(options: { file: UploadFile; index: number; e: MouseEvent }) => void`
trigger on preview elements click | N onProgress | Function | | Typescript: `(options: ProgressContext) => void`
uploading request progress event。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface ProgressContext { e?: ProgressEvent; file?: UploadFile; currentFiles: UploadFile[]; percent: number; type: UploadProgressType; XMLHttpRequest?: XMLHttpRequest }`

`type UploadProgressType = 'real' \| 'mock'`
| N onRemove | Function | | Typescript: `(context: UploadRemoveContext) => void`
trigger on file removed。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadRemoveContext { index?: number; file?: UploadFile; e: MouseEvent }`
| N onSelectChange | Function | | Typescript: `(files: File[], context: UploadSelectChangeContext) => void`
trigger after file choose and before upload。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadSelectChangeContext { currentSelectedFiles: UploadFile[] }`
| N onSuccess | Function | | Typescript: `(context: SuccessContext) => void`
trigger on all files uploaded successfully。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface SuccessContext { e?: ProgressEvent; file?: UploadFile; fileList?: UploadFile[]; currentFiles?: UploadFile[]; response?: any; results?: SuccessContext[]; XMLHttpRequest?: XMLHttpRequest }`
| N onValidate | Function | | Typescript: `(context: { type: UploadValidateType, files: UploadFile[] }) => void`
trigger on length over limit, or trigger on file size over limit。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`type UploadValidateType = 'FILE_OVER_SIZE_LIMIT' \| 'FILES_OVER_LENGTH_LIMIT' \| 'FILTER_FILE_SAME_NAME' \| 'BEFORE_ALL_FILES_UPLOAD' \| 'CUSTOM_BEFORE_UPLOAD'`
| N +onWaitingUploadFilesChange | Function | | Typescript: `(context: { files: Array, trigger: 'validate' \| 'remove' \| 'uploaded' }) => void`
trigger on waiting upload files changed | N ### Upload Events name | params | description -- | -- | -- -change | `(value: Array, context: UploadChangeContext)` | trigger on uploaded files change。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail'`
+cancel-upload | \- | trigger on cancel button click +change | `(value: Array, context: UploadChangeContext)` | trigger on uploaded files change。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail' \| 'sort'`
click-upload | `(context: { e: MouseEvent })` | \- +drag | \- | \- +drop | `(value: Array)` | \- fail | `(options: UploadFailContext)` | `response.error` used for error tips, `formatResponse` can format `response`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadFailContext { e?: ProgressEvent; failedFiles: UploadFile[]; currentFiles: UploadFile[]; response?: any; file: UploadFile; XMLHttpRequest?: XMLHttpRequest}`
+one-file-fail | `(options: UploadFailContext)` | trigger on one file upload failed +one-file-success | `(context: Pick)` | trigger on file uploaded successfully preview | `(options: { file: UploadFile; index: number; e: MouseEvent })` | trigger on preview elements click progress | `(options: ProgressContext)` | uploading request progress event。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface ProgressContext { e?: ProgressEvent; file?: UploadFile; currentFiles: UploadFile[]; percent: number; type: UploadProgressType; XMLHttpRequest?: XMLHttpRequest }`

`type UploadProgressType = 'real' \| 'mock'`
remove | `(context: UploadRemoveContext)` | trigger on file removed。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadRemoveContext { index?: number; file?: UploadFile; e: MouseEvent }`
select-change | `(files: File[], context: UploadSelectChangeContext)` | trigger after file choose and before upload。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadSelectChangeContext { currentSelectedFiles: UploadFile[] }`
success | `(context: SuccessContext)` | trigger on all files uploaded successfully。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface SuccessContext { e?: ProgressEvent; file?: UploadFile; fileList?: UploadFile[]; currentFiles?: UploadFile[]; response?: any; results?: SuccessContext[]; XMLHttpRequest?: XMLHttpRequest }`
validate | `(context: { type: UploadValidateType, files: UploadFile[] })` | trigger on length over limit, or trigger on file size over limit。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`type UploadValidateType = 'FILE_OVER_SIZE_LIMIT' \| 'FILES_OVER_LENGTH_LIMIT' \| 'FILTER_FILE_SAME_NAME' \| 'BEFORE_ALL_FILES_UPLOAD' \| 'CUSTOM_BEFORE_UPLOAD'`
+waiting-upload-files-change | `(context: { files: Array, trigger: 'validate' \| 'remove' \| 'uploaded' })` | trigger on waiting upload files changed ### UploadFile diff --git a/src/upload/upload.md b/src/upload/upload.md index d85f2f3de..c952a0542 100644 --- a/src/upload/upload.md +++ b/src/upload/upload.md @@ -12,10 +12,13 @@ addBtn | Boolean | true | `1.11.1`。添加按钮 | N addContent | String / Slot / Function | - | 添加按钮内容。值为空,使用默认图标渲染;值为 slot 则表示使用插槽渲染;其他值无效。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N allowUploadDuplicateFile | Boolean | false | 是否允许重复上传相同文件名的文件。在 `capture = ''` + 拍照上传场景中,`allowUploadDuplicateFile` 应取 `true`,避免拍照上传的文件被同名文件校验过滤 | N autoUpload | Boolean | true | 是否在选择文件后自动发起请求上传文件 | N +beforeAllFilesUpload | Function | - | 如果是自动上传模式 `autoUpload=true`,表示全部文件上传之前的钩子函数,函数参数为上传的文件,函数返回值决定是否继续上传,若返回值为 `false` 则终止上传。
如果是非自动上传模式 `autoUpload=false`,则函数返回值为 `false` 时表示本次选中的文件不会加入到文件列表中,即不触发 `onChange` 事件。TS 类型:`(file: UploadFile[]) => boolean \| Promise` | N beforeUpload | Function | - | 如果是自动上传模式 `autoUpload=true`,表示单个文件上传之前的钩子函数,若函数返回值为 `false` 则表示不上传当前文件。
如果是非自动上传模式 `autoUpload=false`,函数返回值为 `false` 时表示从上传文件中剔除当前文件。TS 类型:`(file: UploadFile) => boolean \| Promise` | N -capture | String | - | 图片选取模式,可选值为 camera (直接调起摄像头) | N +capture | String / Boolean | - | 图片选取模式,可选值为 camera (直接调起摄像头) | N data | Object | - | 上传请求所需的额外字段,默认字段有 `file`,表示文件信息。可以添加额外的文件名字段,如:`{file_name: "custom-file-name.txt"}`。`autoUpload=true` 时有效。也可以使用 `formatRequest` 完全自定义上传请求的字段。TS 类型:`Record \| ((files: UploadFile[]) => Record)` | N disabled | Boolean | undefined | 是否禁用组件 | N +draggable | Boolean | undefined | 是否启用拖拽上传 | N +fileListDisplay | Slot / Function | - | 用于完全自定义文件列表界面内容(UI),单文件和多文件均有效。TS 类型:`TNode<{ files: UploadFile[]; dragEvents?: UploadDisplayDragEvents }>`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N files | Array | [] | 已上传文件列表,同 `value`。TS 类型:`UploadFile`。支持语法糖 `v-model:files`。TS 类型:`Array` | N defaultFiles | Array | [] | 已上传文件列表,同 `value`。TS 类型:`UploadFile`。非受控属性。TS 类型:`Array` | N format | Function | - | 转换文件 `UploadFile` 的数据结构,可新增或修改 `UploadFile` 的属性,注意不能删除 `UploadFile` 属性。`action` 存在时有效。TS 类型:`(file: File) => UploadFile` | N @@ -23,40 +26,56 @@ formatRequest | Function | - | 用于新增或修改文件上传请求 参数。 formatResponse | Function | - | 用于格式化文件上传后的接口响应数据,`response` 便是接口响应的原始数据。`action` 存在时有效。
此函数的返回值 `error` 或 `response.error` 会作为错误文本提醒,如果存在会判定为本次上传失败。
此函数的返回值 `url` 或 `response.url` 会作为上传成功后的链接。TS 类型:`(response: any, context: FormatResponseContext) => ResponseType` `type ResponseType = { error?: string; url?: string } & Record` ` interface FormatResponseContext { file: UploadFile; currentFiles?: UploadFile[] }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N headers | Object | - | 设置上传的请求头部,`action` 存在时有效。TS 类型:`{[key: string]: string}` | N imageProps | Object | - | 透传 Image 组件全部属性。TS 类型:`ImageProps`,[Image API Documents](./image?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N +isBatchUpload | Boolean | false | 多个文件是否作为一个独立文件包,整体替换,整体删除。不允许追加文件,只允许替换文件。`theme=file-flow` 时有效 | N max | Number | 0 | 用于控制文件上传数量,值为 0 则不限制 | N method | String | POST | HTTP 请求类型。可选项:POST/GET/PUT/OPTIONS/PATCH/post/get/put/options/patch | N +mockProgressDuration | Number | - | 模拟进度间隔时间,单位:毫秒,默认:300。由于原始的上传请求,小文件上传进度只有 0 和 100,故而新增模拟进度,每间隔 `mockProgressDuration` 毫秒刷新一次模拟进度。小文件设置小一点,大文件设置大一点。注意:当 `useMockProgress` 为真时,当前设置有效 | N multiple | Boolean | false | 支持多文件上传 | N +name | String | file | 文件上传时的名称 | N preview | Boolean | true | `1.6.0`。是否支持图片预览,文件没有预览 | N removeBtn | Boolean | true | `1.10.0`。移除按钮 | N requestMethod | Function | - | 自定义上传方法。返回值 `status` 表示上传成功或失败;`error` 或 `response.error` 表示上传失败的原因;
`response` 表示请求上传成功后的返回数据,`response.url` 表示上传成功后的图片/文件地址,`response.files` 表示一个请求上传多个文件/图片后的返回值。
示例一:`{ status: 'fail', error: '上传失败', response }`。
示例二:`{ status: 'success', response: { url: 'https://tdesign.gtimg.com/site/avatar.jpg' } }`。
示例三:`{ status: 'success', files: [{ url: 'https://xxx.png', name: 'xxx.png' }]}`。TS 类型:`(files: UploadFile \| UploadFile[]) => Promise` `interface RequestMethodResponse { status: 'success' \| 'fail'; error?: string; response: { url?: string; files?: UploadFile[]; [key: string]: any } }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N sizeLimit | Number / Object | - | 图片文件大小限制,默认单位 KB。可选单位有:`'B' \| 'KB' \| 'MB' \| 'GB'`。示例一:`1000`。示例二:`{ size: 2, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB' }`。TS 类型:`number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts) | N +uploadAllFilesInOneRequest | Boolean | false | 是否在同一个请求中上传全部文件,默认一个请求上传一个文件。多文件上传时有效 | N useMockProgress | Boolean | true | 是否在请求时间超过 300ms 后显示模拟进度。上传进度有模拟进度和真实进度两种。一般大小的文件上传,真实的上传进度只有 0 和 100,不利于交互呈现,因此组件内置模拟上传进度。真实上传进度一般用于大文件上传 | N value | Array | [] | 已上传文件列表,同 `files`。TS 类型:`UploadFile`。支持语法糖 `v-model` 或 `v-model:value`。TS 类型:`Array` | N defaultValue | Array | [] | 已上传文件列表,同 `files`。TS 类型:`UploadFile`。非受控属性。TS 类型:`Array` | N withCredentials | Boolean | false | 上传请求时是否携带 cookie | N -onChange | Function | | TS 类型:`(value: Array, context: UploadChangeContext) => void`
已上传文件列表发生变化时触发,`trigger` 表示触发本次的来源。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail'`
| N +onCancelUpload | Function | | TS 类型:`() => void`
点击「取消上传」时触发 | N +onChange | Function | | TS 类型:`(value: Array, context: UploadChangeContext) => void`
已上传文件列表发生变化时触发,`trigger` 表示触发本次的来源。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail' \| 'sort'`
| N onClickUpload | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
点击上传区域时触发 | N +onDrag | Function | | TS 类型:`() => void`
拖拽开始时触发 | N +onDrop | Function | | TS 类型:`(value: Array) => void`
拖拽结束后触发,返回上传的文件列表(拖拽后的文件顺序) | N onFail | Function | | TS 类型:`(options: UploadFailContext) => void`
上传失败后触发。`response` 指接口响应结果,`response.error` 会作为错误文本提醒。如果希望判定为上传失败,但接口响应数据不包含 `error` 字段,可以使用 `formatResponse` 格式化 `response` 数据结构。如果是多文件多请求上传场景,请到事件 `onOneFileFail` 中查看 `response`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadFailContext { e?: ProgressEvent; failedFiles: UploadFile[]; currentFiles: UploadFile[]; response?: any; file: UploadFile; XMLHttpRequest?: XMLHttpRequest}`
| N +onOneFileFail | Function | | TS 类型:`(options: UploadFailContext) => void`
多文件/图片场景下,单个文件上传失败后触发,如果一个请求上传一个文件,则会触发多次。单文件/图片不会触发 | N +onOneFileSuccess | Function | | TS 类型:`(context: Pick) => void`
单个文件上传成功后触发,在多文件场景下会触发多次。`context.file` 表示当前上传成功的单个文件,`context.response` 表示上传请求的返回数据 | N onPreview | Function | | TS 类型:`(options: { file: UploadFile; index: number; e: MouseEvent }) => void`
点击图片预览时触发,文件没有预览 | N onProgress | Function | | TS 类型:`(options: ProgressContext) => void`
上传进度变化时触发,真实进度和模拟进度都会触发。
⚠️ 原始上传请求,小文件的上传进度只有 0 和 100,故而不会触发 `progress` 事件;只有大文件才有真实的中间进度。如果你希望很小的文件也显示上传进度,保证 `useMockProgress=true` 的情况下,设置 `mockProgressDuration` 为更小的值。
参数 `options.type=real` 表示真实上传进度,`options.type=mock` 表示模拟上传进度。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface ProgressContext { e?: ProgressEvent; file?: UploadFile; currentFiles: UploadFile[]; percent: number; type: UploadProgressType; XMLHttpRequest?: XMLHttpRequest }`

`type UploadProgressType = 'real' \| 'mock'`
| N onRemove | Function | | TS 类型:`(context: UploadRemoveContext) => void`
移除文件时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadRemoveContext { index?: number; file?: UploadFile; e: MouseEvent }`
| N onSelectChange | Function | | TS 类型:`(files: File[], context: UploadSelectChangeContext) => void`
选择文件或图片之后,上传之前,触发该事件。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadSelectChangeContext { currentSelectedFiles: UploadFile[] }`
| N onSuccess | Function | | TS 类型:`(context: SuccessContext) => void`
上传成功后触发。
`context.currentFiles` 表示当次请求上传的文件(无论成功或失败),`context.fileList` 表示上传成功后的文件,`context.response` 表示上传请求的返回数据。
`context.results` 表示单次选择全部文件上传成功后的响应结果,可以在这个字段存在时提醒用户上传成功或失败。
。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface SuccessContext { e?: ProgressEvent; file?: UploadFile; fileList?: UploadFile[]; currentFiles?: UploadFile[]; response?: any; results?: SuccessContext[]; XMLHttpRequest?: XMLHttpRequest }`
| N onValidate | Function | | TS 类型:`(context: { type: UploadValidateType, files: UploadFile[] }) => void`
文件上传校验结束事件,文件数量超出、文件大小超出限制、文件同名、`beforeAllFilesUpload` 返回值为假、`beforeUpload` 返回值为假等场景会触发。
注意:如果设置允许上传同名文件,即 `allowUploadDuplicateFile=true`,则不会因为文件重名触发该事件。
结合 `status` 和 `tips` 可以在组件中呈现不同类型的错误(或告警)提示。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`type UploadValidateType = 'FILE_OVER_SIZE_LIMIT' \| 'FILES_OVER_LENGTH_LIMIT' \| 'FILTER_FILE_SAME_NAME' \| 'BEFORE_ALL_FILES_UPLOAD' \| 'CUSTOM_BEFORE_UPLOAD'`
| N +onWaitingUploadFilesChange | Function | | TS 类型:`(context: { files: Array, trigger: 'validate' \| 'remove' \| 'uploaded' }) => void`
待上传文件列表发生变化时触发。`context.files` 表示事件参数为待上传文件,`context.trigger` 引起此次变化的触发来源 | N ### Upload Events 名称 | 参数 | 描述 -- | -- | -- -change | `(value: Array, context: UploadChangeContext)` | 已上传文件列表发生变化时触发,`trigger` 表示触发本次的来源。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail'`
+cancel-upload | \- | 点击「取消上传」时触发 +change | `(value: Array, context: UploadChangeContext)` | 已上传文件列表发生变化时触发,`trigger` 表示触发本次的来源。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadChangeContext { e?: MouseEvent \| ProgressEvent; response?: any; trigger: UploadChangeTrigger; index?: number; file?: UploadFile; files?: UploadFile[] }`

`type UploadChangeTrigger = 'add' \| 'remove' \| 'abort' \| 'progress-success' \| 'progress' \| 'progress-fail' \| 'sort'`
click-upload | `(context: { e: MouseEvent })` | 点击上传区域时触发 +drag | \- | 拖拽开始时触发 +drop | `(value: Array)` | 拖拽结束后触发,返回上传的文件列表(拖拽后的文件顺序) fail | `(options: UploadFailContext)` | 上传失败后触发。`response` 指接口响应结果,`response.error` 会作为错误文本提醒。如果希望判定为上传失败,但接口响应数据不包含 `error` 字段,可以使用 `formatResponse` 格式化 `response` 数据结构。如果是多文件多请求上传场景,请到事件 `onOneFileFail` 中查看 `response`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadFailContext { e?: ProgressEvent; failedFiles: UploadFile[]; currentFiles: UploadFile[]; response?: any; file: UploadFile; XMLHttpRequest?: XMLHttpRequest}`
+one-file-fail | `(options: UploadFailContext)` | 多文件/图片场景下,单个文件上传失败后触发,如果一个请求上传一个文件,则会触发多次。单文件/图片不会触发 +one-file-success | `(context: Pick)` | 单个文件上传成功后触发,在多文件场景下会触发多次。`context.file` 表示当前上传成功的单个文件,`context.response` 表示上传请求的返回数据 preview | `(options: { file: UploadFile; index: number; e: MouseEvent })` | 点击图片预览时触发,文件没有预览 progress | `(options: ProgressContext)` | 上传进度变化时触发,真实进度和模拟进度都会触发。
⚠️ 原始上传请求,小文件的上传进度只有 0 和 100,故而不会触发 `progress` 事件;只有大文件才有真实的中间进度。如果你希望很小的文件也显示上传进度,保证 `useMockProgress=true` 的情况下,设置 `mockProgressDuration` 为更小的值。
参数 `options.type=real` 表示真实上传进度,`options.type=mock` 表示模拟上传进度。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface ProgressContext { e?: ProgressEvent; file?: UploadFile; currentFiles: UploadFile[]; percent: number; type: UploadProgressType; XMLHttpRequest?: XMLHttpRequest }`

`type UploadProgressType = 'real' \| 'mock'`
remove | `(context: UploadRemoveContext)` | 移除文件时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadRemoveContext { index?: number; file?: UploadFile; e: MouseEvent }`
select-change | `(files: File[], context: UploadSelectChangeContext)` | 选择文件或图片之后,上传之前,触发该事件。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface UploadSelectChangeContext { currentSelectedFiles: UploadFile[] }`
success | `(context: SuccessContext)` | 上传成功后触发。
`context.currentFiles` 表示当次请求上传的文件(无论成功或失败),`context.fileList` 表示上传成功后的文件,`context.response` 表示上传请求的返回数据。
`context.results` 表示单次选择全部文件上传成功后的响应结果,可以在这个字段存在时提醒用户上传成功或失败。
。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`interface SuccessContext { e?: ProgressEvent; file?: UploadFile; fileList?: UploadFile[]; currentFiles?: UploadFile[]; response?: any; results?: SuccessContext[]; XMLHttpRequest?: XMLHttpRequest }`
validate | `(context: { type: UploadValidateType, files: UploadFile[] })` | 文件上传校验结束事件,文件数量超出、文件大小超出限制、文件同名、`beforeAllFilesUpload` 返回值为假、`beforeUpload` 返回值为假等场景会触发。
注意:如果设置允许上传同名文件,即 `allowUploadDuplicateFile=true`,则不会因为文件重名触发该事件。
结合 `status` 和 `tips` 可以在组件中呈现不同类型的错误(或告警)提示。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/upload/type.ts)。
`type UploadValidateType = 'FILE_OVER_SIZE_LIMIT' \| 'FILES_OVER_LENGTH_LIMIT' \| 'FILTER_FILE_SAME_NAME' \| 'BEFORE_ALL_FILES_UPLOAD' \| 'CUSTOM_BEFORE_UPLOAD'`
+waiting-upload-files-change | `(context: { files: Array, trigger: 'validate' \| 'remove' \| 'uploaded' })` | 待上传文件列表发生变化时触发。`context.files` 表示事件参数为待上传文件,`context.trigger` 引起此次变化的触发来源 ### UploadFile diff --git a/src/upload/upload.tsx b/src/upload/upload.tsx index a76db870d..5fd27ee8e 100644 --- a/src/upload/upload.tsx +++ b/src/upload/upload.tsx @@ -1,23 +1,21 @@ -import { defineComponent, ref, computed } from 'vue'; +import { defineComponent, ref, computed, TransitionGroup } from 'vue'; import type { InputHTMLAttributes } from 'vue'; import { AddIcon, LoadingIcon, CloseIcon, CloseCircleIcon } from 'tdesign-icons-vue-next'; import { isBoolean } from 'lodash-es'; import TImage from '../image'; import TImageViewer from '../image-viewer'; -import { TdUploadProps, UploadFile } from './type'; +import { TdUploadProps, UploadFile, UploadRemoveContext } from './type'; import UploadProps from './props'; import config from '../config'; import useUpload from './hooks/useUpload'; -import { useTNodeJSX, useContent } from '../hooks/tnode'; +import useDrag from './hooks/useDrag'; +import { useTNodeJSX } from '../hooks/tnode'; import { usePrefixClass, useConfig } from '../hooks/useClass'; const { prefix } = config; export default defineComponent({ name: `${prefix}-upload`, - components: { - TImage, - }, props: UploadProps, emits: [ 'update:files', @@ -46,14 +44,39 @@ export default defineComponent({ onNormalFileChange, onInnerRemove, cancelUpload, + setUploadValue, } = useUpload(props); const renderTNodeJSX = useTNodeJSX(); const showViewer = ref(false); const initialIndex = ref(0); + const uploadListRef = ref(); + + const { onDragstart, onDragover, onDragend, onTouchstart, onTouchmove, dragIndex, getFileId } = useDrag( + props, + setUploadValue, + uploadClass, + uploadListRef, + ); + + const onDragSortStart = (e: DragEvent, index: number) => { + onDragstart(e, index); + props.onDrag?.(); + }; + + const onTouchDragStart = (e: TouchEvent, index: number) => { + onTouchstart(e, index); + props.onDrag?.(); + }; + + const onDragSortEnd = () => { + onDragend(); + props.onDrop?.(displayFiles.value); + }; const handlePreview = (e: MouseEvent, file: UploadFile, index: number) => { + if (dragIndex.value !== -1) return; initialIndex.value = index; showViewer.value = props.preview; props.onPreview?.({ @@ -109,13 +132,14 @@ export default defineComponent({ const addBtnNode = renderTNodeJSX('addBtn', ); const addContentNode = renderTNodeJSX('addContent'); return ( -
+
{
{addContentNode || addBtnNode}
}
); } return null; }; + expose({ upload: inputRef.value, uploading, @@ -124,29 +148,58 @@ export default defineComponent({ cancelUpload, uploadFilePercent, }); + return () => { + const children = displayFiles.value.map((file, index) => ( +
onDragSortStart(e, index)} + onDragover={(e: DragEvent) => onDragover(e, index, displayFiles.value)} + onDragend={() => { + onDragSortEnd(); + }} + onDrop={(e: DragEvent) => { + e.preventDefault(); + onDragSortEnd(); + }} + onTouchstart={(e: TouchEvent) => onTouchDragStart(e, index)} + onTouchmove={(e: TouchEvent) => onTouchmove(e, displayFiles.value)} + onTouchend={() => { + onDragSortEnd(); + }} + onTouchcancel={() => { + onDragSortEnd(); + }} + > + {file.url && ( + handlePreview(e, file, index)} + /> + )} + {renderStatus(file)} + {(isBoolean(file.removeBtn) ? file.removeBtn : props.removeBtn) && ( + onInnerRemove({ e, file, index })} + /> + )} +
+ )); + return ( -
- {displayFiles.value.map((file, index) => ( -
- {file.url && ( - handlePreview(e, file, index)} - /> - )} - {renderStatus(file)} - {(isBoolean(file.removeBtn) ? file.removeBtn : props.removeBtn) && ( - onInnerRemove({ e, file, index })} - /> - )} -
- ))} +
+ {children} {renderAddContent()}