perf: upload motion

pull/6265/head^2
tangjinzhou 2023-02-15 13:38:57 +08:00
parent 0464c84afc
commit 8a233d7c3a
6 changed files with 95 additions and 74 deletions

View File

@ -4,8 +4,8 @@ import './radio/style';
// import './checkbox/style'; // import './checkbox/style';
// import './grid/style'; // import './grid/style';
// import './tag/style'; // import './tag/style';
import './rate/style'; // import './rate/style';
import './pagination/style'; // import './pagination/style';
// import './avatar/style'; // import './avatar/style';
// import './badge/style'; // import './badge/style';
import './tabs/style'; import './tabs/style';
@ -29,14 +29,14 @@ import './auto-complete/style';
// import './affix/style'; // import './affix/style';
import './cascader/style'; import './cascader/style';
// import './back-top/style'; // import './back-top/style';
import './modal/style'; // import './modal/style';
// import './alert/style'; // import './alert/style';
import './time-picker/style'; import './time-picker/style';
import './steps/style'; import './steps/style';
// import './breadcrumb/style'; // import './breadcrumb/style';
import './calendar/style'; import './calendar/style';
import './date-picker/style'; // import './date-picker/style';
import './slider/style'; // import './slider/style';
import './table/style'; import './table/style';
// import './progress/style'; // import './progress/style';
import './timeline/style'; import './timeline/style';
@ -52,7 +52,7 @@ import './drawer/style';
// import './skeleton/style'; // import './skeleton/style';
// import './comment/style'; // import './comment/style';
// import './config-provider/style'; // import './config-provider/style';
import './empty/style'; // import './empty/style';
// import './statistic/style'; // import './statistic/style';
// import './result/style'; // import './result/style';
// import './descriptions/style'; // import './descriptions/style';

View File

