Files
element-plus/packages/components/upload/src/upload-content.vue
Jianjun Yu d96555fa9c 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>
2023-09-08 15:57:05 +08:00

226 lines
4.9 KiB
Vue

<template>
<div
:class="[ns.b(), ns.m(listType), ns.is('drag', drag)]"
tabindex="0"
@click="handleClick"
@keydown.self.enter.space="handleKeydown"
>
<template v-if="drag">
<upload-dragger :disabled="disabled" @file="uploadFiles">
<slot />
</upload-dragger>
</template>
<template v-else>
<slot />
</template>
<input
ref="inputRef"
:class="ns.e('input')"
:name="name"
:multiple="multiple"
:accept="accept"
type="file"
@change="handleChange"
@click.stop
/>
</div>
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue'
import { isPlainObject } from '@vue/shared'
import { cloneDeep, isEqual } from 'lodash-unified'
import { useNamespace } from '@element-plus/hooks'
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'
import { genFileId } from './upload'
import type { UploadContentProps } from './upload-content'
import type {
UploadFile,
UploadHooks,
UploadRawFile,
UploadRequestOptions,
} from './upload'
defineOptions({
name: 'ElUploadContent',
inheritAttrs: false,
})
const props = defineProps(uploadContentProps)
const ns = useNamespace('upload')
const disabled = useFormDisabled()
const requests = shallowRef<Record<string, XMLHttpRequest | Promise<unknown>>>(
{}
)
const inputRef = shallowRef<HTMLInputElement>()
const uploadFiles = (files: File[]) => {
if (files.length === 0) return
const { autoUpload, limit, fileList, multiple, onStart, onExceed } = props
if (limit && fileList.length + files.length > limit) {
onExceed(files, fileList)
return
}
if (!multiple) {
files = files.slice(0, 1)
}
for (const file of files) {
const rawFile = file as UploadRawFile
rawFile.uid = genFileId()
onStart(rawFile)
if (autoUpload) upload(rawFile)
}
}
const upload = async (rawFile: UploadRawFile): Promise<void> => {
inputRef.value!.value = ''
if (!props.beforeUpload) {
return doUpload(rawFile)
}
let hookResult: Exclude<ReturnType<UploadHooks['beforeUpload']>, Promise<any>>
let beforeData: UploadContentProps['data'] = {}
try {
// origin data: Handle data changes after synchronization tasks are executed
const originData = props.data
const beforeUploadPromise = props.beforeUpload(rawFile)
beforeData = isPlainObject(props.data) ? cloneDeep(props.data) : props.data
hookResult = await beforeUploadPromise
if (isPlainObject(props.data) && isEqual(originData, beforeData)) {
beforeData = cloneDeep(props.data)
}
} catch {
hookResult = false
}
if (hookResult === false) {
props.onRemove(rawFile)
return
}
let file: File = rawFile
if (hookResult instanceof Blob) {
if (hookResult instanceof File) {
file = hookResult
} else {
file = new File([hookResult], rawFile.name, {
type: rawFile.type,
})
}
}
doUpload(
Object.assign(file, {
uid: rawFile.uid,
}),
beforeData
)
}
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']
) => {
const {
headers,
data,
method,
withCredentials,
name: filename,
action,
onProgress,
onSuccess,
onError,
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,
method,
filename,
action,
onProgress: (evt) => {
onProgress(evt, rawFile)
},
onSuccess: (res) => {
onSuccess(res, rawFile)
delete requests.value[uid]
},
onError: (err) => {
onError(err, rawFile)
delete requests.value[uid]
},
}
const request = httpRequest(options)
requests.value[uid] = request
if (request instanceof Promise) {
request.then(options.onSuccess, options.onError)
}
}
const handleChange = (e: Event) => {
const files = (e.target as HTMLInputElement).files
if (!files) return
uploadFiles(Array.from(files))
}
const handleClick = () => {
if (!disabled.value) {
inputRef.value!.value = ''
inputRef.value!.click()
}
}
const handleKeydown = () => {
handleClick()
}
const abort = (file?: UploadFile) => {
const _reqs = entriesOf(requests.value).filter(
file ? ([uid]) => String(file.uid) === uid : () => true
)
_reqs.forEach(([uid, req]) => {
if (req instanceof XMLHttpRequest) req.abort()
delete requests.value[uid]
})
}
defineExpose({
abort,
upload,
})
</script>