feat: add download transformFile previewFile actio

pull/1845/head
tangjinzhou 2020-02-23 20:18:20 +08:00
parent 3e8e90da5e
commit 548595959d
19 changed files with 619 additions and 250 deletions

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
dev: { dev: {
componentName: 'select', // dev components componentName: 'upload', // dev components
}, },
}; };

View File

@ -1,6 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import findIndex from 'lodash/findIndex'; import findIndex from 'lodash/findIndex';
import pick from 'lodash/pick';
import VcUpload from '../vc-upload'; import VcUpload from '../vc-upload';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import { getOptionProps, initDefaultProps, hasProp, getListeners } from '../_util/props-util'; import { getOptionProps, initDefaultProps, hasProp, getListeners } from '../_util/props-util';
@ -66,25 +67,12 @@ export default {
fileList: nextFileList, fileList: nextFileList,
}); });
// fix ie progress // fix ie progress
if (!window.FormData) { if (!window.File || process.env.TEST_IE) {
this.autoUpdateProgress(0, targetItem); this.autoUpdateProgress(0, targetItem);
} }
}, },
autoUpdateProgress(_, file) {
const getPercent = genPercentAdd(); onSuccess(response, file, xhr) {
let curPercent = 0;
this.clearProgressTimer();
this.progressTimer = setInterval(() => {
curPercent = getPercent(curPercent);
this.onProgress(
{
percent: curPercent * 100,
},
file,
);
}, 200);
},
onSuccess(response, file) {
this.clearProgressTimer(); this.clearProgressTimer();
try { try {
if (typeof response === 'string') { if (typeof response === 'string') {
@ -101,6 +89,7 @@ export default {
} }
targetItem.status = 'done'; targetItem.status = 'done';
targetItem.response = response; targetItem.response = response;
targetItem.xhr = xhr;
this.onChange({ this.onChange({
file: { ...targetItem }, file: { ...targetItem },
fileList, fileList,
@ -140,19 +129,24 @@ export default {
this.$emit('reject', fileList); this.$emit('reject', fileList);
}, },
handleRemove(file) { handleRemove(file) {
const { remove } = this; const { remove: onRemove } = this;
const { status } = file; const { sFileList: fileList } = this.$data;
file.status = 'removed'; // eslint-disable-line
Promise.resolve(typeof remove === 'function' ? remove(file) : remove).then(ret => { Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => {
// Prevent removing file // Prevent removing file
if (ret === false) { if (ret === false) {
file.status = status;
return; return;
} }
const removedFileList = removeFileItem(file, this.sFileList); const removedFileList = removeFileItem(file, fileList);
if (removedFileList) { if (removedFileList) {
file.status = 'removed'; // eslint-disable-line
if (this.upload) {
this.upload.abort(file);
}
this.onChange({ this.onChange({
file, file,
fileList: removedFileList, fileList: removedFileList,
@ -178,14 +172,16 @@ export default {
}); });
}, },
reBeforeUpload(file, fileList) { reBeforeUpload(file, fileList) {
if (!this.beforeUpload) { const { beforeUpload } = this.$props;
const { sFileList: stateFileList } = this.$data;
if (!beforeUpload) {
return true; return true;
} }
const result = this.beforeUpload(file, fileList); const result = beforeUpload(file, fileList);
if (result === false) { if (result === false) {
this.onChange({ this.onChange({
file, file,
fileList: uniqBy(this.sFileList.concat(fileList.map(fileToObject)), item => item.uid), fileList: uniqBy(stateFileList.concat(fileList.map(fileToObject)), item => item.uid),
}); });
return false; return false;
} }
@ -197,25 +193,45 @@ export default {
clearProgressTimer() { clearProgressTimer() {
clearInterval(this.progressTimer); clearInterval(this.progressTimer);
}, },
autoUpdateProgress(_, file) {
const getPercent = genPercentAdd();
let curPercent = 0;
this.clearProgressTimer();
this.progressTimer = setInterval(() => {
curPercent = getPercent(curPercent);
this.onProgress(
{
percent: curPercent * 100,
},
file,
);
}, 200);
},
renderUploadList(locale) { renderUploadList(locale) {
const { showUploadList = {}, listType } = getOptionProps(this); const {
const { showRemoveIcon, showPreviewIcon } = showUploadList; showUploadList = {},
listType,
previewFile,
disabled,
locale: propLocale,
} = getOptionProps(this);
const { showRemoveIcon, showPreviewIcon, showDownloadIcon } = showUploadList;
const { sFileList: fileList } = this.$data;
const uploadListProps = { const uploadListProps = {
props: { props: {
listType, listType,
items: this.sFileList, items: fileList,
showRemoveIcon, previewFile,
showRemoveIcon: !disabled && showRemoveIcon,
showPreviewIcon, showPreviewIcon,
locale: { ...locale, ...this.$props.locale }, showDownloadIcon,
locale: { ...locale, ...propLocale },
}, },
on: { on: {
remove: this.handleManualRemove, remove: this.handleManualRemove,
...pick(getListeners(this), ['download', 'preview']), // uploadlist
}, },
}; };
const listeners = getListeners(this);
if (listeners.preview) {
uploadListProps.on.preview = listeners.preview;
}
return <UploadList {...uploadListProps} />; return <UploadList {...uploadListProps} />;
}, },
}, },
@ -227,7 +243,7 @@ export default {
type, type,
disabled, disabled,
} = getOptionProps(this); } = getOptionProps(this);
const { sFileList: fileList, dragState } = this.$data;
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('upload', customizePrefixCls); const prefixCls = getPrefixCls('upload', customizePrefixCls);
@ -261,8 +277,8 @@ export default {
if (type === 'drag') { if (type === 'drag') {
const dragCls = classNames(prefixCls, { const dragCls = classNames(prefixCls, {
[`${prefixCls}-drag`]: true, [`${prefixCls}-drag`]: true,
[`${prefixCls}-drag-uploading`]: this.sFileList.some(file => file.status === 'uploading'), [`${prefixCls}-drag-uploading`]: fileList.some(file => file.status === 'uploading'),
[`${prefixCls}-drag-hover`]: this.dragState === 'dragover', [`${prefixCls}-drag-hover`]: dragState === 'dragover',
[`${prefixCls}-disabled`]: disabled, [`${prefixCls}-disabled`]: disabled,
}); });
return ( return (
@ -290,7 +306,7 @@ export default {
// Remove id to avoid open by label when trigger is hidden // Remove id to avoid open by label when trigger is hidden
// https://github.com/ant-design/ant-design/issues/14298 // https://github.com/ant-design/ant-design/issues/14298
if (!children) { if (!children || disabled) {
delete vcUploadProps.props.id; delete vcUploadProps.props.id;
} }
@ -302,7 +318,7 @@ export default {
if (listType === 'picture-card') { if (listType === 'picture-card') {
return ( return (
<span> <span class={`${prefixCls}-picture-card-wrapper`}>
{uploadList} {uploadList}
{uploadButton} {uploadButton}
</span> </span>

View File

@ -2,51 +2,13 @@ import BaseMixin from '../_util/BaseMixin';
import { getOptionProps, initDefaultProps, getListeners } from '../_util/props-util'; import { getOptionProps, initDefaultProps, getListeners } from '../_util/props-util';
import getTransitionProps from '../_util/getTransitionProps'; import getTransitionProps from '../_util/getTransitionProps';
import { ConfigConsumerProps } from '../config-provider'; import { ConfigConsumerProps } from '../config-provider';
import { previewImage, isImageUrl } from './utils';
import Icon from '../icon'; import Icon from '../icon';
import Tooltip from '../tooltip'; import Tooltip from '../tooltip';
import Progress from '../progress'; import Progress from '../progress';
import classNames from 'classnames'; import classNames from 'classnames';
import { UploadListProps } from './interface'; import { UploadListProps } from './interface';
const imageTypes = ['image', 'webp', 'png', 'svg', 'gif', 'jpg', 'jpeg', 'bmp', 'dpg', 'ico'];
// https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
const previewFile = (file, callback) => {
if (file.type && !imageTypes.includes(file.type)) {
callback('');
}
const reader = new window.FileReader();
reader.onloadend = () => callback(reader.result);
reader.readAsDataURL(file);
};
const extname = url => {
if (!url) {
return '';
}
const temp = url.split('/');
const filename = temp[temp.length - 1];
const filenameWithoutSuffix = filename.split(/#|\?/)[0];
return (/\.[^./\\]*$/.exec(filenameWithoutSuffix) || [''])[0];
};
const isImageUrl = file => {
if (imageTypes.includes(file.type)) {
return true;
}
const url = file.thumbUrl || file.url;
const extension = extname(url);
if (/^data:image\//.test(url) || /(webp|svg|png|gif|jpg|jpeg|bmp|dpg|ico)$/i.test(extension)) {
return true;
} else if (/^data:/.test(url)) {
// other file types of base64
return false;
} else if (extension) {
// other file types which have extension
return false;
}
return true;
};
export default { export default {
name: 'AUploadList', name: 'AUploadList',
mixins: [BaseMixin], mixins: [BaseMixin],
@ -57,43 +19,43 @@ export default {
showInfo: false, showInfo: false,
}, },
showRemoveIcon: true, showRemoveIcon: true,
showDownloadIcon: true,
showPreviewIcon: true, showPreviewIcon: true,
previewFile: previewImage,
}), }),
inject: { inject: {
configProvider: { default: () => ConfigConsumerProps }, configProvider: { default: () => ConfigConsumerProps },
}, },
updated() { updated() {
this.$nextTick(() => { this.$nextTick(() => {
if (this.listType !== 'picture' && this.listType !== 'picture-card') { const { listType, items, previewFile } = this.$props;
if (listType !== 'picture' && listType !== 'picture-card') {
return; return;
} }
(this.items || []).forEach(file => { (items || []).forEach(file => {
if ( if (
typeof document === 'undefined' || typeof document === 'undefined' ||
typeof window === 'undefined' || typeof window === 'undefined' ||
!window.FileReader || !window.FileReader ||
!window.File || !window.File ||
!(file.originFileObj instanceof window.File) || !(file.originFileObj instanceof File || file.originFileObj instanceof Blob) ||
file.thumbUrl !== undefined file.thumbUrl !== undefined
) { ) {
return; return;
} }
/*eslint-disable */ /*eslint-disable */
file.thumbUrl = ''; file.thumbUrl = '';
/*eslint -enable */ if (previewFile) {
previewFile(file.originFileObj, previewDataUrl => { previewFile(file.originFileObj).then(previewDataUrl => {
// Need append '' to avoid dead loop // Need append '' to avoid dead loop
file.thumbUrl = previewDataUrl || ''; file.thumbUrl = previewDataUrl || '';
/*eslint -enable */
this.$forceUpdate(); this.$forceUpdate();
}); });
}
}); });
}); });
}, },
methods: { methods: {
handleClose(file) {
this.$emit('remove', file);
},
handlePreview(file, e) { handlePreview(file, e) {
const { preview } = getListeners(this); const { preview } = getListeners(this);
if (!preview) { if (!preview) {
@ -102,6 +64,18 @@ export default {
e.preventDefault(); e.preventDefault();
return this.$emit('preview', file); return this.$emit('preview', file);
}, },
handleDownload(file) {
const { download } = getListeners(this);
if (typeof download === 'function') {
download(file);
} else if (file.url) {
window.open(file.url);
}
},
handleClose(file) {
this.$emit('remove', file);
},
}, },
render() { render() {
const { const {
@ -110,7 +84,9 @@ export default {
listType, listType,
showPreviewIcon, showPreviewIcon,
showRemoveIcon, showRemoveIcon,
showDownloadIcon,
locale, locale,
progressAttr,
} = getOptionProps(this); } = getOptionProps(this);
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('upload', customizePrefixCls); const prefixCls = getPrefixCls('upload', customizePrefixCls);
@ -126,7 +102,11 @@ export default {
icon = <Icon class={`${prefixCls}-list-item-thumbnail`} type="picture" theme="twoTone" />; icon = <Icon class={`${prefixCls}-list-item-thumbnail`} type="picture" theme="twoTone" />;
} else { } else {
const thumbnail = isImageUrl(file) ? ( const thumbnail = isImageUrl(file) ? (
<img src={file.thumbUrl || file.url} alt={file.name} /> <img
src={file.thumbUrl || file.url}
alt={file.name}
class={`${prefixCls}-list-item-image`}
/>
) : ( ) : (
<Icon type="file" class={`${prefixCls}-list-item-icon`} theme="twoTone" /> <Icon type="file" class={`${prefixCls}-list-item-icon`} theme="twoTone" />
); );
@ -147,7 +127,7 @@ export default {
if (file.status === 'uploading') { if (file.status === 'uploading') {
const progressProps = { const progressProps = {
props: { props: {
...this.progressAttr, ...progressAttr,
type: 'line', type: 'line',
percent: file.percent, percent: file.percent,
}, },
@ -164,30 +144,64 @@ export default {
const infoUploadingClass = classNames({ const infoUploadingClass = classNames({
[`${prefixCls}-list-item`]: true, [`${prefixCls}-list-item`]: true,
[`${prefixCls}-list-item-${file.status}`]: true, [`${prefixCls}-list-item-${file.status}`]: true,
[`${prefixCls}-list-item-list-type-${listType}`]: true,
}); });
const linkProps = const linkProps =
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps; typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
const preview = file.url ? (
const removeIcon = showRemoveIcon ? (
<Icon type="delete" title={locale.removeFile} onClick={() => this.handleClose(file)} />
) : null;
const downloadIcon =
showDownloadIcon && file.status === 'done' ? (
<Icon
type="download"
title={locale.downloadFile}
onClick={() => this.handleDownload(file)}
/>
) : null;
const downloadOrDelete = listType !== 'picture-card' && (
<span
key="download-delete"
class={`${prefixCls}-list-item-card-actions ${listType === 'picture' ? 'picture' : ''}`}
>
{downloadIcon && <a title={locale.downloadFile}>{downloadIcon}</a>}
{removeIcon && <a title={locale.removeFile}>{removeIcon}</a>}
</span>
);
const listItemNameClass = classNames({
[`${prefixCls}-list-item-name`]: true,
[`${prefixCls}-list-item-name-icon-count-${
[downloadIcon, removeIcon].filter(x => x).length
}`]: true,
});
const preview = file.url
? [
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class={`${prefixCls}-list-item-name`} class={listItemNameClass}
title={file.name} title={file.name}
{...linkProps} {...linkProps}
href={file.url} href={file.url}
onClick={e => this.handlePreview(file, e)} onClick={e => this.handlePreview(file, e)}
> >
{file.name} {file.name}
</a> </a>,
) : ( downloadOrDelete,
]
: [
<span <span
key="view"
class={`${prefixCls}-list-item-name`} class={`${prefixCls}-list-item-name`}
onClick={e => this.handlePreview(file, e)} onClick={e => this.handlePreview(file, e)}
title={file.name} title={file.name}
> >
{file.name} {file.name}
</span> </span>,
); downloadOrDelete,
];
const style = const style =
file.url || file.thumbUrl file.url || file.thumbUrl
? undefined ? undefined
@ -207,28 +221,12 @@ export default {
<Icon type="eye-o" /> <Icon type="eye-o" />
</a> </a>
) : null; ) : null;
const iconProps = { const actions = listType === 'picture-card' && file.status !== 'uploading' && (
props: {
type: 'delete',
title: locale.removeFile,
},
on: {
click: () => {
this.handleClose(file);
},
},
};
const iconProps1 = { ...iconProps, ...{ props: { type: 'close' } } };
const removeIcon = showRemoveIcon ? <Icon {...iconProps} /> : null;
const removeIconClose = showRemoveIcon ? <Icon {...iconProps1} /> : null;
const actions =
listType === 'picture-card' && file.status !== 'uploading' ? (
<span class={`${prefixCls}-list-item-actions`}> <span class={`${prefixCls}-list-item-actions`}>
{previewIcon} {previewIcon}
{file.status === 'done' && downloadIcon}
{removeIcon} {removeIcon}
</span> </span>
) : (
removeIconClose
); );
let message; let message;
if (file.response && typeof file.response === 'string') { if (file.response && typeof file.response === 'string') {
@ -236,26 +234,28 @@ export default {
} else { } else {
message = (file.error && file.error.statusText) || locale.uploadError; message = (file.error && file.error.statusText) || locale.uploadError;
} }
const iconAndPreview = const iconAndPreview = (
file.status === 'error' ? (
<Tooltip title={message}>
{icon}
{preview}
</Tooltip>
) : (
<span> <span>
{icon} {icon}
{preview} {preview}
</span> </span>
); );
const transitionProps = getTransitionProps('fade'); const transitionProps = getTransitionProps('fade');
return ( const dom = (
<div class={infoUploadingClass} key={file.uid}> <div class={infoUploadingClass} key={file.uid}>
<div class={`${prefixCls}-list-item-info`}>{iconAndPreview}</div> <div class={`${prefixCls}-list-item-info`}>{iconAndPreview}</div>
{actions} {actions}
<transition {...transitionProps}>{progress}</transition> <transition {...transitionProps}>{progress}</transition>
</div> </div>
); );
const listContainerNameClass = classNames({
[`${prefixCls}-list-picture-card-container`]: listType === 'picture-card',
});
return (
<div key={file.uid} class={listContainerNameClass}>
{file.status === 'error' ? <Tooltip title={message}>{dom}</Tooltip> : <span>{dom}</span>}
</div>
);
}); });
const listClassNames = classNames({ const listClassNames = classNames({
[`${prefixCls}-list`]: true, [`${prefixCls}-list`]: true,

View File

@ -56,15 +56,15 @@ The return value of function `beforeUpload` can be a Promise to check asynchrono
} }
}, },
beforeUpload(file) { beforeUpload(file) {
const isJPG = file.type === 'image/jpeg'; const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJPG) { if (!isJpgOrPng) {
this.$message.error('You can only upload JPG file!'); this.$message.error('You can only upload JPG file!');
} }
const isLt2M = file.size / 1024 / 1024 < 2; const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) { if (!isLt2M) {
this.$message.error('Image must smaller than 2MB!'); this.$message.error('Image must smaller than 2MB!');
} }
return isJPG && isLt2M; return isJpgOrPng && isLt2M;
}, },
}, },
}; };

View File

@ -0,0 +1,46 @@
<cn>
#### 点击上传
经典款式,用户点击按钮弹出文件选择框。
</cn>
<us>
#### Upload by clicking
Classic mode. File selection dialog pops up when upload button is clicked.
</us>
```tpl
<template>
<a-upload
name="file"
:multiple="true"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:headers="headers"
@change="handleChange"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</a-upload>
</template>
<script>
export default {
data() {
return {
headers: {
authorization: 'authorization-text',
},
};
},
methods: {
handleChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
this.$message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} file upload failed.`);
}
},
},
};
</script>
```

View File

@ -3,7 +3,6 @@
使用 `fileList` 对列表进行完全控制,可以实现各种自定义功能,以下演示三种情况: 使用 `fileList` 对列表进行完全控制,可以实现各种自定义功能,以下演示三种情况:
1) 上传列表数量的限制。 1) 上传列表数量的限制。
2) 读取远程路径并显示链接。 2) 读取远程路径并显示链接。
3) 按照服务器返回信息筛选成功上传的文件。
</cn> </cn>
<us> <us>
@ -11,7 +10,6 @@
You can gain full control over filelist by configuring `fileList`. You can accomplish all kinds of customed functions. The following shows three circumstances: You can gain full control over filelist by configuring `fileList`. You can accomplish all kinds of customed functions. The following shows three circumstances:
1) limit the number of uploaded files. 1) limit the number of uploaded files.
2) read from response and show file link. 2) read from response and show file link.
3) filter successfully uploaded files according to response from server.
</us> </us>
```tpl ```tpl

View File

@ -8,6 +8,8 @@ import Drag from './drag.md';
import PictureStyle from './picture-style.md'; import PictureStyle from './picture-style.md';
import UploadManually from './upload-manually.md'; import UploadManually from './upload-manually.md';
import Directory from './directory.md'; import Directory from './directory.md';
import PreviewFile from './preview-file';
import TransformFile from './transform-file';
import CN from '../index.zh-CN.md'; import CN from '../index.zh-CN.md';
import US from '../index.en-US.md'; import US from '../index.en-US.md';
@ -52,6 +54,8 @@ export default {
<PictureStyle /> <PictureStyle />
<UploadManually /> <UploadManually />
<Directory /> <Directory />
<PreviewFile />
<TransformFile />
<api> <api>
<template slot="cn"> <template slot="cn">
<CN /> <CN />

View File

@ -18,7 +18,7 @@ After users upload picture, the thumbnail will be shown in list. The upload butt
@preview="handlePreview" @preview="handlePreview"
@change="handleChange" @change="handleChange"
> >
<div v-if="fileList.length < 3"> <div v-if="fileList.length < 8">
<a-icon type="plus" /> <a-icon type="plus" />
<div class="ant-upload-text">Upload</div> <div class="ant-upload-text">Upload</div>
</div> </div>
@ -29,6 +29,14 @@ After users upload picture, the thumbnail will be shown in list. The upload butt
</div> </div>
</template> </template>
<script> <script>
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
export default { export default {
data() { data() {
return { return {
@ -37,10 +45,33 @@ After users upload picture, the thumbnail will be shown in list. The upload butt
fileList: [ fileList: [
{ {
uid: '-1', uid: '-1',
name: 'xxx.png', name: 'image.png',
status: 'done', status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}, },
{
uid: '-2',
name: 'image.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-3',
name: 'image.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-4',
name: 'image.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-5',
name: 'image.png',
status: 'error',
},
], ],
}; };
}, },
@ -48,8 +79,11 @@ After users upload picture, the thumbnail will be shown in list. The upload butt
handleCancel() { handleCancel() {
this.previewVisible = false; this.previewVisible = false;
}, },
handlePreview(file) { async handlePreview(file) {
this.previewImage = file.url || file.thumbUrl; if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.previewImage = file.url || file.preview;
this.previewVisible = true; this.previewVisible = true;
}, },
handleChange({ fileList }) { handleChange({ fileList }) {

View File

@ -0,0 +1,39 @@
<cn>
#### 自定义预览
自定义本地预览,用于处理非图片格式文件(例如视频文件)。
</cn>
<us>
#### Customize preview file
Customize local preview. Can handle with non-image format files such as video.
</us>
```tpl
<template>
<div>
<a-upload
listType="picture"
action="//jsonplaceholder.typicode.com/posts/"
:previewFile="previewFile"
>
<a-button> <a-icon type="upload" /> Upload </a-button>
</a-upload>
</div>
</template>
<script>
export default {
methods: {
previewFile(file) {
console.log('Your upload file:', file);
// Your process logic. Here we just mock to the same file
return fetch('https://next.json-generator.com/api/json/get/4ytyBoLK8', {
method: 'POST',
body: file,
})
.then(res => res.json())
.then(({ thumbnail }) => thumbnail);
},
},
};
</script>
```

View File

@ -0,0 +1,48 @@
<cn>
#### 上传前转换文件
使用 `transformFile` 转换上传的文件(例如添加水印)。
</cn>
<us>
#### Transform file before request
Use `transformFile` for transform file before request such as add a watermark.
</us>
```tpl
<template>
<div>
<a-upload
listType="picture"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:transformFile="transformFile"
>
<a-button> <a-icon type="upload" /> Upload </a-button>
</a-upload>
</div>
</template>
<script>
export default {
methods: {
transformFile(file) {
return new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const canvas = document.createElement('canvas');
const img = document.createElement('img');
img.src = reader.result;
img.onload = () => {
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
ctx.fillStyle = 'red';
ctx.textBaseline = 'middle';
ctx.fillText('Ant Design', 20, 20);
canvas.toBlob(resolve);
};
};
});
},
},
};
</script>
```

View File

@ -1,33 +1,37 @@
## API ## API
| Property | Description | Type | Default | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| accept | File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - | | accept | File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - | |
| action | Uploading URL | string\|(file) => `Promise` | - | | action | Uploading URL | string\|(file) => `Promise` | - | |
| directory | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false | | method | http method of upload request | string | 'post' | 1.5.0 |
| beforeUpload | Hook function which will be executed before uploading. Uploading will be stopped with `false` or a rejected Promise returned. **Warningthis function is not supported in IE9**。 | (file, fileList) => `boolean | Promise` | - | | directory | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false | |
| customRequest | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest | Function | - | | beforeUpload | Hook function which will be executed before uploading. Uploading will be stopped with `false` or a rejected Promise returned. **Warningthis function is not supported in IE9**。 | (file, fileList) => `boolean | Promise` | - | |
| data | Uploading params or function which can return uploading params. | object\|function(file) | - | | customRequest | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest | Function | - | |
| defaultFileList | Default list of files that have been uploaded. | object\[] | - | | data | Uploading params or function which can return uploading params. | object\|function(file) | - | |
| disabled | disable upload button | boolean | false | | defaultFileList | Default list of files that have been uploaded. | object\[] | - | |
| fileList | List of files that have been uploaded (controlled). Here is a common issue [#2423](https://github.com/ant-design/ant-design/issues/2423) when using it | object\[] | - | | disabled | disable upload button | boolean | false | |
| headers | Set request headers, valid above IE10. | object | - | | fileList | List of files that have been uploaded (controlled). Here is a common issue [#2423](https://github.com/ant-design/ant-design/issues/2423) when using it | object\[] | - | |
| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | 'text' | | headers | Set request headers, valid above IE10. | object | - | |
| multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | | listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | 'text' | |
| name | The name of uploading file | string | 'file' | | multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | | name | The name of uploading file | string | 'file' | |
| supportServerRender | Need to be turned on while the server side is rendering. | boolean | false | | previewFile | Customize preview file logic | (file: File \| Blob) => Promise<dataURL: string> | - | 1.5.0 |
| withCredentials | ajax upload with cookie sent | boolean | false | | showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| openFileDialogOnClick | click open file dialog | boolean | true | | supportServerRender | Need to be turned on while the server side is rendering. | boolean | false | |
| remove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is `false` or a Promise which resolve(false) or reject. | Function(file): `boolean | Promise` | - | | withCredentials | ajax upload with cookie sent | boolean | false | |
| openFileDialogOnClick | click open file dialog | boolean | true | |
| remove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is `false` or a Promise which resolve(false) or reject. | Function(file): `boolean | Promise` | - | |
| transformFile   | Customize transform file before request | Function(file): `string | Blob | File | Promise<string | Blob | File>` | - | 1.5.0 |
### events ### events
| Events Name | Description | Arguments | | Events Name | Description | Arguments | Version |
| --- | --- | --- | | --- | --- | --- | --- |
| change | A callback function, can be executed when uploading state is changing. See [change](#change) | Function | - | | change | A callback function, can be executed when uploading state is changing. See [change](#change) | Function | - | |
| preview | A callback function, will be executed when file link or preview icon is clicked. | Function(file) | - | | preview | A callback function, will be executed when file link or preview icon is clicked. | Function(file) | - | |
| reject | A callback function, will be executed when drop files is not accept. | Function(fileList) | - | | download | Click the method to download the file, pass the method to perform the method logic, do not pass the default jump to the new TAB. | Function(file): void | Jump to new TAB | 1.5.0 |
| reject | A callback function, will be executed when drop files is not accept. | Function(fileList) | - | |
### change ### change
@ -48,10 +52,11 @@ When uploading state change, it returns:
```js ```js
{ {
uid: 'uid', // unique identifier, negative is recommend, to prevent interference with internal generated id uid: 'uid', // unique identifier, negative is recommend, to prevent interference with internal generated id
name: 'xx.png' // file name name: 'xx.png', // file name
status: 'done', // optionsuploading, done, error, removed status: 'done', // optionsuploading, done, error, removed
response: '{"status": "success"}', // response from server response: '{"status": "success"}', // response from server
linkProps: '{"download": "image"}', // additional html props of file link linkProps: '{"download": "image"}', // additional html props of file link
xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header
} }
``` ```

View File

@ -1,33 +1,37 @@
## API ## API
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | 无 | | accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | 无 | |
| action | 上传的地址 | string\|(file) => `Promise` | 无 | | action | 上传的地址 | string\|(file) => `Promise` | 无 | |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | | method | 上传请求的 http method | string | 'post' | 1.5.0 |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。**注意IE9 不支持该方法**。 | (file, fileList) => `boolean | Promise` | 无 | | directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | Function | 无 | | beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。**注意IE9 不支持该方法**。 | (file, fileList) => `boolean | Promise` | 无 | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | 无 | | customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | Function | 无 | |
| defaultFileList | 默认已经上传的文件列表 | object\[] | 无 | | data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | 无 | |
| disabled | 是否禁用 | boolean | false | | defaultFileList | 默认已经上传的文件列表 | object\[] | 无 | |
| fileList | 已经上传的文件列表(受控) | object\[] | 无 | | disabled | 是否禁用 | boolean | false | |
| headers | 设置上传的请求头部IE10 以上有效 | object | 无 | | fileList | 已经上传的文件列表(受控) | object\[] | 无 | |
| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | 'text' | | headers | 设置上传的请求头部IE10 以上有效 | object | 无 | |
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | | listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | 'text' | |
| name | 发到后台的文件参数名 | string | 'file' | | multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | |
| showUploadList | 是否展示 uploadList, 可设为一个对象,用于单独设定 showPreviewIcon 和 showRemoveIcon | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | | name | 发到后台的文件参数名 | string | 'file' | |
| supportServerRender | 服务端渲染时需要打开这个 | boolean | false | | previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise<dataURL: string> | 无 | 1.5.0 |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | | showUploadList | 是否展示 uploadList, 可设为一个对象,用于单独设定 showPreviewIcon 和 showRemoveIcon | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | | supportServerRender | 服务端渲染时需要打开这个 | boolean | false | |
| remove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除。               | Function(file): `boolean | Promise` | 无   | | withCredentials | 上传请求时是否携带 cookie | boolean | false | |
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | |
| remove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除。               | Function(file): `boolean | Promise` | 无   | |
| transformFile   | 在上传之前转换文件。支持返回一个 Promise 对象   | Function(file): `string | Blob | File | Promise<string | Blob | File>` | 无   | 1.5.0 |
### 事件 ### 事件
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 | 版本 |
| -------- | -------------------------------------------- | ------------------ | | --- | --- | --- | --- |
| change | 上传文件改变时的状态,详见 [change](#change) | Function | 无 | | change | 上传文件改变时的状态,详见 [change](#change) | Function | 无 | |
| preview | 点击文件链接或预览图标时的回调 | Function(file) | 无 | | preview | 点击文件链接或预览图标时的回调 | Function(file) | 无 | |
| reject | 拖拽文件不符合 accept 类型时的回调 | Function(fileList) | 无 | | download | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页。 | Function(file): void | 跳转新标签页 | 1.5.0 |
| reject | 拖拽文件不符合 accept 类型时的回调 | Function(fileList) | 无 | |
### change ### change
@ -48,10 +52,11 @@
```js ```js
{ {
uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突 uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
name: 'xx.png' // 文件名 name: 'xx.png', // 文件名
status: 'done', // 状态有uploading done error removed status: 'done', // 状态有uploading done error removed
response: '{"status": "success"}', // 服务端响应内容 response: '{"status": "success"}', // 服务端响应内容
linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性 linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header
} }
``` ```

View File

@ -54,6 +54,7 @@ export const ShowUploadListInterface = PropsTypes.shape({
export const UploadLocale = PropsTypes.shape({ export const UploadLocale = PropsTypes.shape({
uploading: PropsTypes.string, uploading: PropsTypes.string,
removeFile: PropsTypes.string, removeFile: PropsTypes.string,
downloadFile: PropsTypes.string,
uploadError: PropsTypes.string, uploadError: PropsTypes.string,
previewFile: PropsTypes.string, previewFile: PropsTypes.string,
}).loose; }).loose;
@ -66,6 +67,7 @@ export const UploadProps = {
action: PropsTypes.oneOfType([PropsTypes.string, PropsTypes.func]), action: PropsTypes.oneOfType([PropsTypes.string, PropsTypes.func]),
directory: PropsTypes.bool, directory: PropsTypes.bool,
data: PropsTypes.oneOfType([PropsTypes.object, PropsTypes.func]), data: PropsTypes.oneOfType([PropsTypes.object, PropsTypes.func]),
method: PropsTypes.oneOf(['POST', 'PUT', 'post', 'put']),
headers: PropsTypes.object, headers: PropsTypes.object,
showUploadList: PropsTypes.oneOfType([PropsTypes.bool, ShowUploadListInterface]), showUploadList: PropsTypes.oneOfType([PropsTypes.bool, ShowUploadListInterface]),
multiple: PropsTypes.bool, multiple: PropsTypes.bool,
@ -86,6 +88,8 @@ export const UploadProps = {
locale: UploadLocale, locale: UploadLocale,
height: PropsTypes.number, height: PropsTypes.number,
id: PropsTypes.string, id: PropsTypes.string,
previewFile: PropsTypes.func,
transformFile: PropsTypes.func,
}; };
export const UploadState = { export const UploadState = {
@ -103,6 +107,8 @@ export const UploadListProps = {
progressAttr: PropsTypes.object, progressAttr: PropsTypes.object,
prefixCls: PropsTypes.string, prefixCls: PropsTypes.string,
showRemoveIcon: PropsTypes.bool, showRemoveIcon: PropsTypes.bool,
showDownloadIcon: PropsTypes.bool,
showPreviewIcon: PropsTypes.bool, showPreviewIcon: PropsTypes.bool,
locale: UploadLocale, locale: UploadLocale,
previewFile: PropsTypes.func,
}; };

View File

@ -54,3 +54,77 @@ export function removeFileItem(file, fileList) {
} }
return removed; return removed;
} }
// ==================== Default Image Preview ====================
const extname = (url = '') => {
const temp = url.split('/');
const filename = temp[temp.length - 1];
const filenameWithoutSuffix = filename.split(/#|\?/)[0];
return (/\.[^./\\]*$/.exec(filenameWithoutSuffix) || [''])[0];
};
const isImageFileType = type => !!type && type.indexOf('image/') === 0;
export const isImageUrl = file => {
if (isImageFileType(file.type)) {
return true;
}
const url = file.thumbUrl || file.url;
const extension = extname(url);
if (
/^data:image\//.test(url) ||
/(webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico)$/i.test(extension)
) {
return true;
}
if (/^data:/.test(url)) {
// other file types of base64
return false;
}
if (extension) {
// other file types which have extension
return false;
}
return true;
};
const MEASURE_SIZE = 200;
export function previewImage(file) {
return new Promise(resolve => {
if (!isImageFileType(file.type)) {
resolve('');
return;
}
const canvas = document.createElement('canvas');
canvas.width = MEASURE_SIZE;
canvas.height = MEASURE_SIZE;
canvas.style.cssText = `position: fixed; left: 0; top: 0; width: ${MEASURE_SIZE}px; height: ${MEASURE_SIZE}px; z-index: 9999; display: none;`;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
const { width, height } = img;
let drawWidth = MEASURE_SIZE;
let drawHeight = MEASURE_SIZE;
let offsetX = 0;
let offsetY = 0;
if (width < height) {
drawHeight = height * (MEASURE_SIZE / width);
offsetY = -(drawHeight - drawWidth) / 2;
} else {
drawWidth = width * (MEASURE_SIZE / height);
offsetX = -(drawWidth - drawHeight) / 2;
}
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
const dataURL = canvas.toDataURL();
document.body.removeChild(canvas);
resolve(dataURL);
};
img.src = window.URL.createObjectURL(file);
});
}

View File

@ -1,4 +1,4 @@
// rc-upload 2.6.3 // rc-upload 2.9.4
import upload from './src'; import upload from './src';
export default upload; export default upload;

View File

@ -28,6 +28,7 @@ const upLoadPropTypes = {
// onProgress: PropTypes.func, // onProgress: PropTypes.func,
withCredentials: PropTypes.bool, withCredentials: PropTypes.bool,
openFileDialogOnClick: PropTypes.bool, openFileDialogOnClick: PropTypes.bool,
transformFile: PropTypes.func,
}; };
const AjaxUploader = { const AjaxUploader = {
@ -67,6 +68,7 @@ const AjaxUploader = {
} }
}, },
onFileDrop(e) { onFileDrop(e) {
const { multiple } = this.$props;
e.preventDefault(); e.preventDefault();
if (e.type === 'dragover') { if (e.type === 'dragover') {
return; return;
@ -76,19 +78,29 @@ const AjaxUploader = {
attrAccept(_file, this.accept), attrAccept(_file, this.accept),
); );
} else { } else {
const files = partition(Array.prototype.slice.call(e.dataTransfer.files), file => let files = partition(Array.prototype.slice.call(e.dataTransfer.files), file =>
attrAccept(file, this.accept), attrAccept(file, this.accept),
); );
this.uploadFiles(files[0]); let successFiles = files[0];
if (files[1].length) { const errorFiles = files[1];
this.$emit('reject', files[1]); if (multiple === false) {
successFiles = successFiles.slice(0, 1);
}
this.uploadFiles(successFiles);
if (errorFiles.length) {
this.$emit('reject', errorFiles);
} }
} }
}, },
uploadFiles(files) { uploadFiles(files) {
const postFiles = Array.prototype.slice.call(files); const postFiles = Array.prototype.slice.call(files);
postFiles.forEach(file => { postFiles
.map(file => {
file.uid = getUid(); file.uid = getUid();
return file;
})
.forEach(file => {
this.upload(file, postFiles); this.upload(file, postFiles);
}); });
}, },
@ -119,10 +131,10 @@ const AjaxUploader = {
if (!this._isMounted) { if (!this._isMounted) {
return; return;
} }
let { data } = this.$props; const { $props: props } = this;
if (typeof data === 'function') { let { data } = props;
data = data(file); const { transformFile = originFile => originFile } = props;
}
new Promise(resolve => { new Promise(resolve => {
const { action } = this; const { action } = this;
if (typeof action === 'function') { if (typeof action === 'function') {
@ -132,13 +144,22 @@ const AjaxUploader = {
}).then(action => { }).then(action => {
const { uid } = file; const { uid } = file;
const request = this.customRequest || defaultRequest; const request = this.customRequest || defaultRequest;
this.reqs[uid] = request({ const transform = Promise.resolve(transformFile(file)).catch(e => {
console.error(e); // eslint-disable-line no-console
});
transform.then(transformedFile => {
if (typeof data === 'function') {
data = data(file);
}
const requestOption = {
action, action,
filename: this.name, filename: this.name,
file,
data, data,
file: transformedFile,
headers: this.headers, headers: this.headers,
withCredentials: this.withCredentials, withCredentials: this.withCredentials,
method: props.method || 'post',
onProgress: e => { onProgress: e => {
this.$emit('progress', e, file); this.$emit('progress', e, file);
}, },
@ -150,9 +171,11 @@ const AjaxUploader = {
delete this.reqs[uid]; delete this.reqs[uid];
this.$emit('error', err, ret, file); this.$emit('error', err, ret, file);
}, },
}); };
this.reqs[uid] = request(requestOption);
this.$emit('start', file); this.$emit('start', file);
}); });
});
}, },
reset() { reset() {
this.setState({ this.setState({
@ -166,13 +189,13 @@ const AjaxUploader = {
if (file && file.uid) { if (file && file.uid) {
uid = file.uid; uid = file.uid;
} }
if (reqs[uid]) { if (reqs[uid] && reqs[uid].abort) {
reqs[uid].abort(); reqs[uid].abort();
delete reqs[uid];
} }
delete reqs[uid];
} else { } else {
Object.keys(reqs).forEach(uid => { Object.keys(reqs).forEach(uid => {
if (reqs[uid]) { if (reqs[uid] && reqs[uid].abort) {
reqs[uid].abort(); reqs[uid].abort();
} }
@ -201,7 +224,7 @@ const AjaxUploader = {
? {} ? {}
: { : {
click: openFileDialogOnClick ? this.onClick : () => {}, click: openFileDialogOnClick ? this.onClick : () => {},
keydown: this.onKeyDown, keydown: openFileDialogOnClick ? this.onKeyDown : () => {},
drop: this.onFileDrop, drop: this.onFileDrop,
dragover: this.onFileDrop, dragover: this.onFileDrop,
}; };
@ -222,6 +245,7 @@ const AjaxUploader = {
id={$attrs.id} id={$attrs.id}
type="file" type="file"
ref="fileInputRef" ref="fileInputRef"
onClick={e => e.stopPropagation()} // https://github.com/ant-design/ant-design/issues/19948
key={this.uid} key={this.uid}
style={{ display: 'none' }} style={{ display: 'none' }}
accept={accept} accept={accept}

View File

@ -1,8 +1,8 @@
function getError(option, xhr) { function getError(option, xhr) {
const msg = `cannot post ${option.action} ${xhr.status}'`; const msg = `cannot ${option.method} ${option.action} ${xhr.status}'`;
const err = new Error(msg); const err = new Error(msg);
err.status = xhr.status; err.status = xhr.status;
err.method = 'post'; err.method = option.method;
err.url = option.action; err.url = option.action;
return err; return err;
} }
@ -46,7 +46,18 @@ export default function upload(option) {
const formData = new window.FormData(); const formData = new window.FormData();
if (option.data) { if (option.data) {
Object.keys(option.data).map(key => { Object.keys(option.data).forEach(key => {
const value = option.data[key];
// support key-value array data
if (Array.isArray(value)) {
value.forEach(item => {
// { list: [ 11, 22 ] }
// formData.append('list[]', 11);
formData.append(`${key}[]`, item);
});
return;
}
formData.append(key, option.data[key]); formData.append(key, option.data[key]);
}); });
} }
@ -67,7 +78,7 @@ export default function upload(option) {
option.onSuccess(getBody(xhr), xhr); option.onSuccess(getBody(xhr), xhr);
}; };
xhr.open('post', option.action, true); xhr.open(option.method, option.action, true);
// Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179 // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
if (option.withCredentials && 'withCredentials' in xhr) { if (option.withCredentials && 'withCredentials' in xhr) {

View File

@ -27,6 +27,20 @@ const traverseFileTree = (files, callback, isAccepted) => {
if (item.isFile) { if (item.isFile) {
item.file(file => { item.file(file => {
if (isAccepted(file)) { if (isAccepted(file)) {
// https://github.com/ant-design/ant-design/issues/16426
if (item.fullPath && !file.webkitRelativePath) {
Object.defineProperties(file, {
webkitRelativePath: {
writable: true,
},
});
file.webkitRelativePath = item.fullPath.replace(/^\//, '');
Object.defineProperties(file, {
webkitRelativePath: {
writable: false,
},
});
}
callback([file]); callback([file]);
} }
}); });

49
types/upload.d.ts vendored
View File

@ -4,16 +4,54 @@
import { AntdComponent } from './component'; import { AntdComponent } from './component';
export interface UploadFile { export interface VcFile extends File {
uid: string | number; uid: string;
readonly lastModifiedDate: Date;
readonly webkitRelativePath: string;
}
export interface UploadFile<T = any> {
uid: string;
size: number;
name: string; name: string;
fileName?: string;
lastModified?: number;
lastModifiedDate?: Date;
url?: string;
status?: UploadFileStatus;
percent?: number;
thumbUrl?: string;
originFileObj?: File | Blob;
response?: T;
error?: any;
linkProps?: any;
type: string;
xhr?: T;
preview?: string;
} }
export interface ShowUploadList { export interface ShowUploadList {
showRemoveIcon?: boolean; showRemoveIcon?: boolean;
showPreviewIcon?: boolean; showPreviewIcon?: boolean;
showDownloadIcon?: boolean;
} }
export interface UploadLocale {
uploading?: string;
removeFile?: string;
downloadFile?: string;
uploadError?: string;
previewFile?: string;
}
export type UploadType = 'drag' | 'select';
export type UploadListType = 'text' | 'picture' | 'picture-card';
type PreviewFileHandler = (file: File | Blob) => PromiseLike<string>;
type TransformFileHandler = (
file: VcFile,
) => string | Blob | File | PromiseLike<string | Blob | File>;
export declare class Upload extends AntdComponent { export declare class Upload extends AntdComponent {
static Dragger: typeof Upload; static Dragger: typeof Upload;
@ -56,6 +94,8 @@ export declare class Upload extends AntdComponent {
*/ */
data: object | Function; data: object | Function;
method?: 'POST' | 'PUT' | 'post' | 'put';
/** /**
* Default list of files that have been uploaded. * Default list of files that have been uploaded.
* @type UploadFile[] * @type UploadFile[]
@ -137,4 +177,9 @@ export declare class Upload extends AntdComponent {
* @type Function * @type Function
*/ */
remove: (file: any) => boolean | Promise<boolean>; remove: (file: any) => boolean | Promise<boolean>;
locale?: UploadLocale;
id?: string;
previewFile?: PreviewFileHandler;
transformFile?: TransformFileHandler;
} }