From 632a54d672ad063caa832e42d5e0f3b55102905a Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Tue, 15 Sep 2020 19:01:10 +0800 Subject: [PATCH] refactor(Uploader): extract PreviewItem component --- src/uploader/PreviewItem.js | 123 ++++++++++++++++++++ src/uploader/index.js | 224 +++++++++++++----------------------- src/uploader/shared.js | 5 + src/uploader/utils.ts | 4 +- 4 files changed, 209 insertions(+), 147 deletions(-) create mode 100644 src/uploader/PreviewItem.js create mode 100644 src/uploader/shared.js diff --git a/src/uploader/PreviewItem.js b/src/uploader/PreviewItem.js new file mode 100644 index 000000000..d8e0f155d --- /dev/null +++ b/src/uploader/PreviewItem.js @@ -0,0 +1,123 @@ +import { bem } from './shared'; +import { isImageFile } from './utils'; +import { isDef, getSizeStyle } from '../utils'; +import { callInterceptor } from '../utils/interceptor'; + +// Components +import Icon from '../icon'; +import Image from '../image'; +import Loading from '../loading'; + +export default { + props: { + name: String, + item: Object, + imageFit: String, + lazyLoad: Boolean, + deletable: Boolean, + previewSize: [Number, String], + beforeDelete: Function, + }, + + emits: ['delete', 'preview'], + + setup(props, { emit, slots }) { + const renderPreviewMask = () => { + const { status, message } = props.item; + + if (status === 'uploading' || status === 'failed') { + const MaskIcon = + status === 'failed' ? ( + + ) : ( + + ); + + const showMessage = isDef(message) && message !== ''; + + return ( +
+ {MaskIcon} + {showMessage &&
{message}
} +
+ ); + } + }; + + const onDelete = (event) => { + const { name, item, index, beforeDelete } = props; + + event.stopPropagation(); + callInterceptor({ + interceptor: beforeDelete, + args: [item, { name, index }], + done() { + emit('delete'); + }, + }); + }; + + const onPreview = () => { + emit('preview'); + }; + + const renderDeleteIcon = () => { + if (props.deletable && props.item.status !== 'uploading') { + return ( +
+ +
+ ); + } + }; + + const renderPreviewCover = () => { + if (slots['preview-cover']) { + const { index, item } = props; + return ( +
+ {slots['preview-cover']({ index, ...item })} +
+ ); + } + }; + + const renderPreview = () => { + const { item } = props; + + if (isImageFile(item)) { + return ( + + {renderPreviewCover()} + + ); + } + + return ( +
+ +
+ {item.file ? item.file.name : item.url} +
+ {renderPreviewCover()} +
+ ); + }; + + return () => ( +
+ {renderPreview()} + {renderPreviewMask()} + {renderDeleteIcon()} +
+ ); + }, +}; diff --git a/src/uploader/index.js b/src/uploader/index.js index 888da59ce..cb4bfe2c6 100644 --- a/src/uploader/index.js +++ b/src/uploader/index.js @@ -1,8 +1,8 @@ import { ref } from 'vue'; // Utils -import { callInterceptor } from '../utils/interceptor'; -import { isDef, isPromise, getSizeStyle, createNamespace } from '../utils'; +import { bem, createComponent } from './shared'; +import { isPromise, getSizeStyle, pick } from '../utils'; import { toArray, isOversize, isImageFile, readFileContent } from './utils'; // Composition @@ -11,12 +11,9 @@ import { useParentField } from '../composition/use-parent-field'; // Components import Icon from '../icon'; -import Image from '../image'; -import Loading from '../loading'; +import PreviewItem from './PreviewItem'; import ImagePreview from '../image-preview'; -const [createComponent, bem] = createNamespace('uploader'); - export default createComponent({ props: { capture: String, @@ -101,34 +98,42 @@ export default createComponent({ } }; - const onAfterRead = (files, oversize) => { + const findOversizeFiles = (files) => { + const valid = []; + const oversize = []; + + files.forEach((item) => { + if (item.file && item.file.size > props.maxSize) { + oversize.push(item); + } else { + valid.push(item); + } + }); + + return { valid, oversize }; + }; + + const onAfterRead = (items) => { resetInput(); - let validFiles = files; + let validFiles = items; - if (oversize) { - let oversizeFiles = files; - if (Array.isArray(files)) { - oversizeFiles = []; - validFiles = []; - files.forEach((item) => { - if (item.file) { - if (item.file.size > props.maxSize) { - oversizeFiles.push(item); - } else { - validFiles.push(item); - } - } - }); + const hasOversize = isOversize(items, props.maxSize); + + if (hasOversize) { + if (Array.isArray(items)) { + const result = findOversizeFiles(items); + validFiles = result.valid; + emit('oversize', result.oversizeFiles, getDetail()); } else { - validFiles = null; + emit('oversize', items, getDetail()); + return; } - emit('oversize', oversizeFiles, getDetail()); } - const isValidFiles = Array.isArray(validFiles) - ? Boolean(validFiles.length) - : Boolean(validFiles); + const isValidFiles = Boolean( + Array.isArray(validFiles) ? validFiles.length : validFiles + ); if (isValidFiles) { emit('update:modelValue', [ @@ -143,8 +148,7 @@ export default createComponent({ }; const readFile = (files) => { - const { maxSize, maxCount, modelValue, resultType } = props; - const oversize = isOversize(files, maxSize); + const { maxCount, modelValue, resultType } = props; if (Array.isArray(files)) { const remainCount = maxCount - modelValue.length; @@ -166,7 +170,7 @@ export default createComponent({ return result; }); - onAfterRead(fileList, oversize); + onAfterRead(fileList); }); } else { readFileContent(files, resultType).then((content) => { @@ -176,29 +180,11 @@ export default createComponent({ result.content = content; } - onAfterRead(result, oversize); + onAfterRead(result); }); } }; - const deleteFile = (file, index) => { - const fileList = props.modelValue.slice(0); - fileList.splice(index, 1); - - emit('update:modelValue', fileList); - emit('delete', file, getDetail(index)); - }; - - const onDelete = (file, index) => { - callInterceptor({ - interceptor: props.beforeDelete, - args: [file, getDetail(index)], - done() { - deleteFile(file, index); - }, - }); - }; - const onChange = (event) => { let { files } = event.target; @@ -234,19 +220,19 @@ export default createComponent({ let imagePreview; - const onPreviewImage = (item) => { + const onClosePreview = () => { + emit('close-preview'); + }; + + const previewImage = (item) => { if (props.previewFullImage) { - const imageFiles = props.modelValue.filter((item) => isImageFile(item)); - const imageContents = imageFiles.map( - (item) => item.content || item.url - ); + const imageFiles = props.modelValue.filter(isImageFile); + const images = imageFiles.map((item) => item.content || item.url); imagePreview = ImagePreview({ - images: imageContents, + images, startPosition: imageFiles.indexOf(item), - onClose: () => { - emit('close-preview'); - }, + onClose: onClosePreview, ...props.previewOptions, }); } @@ -258,95 +244,37 @@ export default createComponent({ } }; - const chooseFile = () => { - if (inputRef.value && !props.disabled) { - inputRef.value.click(); - } + const deleteFile = (item, index) => { + const fileList = props.modelValue.slice(0); + fileList.splice(index, 1); + + emit('update:modelValue', fileList); + emit('delete', item, getDetail(index)); }; - const renderPreviewMask = (item) => { - const { status, message } = item; - - if (status === 'uploading' || status === 'failed') { - const MaskIcon = - status === 'failed' ? ( - - ) : ( - - ); - - const showMessage = isDef(message) && message !== ''; - - return ( -
- {MaskIcon} - {showMessage &&
{message}
} -
- ); - } - }; - - const renderPreviewItem = (item, index) => { - const showDelete = item.status !== 'uploading' && props.deletable; - - const DeleteIcon = showDelete && ( -
{ - event.stopPropagation(); - onDelete(item, index); - }} - > - -
- ); - - const PreviewCover = slots['preview-cover'] && ( -
- {slots['preview-cover']({ - index, - ...item, - })} -
- ); - - const Preview = isImageFile(item) ? ( - { - onPreviewImage(item); - }} - > - {PreviewCover} - - ) : ( -
- -
- {item.file ? item.file.name : item.url} -
- {PreviewCover} -
- ); - - return ( -
{ - emit('click-preview', item, getDetail(index)); - }} - > - {Preview} - {renderPreviewMask(item)} - {DeleteIcon} -
- ); - }; + const renderPreviewItem = (item, index) => ( + { + emit('click-preview', item, getDetail(index)); + }} + onDelete={() => { + deleteFile(item, index); + }} + onPreview={() => { + previewImage(item); + }} + {...pick(props, [ + 'name', + 'lazyLoad', + 'imageFit', + 'deletable', + 'previewSize', + 'beforeDelete', + ])} + /> + ); const renderPreviewList = () => { if (props.previewImage) { @@ -392,6 +320,12 @@ export default createComponent({ ); }; + const chooseFile = () => { + if (inputRef.value && !props.disabled) { + inputRef.value.click(); + } + }; + usePublicApi({ chooseFile, closeImagePreview, diff --git a/src/uploader/shared.js b/src/uploader/shared.js new file mode 100644 index 000000000..25387ce5c --- /dev/null +++ b/src/uploader/shared.js @@ -0,0 +1,5 @@ +import { createNamespace } from '../utils'; + +const [createComponent, bem] = createNamespace('uploader'); + +export { bem, createComponent }; diff --git a/src/uploader/utils.ts b/src/uploader/utils.ts index 7533734b9..cee47b459 100644 --- a/src/uploader/utils.ts +++ b/src/uploader/utils.ts @@ -30,10 +30,10 @@ export function readFileContent(file: File, resultType: ResultType) { } export function isOversize( - files: File | File[], + items: FileListItem | FileListItem[], maxSize: number | string ): boolean { - return toArray(files).some((file) => file.size > maxSize); + return toArray(items).some((item) => item.file && item.file.size > maxSize); } export type FileListItem = {