// Utils import { createNamespace, addUnit, noop, isPromise, isDef } from '../utils'; import { toArray, readFile, isOversize, isImageFile } from './utils'; // Mixins import { FieldMixin } from '../mixins/field'; // Components import Icon from '../icon'; import Image from '../image'; import Loading from '../loading'; import ImagePreview from '../image-preview'; const [createComponent, bem] = createNamespace('uploader'); export default createComponent({ inheritAttrs: false, mixins: [FieldMixin], props: { disabled: Boolean, lazyLoad: Boolean, uploadText: String, afterRead: Function, beforeRead: Function, beforeDelete: Function, previewSize: [Number, String], previewOptions: Object, name: { type: [Number, String], default: '', }, accept: { type: String, default: 'image/*', }, modelValue: { type: Array, default: () => [], }, maxSize: { type: [Number, String], default: Number.MAX_VALUE, }, maxCount: { type: [Number, String], default: Number.MAX_VALUE, }, deletable: { type: Boolean, default: true, }, showUpload: { type: Boolean, default: true, }, previewImage: { type: Boolean, default: true, }, previewFullImage: { type: Boolean, default: true, }, imageFit: { type: String, default: 'cover', }, resultType: { type: String, default: 'dataUrl', }, uploadIcon: { type: String, default: 'photograph', }, }, emits: [ 'delete', 'oversize', 'close-preview', 'click-preview', 'update:modelValue', ], computed: { previewSizeWithUnit() { return addUnit(this.previewSize); }, }, methods: { getDetail(index = this.modelValue.length) { return { name: this.name, index, }; }, onChange(event) { let { files } = event.target; if (this.disabled || !files.length) { return; } files = files.length === 1 ? files[0] : [].slice.call(files); if (this.beforeRead) { const response = this.beforeRead(files, this.getDetail()); if (!response) { this.resetInput(); return; } if (isPromise(response)) { response .then((data) => { if (data) { this.readFile(data); } else { this.readFile(files); } }) .catch(this.resetInput); return; } } this.readFile(files); }, readFile(files) { const oversize = isOversize(files, this.maxSize); if (Array.isArray(files)) { const maxCount = this.maxCount - this.modelValue.length; if (files.length > maxCount) { files = files.slice(0, maxCount); } Promise.all(files.map((file) => readFile(file, this.resultType))).then( (contents) => { const fileList = files.map((file, index) => { const result = { file, status: '', message: '' }; if (contents[index]) { result.content = contents[index]; } return result; }); this.onAfterRead(fileList, oversize); } ); } else { readFile(files, this.resultType).then((content) => { const result = { file: files, status: '', message: '' }; if (content) { result.content = content; } this.onAfterRead(result, oversize); }); } }, onAfterRead(files, oversize) { this.resetInput(); let validFiles = files; if (oversize) { let oversizeFiles = files; if (Array.isArray(files)) { oversizeFiles = []; validFiles = []; files.forEach((item) => { if (item.file) { if (item.file.size > this.maxSize) { oversizeFiles.push(item); } else { validFiles.push(item); } } }); } else { validFiles = null; } this.$emit('oversize', oversizeFiles, this.getDetail()); } const isValidFiles = Array.isArray(validFiles) ? Boolean(validFiles.length) : Boolean(validFiles); if (isValidFiles) { this.$emit('update:modelValue', [ ...this.modelValue, ...toArray(validFiles), ]); if (this.afterRead) { this.afterRead(validFiles, this.getDetail()); } } }, onDelete(file, index) { if (this.beforeDelete) { const response = this.beforeDelete(file, this.getDetail(index)); if (!response) { return; } if (isPromise(response)) { response .then(() => { this.deleteFile(file, index); }) .catch(noop); return; } } this.deleteFile(file, index); }, deleteFile(file, index) { const fileList = this.modelValue.slice(0); fileList.splice(index, 1); this.$emit('update:modelValue', fileList); this.$emit('delete', file, this.getDetail(index)); }, resetInput() { /* istanbul ignore else */ if (this.$refs.input) { this.$refs.input.value = ''; } }, onPreviewImage(item) { if (!this.previewFullImage) { return; } const imageFiles = this.modelValue.filter((item) => isImageFile(item)); const imageContents = imageFiles.map((item) => item.content || item.url); this.imagePreview = ImagePreview({ images: imageContents, startPosition: imageFiles.indexOf(item), onClose: () => { this.$emit('close-preview'); }, ...this.previewOptions, }); }, // @exposed-api closeImagePreview() { if (this.imagePreview) { this.imagePreview.close(); } }, // @exposed-api chooseFile() { if (this.disabled) { return; } /* istanbul ignore else */ if (this.$refs.input) { this.$refs.input.click(); } }, genPreviewMask(item) { const { status, message } = item; if (status === 'uploading' || status === 'failed') { const MaskIcon = status === 'failed' ? ( ) : ( ); const showMessage = isDef(message) && message !== ''; return (
{MaskIcon} {showMessage &&
{message}
}
); } }, genPreviewItem(item, index) { const showDelete = item.status !== 'uploading' && this.deletable; const DeleteIcon = showDelete && (
{ event.stopPropagation(); this.onDelete(item, index); }} >
); const PreviewCover = this.$slots['preview-cover'] && (
{this.$slots['preview-cover'](item)}
); const Preview = isImageFile(item) ? ( { this.onPreviewImage(item); }} > {PreviewCover} ) : (
{item.file ? item.file.name : item.url}
{PreviewCover}
); return (
{ this.$emit('click-preview', item, this.getDetail(index)); }} > {Preview} {this.genPreviewMask(item)} {DeleteIcon}
); }, genPreviewList() { if (this.previewImage) { return this.modelValue.map(this.genPreviewItem); } }, genUpload() { if (this.modelValue.length >= this.maxCount || !this.showUpload) { return; } const Input = ( ); if (this.$slots.default) { return (
{this.$slots.default()} {Input}
); } let style; if (this.previewSize) { const size = this.previewSizeWithUnit; style = { width: size, height: size, }; } return (
{this.uploadText && ( {this.uploadText} )} {Input}
); }, }, render() { return (
{this.genPreviewList()} {this.genUpload()}
); }, });