refactor: vc-upload

refactor-upload
tangjinzhou 2022-02-21 22:36:54 +08:00
parent b0c3677d50
commit 67f5226cdc
32 changed files with 4095 additions and 0 deletions

View File

@ -965,6 +965,10 @@
@typography-title-margin-top: 1.2em;
@typography-title-margin-bottom: 0.5em;
// Upload
// ---
@upload-actions-color: @text-color-secondary;
// Image
// ---
@image-size-base: 48px;

View File

@ -0,0 +1,22 @@
import { defineComponent } from 'vue';
import Upload from './Upload';
import { uploadProps } from './interface';
export default defineComponent({
name: 'AUploadDragger',
inheritAttrs: false,
props: uploadProps(),
setup(props, { slots, attrs }) {
return () => {
const { height, ...restProps } = props;
const { style, ...restAttrs } = attrs;
const draggerProps = {
...restProps,
...restAttrs,
type: 'drag',
style: { ...(style as any), height: typeof height === 'number' ? `${height}px` : height },
} as any;
return <Upload {...draggerProps} v-slots={slots}></Upload>;
};
},
});

View File

@ -0,0 +1,342 @@
import classNames from '../_util/classNames';
import uniqBy from 'lodash-es/uniqBy';
import findIndex from 'lodash-es/findIndex';
import VcUpload from '../vc-upload';
import BaseMixin from '../_util/BaseMixin';
import { getOptionProps, hasProp, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale-provider/default';
import { defaultConfigProvider } from '../config-provider';
import Dragger from './Dragger';
import UploadList from './UploadList';
import type { UploadFile } from './interface';
import { uploadProps } from './interface';
import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils';
import { defineComponent, inject } from 'vue';
import { getDataAndAriaProps } from '../_util/util';
import { useInjectFormItemContext } from '../form/FormItemContext';
export default defineComponent({
name: 'AUpload',
mixins: [BaseMixin],
inheritAttrs: false,
Dragger,
props: initDefaultProps(uploadProps, {
type: 'select',
multiple: false,
action: '',
data: {},
accept: '',
beforeUpload: T,
showUploadList: true,
listType: 'text', // or pictrue
disabled: false,
supportServerRender: true,
}),
setup() {
const formItemContext = useInjectFormItemContext();
return {
upload: null,
progressTimer: null,
configProvider: inject('configProvider', defaultConfigProvider),
formItemContext,
};
},
// recentUploadStatus: boolean | PromiseLike<any>;
data() {
return {
sFileList: this.fileList || this.defaultFileList || [],
dragState: 'drop',
};
},
watch: {
fileList(val) {
this.sFileList = val || [];
},
},
beforeUnmount() {
this.clearProgressTimer();
},
methods: {
onStart(file) {
const targetItem = fileToObject(file);
targetItem.status = 'uploading';
const nextFileList = this.sFileList.concat();
const fileIndex = findIndex(nextFileList, ({ uid }) => uid === targetItem.uid);
if (fileIndex === -1) {
nextFileList.push(targetItem);
} else {
nextFileList[fileIndex] = targetItem;
}
this.handleChange({
file: targetItem,
fileList: nextFileList,
});
// fix ie progress
if (!window.File || (typeof process === 'object' && process.env.TEST_IE)) {
this.autoUpdateProgress(0, targetItem);
}
},
onSuccess(response, file, xhr) {
this.clearProgressTimer();
try {
if (typeof response === 'string') {
response = JSON.parse(response);
}
} catch (e) {
/* do nothing */
}
const fileList = this.sFileList;
const targetItem = getFileItem(file, fileList);
// removed
if (!targetItem) {
return;
}
targetItem.status = 'done';
targetItem.response = response;
targetItem.xhr = xhr;
this.handleChange({
file: { ...targetItem },
fileList,
});
},
onProgress(e, file) {
const fileList = this.sFileList;
const targetItem = getFileItem(file, fileList);
// removed
if (!targetItem) {
return;
}
targetItem.percent = e.percent;
this.handleChange({
event: e,
file: { ...targetItem },
fileList: this.sFileList,
});
},
onError(error, response, file) {
this.clearProgressTimer();
const fileList = this.sFileList;
const targetItem = getFileItem(file, fileList);
// removed
if (!targetItem) {
return;
}
targetItem.error = error;
targetItem.response = response;
targetItem.status = 'error';
this.handleChange({
file: { ...targetItem },
fileList,
});
},
onReject(fileList) {
this.$emit('reject', fileList);
},
handleRemove(file) {
const { remove: onRemove } = this;
const { sFileList: fileList } = this.$data;
Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => {
// Prevent removing file
if (ret === false) {
return;
}
const removedFileList = removeFileItem(file, fileList);
if (removedFileList) {
file.status = 'removed'; // eslint-disable-line
if (this.upload) {
this.upload.abort(file);
}
this.handleChange({
file,
fileList: removedFileList,
});
}
});
},
handleManualRemove(file) {
if (this.$refs.uploadRef) {
(this.$refs.uploadRef as any).abort(file);
}
this.handleRemove(file);
},
handleChange(info) {
if (!hasProp(this, 'fileList')) {
this.setState({ sFileList: info.fileList });
}
this.$emit('update:fileList', info.fileList);
this.$emit('change', info);
this.formItemContext.onFieldChange();
},
onFileDrop(e) {
this.setState({
dragState: e.type,
});
},
reBeforeUpload(file, fileList) {
const { beforeUpload } = this.$props;
const { sFileList: stateFileList } = this.$data;
if (!beforeUpload) {
return true;
}
const result = beforeUpload(file, fileList);
if (result === false) {
this.handleChange({
file,
fileList: uniqBy(
stateFileList.concat(fileList.map(fileToObject)),
(item: UploadFile) => item.uid,
),
});
return false;
}
if (result && result.then) {
return result;
}
return true;
},
clearProgressTimer() {
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) {
const {
showUploadList = {},
listType,
previewFile,
disabled,
locale: propLocale,
} = getOptionProps(this);
const { showRemoveIcon, showPreviewIcon, showDownloadIcon } = showUploadList;
const { sFileList: fileList } = this.$data;
const { onDownload, onPreview } = this.$props;
const uploadListProps = {
listType,
items: fileList,
previewFile,
showRemoveIcon: !disabled && showRemoveIcon,
showPreviewIcon,
showDownloadIcon,
locale: { ...locale, ...propLocale },
onRemove: this.handleManualRemove,
onDownload,
onPreview,
};
return <UploadList {...uploadListProps} />;
},
},
render() {
const {
prefixCls: customizePrefixCls,
showUploadList,
listType,
type,
disabled,
} = getOptionProps(this);
const { sFileList: fileList, dragState } = this.$data;
const { class: className, style } = this.$attrs;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('upload', customizePrefixCls);
const vcUploadProps = {
...this.$props,
id: this.$props.id ?? this.formItemContext.id.value,
prefixCls,
beforeUpload: this.reBeforeUpload,
onStart: this.onStart,
onError: this.onError,
onProgress: this.onProgress,
onSuccess: this.onSuccess,
onReject: this.onReject,
ref: 'uploadRef',
};
const uploadList = showUploadList ? (
<LocaleReceiver
componentName="Upload"
defaultLocale={defaultLocale.Upload}
children={this.renderUploadList}
/>
) : null;
const children = getSlot(this);
if (type === 'drag') {
const dragCls = classNames(prefixCls, {
[`${prefixCls}-drag`]: true,
[`${prefixCls}-drag-uploading`]: fileList.some((file: any) => file.status === 'uploading'),
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
[`${prefixCls}-disabled`]: disabled,
});
return (
<span class={className} {...getDataAndAriaProps(this.$attrs)}>
<div
class={dragCls}
onDrop={this.onFileDrop}
onDragover={this.onFileDrop}
onDragleave={this.onFileDrop}
style={style}
>
<VcUpload {...vcUploadProps} class={`${prefixCls}-btn`}>
<div class={`${prefixCls}-drag-container`}>{children}</div>
</VcUpload>
</div>
{uploadList}
</span>
);
}
const uploadButtonCls = classNames(prefixCls, {
[`${prefixCls}-select`]: true,
[`${prefixCls}-select-${listType}`]: true,
[`${prefixCls}-disabled`]: disabled,
});
// Remove id to avoid open by label when trigger is hidden
// https://github.com/ant-design/ant-design/issues/14298
if (!children.length || disabled) {
delete vcUploadProps.id;
}
const uploadButton = (
<div class={uploadButtonCls} style={children.length ? undefined : { display: 'none' }}>
<VcUpload {...vcUploadProps}>{children}</VcUpload>
</div>
);
if (listType === 'picture-card') {
return (
<span class={classNames(`${prefixCls}-picture-card-wrapper`, className)}>
{uploadList}
{uploadButton}
</span>
);
}
return (
<span class={className}>
{uploadButton}
{uploadList}
</span>
);
},
});

View File

