import type { CSSProperties } from 'vue'; import { defineComponent, inject, nextTick } from 'vue'; import BaseMixin from '../_util/BaseMixin'; import { getOptionProps, initDefaultProps } from '../_util/props-util'; import { getTransitionProps, Transition, getTransitionGroupProps, TransitionGroup, } from '../_util/transition'; import { defaultConfigProvider } from '../config-provider'; import { previewImage, isImageUrl } from './utils'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import PaperClipOutlined from '@ant-design/icons-vue/PaperClipOutlined'; import PictureTwoTone from '@ant-design/icons-vue/PictureTwoTone'; import FileTwoTone from '@ant-design/icons-vue/FileOutlined'; import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined'; import DownloadOutlined from '@ant-design/icons-vue/DownloadOutlined'; import EyeOutlined from '@ant-design/icons-vue/EyeOutlined'; import Tooltip from '../tooltip'; import Progress from '../progress'; import classNames from '../_util/classNames'; import { uploadListProps } from './interface'; export default defineComponent({ name: 'AUploadList', mixins: [BaseMixin], props: initDefaultProps(uploadListProps, { listType: 'text', // or picture progressAttr: { strokeWidth: 2, showInfo: false, }, showRemoveIcon: true, showDownloadIcon: false, showPreviewIcon: true, previewFile: previewImage, }), setup() { return { configProvider: inject('configProvider', defaultConfigProvider), }; }, updated() { nextTick(() => { const { listType, items, previewFile } = this.$props; if (listType !== 'picture' && listType !== 'picture-card') { return; } (items || []).forEach(file => { if ( typeof document === 'undefined' || typeof window === 'undefined' || !window.FileReader || !window.File || !(file.originFileObj instanceof File || file.originFileObj instanceof Blob) || file.thumbUrl !== undefined ) { return; } /*eslint-disable */ file.thumbUrl = ''; if (previewFile) { previewFile(file.originFileObj).then(previewDataUrl => { // Need append '' to avoid dead loop file.thumbUrl = previewDataUrl || ''; (this as any).$forceUpdate(); }); } }); }); }, methods: { handlePreview(file, e) { const { onPreview } = this.$props; if (!onPreview) { return; } e.preventDefault(); return this.$emit('preview', file); }, handleDownload(file) { const { onDownload } = this.$props; if (typeof onDownload === 'function') { this.$emit('download', file); } else if (file.url) { window.open(file.url); } }, handleClose(file) { this.$emit('remove', file); }, }, render() { const { prefixCls: customizePrefixCls, items = [], listType, showPreviewIcon, showRemoveIcon, showDownloadIcon, locale, progressAttr, } = getOptionProps(this); const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('upload', customizePrefixCls); const list = items.map(file => { let progress; let icon = file.status === 'uploading' ? <LoadingOutlined /> : <PaperClipOutlined />; if (listType === 'picture' || listType === 'picture-card') { if (listType === 'picture-card' && file.status === 'uploading') { icon = <div class={`${prefixCls}-list-item-uploading-text`}>{locale.uploading}</div>; } else if (!file.thumbUrl && !file.url) { icon = <PictureTwoTone class={`${prefixCls}-list-item-thumbnail`} />; } else { const thumbnail = isImageUrl(file) ? ( <img src={file.thumbUrl || file.url} alt={file.name} class={`${prefixCls}-list-item-image`} /> ) : ( <FileTwoTone class={`${prefixCls}-list-item-icon`} /> ); icon = ( <a class={`${prefixCls}-list-item-thumbnail`} onClick={e => this.handlePreview(file, e)} href={file.url || file.thumbUrl} target="_blank" rel="noopener noreferrer" > {thumbnail} </a> ); } } if (file.status === 'uploading') { const progressProps = { ...progressAttr, type: 'line', percent: file.percent, }; // show loading icon if upload progress listener is disabled const loadingProgress = 'percent' in file ? <Progress {...progressProps} /> : null; progress = ( <div class={`${prefixCls}-list-item-progress`} key="progress"> {loadingProgress} </div> ); } const infoUploadingClass = classNames({ [`${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 ? ( <DeleteOutlined title={locale.removeFile} onClick={() => this.handleClose(file)} /> ) : null; const downloadIcon = showDownloadIcon && file.status === 'done' ? ( <DownloadOutlined title={locale.downloadFile} onClick={() => this.handleDownload(file)} /> ) : null; const downloadOrDelete = listType !== 'picture-card' && ( <span key="download-delete" class={`${prefixCls}-list-item-card-actions ${listType === 'picture' ? 'picture' : ''}`} > {downloadIcon && <a title={locale.downloadFile}>{downloadIcon}</a>} {removeIcon && <a title={locale.removeFile}>{removeIcon}</a>} </span> ); const listItemNameClass = classNames({ [`${prefixCls}-list-item-name`]: true, [`${prefixCls}-list-item-name-icon-count-${ [downloadIcon, removeIcon].filter(x => x).length }`]: true, }); const preview = file.url ? [ <a target="_blank" rel="noopener noreferrer" class={listItemNameClass} title={file.name} {...linkProps} href={file.url} onClick={e => this.handlePreview(file, e)} > {file.name} </a>, downloadOrDelete, ] : [ <span key="view" class={`${prefixCls}-list-item-name`} onClick={e => this.handlePreview(file, e)} title={file.name} > {file.name} </span>, downloadOrDelete, ]; const style: CSSProperties | undefined = file.url || file.thumbUrl ? undefined : { pointerEvents: 'none', opacity: 0.5, }; const previewIcon = showPreviewIcon ? ( <a href={file.url || file.thumbUrl} target="_blank" rel="noopener noreferrer" style={style} onClick={e => this.handlePreview(file, e)} title={locale.previewFile} > <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 && file.error.statusText) || locale.uploadError; } const iconAndPreview = ( <span> {icon} {preview} </span> ); const transitionProps = getTransitionProps('fade'); const dom = ( <div class={infoUploadingClass} key={file.uid}> <div class={`${prefixCls}-list-item-info`}>{iconAndPreview}</div> {actions} <Transition {...transitionProps}>{progress}</Transition> </div> ); const listContainerNameClass = classNames({ [`${prefixCls}-list-picture-card-container`]: listType === 'picture-card', }); return ( <div key={file.uid} class={listContainerNameClass}> {file.status === 'error' ? <Tooltip title={message}>{dom}</Tooltip> : <span>{dom}</span>} </div> ); }); const listClassNames = classNames({ [`${prefixCls}-list`]: true, [`${prefixCls}-list-${listType}`]: true, }); const animationDirection = listType === 'picture-card' ? 'animate-inline' : 'animate'; const transitionGroupProps = { ...getTransitionGroupProps(`${prefixCls}-${animationDirection}`), class: listClassNames, }; return ( <TransitionGroup {...transitionGroupProps} tag="div"> {list} </TransitionGroup> ); }, });