@ -1,14 +1,7 @@
import type { UploadProps as RcUploadProps } from '../vc-upload'; import type { UploadProps as RcUploadProps } from '../vc-upload';
import VcUpload from '../vc-upload'; import VcUpload from '../vc-upload';
import UploadList from './UploadList'; import UploadList from './UploadList';
import type { import type { UploadFile, UploadChangeParam, ShowUploadListInterface, FileType } from './interface';
UploadType,
UploadListType,
UploadFile,
UploadChangeParam,
ShowUploadListInterface,
FileType,
} from './interface';
import { uploadProps } from './interface'; import { uploadProps } from './interface';
import { file2Obj, getFileItem, removeFileItem, updateFileList } from './utils'; import { file2Obj, getFileItem, removeFileItem, updateFileList } from './utils';
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver'; import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
@ -25,6 +18,7 @@ import { useInjectFormItemContext } from '../form';
// CSSINJS // CSSINJS
import useStyle from './style'; import useStyle from './style';
import { useInjectDisabled } from '../config-provider/DisabledContext';
export const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`; export const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`;
@ -33,18 +27,25 @@ export default defineComponent({
name: 'AUpload', name: 'AUpload',
inheritAttrs: false, inheritAttrs: false,
props: initDefaultProps(uploadProps(), { props: initDefaultProps(uploadProps(), {
type: 'select' as UploadType, type: 'select',
multiple: false, multiple: false,
action: '', action: '',
data: {}, data: {},
accept: '', accept: '',
showUploadList: true, showUploadList: true,
listType: 'text' as UploadListType, // or picture listType: 'text', // or picture
disabled: false, disabled: false,
supportServerRender: true, supportServerRender: true,
}), }),
setup(props, { slots, attrs, expose }) { setup(props, { slots, attrs, expose }) {
const formItemContext = useInjectFormItemContext(); const formItemContext = useInjectFormItemContext();
const { prefixCls, direction, disabled } = useConfigInject('upload', props);
// style
const [wrapSSR, hashId] = useStyle(prefixCls);
const disabledContext = useInjectDisabled();
const mergedDisabled = computed(() => disabledContext.value ?? disabled.value);
const [mergedFileList, setMergedFileList] = useMergedState(props.defaultFileList || [], { const [mergedFileList, setMergedFileList] = useMergedState(props.defaultFileList || [], {
value: toRef(props, 'fileList'), value: toRef(props, 'fileList'),
postState: list => { postState: list => {
@ -59,7 +60,7 @@ export default defineComponent({
}); });
const dragState = ref('drop'); const dragState = ref('drop');
const upload = ref(); const upload = ref(null);
onMounted(() => { onMounted(() => {
devWarning( devWarning(
props.fileList !== undefined || attrs.value === undefined, props.fileList !== undefined || attrs.value === undefined,
@ -294,11 +295,6 @@ export default defineComponent({
upload, upload,
}); });
const { prefixCls, direction } = useConfigInject('upload', props);
// style
const [wrapSSR, hashId] = useStyle(prefixCls);
const [locale] = useLocaleReceiver( const [locale] = useLocaleReceiver(
'Upload', 'Upload',
defaultLocale.Upload, defaultLocale.Upload,
@ -312,7 +308,6 @@ export default defineComponent({
previewFile, previewFile,
onPreview, onPreview,
onDownload, onDownload,
disabled,
isImageUrl, isImageUrl,
progress, progress,
itemRender, itemRender,
@ -330,7 +325,7 @@ export default defineComponent({
onPreview={onPreview} onPreview={onPreview}
onDownload={onDownload} onDownload={onDownload}
onRemove={handleRemove} onRemove={handleRemove}
showRemoveIcon={!disabled && showRemoveIcon} showRemoveIcon={!mergedDisabled.value && showRemoveIcon}
showPreviewIcon={showPreviewIcon} showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon} showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon} removeIcon={removeIcon}
@ -350,7 +345,7 @@ export default defineComponent({
); );
}; };
return () => { return () => {
const { listType, disabled, type } = props; const { listType, type } = props;
const { class: className, style: styleName, ...transAttrs } = attrs; const { class: className, style: styleName, ...transAttrs } = attrs;
const rcUploadProps = { const rcUploadProps = {
onBatchStart, onBatchStart,
@ -363,6 +358,7 @@ export default defineComponent({
prefixCls: prefixCls.value, prefixCls: prefixCls.value,
beforeUpload: mergedBeforeUpload, beforeUpload: mergedBeforeUpload,
onChange: undefined, onChange: undefined,
disabled: mergedDisabled.value,
}; };
delete (rcUploadProps as any).remove; delete (rcUploadProps as any).remove;
@ -370,12 +366,12 @@ export default defineComponent({
// !children: https://github.com/ant-design/ant-design/issues/14298 // !children: https://github.com/ant-design/ant-design/issues/14298
// disabled: https://github.com/ant-design/ant-design/issues/16478 // disabled: https://github.com/ant-design/ant-design/issues/16478
// https://github.com/ant-design/ant-design/issues/24197 // https://github.com/ant-design/ant-design/issues/24197
if (!slots.default || disabled) { if (!slots.default || mergedDisabled.value) {
delete rcUploadProps.id; delete rcUploadProps.id;
} }
const rtlCls = { const rtlCls = {
[`${prefixCls}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}; };
if (type === 'drag') { if (type === 'drag') {
@ -387,7 +383,7 @@ export default defineComponent({
file => file.status === 'uploading', file => file.status === 'uploading',
), ),
[`${prefixCls.value}-drag-hover`]: dragState.value === 'dragover', [`${prefixCls.value}-drag-hover`]: dragState.value === 'dragover',
[`${prefixCls.value}-disabled`]: disabled, [`${prefixCls.value}-disabled`]: mergedDisabled.value,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}, },
attrs.class, attrs.class,
@ -423,7 +419,7 @@ export default defineComponent({
const uploadButtonCls = classNames(prefixCls.value, { const uploadButtonCls = classNames(prefixCls.value, {
[`${prefixCls.value}-select`]: true, [`${prefixCls.value}-select`]: true,
[`${prefixCls.value}-select-${listType}`]: true, [`${prefixCls.value}-select-${listType}`]: true,
[`${prefixCls.value}-disabled`]: disabled, [`${prefixCls.value}-disabled`]: mergedDisabled.value,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}); });
const children = flattenChildren(slots.default?.()); const children = flattenChildren(slots.default?.());

View File

@ -1,4 +1,4 @@
import { computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue'; import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import type { ExtractPropTypes, CSSProperties } from 'vue'; import type { ExtractPropTypes, CSSProperties } from 'vue';
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined'; import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined'; import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
@ -69,6 +69,15 @@ export default defineComponent({
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearTimeout(progressRafRef.value); clearTimeout(progressRafRef.value);
}); });
const mergedStatus = ref(props.file?.status);
watch(
() => props.file?.status,
status => {
if (status !== 'removed') {
mergedStatus.value = status;
}
},
);
const { rootPrefixCls } = useConfigInject('upload', props); const { rootPrefixCls } = useConfigInject('upload', props);
const transitionProps = computed(() => getTransitionProps(`${rootPrefixCls.value}-fade`)); const transitionProps = computed(() => getTransitionProps(`${rootPrefixCls.value}-fade`));
return () => { return () => {
@ -100,10 +109,10 @@ export default defineComponent({
const iconNode = iconRender({ file }); const iconNode = iconRender({ file });
let icon = <div class={`${prefixCls}-text-icon`}>{iconNode}</div>; let icon = <div class={`${prefixCls}-text-icon`}>{iconNode}</div>;
if (listType === 'picture' || listType === 'picture-card') { if (listType === 'picture' || listType === 'picture-card') {
if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) { if (mergedStatus.value === 'uploading' || (!file.thumbUrl && !file.url)) {
const uploadingClassName = { const uploadingClassName = {
[`${prefixCls}-list-item-thumbnail`]: true, [`${prefixCls}-list-item-thumbnail`]: true,
[`${prefixCls}-list-item-file`]: file.status !== 'uploading', [`${prefixCls}-list-item-file`]: mergedStatus.value !== 'uploading',
}; };
icon = <div class={uploadingClassName}>{iconNode}</div>; icon = <div class={uploadingClassName}>{iconNode}</div>;
} else { } else {
@ -137,8 +146,7 @@ export default defineComponent({
const infoUploadingClass = { const infoUploadingClass = {
[`${prefixCls}-list-item`]: true, [`${prefixCls}-list-item`]: true,
[`${prefixCls}-list-item-${file.status}`]: true, [`${prefixCls}-list-item-${mergedStatus.value}`]: true,
[`${prefixCls}-list-item-list-type-${listType}`]: true,
}; };
const linkProps = const linkProps =
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps; typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
@ -152,7 +160,7 @@ export default defineComponent({
}) })
: null; : null;
const downloadIcon = const downloadIcon =
showDownloadIcon && file.status === 'done' showDownloadIcon && mergedStatus.value === 'done'
? actionIconRender({ ? actionIconRender({
customIcon: customDownloadIcon ? customDownloadIcon({ file }) : <DownloadOutlined />, customIcon: customDownloadIcon ? customDownloadIcon({ file }) : <DownloadOutlined />,
callback: () => onDownload(file), callback: () => onDownload(file),
@ -175,7 +183,7 @@ export default defineComponent({
</span> </span>
); );
const listItemNameClass = `${prefixCls}-list-item-name`; const listItemNameClass = `${prefixCls}-list-item-name`;
const preview = file.url const fileName = file.url
? [ ? [
<a <a
key="view" key="view"
@ -219,29 +227,26 @@ export default defineComponent({
</a> </a>
) : null; ) : null;
const actions = listType === 'picture-card' && file.status !== 'uploading' && ( const pictureCardActions = listType === 'picture-card' &&
mergedStatus.value !== 'uploading' && (
<span class={`${prefixCls}-list-item-actions`}> <span class={`${prefixCls}-list-item-actions`}>
{previewIcon} {previewIcon}
{file.status === 'done' && downloadIcon} {mergedStatus.value === 'done' && downloadIcon}
{removeIcon} {removeIcon}
</span> </span>
); );
let message;
if (file.response && typeof file.response === 'string') {
message = file.response;
} else {
message = file.error?.statusText || file.error?.message || locale.uploadError;
}
const dom = ( const dom = (
<div class={infoUploadingClass}> <div class={infoUploadingClass}>
{icon} {icon}
{preview} {fileName}
{actions} {pictureCardActions}
{showProgress.value && ( {showProgress.value && (
<Transition {...transitionProps.value}> <Transition {...transitionProps.value}>
<div v-show={file.status === 'uploading'} class={`${prefixCls}-list-item-progress`}> <div
v-show={mergedStatus.value === 'uploading'}
class={`${prefixCls}-list-item-progress`}
>
{'percent' in file ? ( {'percent' in file ? (
<Progress <Progress
{...(progressProps as UploadListProgressProps)} {...(progressProps as UploadListProgressProps)}
@ -258,8 +263,12 @@ export default defineComponent({
[`${prefixCls}-list-item-container`]: true, [`${prefixCls}-list-item-container`]: true,
[`${className}`]: !!className, [`${className}`]: !!className,
}; };
const message =
file.response && typeof file.response === 'string'
? file.response
: file.error?.statusText || file.error?.message || locale.uploadError;
const item = const item =
file.status === 'error' ? ( mergedStatus.value === 'error' ? (
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}> <Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
{dom} {dom}
</Tooltip> </Tooltip>

View File

@ -2,7 +2,7 @@ import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import PaperClipOutlined from '@ant-design/icons-vue/PaperClipOutlined'; import PaperClipOutlined from '@ant-design/icons-vue/PaperClipOutlined';
import PictureTwoTone from '@ant-design/icons-vue/PictureTwoTone'; import PictureTwoTone from '@ant-design/icons-vue/PictureTwoTone';
import FileTwoTone from '@ant-design/icons-vue/FileTwoTone'; import FileTwoTone from '@ant-design/icons-vue/FileTwoTone';
import type { UploadListType, InternalUploadFile, UploadFile } from '../interface'; import type { InternalUploadFile, UploadFile } from '../interface';
import { uploadListProps } from '../interface'; import { uploadListProps } from '../interface';
import { previewImage, isImageUrl } from '../utils'; import { previewImage, isImageUrl } from '../utils';
import type { ButtonProps } from '../../button'; import type { ButtonProps } from '../../button';
@ -24,7 +24,7 @@ export default defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },
name: 'AUploadList', name: 'AUploadList',
props: initDefaultProps(uploadListProps(), { props: initDefaultProps(uploadListProps(), {
listType: 'text' as UploadListType, // or picture listType: 'text', // or picture
progress: { progress: {
strokeWidth: 2, strokeWidth: 2,
showInfo: false, showInfo: false,
@ -138,23 +138,33 @@ export default defineComponent({
handleDownload: onInternalDownload, handleDownload: onInternalDownload,
}); });
const { prefixCls, direction } = useConfigInject('upload', props); const { prefixCls, rootPrefixCls } = useConfigInject('upload', props);
const listClassNames = computed(() => ({ const listClassNames = computed(() => ({
[`${prefixCls.value}-list`]: true, [`${prefixCls.value}-list`]: true,
[`${prefixCls.value}-list-${props.listType}`]: true, [`${prefixCls.value}-list-${props.listType}`]: true,
[`${prefixCls.value}-list-rtl`]: direction.value === 'rtl',
})); }));
const transitionGroupProps = computed(() => ({ const transitionGroupProps = computed(() => {
...collapseMotion( const motion = {
`${prefixCls.value}-${props.listType === 'picture-card' ? 'animate-inline' : 'animate'}`, ...collapseMotion(`${rootPrefixCls.value}-motion-collapse`),
), };
delete motion.onAfterAppear;
delete motion.onAfterEnter;
delete motion.onAfterLeave;
const motionConfig = {
...getTransitionGroupProps( ...getTransitionGroupProps(
`${prefixCls.value}-${props.listType === 'picture-card' ? 'animate-inline' : 'animate'}`, `${prefixCls.value}-${props.listType === 'picture-card' ? 'animate-inline' : 'animate'}`,
), ),
class: listClassNames.value, class: listClassNames.value,
appear: motionAppear.value, appear: motionAppear.value,
})); };
return props.listType !== 'picture-card'
? {
...motion,
...motionConfig,
}
: motionConfig;
});
return () => { return () => {
const { const {
listType, listType,

View File

@ -111,10 +111,7 @@ function uploadProps<T = any>() {
>([Object, Function]), >([Object, Function]),
method: stringType<'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'>(), method: stringType<'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'>(),
headers: objectType<HttpRequestHeader>(), headers: objectType<HttpRequestHeader>(),
showUploadList: someType<boolean | ShowUploadListInterface>( showUploadList: someType<boolean | ShowUploadListInterface>([Boolean, Object]),
[Boolean, Object],
undefined as boolean | ShowUploadListInterface,
),
multiple: booleanType(), multiple: booleanType(),
accept: String, accept: String,
beforeUpload: beforeUpload:
@ -141,7 +138,7 @@ function uploadProps<T = any>() {
customRequest: functionType<(options: RcCustomRequestOptions) => void>(), customRequest: functionType<(options: RcCustomRequestOptions) => void>(),
withCredentials: booleanType(), withCredentials: booleanType(),
openFileDialogOnClick: booleanType(), openFileDialogOnClick: booleanType(),
locale: objectType<UploadLocale>(undefined as UploadLocale), locale: objectType<UploadLocale>(),
id: String, id: String,
previewFile: functionType<PreviewFileHandler>(), previewFile: functionType<PreviewFileHandler>(),
/** @deprecated Please use `beforeUpload` directly */ /** @deprecated Please use `beforeUpload` directly */

View File

@ -110,6 +110,15 @@ export function previewImage(file: File | Blob): Promise<string> {
resolve(dataURL); resolve(dataURL);
}; };
img.crossOrigin = 'anonymous';
if (file.type.startsWith('image/svg+xml')) {
const reader = new FileReader();
reader.addEventListener('load', () => {
if (reader.result) img.src = reader.result as string;
});
reader.readAsDataURL(file);
} else {
img.src = window.URL.createObjectURL(file); img.src = window.URL.createObjectURL(file);
}
}); });
} }