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(undefined as UploadLocale), file: objectType(), items: arrayType(), listType: stringType(), 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(), onPreview: functionType<(file: UploadFile, e: Event) => void>(), onClose: functionType<(file: UploadFile) => void>(), onDownload: functionType<(file: UploadFile) => void>(), progress: objectType(), }; }; export type ListItemProps = Partial>>; 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 =
{iconNode}
; 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 =
{iconNode}
; } else { const thumbnail = isImgUrl?.(file) ? ( {file.name} ) : ( iconNode ); const aClassName = { [`${prefixCls}-list-item-thumbnail`]: true, [`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file), }; icon = ( onPreview(file, e)} href={file.url || file.thumbUrl} target="_blank" rel="noopener noreferrer" > {thumbnail} ); } } 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 }) : , callback: () => onClose(file), prefixCls, title: locale.removeFile, }) : null; const downloadIcon = showDownloadIcon && mergedStatus.value === 'done' ? actionIconRender({ customIcon: customDownloadIcon ? customDownloadIcon({ file }) : , callback: () => onDownload(file), prefixCls, title: locale.downloadFile, }) : null; const downloadOrDelete = listType !== 'picture-card' && ( {downloadIcon} {removeIcon} ); const listItemNameClass = `${prefixCls}-list-item-name`; const fileName = file.url ? [ onPreview(file, e)} > {file.name} , downloadOrDelete, ] : [ onPreview(file, e)} title={file.name} > {file.name} , downloadOrDelete, ]; const previewStyle: CSSProperties = { pointerEvents: 'none', opacity: 0.5, }; const previewIcon = showPreviewIcon ? ( onPreview(file, e)} title={locale.previewFile} > {customPreviewIcon ? customPreviewIcon({ file }) : } ) : null; const pictureCardActions = listType === 'picture-card' && mergedStatus.value !== 'uploading' && ( {previewIcon} {mergedStatus.value === 'done' && downloadIcon} {removeIcon} ); const dom = (
{icon} {fileName} {pictureCardActions} {showProgress.value && (
{'percent' in file ? ( ) : null}
)}
); 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' ? ( node.parentNode as HTMLElement}> {dom} ) : ( dom ); return (
{itemRender ? itemRender({ originNode: item, file, fileList: items, actions: { download: onDownload.bind(null, file), preview: onPreview.bind(null, file), remove: onClose.bind(null, file), }, }) : item}
); }; }, });