@ -0,0 +1,287 @@
import { computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue';
import type { ExtractPropTypes, PropType, CSSProperties } from 'vue';
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
import DownloadOutlined from '@ant-design/icons-vue/DownloadOutlined';
import Tooltip from '../../tooltip';
import Progress from '../../progress';
import type {
ItemRender,
UploadFile,
UploadListProgressProps,
UploadListType,
UploadLocale,
} from '../interface';
import type { VueNode } from '../../_util/type';
import useConfigInject from '../../_util/hooks/useConfigInject';
import Transition, { getTransitionProps } from '../../_util/transition';
export const listItemProps = () => {
return {
prefixCls: String,
locale: { type: Object as PropType<UploadLocale>, default: undefined as UploadLocale },
file: Object as PropType<UploadFile>,
items: Array as PropType<UploadFile[]>,
listType: String as PropType<UploadListType>,
isImgUrl: Function as PropType<(file: UploadFile) => boolean>,
showRemoveIcon: Boolean,
showDownloadIcon: Boolean,
showPreviewIcon: Boolean,
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
iconRender: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
actionIconRender: Function as PropType<
(opt: {
customIcon: VueNode;
callback: () => void;
prefixCls: string;
title?: string | undefined;
}) => VueNode
>,
itemRender: Function as PropType<ItemRender>,
onPreview: Function as PropType<(file: UploadFile, e: Event) => void>,
onClose: Function as PropType<(file: UploadFile) => void>,
onDownload: Function as PropType<(file: UploadFile) => void>,
progress: Object as PropType<UploadListProgressProps>,
};
};
export type ListItemProps = Partial<ExtractPropTypes<ReturnType<typeof listItemProps>>>;
export default defineComponent({
name: 'ListItem',
inheritAttrs: false,
props: listItemProps(),
setup(props, { slots, attrs }) {
const showProgress = ref(false);
const progressRafRef = ref();
onMounted(() => {
progressRafRef.value = setTimeout(() => {
showProgress.value = true;
}, 300);
});
onBeforeUnmount(() => {
clearTimeout(progressRafRef.value);
});
const { rootPrefixCls } = useConfigInject('upload', props);
const transitionProps = computed(() => getTransitionProps(`${rootPrefixCls.value}-fade`));
return () => {
const {
prefixCls,
locale,
listType,
file,
items,
progress: progressProps,
iconRender,
actionIconRender,
itemRender = slots.itemRender,
isImgUrl,
showPreviewIcon,
showRemoveIcon,
showDownloadIcon,
previewIcon: customPreviewIcon = slots.previewIcon,
removeIcon: customRemoveIcon = slots.removeIcon,
downloadIcon: customDownloadIcon = slots.downloadIcon,
onPreview,
onDownload,
onClose,
} = props;
const { class: className, style } = attrs;
// This is used for legacy span make scrollHeight the wrong value.
// We will force these to be `display: block` with non `picture-card`
const spanClassName = `${prefixCls}-span`;
const iconNode = iconRender({ file });
let icon = <div class={`${prefixCls}-text-icon`}>{iconNode}</div>;
if (listType === 'picture' || listType === 'picture-card') {
if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) {
const uploadingClassName = {
[`${prefixCls}-list-item-thumbnail`]: true,
[`${prefixCls}-list-item-file`]: file.status !== 'uploading',
};
icon = <div class={uploadingClassName}>{iconNode}</div>;
} else {
const thumbnail = isImgUrl?.(file) ? (
<img
src={file.thumbUrl || file.url}
alt={file.name}
class={`${prefixCls}-list-item-image`}
/>
) : (
iconNode
);
const aClassName = {
[`${prefixCls}-list-item-thumbnail`]: true,
[`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file),
};
icon = (
<a
class={aClassName}
onClick={e => onPreview(file, e)}
href={file.url || file.thumbUrl}
target="_blank"
rel="noopener noreferrer"
>
{thumbnail}
</a>
);
}
}
const infoUploadingClass = {
[`${prefixCls}-list-item`]: true,
[`${prefixCls}-list-item-${file.status}`]: true,
[`${prefixCls}-list-item-list-type-${listType}`]: true,
};
const linkProps =
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
const removeIcon = showRemoveIcon
? actionIconRender({
customIcon: customRemoveIcon ? customRemoveIcon({ file }) : <DeleteOutlined />,
callback: () => onClose(file),
prefixCls,
title: locale.removeFile,
})
: null;
const downloadIcon =
showDownloadIcon && file.status === 'done'
? actionIconRender({
customIcon: customDownloadIcon ? customDownloadIcon({ file }) : <DownloadOutlined />,
callback: () => onDownload(file),
prefixCls,
title: locale.downloadFile,
})
: null;
const downloadOrDelete = listType !== 'picture-card' && (
<span
key="download-delete"
class={[
`${prefixCls}-list-item-card-actions`,
{
picture: listType === 'picture',
},
]}
>
{downloadIcon}
{removeIcon}
</span>
);
const listItemNameClass = `${prefixCls}-list-item-name`;
const preview = file.url
? [
<a
key="view"
target="_blank"
rel="noopener noreferrer"
class={listItemNameClass}
title={file.name}
{...linkProps}
href={file.url}
onClick={e => onPreview(file, e)}
>
{file.name}
</a>,
downloadOrDelete,
]
: [
<span
key="view"
class={listItemNameClass}
onClick={e => onPreview(file, e)}
title={file.name}
>
{file.name}
</span>,
downloadOrDelete,
];
const previewStyle: CSSProperties = {
pointerEvents: 'none',
opacity: 0.5,
};
const previewIcon = showPreviewIcon ? (
<a
href={file.url || file.thumbUrl}
target="_blank"
rel="noopener noreferrer"
style={file.url || file.thumbUrl ? undefined : previewStyle}
onClick={e => onPreview(file, e)}
title={locale.previewFile}
>
{customPreviewIcon ? customPreviewIcon({ file }) : <EyeOutlined />}
</a>
) : null;
const actions = listType === 'picture-card' && file.status !== 'uploading' && (
<span class={`${prefixCls}-list-item-actions`}>
{previewIcon}
{file.status === 'done' && downloadIcon}
{removeIcon}
</span>
);
let message;
if (file.response && typeof file.response === 'string') {
message = file.response;
} else {
message = file.error?.statusText || file.error?.message || locale.uploadError;
}
const iconAndPreview = (
<span class={spanClassName}>
{icon}
{preview}
</span>
);
const dom = (
<div class={infoUploadingClass}>
<div class={`${prefixCls}-list-item-info`}>{iconAndPreview}</div>
{actions}
{showProgress.value && (
<Transition {...transitionProps.value}>
<div v-show={file.status === 'uploading'} class={`${prefixCls}-list-item-progress`}>
{'percent' in file ? (
<Progress {...progressProps} type="line" percent={file.percent} />
) : null}
</div>
</Transition>
)}
</div>
);
const listContainerNameClass = {
[`${prefixCls}-list-${listType}-container`]: true,
[`${className}`]: !!className,
};
const item =
file.status === 'error' ? (
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
{dom}
</Tooltip>
) : (
dom
);
return (
<div class={listContainerNameClass} style={style} ref={ref}>
{itemRender
? itemRender({
originNode: item,
file,
fileList: items,
actions: {
download: onDownload.bind(null, file),
preview: onPreview.bind(null, file),
remove: onClose.bind(null, file),
},
})
: item}
</div>
);
};
},
});

View File

@ -0,0 +1,261 @@
import * as React from 'react';
import CSSMotion, { CSSMotionList, CSSMotionListProps } from 'rc-motion';
import classNames from 'classnames';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import PaperClipOutlined from '@ant-design/icons/PaperClipOutlined';
import PictureTwoTone from '@ant-design/icons/PictureTwoTone';
import FileTwoTone from '@ant-design/icons/FileTwoTone';
import { cloneElement, isValidElement } from '../../_util/reactNode';
import { UploadListProps, UploadFile, UploadListType, InternalUploadFile } from '../interface';
import { previewImage, isImageUrl } from '../utils';
import collapseMotion from '../../_util/motion';
import { ConfigContext } from '../../config-provider';
import Button, { ButtonProps } from '../../button';
import useForceUpdate from '../../_util/hooks/useForceUpdate';
import ListItem from './ListItem';
const listItemMotion: Partial<CSSMotionListProps> = {
...collapseMotion,
};
delete listItemMotion.onAppearEnd;
delete listItemMotion.onEnterEnd;
delete listItemMotion.onLeaveEnd;
const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProps> = (
{
listType,
previewFile,
onPreview,
onDownload,
onRemove,
locale,
iconRender,
isImageUrl: isImgUrl,
prefixCls: customizePrefixCls,
items = [],
showPreviewIcon,
showRemoveIcon,
showDownloadIcon,
removeIcon,
previewIcon,
downloadIcon,
progress,
appendAction,
itemRender,
},
ref,
) => {
const forceUpdate = useForceUpdate();
const [motionAppear, setMotionAppear] = React.useState(false);
// ============================= Effect =============================
React.useEffect(() => {
if (listType !== 'picture' && listType !== 'picture-card') {
return;
}
(items || []).forEach((file: InternalUploadFile) => {
if (
typeof document === 'undefined' ||
typeof window === 'undefined' ||
!(window as any).FileReader ||
!(window as any).File ||
!(file.originFileObj instanceof File || (file.originFileObj as Blob) instanceof Blob) ||
file.thumbUrl !== undefined
) {
return;
}
file.thumbUrl = '';
if (previewFile) {
previewFile(file.originFileObj as File).then((previewDataUrl: string) => {
// Need append '' to avoid dead loop
file.thumbUrl = previewDataUrl || '';
forceUpdate();
});
}
});
}, [listType, items, previewFile]);
React.useEffect(() => {
setMotionAppear(true);
}, []);
// ============================= Events =============================
const onInternalPreview = (file: UploadFile, e?: React.SyntheticEvent<HTMLElement>) => {
if (!onPreview) {
return;
}
e?.preventDefault();
return onPreview(file);
};
const onInternalDownload = (file: UploadFile) => {
if (typeof onDownload === 'function') {
onDownload(file);
} else if (file.url) {
window.open(file.url);
}
};
const onInternalClose = (file: UploadFile) => {
onRemove?.(file);
};
const internalIconRender = (file: UploadFile) => {
if (iconRender) {
return iconRender(file, listType);
}
const isLoading = file.status === 'uploading';
const fileIcon = isImgUrl && isImgUrl(file) ? <PictureTwoTone /> : <FileTwoTone />;
let icon: React.ReactNode = isLoading ? <LoadingOutlined /> : <PaperClipOutlined />;
if (listType === 'picture') {
icon = isLoading ? <LoadingOutlined /> : fileIcon;
} else if (listType === 'picture-card') {
icon = isLoading ? locale.uploading : fileIcon;
}
return icon;
};
const actionIconRender = (
customIcon: React.ReactNode,
callback: () => void,
prefixCls: string,
title?: string,
) => {
const btnProps: ButtonProps = {
type: 'text',
size: 'small',
title,
onClick: (e: React.MouseEvent<HTMLElement>) => {
callback();
if (isValidElement(customIcon) && customIcon.props.onClick) {
customIcon.props.onClick(e);
}
},
className: `${prefixCls}-list-item-card-actions-btn`,
};
if (isValidElement(customIcon)) {
const btnIcon = cloneElement(customIcon, {
...customIcon.props,
onClick: () => {},
});
return <Button {...btnProps} icon={btnIcon} />;
}
return (
<Button {...btnProps}>
<span>{customIcon}</span>
</Button>
);
};
// ============================== Ref ===============================
// Test needs
React.useImperativeHandle(ref, () => ({
handlePreview: onInternalPreview,
handleDownload: onInternalDownload,
}));
const { getPrefixCls, direction } = React.useContext(ConfigContext);
// ============================= Render =============================
const prefixCls = getPrefixCls('upload', customizePrefixCls);
const listClassNames = classNames({
[`${prefixCls}-list`]: true,
[`${prefixCls}-list-${listType}`]: true,
[`${prefixCls}-list-rtl`]: direction === 'rtl',
});
// >>> Motion config
const motionKeyList = [
...items.map(file => ({
key: file.uid,
file,
})),
];
const animationDirection = listType === 'picture-card' ? 'animate-inline' : 'animate';
// const transitionName = list.length === 0 ? '' : `${prefixCls}-${animationDirection}`;
let motionConfig: Omit<CSSMotionListProps, 'onVisibleChanged'> = {
motionDeadline: 2000,
motionName: `${prefixCls}-${animationDirection}`,
keys: motionKeyList,
motionAppear,
};
if (listType !== 'picture-card') {
motionConfig = {
...listItemMotion,
...motionConfig,
};
}
return (
<div className={listClassNames}>
<CSSMotionList {...motionConfig} component={false}>
{({ key, file, className: motionClassName, style: motionStyle }) => (
<ListItem
key={key}
locale={locale}
prefixCls={prefixCls}
className={motionClassName}
style={motionStyle}
file={file}
items={items}
progress={progress}
listType={listType}
isImgUrl={isImgUrl}
showPreviewIcon={showPreviewIcon}
showRemoveIcon={showRemoveIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
previewIcon={previewIcon}
downloadIcon={downloadIcon}
iconRender={internalIconRender}
actionIconRender={actionIconRender}
itemRender={itemRender}
onPreview={onInternalPreview}
onDownload={onInternalDownload}
onClose={onInternalClose}
/>
)}
</CSSMotionList>
{/* Append action */}
{appendAction && (
<CSSMotion {...motionConfig}>
{({ className: motionClassName, style: motionStyle }) =>
cloneElement(appendAction, oriProps => ({
className: classNames(oriProps.className, motionClassName),
style: {
...motionStyle,
...oriProps.style,
},
}))
}
</CSSMotion>
)}
</div>
);
};
const UploadList = React.forwardRef<unknown, UploadListProps>(InternalUploadList);
UploadList.displayName = 'UploadList';
UploadList.defaultProps = {
listType: 'text' as UploadListType, // or picture
progress: {
strokeWidth: 2,
showInfo: false,
},
showRemoveIcon: true,
showDownloadIcon: false,
showPreviewIcon: true,
previewFile: previewImage,
isImageUrl,
};
export default UploadList;

View File

@ -0,0 +1,126 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/upload/demo/avatar.vue correctly 1`] = `
<span class="ant-upload-picture-card-wrapper avatar-uploader"><!----><div class="ant-upload ant-upload-select ant-upload-select-picture-card"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><div><span role="img" aria-label="plus" class="anticon anticon-plus"><svg focusable="false" class="" data-icon="plus" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><defs><style></style></defs><path d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"></path><path d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"></path></svg></span>
<div class="ant-upload-text">Upload</div>
</div></span></div></span>
`;
exports[`renders ./components/upload/demo/basic.vue correctly 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept="" multiple=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Click to Upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-text"></div></span>
`;
exports[`renders ./components/upload/demo/defaultFileList.vue correctly 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-text">
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-text"><div class="ant-upload-list-item-info"><span><span role="img" aria-label="paper-clip" class="anticon anticon-paper-clip"><svg focusable="false" class="" data-icon="paper-clip" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 00174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z"></path></svg></span><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="xxx.png" href="http://www.baidu.com/xxx.png">xxx.png</a><span class="ant-upload-list-item-card-actions "><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-text"><div class="ant-upload-list-item-info"><span><span role="img" aria-label="paper-clip" class="anticon anticon-paper-clip"><svg focusable="false" class="" data-icon="paper-clip" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 00174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z"></path></svg></span><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="yyy.png" href="http://www.baidu.com/yyy.png">yyy.png</a><span class="ant-upload-list-item-card-actions "><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class="">
<!---->
<div class="ant-upload-list-item ant-upload-list-item-error ant-upload-list-item-list-type-text">
<div class="ant-upload-list-item-info"><span><span role="img" aria-label="paper-clip" class="anticon anticon-paper-clip"><svg focusable="false" class="" data-icon="paper-clip" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 00174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z"></path></svg></span><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="zzz.png" href="http://www.baidu.com/zzz.png">zzz.png</a><span class="ant-upload-list-item-card-actions "><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div>
</div>
</div></span>
`;
exports[`renders ./components/upload/demo/directory.vue correctly 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept="" directory="directory" webkitdirectory="webkitdirectory"><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Upload Directory</span></button></span></div>
<div class="ant-upload-list ant-upload-list-text"></div></span>
`;
exports[`renders ./components/upload/demo/drag.vue correctly 1`] = `
<span><div class="ant-upload ant-upload-drag"><span role="button" tabindex="0" class="ant-upload ant-upload-btn"><input type="file" style="display: none;" accept="" multiple=""><div class="ant-upload-drag-container"><p class="ant-upload-drag-icon"><span role="img" aria-label="inbox" class="anticon anticon-inbox"><svg focusable="false" class="" data-icon="inbox" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M885.2 446.3l-.2-.8-112.2-285.1c-5-16.1-19.9-27.2-36.8-27.2H281.2c-17 0-32.1 11.3-36.9 27.6L139.4 443l-.3.7-.2.8c-1.3 4.9-1.7 9.9-1 14.8-.1 1.6-.2 3.2-.2 4.8V830a60.9 60.9 0 0060.8 60.8h627.2c33.5 0 60.8-27.3 60.9-60.8V464.1c0-1.3 0-2.6-.1-3.7.4-4.9 0-9.6-1.3-14.1zm-295.8-43l-.3 15.7c-.8 44.9-31.8 75.1-77.1 75.1-22.1 0-41.1-7.1-54.8-20.6S436 441.2 435.6 419l-.3-15.7H229.5L309 210h399.2l81.7 193.3H589.4zm-375 76.8h157.3c24.3 57.1 76 90.8 140.4 90.8 33.7 0 65-9.4 90.3-27.2 22.2-15.6 39.5-37.4 50.7-63.6h156.5V814H214.4V480.1z"></path></svg></span></p>
<p class="ant-upload-text">Click or drag file to this area to upload</p>
<p class="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p>
</div></span></div>
<div class="ant-upload-list ant-upload-list-text"></div></span>
`;
exports[`renders ./components/upload/demo/fileList.vue correctly 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept="" multiple=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-text">
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-text"><div class="ant-upload-list-item-info"><span><span role="img" aria-label="paper-clip" class="anticon anticon-paper-clip"><svg focusable="false" class="" data-icon="paper-clip" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 00174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z"></path></svg></span><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="xxx.png" href="http://www.baidu.com/xxx.png">xxx.png</a><span class="ant-upload-list-item-card-actions "><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
</div></span>
`;
exports[`renders ./components/upload/demo/picture-card.vue correctly 1`] = `
<div class="clearfix"><span class="ant-upload-picture-card-wrapper"><div class="ant-upload-list ant-upload-list-picture-card"><div class="ant-upload-list-picture-card-container"><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="image.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">image.png</a><!----></span></div><span class="ant-upload-list-item-actions"><a href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer" title="Preview file"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span></a>
<!----><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></span>
<!---->
</div></span></div>
<div class="ant-upload-list-picture-card-container"><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="image.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">image.png</a><!----></span></div><span class="ant-upload-list-item-actions"><a href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer" title="Preview file"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span></a>
<!----><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></span>
<!---->
</div></span></div>
<div class="ant-upload-list-picture-card-container"><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="image.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">image.png</a><!----></span></div><span class="ant-upload-list-item-actions"><a href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer" title="Preview file"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span></a>
<!----><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></span>
<!---->
</div></span></div>
<div class="ant-upload-list-picture-card-container"><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="image.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">image.png</a><!----></span></div><span class="ant-upload-list-item-actions"><a href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer" title="Preview file"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span></a>
<!----><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></span>
<!---->
</div></span></div>
<div class="ant-upload-list-picture-card-container">
<!---->
<div class="ant-upload-list-item ant-upload-list-item-error ant-upload-list-item-list-type-picture-card">
<div class="ant-upload-list-item-info"><span><span role="img" aria-label="picture" class="anticon anticon-picture ant-upload-list-item-thumbnail"><svg focusable="false" class="" data-icon="picture" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136v-39.9l138.5-164.3 150.1 178L658.1 489 888 761.6V792zm0-129.8L664.2 396.8c-3.2-3.8-9-3.8-12.2 0L424.6 666.4l-144-170.7c-3.2-3.8-9-3.8-12.2 0L136 652.7V232h752v430.2z" fill="#1890ff"></path><path d="M424.6 765.8l-150.1-178L136 752.1V792h752v-30.4L658.1 489z" fill="#e6f7ff"></path><path d="M136 652.7l132.4-157c3.2-3.8 9-3.8 12.2 0l144 170.7L652 396.8c3.2-3.8 9-3.8 12.2 0L888 662.2V232H136v420.7zM304 280a88 88 0 110 176 88 88 0 010-176z" fill="#e6f7ff"></path><path d="M276 368a28 28 0 1056 0 28 28 0 10-56 0z" fill="#e6f7ff"></path><path d="M304 456a88 88 0 100-176 88 88 0 000 176zm0-116c15.5 0 28 12.5 28 28s-12.5 28-28 28-28-12.5-28-28 12.5-28 28-28z" fill="#1890ff"></path></svg></span><span class="ant-upload-list-item-name" title="image.png">image.png</span>
<!----></span>
</div><span class="ant-upload-list-item-actions"><a target="_blank" rel="noopener noreferrer" style="pointer-events: none; opacity: 0.5;" title="Preview file"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span></a>
<!----><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></span>
<!---->
</div>
</div>
</div>
<div class="ant-upload ant-upload-select ant-upload-select-picture-card"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><div><span role="img" aria-label="plus" class="anticon anticon-plus"><svg focusable="false" class="" data-icon="plus" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><defs><style></style></defs><path d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"></path><path d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"></path></svg></span>
<div class="ant-upload-text">Upload</div>
</div></span></div></span>
<!---->
</div>
`;
exports[`renders ./components/upload/demo/picture-style.vue correctly 1`] = `
<div><span><div class="ant-upload ant-upload-select ant-upload-select-picture"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-picture">
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="xxx.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="xxx.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">xxx.png</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="yyy.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="yyy.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">yyy.png</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
</div></span><br><br><span class="upload-list-inline"><div class="ant-upload ant-upload-select ant-upload-select-picture"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-picture">
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="xxx.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="xxx.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">xxx.png</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" target="_blank" rel="noopener noreferrer"><img src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" alt="yyy.png" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="yyy.png" href="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">yyy.png</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
</div></span></div>
`;
exports[`renders ./components/upload/demo/preview-file.vue correctly 1`] = `
<div><span><div class="ant-upload ant-upload-select ant-upload-select-picture"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-picture"></div></span></div>
`;
exports[`renders ./components/upload/demo/transform-file.vue correctly 1`] = `
<div><span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" style="display: none;" accept=""><button class="ant-btn" type="button"><!----><span role="img" aria-label="upload" class="anticon anticon-upload"><svg focusable="false" class="" data-icon="upload" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg></span><span>Upload</span></button></span></div>
<div class="ant-upload-list ant-upload-list-text"></div></span></div>
`;

View File

@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Upload List handle error 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" accept="" style="display: none;"><button>upload</button></span></div><span tag="div" class="ant-upload-list ant-upload-list-text"><div class=""><span><div class="ant-upload-list-item ant-upload-list-item-uploading ant-upload-list-item-list-type-text"><div class="ant-upload-list-item-info"><span><span role="img" aria-label="loading" class="anticon anticon-loading"><svg viewBox="0 0 1024 1024" focusable="false" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" class="anticon-spin"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span title="foo.png" class="ant-upload-list-item-name">foo.png</span><span class="ant-upload-list-item-card-actions "><a title="Remove file"><span role="img" aria-label="delete" tabindex="-1" title="Remove file" class="anticon anticon-delete"><svg viewBox="64 64 896 896" focusable="false" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<div class="ant-upload-list-item-progress">
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 2px; border-radius: 100px;"></div>
</div>
</div>
</div>
</div>
</div>
</div></span></div></span></span>
`;
exports[`Upload List handle error 2`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" accept="" style="display: none;"><button>upload</button></span></div><span tag="div" class="ant-upload-list ant-upload-list-text"><div class=""><span><div class="ant-upload-list-item ant-upload-list-item-uploading ant-upload-list-item-list-type-text"><div class="ant-upload-list-item-info"><span><span role="img" aria-label="loading" class="anticon anticon-loading"><svg viewBox="0 0 1024 1024" focusable="false" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" class="anticon-spin"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span title="foo.png" class="ant-upload-list-item-name">foo.png</span><span class="ant-upload-list-item-card-actions "><a title="Remove file"><span role="img" aria-label="delete" tabindex="-1" title="Remove file" class="anticon anticon-delete"><svg viewBox="64 64 896 896" focusable="false" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<div class="ant-upload-list-item-progress">
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 2px; border-radius: 100px;"></div>
</div>
</div>
</div>
</div>
</div>
</div></span></div></span></span>
`;
exports[`Upload List should be uploading when upload a file 1`] = `<span><div class="ant-upload ant-upload-select ant-upload-select-text"><span role="button" tabindex="0" class="ant-upload"><input type="file" accept="" style="display: none;"><button>upload</button></span></div><span tag="div" class="ant-upload-list ant-upload-list-text"></span></span>`;
exports[`Upload List should non-image format file preview 1`] = `
<span><div class="ant-upload ant-upload-select ant-upload-select-picture"><!----></div><div class="ant-upload-list ant-upload-list-picture"><div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/aaa.zip" target="_blank" rel="noopener noreferrer"><span role="img" aria-label="file" class="anticon anticon-file ant-upload-list-item-icon"><svg focusable="false" class="" data-icon="file" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"></path></svg></span></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="not-image" href="https://cdn.xxx.com/aaa.zip">not-image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/aaa" target="_blank" rel="noopener noreferrer"><img src="https://cdn.xxx.com/aaa" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/aaa">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/aaa.xx" target="_blank" rel="noopener noreferrer"><span role="img" aria-label="file" class="anticon anticon-file ant-upload-list-item-icon"><svg focusable="false" class="" data-icon="file" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"></path></svg></span></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="not-image" href="https://cdn.xxx.com/aaa.xx">not-image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/aaa.png/xx.xx" target="_blank" rel="noopener noreferrer"><span role="img" aria-label="file" class="anticon anticon-file ant-upload-list-item-icon"><svg focusable="false" class="" data-icon="file" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"></path></svg></span></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="not-image" href="https://cdn.xxx.com/aaa.png/xx.xx">not-image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/xx.xx/aaa.png" target="_blank" rel="noopener noreferrer"><img src="https://cdn.xxx.com/xx.xx/aaa.png" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/xx.xx/aaa.png">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/xx.xx/aaa.png" target="_blank" rel="noopener noreferrer"><img src="data:image/png;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/xx.xx/aaa.png">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/xx.xx/aaa.png?query=123" target="_blank" rel="noopener noreferrer"><img src="https://cdn.xxx.com/xx.xx/aaa.png?query=123" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/xx.xx/aaa.png?query=123">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/xx.xx/aaa.png#anchor" target="_blank" rel="noopener noreferrer"><img src="https://cdn.xxx.com/xx.xx/aaa.png#anchor" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/xx.xx/aaa.png#anchor">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
<div class=""><span><div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture"><div class="ant-upload-list-item-info"><span><a class="ant-upload-list-item-thumbnail" href="https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot" target="_blank" rel="noopener noreferrer"><img src="https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot" alt="image" class="ant-upload-list-item-image"></a><a target="_blank" rel="noopener noreferrer" class="ant-upload-list-item-name ant-upload-list-item-name-icon-count-1" title="image" href="https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot">image</a><span class="ant-upload-list-item-card-actions picture"><!----><a title="Remove file"><span title="Remove file" tabindex="-1" role="img" aria-label="delete" class="anticon anticon-delete"><svg focusable="false" class="" data-icon="delete" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"></path></svg></span></a></span></span></div>
<!---->
<!---->
</div></span></div>
</div></span>
`;

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('upload', { skip: ['upload-manually'] });

View File

@ -0,0 +1,14 @@
import mock from 'xhr-mock';
export function setup() {
mock.setup();
mock.post('http://upload.com/', (req, res) => {
req.headers({
'content-length': 100,
});
req.body('thisisbody');
return res;
});
}
export const teardown = mock.teardown.bind(mock);

View File

@ -0,0 +1,11 @@
export const successRequest = ({ onSuccess, file }) => {
setTimeout(() => {
onSuccess(null, file);
});
};
export const errorRequest = ({ onError }) => {
setTimeout(() => {
onError();
});
};

View File

@ -0,0 +1,331 @@
import { mount } from '@vue/test-utils';
import Upload from '..';
import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from '../utils';
import PropsTypes from '../../_util/vue-types';
import { uploadListProps } from '../interface';
import { setup, teardown } from './mock';
uploadListProps.items = PropsTypes.any;
describe('Upload', () => {
beforeEach(() => setup());
afterEach(() => teardown());
it('should get refs inside Upload in componentDidMount', () => {
let ref = null;
const APP = {
mounted() {
ref = this.$refs.input;
},
render() {
return (
<Upload supportServerRender={false} action="">
<input ref="input" />
</Upload>
);
},
};
mount(APP);
expect(ref).toBeDefined();
});
xit('return promise in beforeUpload', done => {
const data = jest.fn();
const props = {
props: {
action: 'http://upload.com',
beforeUpload: () => new Promise(resolve => setTimeout(() => resolve('success'), 100)),
data,
},
listeners: {
change: ({ file }) => {
if (file.status !== 'uploading') {
expect(data).toBeCalled();
done();
}
},
},
slots: {
default: () => <button>upload</button>,
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
wrapper.findComponent('ajaxUploader').vm.onChange({
target: {
files: [{ file: 'foo.png' }],
},
});
}, 0);
});
xit('upload promise return file in beforeUpload', done => {
const data = jest.fn();
const props = {
action: 'http://upload.com',
beforeUpload: file =>
new Promise(resolve =>
setTimeout(() => {
const result = file;
result.name = 'test.png';
resolve(result);
}, 100),
),
data,
onChange: ({ file }) => {
if (file.status !== 'uploading') {
expect(data).toBeCalled();
expect(file.name).toEqual('test.png');
done();
}
},
slots: {
default: () => <button>upload</button>,
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
wrapper.find({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [{ file: 'foo.png' }],
},
});
}, 0);
});
xit('should not stop upload when return value of beforeUpload is false', done => {
const data = jest.fn();
const props = {
action: 'http://upload.com',
beforeUpload: () => false,
data,
onChange: ({ file }) => {
expect(file instanceof File).toBe(true);
expect(data).not.toBeCalled();
done();
},
slots: {
default: () => <button>upload</button>,
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.find({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
}, 0);
});
xit('should increase percent automaticly when call autoUpdateProgress in IE', done => {
let uploadInstance;
let lastPercent = -1;
const props = {
props: {
action: 'http://upload.com',
},
listeners: {
change: ({ file }) => {
if (file.percent === 0 && file.status === 'uploading') {
// manually call it
uploadInstance.autoUpdateProgress(0, file);
}
if (file.status === 'uploading') {
expect(file.percent).toBeGreaterThan(lastPercent);
lastPercent = file.percent;
}
if (file.status === 'done' || file.status === 'error') {
done();
}
},
},
slots: {
default: '<button>upload</button>',
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.find({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
uploadInstance = wrapper.vm;
}, 0);
});
xit('should not stop upload when return value of beforeUpload is not false', done => {
const data = jest.fn();
const props = {
props: {
action: 'http://upload.com',
beforeUpload() {},
data,
},
listeners: {
change: () => {
expect(data).toBeCalled();
done();
},
},
slots: {
default: '<button>upload</button>',
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.find({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
}, 0);
});
describe('util', () => {
// https://github.com/react-component/upload/issues/36
it('should T() return true', () => {
const res = T();
expect(res).toBe(true);
});
it('should be able to copy file instance', () => {
const file = new File([], 'aaa.zip');
const copiedFile = fileToObject(file);
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
expect(key in copiedFile).toBe(true);
});
});
it('should be able to progress from 0.1 ', () => {
// 0.1 -> 0.98
const getPercent = genPercentAdd();
let curPercent = 0;
curPercent = getPercent(curPercent);
expect(curPercent).toBe(0.1);
});
it('should be able to progress to 0.98 ', () => {
// 0.1 -> 0.98
const getPercent = genPercentAdd();
let curPercent = 0;
for (let i = 0; i < 500; i += 1) {
curPercent = getPercent(curPercent);
}
expect(parseFloat(curPercent.toFixed(2))).toBe(0.98);
});
it('should be able to get fileItem', () => {
const file = { uid: '-1', name: 'item.jpg' };
const fileList = [
{
uid: '-1',
name: 'item.jpg',
},
];
const targetItem = getFileItem(file, fileList);
expect(targetItem).toBe(fileList[0]);
});
it('should be able to remove fileItem', () => {
const file = { uid: '-1', name: 'item.jpg' };
const fileList = [
{
uid: '-1',
name: 'item.jpg',
},
{
uid: '-2',
name: 'item2.jpg',
},
];
const targetItem = removeFileItem(file, fileList);
expect(targetItem).toEqual(fileList.slice(1));
});
it('should not be able to remove fileItem', () => {
const file = { uid: '-3', name: 'item.jpg' };
const fileList = [
{
uid: '-1',
name: 'item.jpg',
},
{
uid: '-2',
name: 'item2.jpg',
},
];
const targetItem = removeFileItem(file, fileList);
expect(targetItem).toBe(null);
});
});
it('should support linkProps as object', () => {
const fileList = [
{
uid: '-1',
name: 'foo.png',
status: 'done',
url: 'http://www.baidu.com/xxx.png',
linkProps: {
download: 'image',
rel: 'noopener',
},
},
];
const props = {
props: {
fileList,
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const linkNode = wrapper.find('a.ant-upload-list-item-name');
expect(linkNode.props().download).toBe('image');
expect(linkNode.props().rel).toBe('noopener');
}, 0);
});
it('should support linkProps as json stringify', () => {
const linkPropsString = JSON.stringify({
download: 'image',
rel: 'noopener',
});
const fileList = [
{
uid: '-1',
name: 'foo.png',
status: 'done',
url: 'http://www.baidu.com/xxx.png',
linkProps: linkPropsString,
},
];
const props = {
props: {
fileList,
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const linkNode = wrapper.find('a.ant-upload-list-item-name');
expect(linkNode.props().download).toBe('image');
expect(linkNode.props().rel).toBe('noopener');
}, 0);
});
});

View File

@ -0,0 +1,433 @@
import { mount } from '@vue/test-utils';
import * as Vue from 'vue';
import Upload from '..';
import { errorRequest, successRequest } from './requests';
import PropsTypes from '../../_util/vue-types';
import { uploadListProps } from '../interface';
import { sleep } from '../../../tests/utils';
import { h } from 'vue';
uploadListProps.items = PropsTypes.any;
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const fileList = [
{
uid: -1,
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
{
uid: -2,
name: 'yyy.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
];
describe('Upload List', () => {
// jsdom not support `createObjectURL` yet. Let's handle this.
const originCreateObjectURL = window.URL.createObjectURL;
window.URL.createObjectURL = jest.fn(() => '');
const originHTMLCanvasElementGetContext = window.HTMLCanvasElement.prototype.getContext;
window.HTMLCanvasElement.prototype.getContext = jest.fn(() => '');
// https://github.com/ant-design/ant-design/issues/4653
afterAll(() => {
window.URL.createObjectURL = originCreateObjectURL;
window.HTMLCanvasElement.prototype.getContext = originHTMLCanvasElementGetContext;
});
it('should use file.thumbUrl for <img /> in priority', done => {
const props = {
props: {
defaultFileList: fileList,
listType: 'picture',
action: '',
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
Vue.nextTick(() => {
fileList.forEach((file, i) => {
const linkNode = wrapper.findAll('.ant-upload-list-item-thumbnail')[i];
const imgNode = wrapper.findAll('.ant-upload-list-item-thumbnail img')[i];
expect(linkNode.attributes().href).toBe(file.url);
expect(imgNode.attributes().src).toBe(file.thumbUrl);
});
done();
});
});
// https://github.com/ant-design/ant-design/issues/7269
it('should remove correct item when uid is 0', done => {
const list = [
{
uid: 0,
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
{
uid: 1,
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
];
const props = {
props: {
defaultFileList: list,
action: '',
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(async () => {
expect(wrapper.findAll('.ant-upload-list-item').length).toBe(2);
wrapper.findAll('.ant-upload-list-item')[0].find('.anticon-delete').trigger('click');
await delay(400);
// wrapper.update();
expect(wrapper.findAll('.ant-upload-list-item').length).toBe(1);
done();
}, 0);
});
xit('should be uploading when upload a file', done => {
const props = {
props: {
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
customRequest: successRequest,
onChange: ({ file }) => {
if (file.status === 'uploading') {
expect(wrapper.html()).toMatchSnapshot();
done();
}
if (file.status === 'done') {
expect(wrapper.html()).toMatchSnapshot();
done();
}
},
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.findComponent({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
}, 0);
});
xit('handle error', done => {
const props = {
props: {
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
customRequest: errorRequest,
},
listeners: {
change: ({ file }) => {
if (file.status !== 'uploading') {
expect(wrapper.html()).toMatchSnapshot();
done();
}
},
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.findComponent({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
}, 0);
});
xit('does concat filelist when beforeUpload returns false', done => {
const handleChange = jest.fn();
const props = {
props: {
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
listType: 'picture',
defaultFileList: fileList,
beforeUpload: () => false,
onChange: handleChange,
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(() => {
const mockFile = new File(['foo'], 'foo.png', {
type: 'image/png',
});
wrapper.findComponent({ name: 'ajaxUploader' }).vm.onChange({
target: {
files: [mockFile],
},
});
Vue.nextTick(() => {
expect(wrapper.vm.sFileList.length).toBe(fileList.length + 1);
expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);
done();
});
}, 0);
});
// https://github.com/ant-design/ant-design/issues/7762
// it('work with form validation', (done) => {
// let errors
// const TestForm = {
// methods: {
// handleSubmit () {
// const { validateFields } = this.form
// validateFields((err) => {
// errors = err
// })
// },
// },
// render () {
// const { getFieldDecorator } = this.form
// return (
// <Form onSubmit={this.handleSubmit}>
// <Form.Item>
// {getFieldDecorator('file', {
// valuePropname: 'fileList',
// getValueFromEvent: e => e.fileList,
// rules: [
// {
// required: true,
// validator: (rule, value, callback) => {
// if (!value || value.length === 0) {
// callback('file required')
// } else {
// callback()
// }
// },
// },
// ],
// })(
// <Upload
// beforeUpload={() => false}
// >
// <button>upload</button>
// </Upload>
// )}
// </Form.Item>
// </Form>
// )
// },
// }
// const App = Form.create()(TestForm)
// console.dir(App)
// const wrapper = mount(() => {
// return <App />
// })
// setTimeout(async () => {
// wrapper.find(Form).trigger('submit')
// expect(errors.file.errors).toEqual([{ message: 'file required', field: 'file' }])
// const mockFile = new File(['foo'], 'foo.png', {
// type: 'image/png',
// })
// wrapper.findComponent({ name: 'ajaxUploader' }).vm.onChange({
// target: {
// files: [mockFile],
// },
// })
// wrapper.find(Form).trigger('submit')
// expect(errors).toBeNull()
// done()
// }, 0)
// })
it('should support onPreview', async () => {
const handlePreview = jest.fn();
const props = {
props: {
defaultFileList: fileList,
listType: 'picture-card',
action: '',
onPreview: handlePreview,
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
await sleep(500);
wrapper.findAll('.anticon-eye')[0].trigger('click');
expect(handlePreview).toBeCalledWith(fileList[0]);
wrapper.findAll('.anticon-eye')[1].trigger('click');
expect(handlePreview).toBeCalledWith(fileList[1]);
});
it('should support onRemove', done => {
const handleRemove = jest.fn();
const handleChange = jest.fn();
const props = {
props: {
defaultFileList: fileList,
listType: 'picture-card',
action: '',
remove: handleRemove,
onChange: handleChange,
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
jest.setTimeout(300000);
setTimeout(async () => {
wrapper.findAll('.anticon-delete')[0].trigger('click');
expect(handleRemove).toBeCalledWith(fileList[0]);
wrapper.findAll('.anticon-delete')[1].trigger('click');
expect(handleRemove).toBeCalledWith(fileList[1]);
await delay(0);
expect(handleChange.mock.calls.length).toBe(2);
done();
}, 0);
});
xit('should generate thumbUrl from file', done => {
const handlePreview = jest.fn();
const newFileList = [...fileList];
const newFile = { ...fileList[0], uid: -3, originFileObj: new File([], 'xxx.png') };
delete newFile.thumbUrl;
newFileList.push(newFile);
const props = {
props: {
defaultFileList: newFileList,
listType: 'picture-card',
action: '',
onPreview: handlePreview,
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
setTimeout(async () => {
const newFile = { ...fileList[2], uid: -4, originFileObj: new File([], 'xxx.png') };
newFileList.push(newFile);
wrapper.setProps({
defaultFileList: [...newFileList],
});
await delay(200);
expect(wrapper.vm.sFileList[2].thumbUrl).not.toBe(undefined);
done();
}, 1000);
});
it('should non-image format file preview', done => {
const list = [
{
name: 'not-image',
status: 'done',
uid: -3,
url: 'https://cdn.xxx.com/aaa.zip',
thumbUrl: 'data:application/zip;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk',
originFileObj: new File([], 'aaa.zip'),
},
{
name: 'image',
status: 'done',
uid: -4,
url: 'https://cdn.xxx.com/aaa',
},
{
name: 'not-image',
status: 'done',
uid: -5,
url: 'https://cdn.xxx.com/aaa.xx',
},
{
name: 'not-image',
status: 'done',
uid: -6,
url: 'https://cdn.xxx.com/aaa.png/xx.xx',
},
{
name: 'image',
status: 'done',
uid: -7,
url: 'https://cdn.xxx.com/xx.xx/aaa.png',
},
{
name: 'image',
status: 'done',
uid: -8,
url: 'https://cdn.xxx.com/xx.xx/aaa.png',
thumbUrl: 'data:image/png;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk',
},
{
name: 'image',
status: 'done',
uid: -9,
url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=123',
},
{
name: 'image',
status: 'done',
uid: -10,
url: 'https://cdn.xxx.com/xx.xx/aaa.png#anchor',
},
{
name: 'image',
status: 'done',
uid: -11,
url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot',
},
];
const props = {
props: {
defaultFileList: list,
listType: 'picture',
action: '',
},
slots: {
default: () => h('button', 'upload'),
},
sync: false,
};
const wrapper = mount(Upload, props);
Vue.nextTick(() => {
expect(wrapper.html()).toMatchSnapshot();
done();
});
});
});

View File

@ -0,0 +1,116 @@
<docs>
---
order: 1
title:
zh-CN: 用户头像
en-US: Avatar
---
## zh-CN
点击上传用户头像并使用 `beforeUpload` 限制用户上传的图片格式和大小
> `beforeUpload` 的返回值可以是一个 Promise 以支持异步处理如服务端校验等[示例](http://react-component.github.io/upload/examples/beforeUpload.html)
## en-US
Click to upload user's avatar, and validate size and format of picture with `beforeUpload`.
> The return value of function `beforeUpload` can be a Promise to check asynchronously. [demo](http://react-component.github.io/upload/examples/beforeUpload.html)
</docs>
<template>
<a-upload
v-model:file-list="fileList"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:before-upload="beforeUpload"
@change="handleChange"
>
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
<div v-else>
<loading-outlined v-if="loading"></loading-outlined>
<plus-outlined v-else></plus-outlined>
<div class="ant-upload-text">Upload</div>
</div>
</a-upload>
</template>
<script lang="ts">
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
function getBase64(img: Blob, callback: (base64Url: string) => void) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result as string));
reader.readAsDataURL(img);
}
export default defineComponent({
components: {
LoadingOutlined,
PlusOutlined,
},
setup() {
const fileList = ref([]);
const loading = ref<boolean>(false);
const imageUrl = ref<string>('');
const handleChange = (info: UploadChangeParam) => {
if (info.file.status === 'uploading') {
loading.value = true;
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, (base64Url: string) => {
imageUrl.value = base64Url;
loading.value = false;
});
}
if (info.file.status === 'error') {
loading.value = false;
message.error('upload error');
}
};
const beforeUpload = (file: UploadProps['fileList'][number]) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
};
return {
fileList,
loading,
imageUrl,
handleChange,
beforeUpload,
};
},
});
</script>
<style>
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>

View File

@ -0,0 +1,65 @@
<docs>
---
order: 0
title:
zh-CN: 点击上传
en-US: Upload by clicking
---
## zh-CN
经典款式用户点击按钮弹出文件选择框
## en-US
Classic mode. File selection dialog pops up when upload button is clicked.
</docs>
<template>
<a-upload
v-model:file-list="fileList"
name="file"
:multiple="true"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:headers="headers"
@change="handleChange"
>
<a-button>
<upload-outlined></upload-outlined>
Click to Upload
</a-button>
</a-upload>
</template>
<script lang="ts">
import { message } from 'ant-design-vue';
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam } from 'ant-design-vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const handleChange = (info: UploadChangeParam) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
};
const fileList = ref([]);
return {
fileList,
headers: {
authorization: 'authorization-text',
},
handleChange,
};
},
});
</script>

View File

@ -0,0 +1,70 @@
<docs>
---
order: 2
title:
zh-CN: 已上传的文件列表
en-US: Default Files
---
## zh-CN
使用 `defaultFileList` 设置已上传的内容
## en-US
Use `defaultFileList` for uploaded files when page init.
</docs>
<template>
<a-upload v-model:file-list="fileList" action="https://www.mocky.io/v2/5cc8019d300000980a055e76">
<a-button>
<upload-outlined></upload-outlined>
Upload
</a-button>
</a-upload>
</template>
<script lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const fileList = ref<UploadProps['fileList']>([
{
uid: '1',
name: 'xxx.png',
status: 'done',
response: 'Server Error 500', // custom error message to show
url: 'http://www.baidu.com/xxx.png',
},
{
uid: '2',
name: 'yyy.png',
status: 'done',
url: 'http://www.baidu.com/yyy.png',
},
{
uid: '3',
name: 'zzz.png',
status: 'error',
response: 'Server Error 500', // custom error message to show
url: 'http://www.baidu.com/zzz.png',
},
]);
const handleChange = ({ file, fileList }: UploadChangeParam) => {
if (file.status !== 'uploading') {
console.log(file, fileList);
}
};
return {
fileList,
handleChange,
};
},
});
</script>

View File

@ -0,0 +1,35 @@
<docs>
---
order: 8
title:
zh-CN: 文件夹上传
en-US: Upload directory
---
## zh-CN
支持上传一个文件夹里的所有文件
## en-US
You can select and upload a whole directory.
</docs>
<template>
<a-upload action="https://www.mocky.io/v2/5cc8019d300000980a055e76" directory>
<a-button>
<upload-outlined></upload-outlined>
Upload Directory
</a-button>
</a-upload>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { UploadOutlined } from '@ant-design/icons-vue';
export default defineComponent({
components: {
UploadOutlined,
},
});
</script>

View File

@ -0,0 +1,68 @@
<docs>
---
order: 5
title:
zh-CN: 拖拽上传
en-US: Drag and Drop
---
## zh-CN
把文件拖入指定区域完成上传同样支持点击上传
设置 `multiple` `IE10+` 可以一次上传多个文件
## en-US
You can drag files to a specific area, to upload. Alternatively, you can also upload by selecting.
We can upload serveral files at once in modern browsers by giving the input the `multiple` attribute.
</docs>
<template>
<a-upload-dragger
v-model:fileList="fileList"
name="file"
:multiple="true"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text">Click or drag file to this area to upload</p>
<p class="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibit from uploading company data or other
band files
</p>
</a-upload-dragger>
</template>
<script lang="ts">
import { InboxOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam } from 'ant-design-vue';
export default defineComponent({
components: {
InboxOutlined,
},
setup() {
const handleChange = (info: UploadChangeParam) => {
const status = info.file.status;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
};
return {
handleChange,
fileList: ref([]),
};
},
});
</script>

View File

@ -0,0 +1,81 @@
<docs>
---
order: 4
title:
zh-CN: 完全控制的上传列表
en-US: Complete control over file list
---
## zh-CN
使用 `fileList` 对列表进行完全控制可以实现各种自定义功能以下演示二种情况
1. 上传列表数量的限制
2. 读取远程路径并显示链接
## en-US
You can gain full control over filelist by configuring `fileList`. You can accomplish all kinds of customed functions. The following shows two circumstances:
1. limit the number of uploaded files.
2. read from response and show file link.
</docs>
<template>
<a-upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:multiple="true"
:file-list="fileList"
@change="handleChange"
>
<a-button>
<upload-outlined></upload-outlined>
Upload
</a-button>
</a-upload>
</template>
<script lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const fileList = ref<UploadProps['fileList']>([
{
uid: '-1',
name: 'xxx.png',
status: 'done',
url: 'http://www.baidu.com/xxx.png',
},
]);
const handleChange = (info: UploadChangeParam) => {
let resFileList = [...info.fileList];
// 1. Limit the number of uploaded files
// Only to show two recent uploaded files, and old ones will be replaced by the new
resFileList = resFileList.slice(-2);
// 2. read from response and show file link
resFileList = resFileList.map(file => {
if (file.response) {
// Component will show file.url as link
file.url = file.response.url;
}
return file;
});
fileList.value = resFileList;
};
return {
fileList,
handleChange,
};
},
});
</script>

View File

@ -0,0 +1,52 @@
<template>
<demo-sort>
<Basic />
<Avatar />
<DefaultFileList />
<PictureCard />
<FileList />
<Drag />
<PictureStyle />
<UploadManually />
<Directory />
<PreviewFile />
<TransformFile />
</demo-sort>
</template>
<script lang="ts">
import Basic from './basic.vue';
import Avatar from './avatar.vue';
import DefaultFileList from './defaultFileList.vue';
import PictureCard from './picture-card.vue';
import FileList from './fileList.vue';
import Drag from './drag.vue';
import PictureStyle from './picture-style.vue';
import UploadManually from './upload-manually.vue';
import Directory from './directory.vue';
import PreviewFile from './preview-file.vue';
import TransformFile from './transform-file.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
export default defineComponent({
CN,
US,
components: {
Basic,
Avatar,
DefaultFileList,
PictureCard,
FileList,
Drag,
PictureStyle,
UploadManually,
Directory,
PreviewFile,
TransformFile,
},
setup() {
return {};
},
});
</script>

View File

@ -0,0 +1,126 @@
<docs>
---
order: 3
title:
zh-CN: 照片墙
en-US: Pictures Wall
---
## zh-CN
用户可以上传图片并在列表中显示缩略图当上传照片数到达限制后上传按钮消失
## en-US
After users upload picture, the thumbnail will be shown in list. The upload button will disappear when count meets limitation.
</docs>
<template>
<div class="clearfix">
<a-upload
v-model:file-list="fileList"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
list-type="picture-card"
@preview="handlePreview"
>
<div v-if="fileList.length < 8">
<plus-outlined />
<div class="ant-upload-text">Upload</div>
</div>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</template>
<script lang="ts">
import { PlusOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
function getBase64(file: 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 defineComponent({
components: {
PlusOutlined,
},
setup() {
const previewVisible = ref<boolean>(false);
const previewImage = ref<string | undefined>('');
const fileList = ref<UploadProps['fileList']>([
{
uid: '-1',
name: 'image.png',
status: 'done',
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',
},
]);
const handleCancel = () => {
previewVisible.value = false;
};
const handlePreview = async (file: UploadProps['fileList'][number]) => {
if (!file.url && !file.preview) {
file.preview = (await getBase64(file.originFileObj)) as string;
}
previewImage.value = file.url || file.preview;
previewVisible.value = true;
};
const handleChange = ({ fileList: newFileList }: UploadChangeParam) => {
fileList.value = newFileList;
};
return {
previewVisible,
previewImage,
fileList,
handleCancel,
handlePreview,
handleChange,
};
},
});
</script>
<style>
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>

View File

@ -0,0 +1,108 @@
<docs>
---
order: 6
title:
zh-CN: 图片列表样式
en-US: Pictures with list style
---
## zh-CN
上传文件为图片可展示本地缩略图`IE8/9` 不支持浏览器本地缩略图展示[Ref](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) `thumbUrl`
## en-US
If uploaded file is a picture, the thumbnail can be shown. `IE8/9` do not support local thumbnail show. Please use `thumbUrl` instead.
</docs>
<template>
<div>
<a-upload
v-model:file-list="fileList"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
list-type="picture"
>
<a-button>
<upload-outlined></upload-outlined>
upload
</a-button>
</a-upload>
<br />
<br />
<a-upload
v-model:file-list="fileList1"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
list-type="picture"
class="upload-list-inline"
>
<a-button>
<upload-outlined></upload-outlined>
upload
</a-button>
</a-upload>
</div>
</template>
<script lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import type { UploadProps } from 'ant-design-vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const fileList = ref<UploadProps['fileList']>([
{
uid: '-1',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-2',
name: 'yyy.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
]);
const fileList1 = ref<UploadProps['fileList']>([
{
uid: '-1',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-2',
name: 'yyy.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
]);
return {
fileList,
fileList1,
};
},
});
</script>
<style scoped>
/* tile uploaded pictures */
.upload-list-inline :deep(.ant-upload-list-item) {
float: left;
width: 200px;
margin-right: 8px;
}
.upload-list-inline :deep(.ant-upload-animate-enter) {
animation-name: uploadAnimateInlineIn;
}
.upload-list-inline :deep(.ant-upload-animate-leave) {
animation-name: uploadAnimateInlineOut;
}
</style>

View File

@ -0,0 +1,58 @@
<docs>
---
order: 9
title:
zh-CN: 自定义预览
en-US: Customize preview file
---
## zh-CN
自定义本地预览用于处理非图片格式文件例如视频文件
## en-US
Customize local preview. Can handle with non-image format files such as video.
</docs>
<template>
<div>
<a-upload
v-model:file-list="fileList"
list-type="picture"
action="//jsonplaceholder.typicode.com/posts/"
:preview-file="previewFile"
>
<a-button>
<upload-outlined></upload-outlined>
Upload
</a-button>
</a-upload>
</div>
</template>
<script lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const previewFile = async (file: any): Promise<Response> => {
console.log('Your upload file:', file);
// Your process logic. Here we just mock to the same file
const res = await fetch('https://next.json-generator.com/api/json/get/4ytyBoLK8', {
method: 'POST',
body: file,
});
const { thumbnail } = await res.json();
return thumbnail;
};
return {
previewFile,
fileList: ref([]),
};
},
});
</script>

View File

@ -0,0 +1,66 @@
<docs>
---
order: 10
title:
zh-CN: 上传前转换文件
en-US: Transform file before request
---
## zh-CN
使用 `beforeUpload` 转换上传的文件例如添加水印
## en-US
Use `beforeUpload` for transform file before request such as add a watermark.
</docs>
<template>
<div>
<a-upload
v-model:file-list="fileList"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:transform-file="transformFile"
>
<a-button>
<upload-outlined></upload-outlined>
Upload
</a-button>
</a-upload>
</div>
</template>
<script lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const transformFile = (file: any) => {
return new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const canvas = document.createElement('canvas');
const img: HTMLImageElement = document.createElement('img');
img.src = reader.result as string;
img.onload = () => {
const ctx: CanvasRenderingContext2D = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0);
ctx.fillStyle = 'red';
ctx.textBaseline = 'middle';
ctx.fillText('Ant Design', 20, 20);
canvas.toBlob(resolve);
};
};
});
};
return {
transformFile,
fileList: ref([]),
};
},
});
</script>

View File

@ -0,0 +1,96 @@
<docs>
---
order: 7
title:
zh-CN: 手动上传
en-US: Upload manually
---
## zh-CN
`beforeUpload` 返回 `false` 手动上传文件
## en-US
Upload files manually after `beforeUpload` returns `false`.
</docs>
<template>
<div class="clearfix">
<a-upload :file-list="fileList" :remove="handleRemove" :before-upload="beforeUpload">
<a-button>
<upload-outlined></upload-outlined>
Select File
</a-button>
</a-upload>
<a-button
type="primary"
:disabled="fileList.length === 0"
:loading="uploading"
style="margin-top: 16px"
@click="handleUpload"
>
{{ uploading ? 'Uploading' : 'Start Upload' }}
</a-button>
</div>
</template>
<script lang="ts">
import request from 'umi-request';
import { UploadOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
import type { UploadProps } from 'ant-design-vue';
export default defineComponent({
components: {
UploadOutlined,
},
setup() {
const fileList = ref<UploadProps['fileList']>([]);
const uploading = ref<boolean>(false);
const handleRemove: UploadProps['onRemove'] = file => {
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
const beforeUpload: UploadProps['beforeUpload'] = file => {
fileList.value = [...fileList.value, file];
return false;
};
const handleUpload = () => {
const formData = new FormData();
fileList.value.forEach((file: UploadProps['fileList'][number]) => {
formData.append('files[]', file as any);
});
uploading.value = true;
// You can use any AJAX library you like
request('https://www.mocky.io/v2/5cc8019d300000980a055e76', {
method: 'post',
data: formData,
})
.then(() => {
fileList.value = [];
uploading.value = false;
message.success('upload successfully.');
})
.catch(() => {
uploading.value = false;
message.error('upload failed.');
});
};
return {
fileList,
uploading,
handleRemove,
beforeUpload,
handleUpload,
};
},
});
</script>

View File

@ -0,0 +1,80 @@
---
category: Components
type: Data Entry
title: Upload
cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
---
Upload file by selecting or dragging.
## When To Use
Uploading is the process of publishing information (web pages, text, pictures, video, etc.) to a remote server via a web page or upload tool.
- When you need to upload one or more files.
- When you need to show the process of uploading.
- When you need to upload files by dragging and dropping.
## API
| 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 | - | |
| action | Uploading URL | string\|(file) => `Promise` | - | |
| method | http method of upload request | string | `post` | 1.5.0 |
| directory | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false | |
| 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` | - | |
| customRequest | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest | Function | - | |
| data | Uploading params or function which can return uploading params. | object\|function(file) | - | |
| disabled | disable upload button | boolean | false | |
| 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\[] | - | |
| headers | Set request headers, valid above IE10. | object | - | |
| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | `text` | |
| 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 | |
| name | The name of uploading file | string | `file` | |
| previewFile | Customize preview file logic | (file: File \| Blob) => Promise<dataURL: string> | - | 1.5.0 |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| supportServerRender | Need to be turned on while the server side is rendering. | boolean | false | |
| 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 Name | Description | Arguments | Version |
| --- | --- | --- | --- | --- |
| 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) | - | |
| 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
> The function will be called when uploading is in progress, completed or failed
When uploading state change, it returns:
```jsx
{
file: { /* ... */ },
fileList: [ /* ... */ ],
event: { /* ... */ },
}
```
1. `file` File object for the current operation.
```jsx
{
uid: 'uid', // unique identifier, negative is recommend, to prevent interference with internal generated id
name: 'xx.png', // file name
status: 'done', // optionsuploading, done, error, removed
response: '{"status": "success"}', // response from server
linkProps: '{"download": "image"}', // additional html props of file link
xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header
}
```
2. `fileList` current list of files
3. `event` response from server, including uploading progress, supported by advanced browsers.

View File

@ -0,0 +1,17 @@
import type { App } from 'vue';
import Upload from './Upload';
import Dragger from './Dragger';
export type { UploadProps, UploadListProps, UploadChangeParam } from './interface';
/* istanbul ignore next */
export const UploadDragger = Dragger;
export default Object.assign(Upload, {
Dragger,
install(app: App) {
app.component(Upload.name, Upload);
app.component(Dragger.name, Dragger);
return app;
},
});

View File

@ -0,0 +1,81 @@
---
category: Components
subtitle: 上传
type: 数据录入
title: Upload
cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
---
文件选择上传和拖拽上传控件。
## 何时使用
上传是将信息(网页、文字、图片、视频等)通过网页或者上传工具发布到远程服务器上的过程。
- 当需要上传一个或一些文件时。
- 当需要展现上传的进度时。
- 当需要使用拖拽交互时。
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | 无 | |
| action | 上传的地址 | string\|(file) => `Promise` | 无 | |
| method | 上传请求的 http method | string | `post` | 1.5.0 |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。**注意IE9 不支持该方法**。 | (file, fileList) => `boolean | Promise` | 无 | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | Function | 无 | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | 无 | |
| disabled | 是否禁用 | boolean | false | |
| fileList | 已经上传的文件列表(受控) | object\[] | 无 | |
| headers | 设置上传的请求头部IE10 以上有效 | object | 无 | |
| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | `text` | |
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | |
| name | 发到后台的文件参数名 | string | `file` | |
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise<dataURL: string> | 无 | 1.5.0 |
| showUploadList | 是否展示 uploadList, 可设为一个对象,用于单独设定 showPreviewIcon 和 showRemoveIcon | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | |
| supportServerRender | 服务端渲染时需要打开这个 | boolean | false | |
| 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 | 无 | |
| preview | 点击文件链接或预览图标时的回调 | Function(file) | 无 | |
| download | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页。 | Function(file): void | 跳转新标签页 | 1.5.0 |
| reject | 拖拽文件不符合 accept 类型时的回调 | Function(fileList) | 无 | |
### change
> 上传中、完成、失败都会调用这个函数。
文件状态改变的回调,返回为:
```jsx
{
file: { /* ... */ },
fileList: [ /* ... */ ],
event: { /* ... */ },
}
```
1. `file` 当前操作的文件对象。
```jsx
{
uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
name: 'xx.png', // 文件名
status: 'done', // 状态有uploading done error removed
response: '{"status": "success"}', // 服务端响应内容
linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header
}
```
2. `fileList` 当前的文件列表。
3. `event` 上传中的服务端响应内容,包含了上传进度等信息,高级浏览器支持。

View File

@ -0,0 +1,183 @@
import type {
RcFile as OriRcFile,
UploadRequestOption as RcCustomRequestOptions,
} from '../vc-upload/interface';
import type { ProgressProps } from '../progress';
import type { VueNode } from '../_util/type';
import type { ExtractPropTypes, PropType } from 'vue';
export interface RcFile extends OriRcFile {
readonly lastModifiedDate: Date;
}
export type UploadFileStatus = 'error' | 'success' | 'done' | 'uploading' | 'removed';
export interface HttpRequestHeader {
[key: string]: string;
}
export interface UploadFile<T = any> {
uid: string;
size?: number;
name: string;
fileName?: string;
lastModified?: number;
lastModifiedDate?: Date;
url?: string;
status?: UploadFileStatus;
percent?: number;
thumbUrl?: string;
originFileObj?: RcFile;
response?: T;
error?: any;
linkProps?: any;
type?: string;
xhr?: T;
preview?: string;
}
export interface InternalUploadFile<T = any> extends UploadFile<T> {
originFileObj: RcFile;
}
export interface UploadChangeParam<T = UploadFile> {
// https://github.com/ant-design/ant-design/issues/14420
file: T;
fileList: UploadFile[];
event?: { percent: number };
}
// export interface ShowUploadListInterface {
// showRemoveIcon?: boolean;
// showPreviewIcon?: boolean;
// showDownloadIcon?: boolean;
// removeIcon?: VueNode | ((file: UploadFile) => VueNode);
// downloadIcon?: VueNode | ((file: UploadFile) => VueNode);
// previewIcon?: VueNode | ((file: UploadFile) => VueNode);
// }
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';
export type UploadListProgressProps = Omit<ProgressProps, 'percent' | 'type'>;
export type ItemRender<T = any> = (opt: {
originNode: VueNode;
file: UploadFile;
fileList: Array<UploadFile<T>>;
actions: {
download: () => void;
preview: () => void;
remove: () => void;
};
}) => VueNode;
type PreviewFileHandler = (file: File | Blob) => PromiseLike<string>;
type TransformFileHandler = (
file: RcFile,
) => string | Blob | File | PromiseLike<string | Blob | File>;
type BeforeUploadValueType = void | boolean | string | Blob | File;
function uploadProps<T = any>() {
return {
capture: [Boolean, String] as PropType<boolean | 'user' | 'environment'>,
type: String as PropType<UploadType>,
name: String,
defaultFileList: Array as PropType<Array<UploadFile<T>>>,
fileList: Array as PropType<Array<UploadFile<T>>>,
action: [String, Function] as PropType<
string | ((file: RcFile) => string) | ((file: RcFile) => PromiseLike<string>)
>,
directory: Boolean,
data: [Object, Function] as PropType<
| Record<string, unknown>
| ((file: UploadFile<T>) => Record<string, unknown> | Promise<Record<string, unknown>>)
>,
method: String as PropType<'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'>,
headers: Object as PropType<HttpRequestHeader>,
showUploadList: Boolean,
multiple: Boolean,
accept: String,
beforeUpload: Function as PropType<
(file: RcFile, FileList: RcFile[]) => BeforeUploadValueType | Promise<BeforeUploadValueType>
>,
onChange: Function as PropType<(info: UploadChangeParam<T>) => void>,
onDrop: Function as PropType<(event: DragEvent) => void>,
listType: String as PropType<UploadListType>,
onPreview: Function as PropType<(file: UploadFile<T>) => void>,
onDownload: Function as PropType<(file: UploadFile<T>) => void>,
onRemove: Function as PropType<
(file: UploadFile<T>) => void | boolean | Promise<void | boolean>
>,
supportServerRender: Boolean,
disabled: Boolean,
prefixCls: String,
customRequest: Function as PropType<(options: RcCustomRequestOptions) => void>,
withCredentials: Boolean,
openFileDialogOnClick: Boolean,
locale: Object as PropType<UploadLocale>,
id: String,
previewFile: Function as PropType<PreviewFileHandler>,
/** @deprecated Please use `beforeUpload` directly */
transformFile: Function as PropType<TransformFileHandler>,
iconRender: Function as PropType<
(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode
>,
isImageUrl: Function as PropType<(file: UploadFile) => boolean>,
progress: Object as PropType<UploadListProgressProps>,
itemRender: Function as PropType<ItemRender<T>>,
/** Config max count of `fileList`. Will replace current one when `maxCount` is 1 */
maxCount: Number,
height: [Number, String],
showRemoveIcon: Boolean,
showDownloadIcon: Boolean,
showPreviewIcon: Boolean,
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
};
}
export type UploadProps = Partial<ExtractPropTypes<ReturnType<typeof uploadProps>>>;
export interface UploadState<T = any> {
fileList: UploadFile<T>[];
dragState: string;
}
function uploadListProps<T = any>() {
return {
listType: String as PropType<UploadListType>,
onPreview: Function as PropType<(file: UploadFile<T>) => void>,
onDownload: Function as PropType<(file: UploadFile<T>) => void>,
onRemove: Function as PropType<(file: UploadFile<T>) => void | boolean>,
items: Array as PropType<Array<UploadFile<T>>>,
progress: Object as PropType<UploadListProgressProps>,
prefixCls: String as PropType<string>,
showRemoveIcon: Boolean,
showDownloadIcon: Boolean,
showPreviewIcon: Boolean,
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
locale: Object as PropType<UploadLocale>,
previewFile: Function as PropType<PreviewFileHandler>,
iconRender: Function as PropType<
(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode
>,
isImageUrl: Function as PropType<(file: UploadFile) => boolean>,
appendAction: Function as PropType<() => VueNode>,
itemRender: Function as PropType<ItemRender<T>>,
};
}
export type UploadListProps = Partial<ExtractPropTypes<ReturnType<typeof uploadListProps>>>;
export { uploadProps, uploadListProps };

View File

@ -0,0 +1,589 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@upload-prefix-cls: ~'@{ant-prefix}-upload';
@upload-item: ~'@{ant-prefix}-upload-list-item';
@upload-picture-card-size: 104px;
@upload-picture-card-border-style: @border-style-base;
.@{upload-prefix-cls} {
.reset-component();
outline: 0;
p {
margin: 0;
}
&-btn {
display: block;
width: 100%;
outline: none;
}
input[type='file'] {
cursor: pointer;
}
&&-select {
display: inline-block;
}
&&-disabled {
cursor: not-allowed;
}
&&-select-picture-card {
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin-right: 8px;
margin-bottom: 8px;
text-align: center;
vertical-align: top;
background-color: @background-color-light;
border: @border-width-base dashed @border-color-base;
border-radius: @border-radius-base;
cursor: pointer;
transition: border-color 0.3s;
> .@{upload-prefix-cls} {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
}
&:hover {
border-color: @primary-color;
.@{upload-prefix-cls}-disabled& {
border-color: @border-color-base;
}
}
}
&&-drag {
position: relative;
width: 100%;
height: 100%;
text-align: center;
background: @background-color-light;
border: @border-width-base dashed @border-color-base;
border-radius: @border-radius-base;
cursor: pointer;
transition: border-color 0.3s;
.@{upload-prefix-cls} {
padding: @padding-md 0;
}
&.@{upload-prefix-cls}-drag-hover:not(.@{upload-prefix-cls}-disabled) {
border-color: @primary-7;
}
&.@{upload-prefix-cls}-disabled {
cursor: not-allowed;
}
.@{upload-prefix-cls}-btn {
display: table;
height: 100%;
}
.@{upload-prefix-cls}-drag-container {
display: table-cell;
vertical-align: middle;
}
&:not(.@{upload-prefix-cls}-disabled):hover {
border-color: @primary-5;
}
p.@{upload-prefix-cls}-drag-icon {
.@{iconfont-css-prefix} {
color: @primary-5;
font-size: 48px;
}
margin-bottom: 20px;
}
p.@{upload-prefix-cls}-text {
margin: 0 0 4px;
color: @heading-color;
font-size: @font-size-lg;
}
p.@{upload-prefix-cls}-hint {
color: @text-color-secondary;
font-size: @font-size-base;
}
.@{iconfont-css-prefix}-plus {
color: @disabled-color;
font-size: 30px;
transition: all 0.3s;
&:hover {
color: @text-color-secondary;
}
}
&:hover .@{iconfont-css-prefix}-plus {
color: @text-color-secondary;
}
}
&-picture-card-wrapper {
.clearfix();
display: inline-block;
width: 100%;
}
}
.@{upload-prefix-cls}-list {
.reset-component();
.clearfix();
line-height: @line-height-base;
// ============================ Item ============================
&-item {
position: relative;
height: @line-height-base * @font-size-base;
margin-top: @margin-xs;
font-size: @font-size-base;
&-name {
display: inline-block;
width: 100%;
padding-left: @font-size-base + 8px;
overflow: hidden;
line-height: @line-height-base;
white-space: nowrap;
text-overflow: ellipsis;
}
&-card-actions {
position: absolute;
right: 0;
&-btn {
opacity: 0;
}
&-btn.@{ant-prefix}-btn-sm {
height: 20px;
line-height: 1;
}
&.picture {
top: 22px;
line-height: 0;
}
&-btn:focus,
&.picture &-btn {
opacity: 1;
}
.@{iconfont-css-prefix} {
color: @upload-actions-color;
}
}
&-info {
height: 100%;
padding: 0 4px;
transition: background-color 0.3s;
> span {
display: block;
width: 100%;
height: 100%;
}
.@{iconfont-css-prefix}-loading,
.@{upload-prefix-cls}-text-icon {
.@{iconfont-css-prefix} {
position: absolute;
top: (@font-size-base / 2) - 2px;
color: @text-color-secondary;
font-size: @font-size-base;
}
}
}
.@{iconfont-css-prefix}-close {
position: absolute;
top: 6px;
right: 4px;
color: @text-color-secondary;
font-size: 10px;
line-height: 0;
cursor: pointer;
opacity: 0;
transition: all 0.3s;
&:hover {
color: @text-color;
}
}
&:hover &-info {
background-color: @item-hover-bg;
}
&:hover .@{iconfont-css-prefix}-close {
opacity: 1;
}
&:hover &-card-actions-btn {
opacity: 1;
}
&-error,
&-error .@{upload-prefix-cls}-text-icon > .@{iconfont-css-prefix},
&-error &-name {
color: @error-color;
}
&-error &-card-actions {
.@{iconfont-css-prefix} {
color: @error-color;
}
&-btn {
opacity: 1;
}
}
&-progress {
position: absolute;
bottom: -12px;
width: 100%;
padding-left: @font-size-base + 12px;
font-size: @font-size-base;
line-height: 0;
}
}
// =================== Picture & Picture Card ===================
&-picture,
&-picture-card {
.@{upload-item} {
position: relative;
height: 66px;
padding: @padding-xs;
border: @border-width-base @upload-picture-card-border-style @border-color-base;
border-radius: @border-radius-base;
&:hover {
background: transparent;
}
&-error {
border-color: @error-color;
}
}
.@{upload-item}-info {
padding: 0;
}
.@{upload-item}:hover .@{upload-item}-info {
background: transparent;
}
.@{upload-item}-uploading {
border-style: dashed;
}
.@{upload-item}-thumbnail {
width: 48px;
height: 48px;
line-height: 60px;
text-align: center;
opacity: 0.8;
.@{iconfont-css-prefix} {
font-size: 26px;
}
}
// Adjust the color of the error icon : https://github.com/ant-design/ant-design/pull/24160
.@{upload-item}-error .@{upload-item}-thumbnail {
.@{iconfont-css-prefix} {
svg path {
&[fill='#e6f7ff'] {
fill: @error-color-deprecated-bg;
}
&[fill='#1890ff'] {
fill: @error-color;
}
}
}
}
.@{upload-item}-icon {
position: absolute;
top: 50%;
left: 50%;
font-size: 26px;
transform: translate(-50%, -50%);
.@{iconfont-css-prefix} {
font-size: 26px;
}
}
.@{upload-item}-image {
max-width: 100%;
}
.@{upload-item}-thumbnail img {
display: block;
width: 48px;
height: 48px;
overflow: hidden;
}
.@{upload-item}-name {
display: inline-block;
box-sizing: border-box;
max-width: 100%;
margin: 0 0 0 8px;
padding-right: 8px;
padding-left: 48px;
overflow: hidden;
line-height: 44px;
white-space: nowrap;
text-overflow: ellipsis;
transition: all 0.3s;
}
.@{upload-item}-uploading .@{upload-item}-name {
margin-bottom: 12px;
}
.@{upload-item}-progress {
bottom: 14px;
width: ~'calc(100% - 24px)';
margin-top: 0;
padding-left: 56px;
}
.@{iconfont-css-prefix}-close {
position: absolute;
top: 8px;
right: 8px;
line-height: 1;
opacity: 1;
}
}
// ======================== Picture Card ========================
&-picture-card {
&-container {
display: inline-block;
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin: 0 @margin-xs @margin-xs 0;
vertical-align: top;
}
&.@{upload-prefix-cls}-list::after {
display: none;
}
.@{upload-item} {
height: 100%;
margin: 0;
}
.@{upload-item}-info {
position: relative;
height: 100%;
overflow: hidden;
&::before {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
background-color: fade(@black, 50%);
opacity: 0;
transition: all 0.3s;
content: ' ';
}
}
.@{upload-item}:hover .@{upload-item}-info::before {
opacity: 1;
}
.@{upload-item}-actions {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
white-space: nowrap;
transform: translate(-50%, -50%);
opacity: 0;
transition: all 0.3s;
.@{iconfont-css-prefix}-eye,
.@{iconfont-css-prefix}-download,
.@{iconfont-css-prefix}-delete {
z-index: 10;
width: 16px;
margin: 0 4px;
color: @text-color-dark;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @text-color-inverse;
}
}
}
.@{upload-item}-info:hover + .@{upload-item}-actions,
.@{upload-item}-actions:hover {
opacity: 1;
}
.@{upload-item}-thumbnail,
.@{upload-item}-thumbnail img {
position: static;
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.@{upload-item}-name {
display: none;
margin: 8px 0 0;
padding: 0;
line-height: @line-height-base;
text-align: center;
}
.@{upload-item}-file + .@{upload-item}-name {
position: absolute;
bottom: 10px;
display: block;
}
.@{upload-item}-uploading {
&.@{upload-item} {
background-color: @background-color-light;
}
.@{upload-item}-info {
height: auto;
&::before,
.@{iconfont-css-prefix}-eye,
.@{iconfont-css-prefix}-delete {
display: none;
}
}
}
.@{upload-item}-progress {
bottom: 32px;
width: calc(100% - 14px);
padding-left: 0;
}
}
// ======================= Picture & Text =======================
&-text,
&-picture {
&-container {
transition: opacity @animation-duration-slow, height @animation-duration-slow;
&::before {
display: table;
width: 0;
height: 0;
content: '';
}
// Don't know why span here, just stretch it
.@{upload-prefix-cls}-span {
display: block;
flex: auto;
}
}
// text & picture no need this additional element.
// But it used for picture-card, let's keep it.
.@{upload-prefix-cls}-span {
display: flex;
align-items: center;
> * {
flex: none;
}
}
.@{upload-item}-name {
flex: auto;
margin: 0;
padding: 0 @padding-xs;
}
.@{upload-item}-card-actions {
position: static;
}
}
// ============================ Text ============================
&-text {
.@{upload-prefix-cls}-text-icon {
.@{iconfont-css-prefix} {
position: static;
}
}
}
// =========================== Motion ===========================
.@{upload-prefix-cls}-animate-inline-appear,
.@{upload-prefix-cls}-animate-inline-enter,
.@{upload-prefix-cls}-animate-inline-leave {
animation-duration: @animation-duration-slow;
animation-fill-mode: @ease-in-out-circ;
}
.@{upload-prefix-cls}-animate-inline-appear,
.@{upload-prefix-cls}-animate-inline-enter {
animation-name: uploadAnimateInlineIn;
}
.@{upload-prefix-cls}-animate-inline-leave {
animation-name: uploadAnimateInlineOut;
}
}
@keyframes uploadAnimateInlineIn {
from {
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
}
}
@keyframes uploadAnimateInlineOut {
to {
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
}
}
@import './rtl';

View File

@ -2,5 +2,6 @@ import '../../style/index.less';
import './index.less';
// style dependencies
import '../../button/style';
import '../../progress/style';
import '../../tooltip/style';

View File

@ -0,0 +1,179 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@upload-prefix-cls: ~'@{ant-prefix}-upload';
@upload-item: ~'@{ant-prefix}-upload-list-item';
.@{upload-prefix-cls} {
&-rtl {
direction: rtl;
}
&&-select-picture-card {
.@{upload-prefix-cls}-rtl& {
margin-right: auto;
margin-left: 8px;
}
}
}
.@{upload-prefix-cls}-list {
&-rtl {
direction: rtl;
}
&-item-list-type-text {
&:hover {
.@{upload-prefix-cls}-list-item-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 22px;
padding-left: 14px;
}
}
.@{upload-prefix-cls}-list-item-name-icon-count-2 {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 22px;
padding-left: 28px;
}
}
}
}
&-item {
&-name {
.@{upload-prefix-cls}-list-rtl & {
padding-right: @font-size-base + 8px;
padding-left: 0;
}
}
&-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl & {
padding-left: 14px;
}
}
&-card-actions {
.@{upload-prefix-cls}-list-rtl & {
right: auto;
left: 0;
}
.@{iconfont-css-prefix} {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 0;
padding-left: 5px;
}
}
}
&-info {
.@{upload-prefix-cls}-list-rtl & {
padding: 0 4px 0 12px;
}
}
.@{iconfont-css-prefix}-close {
.@{upload-prefix-cls}-list-rtl & {
right: auto;
left: 4px;
}
}
&-error &-card-actions {
.@{iconfont-css-prefix} {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 0;
padding-left: 5px;
}
}
}
&-progress {
.@{upload-prefix-cls}-list-rtl & {
padding-right: @font-size-base + 12px;
padding-left: 0;
}
}
}
&-picture,
&-picture-card {
.@{upload-item}-info {
padding: 0;
}
.@{upload-item}-thumbnail {
.@{upload-prefix-cls}-list-rtl& {
right: 8px;
left: auto;
}
}
.@{upload-item}-icon {
.@{upload-prefix-cls}-list-rtl& {
right: 50%;
left: auto;
transform: translate(50%, -50%);
}
}
.@{upload-item}-name {
.@{upload-prefix-cls}-list-rtl& {
margin: 0 8px 0 0;
padding-right: 48px;
padding-left: 8px;
}
}
.@{upload-item}-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 48px;
padding-left: 18px;
}
}
.@{upload-item}-name-icon-count-2 {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 48px;
padding-left: 36px;
}
}
.@{upload-item}-progress {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 0;
padding-left: 0;
}
}
.@{iconfont-css-prefix}-close {
.@{upload-prefix-cls}-list-rtl& {
right: auto;
left: 8px;
}
}
}
&-picture-card {
&-container {
.@{upload-prefix-cls}-list-rtl & {
margin: 0 0 @margin-xs @margin-xs;
}
}
.@{upload-item}-actions {
.@{upload-prefix-cls}-list-rtl& {
right: 50%;
left: auto;
transform: translate(50%, -50%);
}
}
.@{upload-item}-file + .@{upload-item}-name {
.@{upload-prefix-cls}-list-rtl& {
margin: 8px 0 0;
padding: 0;
}
}
}
}

View File

@ -0,0 +1,115 @@
import type { RcFile, UploadFile, InternalUploadFile } from './interface';
export function file2Obj(file: RcFile): InternalUploadFile {
return {
...file,
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
uid: file.uid,
percent: 0,
originFileObj: file,
};
}
/** Upload fileList. Replace file if exist or just push into it. */
export function updateFileList(file: UploadFile<any>, fileList: UploadFile<any>[]) {
const nextFileList = [...fileList];
const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === file.uid);
if (fileIndex === -1) {
nextFileList.push(file);
} else {
nextFileList[fileIndex] = file;
}
return nextFileList;
}
export function getFileItem(file: RcFile, fileList: UploadFile[]) {
const matchKey = file.uid !== undefined ? 'uid' : 'name';
return fileList.filter(item => item[matchKey] === file[matchKey])[0];
}
export function removeFileItem(file: UploadFile, fileList: UploadFile[]) {
const matchKey = file.uid !== undefined ? 'uid' : 'name';
const removed = fileList.filter(item => item[matchKey] !== file[matchKey]);
if (removed.length === fileList.length) {
return null;
}
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: string): boolean => type.indexOf('image/') === 0;
export const isImageUrl = (file: UploadFile): boolean => {
if (file.type && !file.thumbUrl) {
return isImageFileType(file.type);
}
const url: string = (file.thumbUrl || file.url || '') as string;
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: File | Blob): Promise<string> {
return new Promise(resolve => {
if (!file.type || !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);
});
}