mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [upload] support async data (#14015)
* feat(components): [upload] support async data * feat(components): [upload] support async data * fix(components): [upload] unit test * fix(components): [upload] unit test * feat(components): [upload] update data docs * fix(components): [upload] unit test * chore: add version --------- Co-authored-by: Hefty <yeyuqiudeng@gmail.com>
This commit is contained in:
@@ -99,7 +99,7 @@ upload/manual
|
||||
| `headers` | request headers. | `Headers \| Record<string, any>` | — | No |
|
||||
| `method` | set upload request method. | `string` | `'post'` | No |
|
||||
| `multiple` | whether uploading multiple files is permitted. | `boolean` | `false` | No |
|
||||
| `data` | additions options of request. | `Record<string, any>` | — | No |
|
||||
| `data` | additions options of request. support `Awaitable` data and `Function` since v3.3.13 | `Record<string, any> \| Awaitable<Record<string, any>> \| ((rawFile: UploadRawFile) => Awaitable<Record<string, any>>)` | — | No |
|
||||
| `name` | key name for uploaded file. | `string` | `'file'` | No |
|
||||
| `with-credentials` | whether cookies are sent. | `boolean` | `false` | No |
|
||||
| `show-file-list` | whether to show the uploaded file list. | `boolean` | `true` | No |
|
||||
|
||||
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest'
|
||||
import { EVENT_CODE } from '@element-plus/constants'
|
||||
import Upload from '../src/upload.vue'
|
||||
import UploadContent from '../src/upload-content.vue'
|
||||
import type { UploadRawFile } from '../src/upload'
|
||||
|
||||
const AXIOM = 'Rem is the best girl'
|
||||
|
||||
@@ -203,6 +204,117 @@ describe('<upload />', () => {
|
||||
expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
|
||||
})
|
||||
|
||||
test('data support receive promise', async () => {
|
||||
const onSuccess = vi.fn()
|
||||
const onError = vi.fn()
|
||||
const onRemove = vi.fn()
|
||||
let requestData: any = {}
|
||||
|
||||
const httpRequest = vi.fn((val) => {
|
||||
requestData = val?.data
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
const data = ref(Promise.resolve({ type: 'promise' }))
|
||||
|
||||
const wrapper = mount(() => (
|
||||
<UploadContent
|
||||
data={data.value}
|
||||
multiple={true}
|
||||
httpRequest={httpRequest}
|
||||
onSuccess={onSuccess}
|
||||
onError={onError}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))
|
||||
|
||||
const fileList = [new File(['content'], 'test-file.txt')]
|
||||
mockGetFile(wrapper.find('input').element, fileList)
|
||||
|
||||
await wrapper.find('input').trigger('change')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(requestData).toEqual(await data.value)
|
||||
expect(onSuccess).toHaveBeenCalled()
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
|
||||
vi.clearAllMocks()
|
||||
|
||||
data.value = Promise.reject({ type: 'error promise' })
|
||||
await expect(data.value).rejects.toThrowError(undefined)
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
await wrapper.find('input').trigger('change')
|
||||
|
||||
await flushPromises()
|
||||
expect(onSuccess).not.toHaveBeenCalled()
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
expect(onRemove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('data support receive function', async () => {
|
||||
const keyList: string[] = []
|
||||
const httpRequest = vi.fn((val) => {
|
||||
keyList.push(val?.data?.key)
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
const data = vi.fn((file: UploadRawFile) => ({ key: file.name }))
|
||||
|
||||
const wrapper = mount(() => (
|
||||
<UploadContent data={data} multiple={true} httpRequest={httpRequest} />
|
||||
))
|
||||
|
||||
const fileList = [
|
||||
new File(['content'], 'test-file.txt'),
|
||||
new File(['content2'], 'test-file2.txt'),
|
||||
]
|
||||
mockGetFile(wrapper.find('input').element, fileList)
|
||||
|
||||
await wrapper.find('input').trigger('change')
|
||||
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
await nextTick()
|
||||
|
||||
expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
|
||||
})
|
||||
|
||||
test('data support receive async function', async () => {
|
||||
const keyList: string[] = []
|
||||
const httpRequest = vi.fn(() => Promise.resolve())
|
||||
|
||||
const dataFN = vi.fn(async (file: UploadRawFile) => {
|
||||
return new Promise((resolve) => {
|
||||
keyList.push(file.name)
|
||||
resolve({ key: file.name })
|
||||
})
|
||||
})
|
||||
|
||||
const wrapper = mount(() => (
|
||||
<UploadContent
|
||||
data={dataFN}
|
||||
multiple={true}
|
||||
httpRequest={httpRequest}
|
||||
/>
|
||||
))
|
||||
|
||||
const fileList = [
|
||||
new File(['content'], 'test-file.txt'),
|
||||
new File(['content2'], 'test-file2.txt'),
|
||||
]
|
||||
mockGetFile(wrapper.find('input').element, fileList)
|
||||
|
||||
await wrapper.find('input').trigger('change')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(dataFN).toHaveBeenCalledTimes(2)
|
||||
|
||||
expect(keyList).toEqual(['test-file.txt', 'test-file2.txt'])
|
||||
})
|
||||
|
||||
test('upload files and save keyList', async () => {
|
||||
const keyList: string[] = []
|
||||
const beforeUpload = vi.fn((file: File) => {
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef } from 'vue'
|
||||
import { isObject } from '@vue/shared'
|
||||
import { isPlainObject } from '@vue/shared'
|
||||
import { cloneDeep, isEqual } from 'lodash-unified'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { entriesOf } from '@element-plus/utils'
|
||||
import { entriesOf, isFunction } from '@element-plus/utils'
|
||||
import { useFormDisabled } from '@element-plus/components/form'
|
||||
import UploadDragger from './upload-dragger.vue'
|
||||
import { uploadContentProps } from './upload-content'
|
||||
@@ -81,7 +81,7 @@ const uploadFiles = (files: File[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const upload = async (rawFile: UploadRawFile) => {
|
||||
const upload = async (rawFile: UploadRawFile): Promise<void> => {
|
||||
inputRef.value!.value = ''
|
||||
|
||||
if (!props.beforeUpload) {
|
||||
@@ -95,9 +95,9 @@ const upload = async (rawFile: UploadRawFile) => {
|
||||
// origin data: Handle data changes after synchronization tasks are executed
|
||||
const originData = props.data
|
||||
const beforeUploadPromise = props.beforeUpload(rawFile)
|
||||
beforeData = isObject(props.data) ? cloneDeep(props.data) : props.data
|
||||
beforeData = isPlainObject(props.data) ? cloneDeep(props.data) : props.data
|
||||
hookResult = await beforeUploadPromise
|
||||
if (isObject(props.data) && isEqual(originData, beforeData)) {
|
||||
if (isPlainObject(props.data) && isEqual(originData, beforeData)) {
|
||||
beforeData = cloneDeep(props.data)
|
||||
}
|
||||
} catch {
|
||||
@@ -128,7 +128,18 @@ const upload = async (rawFile: UploadRawFile) => {
|
||||
)
|
||||
}
|
||||
|
||||
const doUpload = (
|
||||
const resolveData = async (
|
||||
data: UploadContentProps['data'],
|
||||
rawFile: UploadRawFile
|
||||
): Promise<Record<string, any>> => {
|
||||
if (isFunction(data)) {
|
||||
return data(rawFile)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const doUpload = async (
|
||||
rawFile: UploadRawFile,
|
||||
beforeData?: UploadContentProps['data']
|
||||
) => {
|
||||
@@ -145,12 +156,19 @@ const doUpload = (
|
||||
httpRequest,
|
||||
} = props
|
||||
|
||||
try {
|
||||
beforeData = await resolveData(beforeData ?? data, rawFile)
|
||||
} catch {
|
||||
props.onRemove(rawFile)
|
||||
return
|
||||
}
|
||||
|
||||
const { uid } = rawFile
|
||||
const options: UploadRequestOptions = {
|
||||
headers: headers || {},
|
||||
withCredentials,
|
||||
file: rawFile,
|
||||
data: beforeData ?? data,
|
||||
data: beforeData,
|
||||
method,
|
||||
filename,
|
||||
action,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { NOOP } from '@vue/shared'
|
||||
import { buildProps, definePropType, mutable } from '@element-plus/utils'
|
||||
import { ajaxUpload } from './ajax'
|
||||
import type { Awaitable, Mutable } from '@element-plus/utils'
|
||||
|
||||
import type { UploadAjaxError } from './ajax'
|
||||
import type { Awaitable } from '@element-plus/utils'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type Upload from './upload.vue'
|
||||
|
||||
@@ -78,6 +78,8 @@ export interface UploadHooks {
|
||||
onExceed: (files: File[], uploadFiles: UploadUserFile[]) => void
|
||||
}
|
||||
|
||||
export type UploadData = Mutable<Record<string, any>>
|
||||
|
||||
export const uploadBaseProps = buildProps({
|
||||
action: {
|
||||
type: String,
|
||||
@@ -91,7 +93,10 @@ export const uploadBaseProps = buildProps({
|
||||
default: 'post',
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
type: definePropType<
|
||||
| Awaitable<UploadData>
|
||||
| ((rawFile: UploadRawFile) => Awaitable<UploadData>)
|
||||
>([Object, Function, Promise]),
|
||||
default: () => mutable({} as const),
|
||||
},
|
||||
multiple: {
|
||||
|
||||
Reference in New Issue
Block a user