You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
9.6 KiB
306 lines
9.6 KiB
import {
|
|
computed,
|
|
defineComponent,
|
|
onBeforeUnmount,
|
|
onMounted,
|
|
shallowRef,
|
|
watch,
|
|
Transition,
|
|
} from 'vue';
|
|
import type { ExtractPropTypes, 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 '../../config-provider/hooks/useConfigInject';
|
|
import { getTransitionProps } from '../../_util/transition';
|
|
import { booleanType, stringType, functionType, arrayType, objectType } from '../../_util/type';
|
|
|
|
export const listItemProps = () => {
|
|
return {
|
|
prefixCls: String,
|
|
locale: objectType<UploadLocale>(undefined as UploadLocale),
|
|
file: objectType<UploadFile>(),
|
|
items: arrayType<UploadFile[]>(),
|
|
listType: stringType<UploadListType>(),
|
|
isImgUrl: functionType<(file: UploadFile) => boolean>(),
|
|
|
|
showRemoveIcon: booleanType(),
|
|
showDownloadIcon: booleanType(),
|
|
showPreviewIcon: booleanType(),
|
|
removeIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
|
|
downloadIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
|
|
previewIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
|
|
|
|
iconRender: functionType<(opt: { file: UploadFile }) => VueNode>(),
|
|
actionIconRender:
|
|
functionType<
|
|
(opt: {
|
|
customIcon: VueNode;
|
|
callback: () => void;
|
|
prefixCls: string;
|
|
title?: string | undefined;
|
|
}) => VueNode
|
|
>(),
|
|
itemRender: functionType<ItemRender>(),
|
|
onPreview: functionType<(file: UploadFile, e: Event) => void>(),
|
|
onClose: functionType<(file: UploadFile) => void>(),
|
|
onDownload: functionType<(file: UploadFile) => void>(),
|
|
progress: objectType<UploadListProgressProps>(),
|
|
};
|
|
};
|
|
|
|
export type ListItemProps = Partial<ExtractPropTypes<ReturnType<typeof listItemProps>>>;
|
|
export default defineComponent({
|
|
compatConfig: { MODE: 3 },
|
|
name: 'ListItem',
|
|
inheritAttrs: false,
|
|
props: listItemProps(),
|
|
setup(props, { slots, attrs }) {
|
|
const showProgress = shallowRef(false);
|
|
const progressRafRef = shallowRef();
|
|
onMounted(() => {
|
|
progressRafRef.value = setTimeout(() => {
|
|
showProgress.value = true;
|
|
}, 300);
|
|
});
|
|
onBeforeUnmount(() => {
|
|
clearTimeout(progressRafRef.value);
|
|
});
|
|
const mergedStatus = shallowRef(props.file?.status);
|
|
watch(
|
|
() => props.file?.status,
|
|
status => {
|
|
if (status !== 'removed') {
|
|
mergedStatus.value = status;
|
|
}
|
|
},
|
|
);
|
|
const { rootPrefixCls } = useConfigInject('upload', props);
|
|
const transitionProps = computed(() => getTransitionProps(`${rootPrefixCls.value}-fade`));
|
|
return () => {
|
|
const {
|
|
prefixCls,
|
|
locale,
|
|
listType,
|
|
file,
|
|
items,
|
|
progress: progressProps,
|
|
iconRender = slots.iconRender,
|
|
actionIconRender = slots.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 iconNode = iconRender({ file });
|
|
let icon = <div class={`${prefixCls}-text-icon`}>{iconNode}</div>;
|
|
if (listType === 'picture' || listType === 'picture-card') {
|
|
if (mergedStatus.value === 'uploading' || (!file.thumbUrl && !file.url)) {
|
|
const uploadingClassName = {
|
|
[`${prefixCls}-list-item-thumbnail`]: true,
|
|
[`${prefixCls}-list-item-file`]: mergedStatus.value !== '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`}
|
|
crossorigin={file.crossOrigin}
|
|
/>
|
|
) : (
|
|
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-${mergedStatus.value}`]: 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 && mergedStatus.value === '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-actions`,
|
|
{
|
|
picture: listType === 'picture',
|
|
},
|
|
]}
|
|
>
|
|
{downloadIcon}
|
|
{removeIcon}
|
|
</span>
|
|
);
|
|
const listItemNameClass = `${prefixCls}-list-item-name`;
|
|
const fileName = 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 pictureCardActions = listType === 'picture-card' &&
|
|
mergedStatus.value !== 'uploading' && (
|
|
<span class={`${prefixCls}-list-item-actions`}>
|
|
{previewIcon}
|
|
{mergedStatus.value === 'done' && downloadIcon}
|
|
{removeIcon}
|
|
</span>
|
|
);
|
|
|
|
const dom = (
|
|
<div class={infoUploadingClass}>
|
|
{icon}
|
|
{fileName}
|
|
{pictureCardActions}
|
|
{showProgress.value && (
|
|
<Transition {...transitionProps.value}>
|
|
<div
|
|
v-show={mergedStatus.value === 'uploading'}
|
|
class={`${prefixCls}-list-item-progress`}
|
|
>
|
|
{'percent' in file ? (
|
|
<Progress
|
|
{...(progressProps as UploadListProgressProps)}
|
|
type="line"
|
|
percent={file.percent}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</Transition>
|
|
)}
|
|
</div>
|
|
);
|
|
const listContainerNameClass = {
|
|
[`${prefixCls}-list-item-container`]: true,
|
|
[`${className}`]: !!className,
|
|
};
|
|
const message =
|
|
file.response && typeof file.response === 'string'
|
|
? file.response
|
|
: file.error?.statusText || file.error?.message || locale.uploadError;
|
|
const item =
|
|
mergedStatus.value === 'error' ? (
|
|
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
|
|
{dom}
|
|
</Tooltip>
|
|
) : (
|
|
dom
|
|
);
|
|
|
|
return (
|
|
<div class={listContainerNameClass} style={style as CSSProperties}>
|
|
{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>
|
|
);
|
|
};
|
|
},
|
|
});
|