UNPKG

vxe-pc-ui

Version:
1,173 lines 70.8 kB
import { defineComponent, ref, h, reactive, watch, computed, TransitionGroup, inject, createCommentVNode, onUnmounted, onMounted } from 'vue'; import XEUtils from 'xe-utils'; import { VxeUI, getConfig, getI18n, getIcon, useSize, createEvent, globalEvents, renderEmptyElement } from '../../ui'; import { getSlotVNs } from '../..//ui/src/vn'; import { errLog, warnLog } from '../../ui/src/log'; import { initTpImg, getTpImg, getEventTargetNode, toCssUnit } from '../../ui/src/dom'; import { readLocalFile } from './util'; import VxeButtonComponent from '../../button/src/button'; export default defineComponent({ name: 'VxeUpload', props: { modelValue: [Array, String, Object], showList: { type: Boolean, default: () => getConfig().upload.showList }, moreConfig: Object, readonly: { type: Boolean, default: null }, disabled: { type: Boolean, default: null }, mode: { type: String, default: () => getConfig().upload.mode }, imageTypes: { type: Array, default: () => XEUtils.clone(getConfig().upload.imageTypes, true) }, imageConfig: { type: Object, default: () => XEUtils.clone(getConfig().upload.imageConfig, true) }, /** * 已废弃,被 image-config 替换 * @deprecated */ imageStyle: { type: Object, default: () => XEUtils.clone(getConfig().upload.imageStyle, true) }, fileTypes: { type: Array, default: () => XEUtils.clone(getConfig().upload.fileTypes, true) }, dragSort: Boolean, dragToUpload: { type: Boolean, default: () => XEUtils.clone(getConfig().upload.dragToUpload, true) }, pasteToUpload: { type: Boolean, default: () => XEUtils.clone(getConfig().upload.pasteToUpload, true) }, keyField: String, singleMode: Boolean, urlMode: Boolean, multiple: Boolean, limitSize: { type: [String, Number], default: () => getConfig().upload.limitSize }, showLimitSize: { type: Boolean, default: () => getConfig().upload.showLimitSize }, limitSizeText: { type: [String, Number, Function], default: () => getConfig().upload.limitSizeText }, limitCount: { type: [String, Number], default: () => getConfig().upload.limitCount }, showLimitCount: { type: Boolean, default: () => getConfig().upload.showLimitCount }, limitCountText: { type: [String, Number, Function], default: () => getConfig().upload.limitCountText }, nameField: { type: String, default: () => getConfig().upload.nameField }, typeField: { type: String, default: () => getConfig().upload.typeField }, urlField: { type: String, default: () => getConfig().upload.urlField }, sizeField: { type: String, default: () => getConfig().upload.sizeField }, showErrorStatus: { type: Boolean, default: () => getConfig().upload.showErrorStatus }, showProgress: { type: Boolean, default: () => getConfig().upload.showProgress }, progressText: { type: [String, Number, Function], default: () => getConfig().upload.progressText }, autoHiddenButton: { type: Boolean, default: () => getConfig().upload.autoHiddenButton }, showUploadButton: { type: Boolean, default: () => getConfig().upload.showUploadButton }, buttonText: { type: [String, Number, Function], default: () => getConfig().upload.buttonText }, buttonIcon: { type: String, default: () => getConfig().upload.buttonIcon }, showButtonText: { type: Boolean, default: () => getConfig().upload.showButtonText }, showButtonIcon: { type: Boolean, default: () => getConfig().upload.showButtonIcon }, showRemoveButton: { type: Boolean, default: () => getConfig().upload.showRemoveButton }, showDownloadButton: { type: Boolean, default: () => getConfig().upload.showDownloadButton }, showPreview: { type: Boolean, default: () => getConfig().upload.showPreview }, showTip: { type: Boolean, default: () => null }, tipText: [String, Number, Function], hintText: String, previewMethod: Function, uploadMethod: Function, beforeRemoveMethod: Function, removeMethod: Function, beforeDownloadMethod: Function, downloadMethod: Function, getUrlMethod: Function, getThumbnailUrlMethod: Function, size: { type: String, default: () => getConfig().upload.size || getConfig().size } }, emits: [ 'update:modelValue', 'add', 'remove', 'remove-fail', 'download', 'download-fail', 'upload-success', 'upload-error', 'sort-dragend' ], setup(props, context) { const { emit, slots } = context; const $xeForm = inject('$xeForm', null); const formItemInfo = inject('xeFormItemInfo', null); const $xeTable = inject('$xeTable', null); const xID = XEUtils.uniqueId(); const { computeSize } = useSize(props); const refElem = ref(); const refPopupElem = ref(); const refDragLineElem = ref(); const refModalDragLineElem = ref(); const reactData = reactive({ isDragUploadStatus: false, showMorePopup: false, isActivated: false, fileList: [], fileCacheMaps: {}, isDragMove: false, dragIndex: -1, dragTipText: '' }); const internalData = { imagePreviewTypes: ['jpg', 'jpeg', 'png', 'gif'], prevDragIndex: -1 // prevDragPos: '' }; const refMaps = { refElem }; const computeFormReadonly = computed(() => { const { readonly } = props; if (readonly === null) { if ($xeForm) { return $xeForm.props.readonly; } return false; } return readonly; }); const computeIsDisabled = computed(() => { const { disabled } = props; if (disabled === null) { if ($xeForm) { return $xeForm.props.disabled; } return false; } return disabled; }); const computeKeyField = computed(() => { return props.keyField || '_X_KEY'; }); const computeIsImage = computed(() => { return props.mode === 'image'; }); const computeNameProp = computed(() => { return props.nameField || 'name'; }); const computeTypeProp = computed(() => { return props.typeField || 'type'; }); const computeUrlProp = computed(() => { return props.urlField || 'url'; }); const computeSizeProp = computed(() => { return props.sizeField || 'size'; }); const computeLimitMaxSize = computed(() => { return XEUtils.toNumber(props.limitSize) * 1024 * 1024; }); const computeLimitMaxCount = computed(() => { return props.multiple ? XEUtils.toNumber(props.limitCount) : 1; }); const computeOverCount = computed(() => { const { multiple } = props; const { fileList } = reactData; const limitMaxCount = computeLimitMaxCount.value; if (multiple) { if (limitMaxCount) { return fileList.length >= limitMaxCount; } return true; } return fileList.length >= 1; }); const computeLimitSizeUnit = computed(() => { const limitSize = XEUtils.toNumber(props.limitSize); if (limitSize) { if (limitSize > 1048576) { return `${limitSize / 1048576}T`; } if (limitSize > 1024) { return `${limitSize / 1024}G`; } return `${limitSize}M`; } return ''; }); const computedShowTipText = computed(() => { const { showTip, tipText } = props; if (XEUtils.isBoolean(showTip)) { return showTip; } const defShowTip = getConfig().upload.showTip; if (XEUtils.isBoolean(defShowTip)) { return defShowTip; } if (tipText) { return true; } return false; }); const computedDefTipText = computed(() => { const { limitSize, fileTypes, multiple, limitCount } = props; const tipText = props.tipText || props.hintText; const isImage = computeIsImage.value; const limitSizeUnit = computeLimitSizeUnit.value; if (XEUtils.isString(tipText)) { return tipText; } if (XEUtils.isFunction(tipText)) { return `${tipText({})}`; } const defTips = []; if (isImage) { if (multiple && limitCount) { defTips.push(getI18n('vxe.upload.imgCountHint', [limitCount])); } if (limitSize && limitSizeUnit) { defTips.push(getI18n('vxe.upload.imgSizeHint', [limitSizeUnit])); } } else { if (fileTypes && fileTypes.length) { defTips.push(getI18n('vxe.upload.fileTypeHint', [fileTypes.join('/')])); } if (limitSize && limitSizeUnit) { defTips.push(getI18n('vxe.upload.fileSizeHint', [limitSizeUnit])); } if (multiple && limitCount) { defTips.push(getI18n('vxe.upload.fileCountHint', [limitCount])); } } return defTips.join(getI18n('vxe.base.comma')); }); const computeImageOpts = computed(() => { return Object.assign({}, props.imageConfig || props.imageStyle); }); const computeImgStyle = computed(() => { const imageOpts = computeImageOpts.value; const { width, height } = imageOpts; const stys = {}; if (width) { stys.width = toCssUnit(width); } if (height) { stys.height = toCssUnit(height); } return stys; }); const computeMoreOpts = computed(() => { return Object.assign({ showMoreButton: true }, props.moreConfig); }); const computeMaps = {}; const $xeUpload = { xID, props, context, reactData, internalData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps }; const getUniqueKey = () => { return XEUtils.uniqueId(); }; const getFieldKey = (item) => { const keyField = computeKeyField.value; return item[keyField]; }; const updateFileList = () => { const { modelValue, multiple } = props; const formReadonly = computeFormReadonly.value; const keyField = computeKeyField.value; const nameProp = computeNameProp.value; const typeProp = computeTypeProp.value; const urlProp = computeUrlProp.value; const sizeProp = computeSizeProp.value; const fileList = modelValue ? (modelValue ? (XEUtils.isArray(modelValue) ? modelValue : [modelValue]) : []).map(item => { if (!item || XEUtils.isString(item)) { const url = `${item || ''}`; const urlObj = XEUtils.parseUrl(item); const name = (urlObj ? urlObj.searchQuery[nameProp] : '') || parseFileName(url); return { [nameProp]: name, [typeProp]: (urlObj ? urlObj.searchQuery[typeProp] : '') || parseFileType(name), [urlProp]: url, [sizeProp]: XEUtils.toNumber(urlObj ? urlObj.searchQuery[sizeProp] : 0) || 0, [keyField]: getUniqueKey() }; } const name = item[nameProp] || ''; item[nameProp] = name; item[typeProp] = item[typeProp] || parseFileType(name); item[urlProp] = item[urlProp] || ''; item[sizeProp] = item[sizeProp] || 0; item[keyField] = item[keyField] || getUniqueKey(); return item; }) : []; reactData.fileList = (formReadonly || multiple) ? fileList : (fileList.slice(0, 1)); }; const parseFileName = (url) => { return decodeURIComponent(`${url || ''}`).split('/').pop() || ''; }; const parseFileType = (name) => { // 这里不用split('.').pop()因为没有后缀时会返回自身 const index = name.lastIndexOf('.'); if (index > 0) { return name.substring(index + 1).toLowerCase(); } return ''; }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $upload: $xeUpload }, params)); }; const handleChange = (value) => { const { singleMode, urlMode } = props; const urlProp = computeUrlProp.value; const nameProp = computeNameProp.value; let restList = value ? value.slice(0) : []; if (urlMode) { restList = restList.map(item => { const url = item[urlProp]; if (url) { const urlObj = XEUtils.parseUrl(url); if (!urlObj.searchQuery[nameProp]) { return `${url}${url.indexOf('?') === -1 ? '?' : '&'}${nameProp}=${encodeURIComponent(item[nameProp] || '')}`; } } return url; }); } emit('update:modelValue', singleMode ? (restList[0] || null) : restList); }; const getThumbnailFileUrl = (item) => { const getThumbnailUrlFn = props.getThumbnailUrlMethod || getConfig().upload.getThumbnailUrlMethod; if (getThumbnailUrlFn) { return getThumbnailUrlFn({ $upload: $xeUpload, option: item }); } return getFileUrl(item); }; const getFileUrl = (item) => { const getUrlFn = props.getUrlMethod || getConfig().upload.getUrlMethod; const urlProp = computeUrlProp.value; return getUrlFn ? getUrlFn({ $upload: $xeUpload, option: item }) : item[urlProp]; }; const handleDefaultFilePreview = (item) => { const { imageTypes, showDownloadButton } = props; const typeProp = computeTypeProp.value; const beforeDownloadFn = props.beforeDownloadMethod || getConfig().upload.beforeDownloadMethod; const { imagePreviewTypes } = internalData; // 如果是预览图片 if (imagePreviewTypes.concat(imageTypes || []).some(type => `${type}`.toLowerCase() === `${item[typeProp]}`.toLowerCase())) { if (VxeUI.previewImage) { VxeUI.previewImage({ urlList: [getFileUrl(item)], showDownloadButton, beforeDownloadMethod: beforeDownloadFn ? () => { return beforeDownloadFn({ $upload: $xeUpload, option: item }); } : undefined }); } } }; const handlePreviewFileEvent = (evnt, item) => { const previewFn = props.previewMethod || getConfig().upload.previewMethod; if (props.showPreview) { if (previewFn) { previewFn({ $upload: $xeUpload, option: item }); } else { handleDefaultFilePreview(item); } } }; const handlePreviewImageEvent = (evnt, item, index) => { const { showDownloadButton } = props; const { fileList } = reactData; const beforeDownloadFn = props.beforeDownloadMethod || getConfig().upload.beforeDownloadMethod; if (props.showPreview) { if (VxeUI.previewImage) { VxeUI.previewImage({ urlList: fileList.map(item => getFileUrl(item)), activeIndex: index, showDownloadButton, beforeDownloadMethod: beforeDownloadFn ? ({ index }) => { return beforeDownloadFn({ $upload: $xeUpload, option: fileList[index] }); } : undefined }); } } }; const handleUploadResult = (item, file) => { const { showErrorStatus } = props; const fileKey = getFieldKey(item); const uploadFn = props.uploadMethod || getConfig().upload.uploadMethod; if (uploadFn) { return Promise.resolve(uploadFn({ $upload: $xeUpload, file, option: item, updateProgress(percentNum) { const { fileCacheMaps } = reactData; const cacheItem = fileCacheMaps[getFieldKey(item)]; if (cacheItem) { cacheItem.percent = Math.max(0, Math.min(99, XEUtils.toNumber(percentNum))); } } })).then(res => { const { fileCacheMaps } = reactData; const cacheItem = fileCacheMaps[fileKey]; if (cacheItem) { cacheItem.percent = 100; } Object.assign(item, res); dispatchEvent('upload-success', { option: item, data: res }, null); }).catch((res) => { const { fileCacheMaps } = reactData; const cacheItem = fileCacheMaps[fileKey]; if (cacheItem) { cacheItem.status = 'error'; } if (showErrorStatus) { Object.assign(item, res); } else { reactData.fileList = reactData.fileList.filter(obj => getFieldKey(obj) !== fileKey); } dispatchEvent('upload-error', { option: item, data: res }, null); }).finally(() => { const { fileCacheMaps } = reactData; const cacheItem = fileCacheMaps[fileKey]; if (cacheItem) { cacheItem.loading = false; } }); } else { const { fileCacheMaps } = reactData; const cacheItem = fileCacheMaps[fileKey]; if (cacheItem) { cacheItem.loading = false; } } return Promise.resolve(); }; const handleReUpload = (item) => { const { uploadMethod, urlMode } = props; const { fileCacheMaps } = reactData; const fileKey = getFieldKey(item); const cacheItem = fileCacheMaps[fileKey]; const uploadFn = uploadMethod || getConfig().upload.uploadMethod; if (uploadFn && cacheItem) { const file = cacheItem.file; cacheItem.loading = true; cacheItem.status = ''; cacheItem.percent = 0; handleUploadResult(item, file).then(() => { if (urlMode) { handleChange(reactData.fileList); } }); } }; const uploadFile = (files, evnt) => { const { multiple, urlMode, showLimitSize, limitSizeText, showLimitCount, limitCountText } = props; const { fileList } = reactData; const uploadFn = props.uploadMethod || getConfig().upload.uploadMethod; const keyField = computeKeyField.value; const nameProp = computeNameProp.value; const typeProp = computeTypeProp.value; const urlProp = computeUrlProp.value; const sizeProp = computeSizeProp.value; const limitMaxSize = computeLimitMaxSize.value; const limitMaxCount = computeLimitMaxCount.value; const limitSizeUnit = computeLimitSizeUnit.value; let selectFiles = files; if (multiple && limitMaxCount) { // 校验文件数量 if (showLimitCount && fileList.length >= limitMaxCount) { if (VxeUI.modal) { VxeUI.modal.notification({ title: getI18n('vxe.modal.errTitle'), status: 'error', content: limitCountText ? `${XEUtils.isFunction(limitCountText) ? limitCountText({ maxCount: limitMaxCount }) : limitCountText}` : getI18n('vxe.upload.overCountErr', [limitMaxCount]) }); } return; } const overNum = selectFiles.length - (limitMaxCount - fileList.length); if (showLimitCount && overNum > 0) { const overExtraList = selectFiles.slice(limitMaxCount - fileList.length); if (limitCountText) { VxeUI.modal.notification({ title: getI18n('vxe.modal.errTitle'), status: 'error', content: `${XEUtils.isFunction(limitCountText) ? limitCountText({ maxCount: limitMaxCount }) : limitCountText}` }); } else if (VxeUI.modal) { VxeUI.modal.notification({ title: getI18n('vxe.modal.errTitle'), status: 'error', width: null, slots: { default() { return h('div', { class: 'vxe-upload--file-message-over-error' }, [ h('div', {}, getI18n('vxe.upload.overCountExtraErr', [limitMaxCount, overNum])), h('div', { class: 'vxe-upload--file-message-over-extra' }, overExtraList.map((file, index) => { return h('div', { key: index, class: 'vxe-upload--file-message-over-extra-item' }, file.name); })) ]); } } }); } } selectFiles = selectFiles.slice(0, limitMaxCount - fileList.length); } // 校验文件大小 if (showLimitSize && limitMaxSize) { for (let i = 0; i < files.length; i++) { const file = files[0]; if (file.size > limitMaxSize) { if (VxeUI.modal) { VxeUI.modal.notification({ title: getI18n('vxe.modal.errTitle'), status: 'error', content: limitSizeText ? `${XEUtils.isFunction(limitSizeText) ? limitSizeText({ maxSize: limitMaxSize }) : limitSizeText}` : getI18n('vxe.upload.overSizeErr', [limitSizeUnit]) }); } return; } } } const cacheMaps = Object.assign({}, reactData.fileCacheMaps); const newFileList = multiple ? fileList : []; const uploadPromiseRests = []; selectFiles.forEach(file => { const { name } = file; const fileKey = getUniqueKey(); const fileObj = { [nameProp]: name, [typeProp]: parseFileType(name), [sizeProp]: file.size, [urlProp]: URL.createObjectURL(file), [keyField]: fileKey }; if (uploadFn) { cacheMaps[fileKey] = { file: file, loading: true, status: '', percent: 0 }; } const item = reactive(fileObj); if (uploadFn) { uploadPromiseRests.push(handleUploadResult(item, file)); } newFileList.push(item); dispatchEvent('add', { option: item }, evnt); }); reactData.fileList = newFileList; reactData.fileCacheMaps = cacheMaps; Promise.all(urlMode ? uploadPromiseRests : []).then(() => { handleChange(newFileList); // 自动更新校验状态 if ($xeForm && formItemInfo) { $xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, newFileList); } }); }; const handleChoose = (evnt) => { const { multiple, imageTypes, fileTypes } = props; const isDisabled = computeIsDisabled.value; const isImage = computeIsImage.value; if (isDisabled) { return Promise.resolve({ status: false, files: [], file: null }); } return readLocalFile({ multiple, types: isImage ? imageTypes : fileTypes }).then((params) => { uploadFile(params.files, evnt); return params; }); }; const clickEvent = (evnt) => { handleChoose(evnt).catch(() => { // 错误文件类型 }); }; const handleRemoveEvent = (evnt, item, index) => { const { fileList } = reactData; fileList.splice(index, 1); handleChange(fileList); // 自动更新校验状态 if ($xeForm && formItemInfo) { $xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, fileList); } dispatchEvent('remove', { option: item }, evnt); }; const removeFileEvent = (evnt, item, index) => { const beforeRemoveFn = props.beforeRemoveMethod || getConfig().upload.beforeRemoveMethod; const removeFn = props.removeMethod || getConfig().upload.removeMethod; Promise.resolve(beforeRemoveFn ? beforeRemoveFn({ $upload: $xeUpload, option: item }) : true).then(status => { if (status) { if (removeFn) { Promise.resolve(removeFn({ $upload: $xeUpload, option: item })).then(() => { handleRemoveEvent(evnt, item, index); }).catch(e => e); } else { handleRemoveEvent(evnt, item, index); } } else { dispatchEvent('remove-fail', { option: item }, evnt); } }); }; const handleDownloadEvent = (evnt, item) => { dispatchEvent('download', { option: item }, evnt); }; const downloadFileEvent = (evnt, item) => { const beforeDownloadFn = props.beforeDownloadMethod || getConfig().upload.beforeDownloadMethod; const downloadFn = props.downloadMethod || getConfig().upload.downloadMethod; Promise.resolve(beforeDownloadFn ? beforeDownloadFn({ $upload: $xeUpload, option: item }) : true).then(status => { if (status) { if (downloadFn) { Promise.resolve(downloadFn({ $upload: $xeUpload, option: item })).then(() => { handleDownloadEvent(evnt, item); }).catch(e => e); } else { handleDownloadEvent(evnt, item); } } else { dispatchEvent('download-fail', { option: item }, evnt); } }); }; const handleUploadDragleaveEvent = (evnt) => { const targetElem = evnt.currentTarget; const { clientX, clientY } = evnt; if (targetElem) { const { x: targetX, y: targetY, height: targetHeight, width: targetWidth } = targetElem.getBoundingClientRect(); if (clientX < targetX || clientX > targetX + targetWidth || clientY < targetY || clientY > targetY + targetHeight) { reactData.isDragUploadStatus = false; } } }; const handleUploadDragoverEvent = (evnt) => { const dataTransfer = evnt.dataTransfer; if (dataTransfer) { const { items } = dataTransfer; if (items && items.length) { evnt.preventDefault(); reactData.isDragUploadStatus = true; } } }; const uploadTransferFileEvent = (evnt, files) => { const { imageTypes } = props; const { imagePreviewTypes } = internalData; const isImage = computeIsImage.value; if (isImage) { const pasteImgTypes = imagePreviewTypes.concat(imageTypes && imageTypes.length ? imageTypes : []); files = files.filter(file => { const fileType = `${file.type.split('/')[1] || ''}`.toLowerCase(); if (pasteImgTypes.some(type => `${type}`.toLowerCase() === fileType)) { return true; } return false; }); } // 如果全部不满足条件 if (!files.length) { if (VxeUI.modal) { VxeUI.modal.notification({ title: getI18n('vxe.modal.errTitle'), status: 'error', content: getI18n('vxe.upload.uploadTypeErr') }); } return; } uploadFile(files, evnt); }; const handleUploadDropEvent = (evnt) => { const dataTransfer = evnt.dataTransfer; if (dataTransfer) { const { items } = dataTransfer; if (items && items.length) { evnt.preventDefault(); const files = handleTransferFiles(items); if (files.length) { uploadTransferFileEvent(evnt, files); } } } reactData.isDragUploadStatus = false; }; const handleTransferFiles = (items) => { const files = []; XEUtils.arrayEach(items, item => { const file = item.getAsFile(); if (file) { files.push(file); } }); return files; }; const handleMoreEvent = () => { const formReadonly = computeFormReadonly.value; const isImage = computeIsImage.value; if (VxeUI.modal) { VxeUI.modal.open({ title: formReadonly ? getI18n('vxe.upload.morePopup.readTitle') : getI18n(`vxe.upload.morePopup.${isImage ? 'imageTitle' : 'fileTitle'}`), width: 660, height: 500, escClosable: true, showMaximize: true, resize: true, maskClosable: true, slots: { default() { const { showErrorStatus, dragToUpload, dragSort } = props; const { isActivated, isDragMove, isDragUploadStatus, dragIndex } = reactData; const { fileList } = reactData; const isDisabled = computeIsDisabled.value; const ons = {}; if (dragToUpload && dragIndex === -1) { ons.onDragover = handleUploadDragoverEvent; ons.onDragleave = handleUploadDragleaveEvent; ons.onDrop = handleUploadDropEvent; } return h('div', Object.assign({ ref: refPopupElem, class: ['vxe-upload--more-popup', { 'is--readonly': formReadonly, 'is--disabled': isDisabled, 'is--active': isActivated, 'show--error': showErrorStatus, 'is--drag': isDragUploadStatus }] }, ons), [ isImage ? (dragSort ? h(TransitionGroup, { name: `vxe-upload--drag-list${isDragMove ? '' : '-disabled'}`, tag: 'div', class: 'vxe-upload--image-more-list' }, { default: () => renderImageItemList(fileList, true).concat(renderImageAction(true)) }) : h('div', { class: 'vxe-upload--image-more-list' }, renderImageItemList(fileList, true).concat(renderImageAction(true)))) : h('div', { class: 'vxe-upload--file-more-list' }, [ renderFileAction(true), dragSort ? h(TransitionGroup, { name: `vxe-upload--drag-list${isDragMove ? '' : '-disabled'}`, tag: 'div', class: 'vxe-upload--file-list' }, { default: () => renderFileItemList(fileList, false) }) : h('div', { class: 'vxe-upload--file-list' }, renderFileItemList(fileList, true)) ]), dragSort ? h('div', { ref: refModalDragLineElem, class: 'vxe-upload--drag-line' }) : renderEmptyElement($xeUpload), isDragUploadStatus ? h('div', { class: 'vxe-upload--drag-placeholder' }, getI18n('vxe.upload.dragPlaceholder')) : renderEmptyElement($xeUpload) ]); } }, onShow() { reactData.showMorePopup = true; }, onHide() { reactData.showMorePopup = false; } }); } }; const showDropTip = (evnt, dragEl, dragPos) => { const { showMorePopup } = reactData; const el = refElem.value; const popupEl = refPopupElem.value; const wrapperEl = showMorePopup ? popupEl : el; if (!wrapperEl) { return; } const wrapperRect = wrapperEl.getBoundingClientRect(); const ddLineEl = refDragLineElem.value; const mdLineEl = refModalDragLineElem.value; const currDLineEl = showMorePopup ? mdLineEl : ddLineEl; if (currDLineEl) { const dragRect = dragEl.getBoundingClientRect(); currDLineEl.style.display = 'block'; currDLineEl.style.top = `${Math.max(1, dragRect.y - wrapperRect.y)}px`; currDLineEl.style.left = `${Math.max(1, dragRect.x - wrapperRect.x)}px`; currDLineEl.style.height = `${dragRect.height}px`; currDLineEl.style.width = `${dragRect.width - 1}px`; currDLineEl.setAttribute('drag-pos', dragPos); } }; const hideDropTip = () => { const ddLineEl = refDragLineElem.value; const mdLineEl = refModalDragLineElem.value; if (ddLineEl) { ddLineEl.style.display = ''; } if (mdLineEl) { mdLineEl.style.display = ''; } }; // 拖拽 const handleDragSortDragstartEvent = (evnt) => { evnt.stopPropagation(); if (evnt.dataTransfer) { evnt.dataTransfer.setDragImage(getTpImg(), 0, 0); } const dragEl = evnt.currentTarget; const parentEl = dragEl.parentElement; const dragIndex = XEUtils.findIndexOf(Array.from(parentEl.children), item => dragEl === item); reactData.isDragMove = true; reactData.dragIndex = dragIndex; setTimeout(() => { reactData.isDragMove = false; }, 500); }; const handleDragSortDragoverEvent = (evnt) => { evnt.stopPropagation(); evnt.preventDefault(); const { dragIndex } = reactData; if (dragIndex === -1) { return; } const isImage = computeIsImage.value; const dragEl = evnt.currentTarget; const parentEl = dragEl.parentElement; const currIndex = XEUtils.findIndexOf(Array.from(parentEl.children), item => dragEl === item); let dragPos = ''; if (isImage) { const offsetX = evnt.clientX - dragEl.getBoundingClientRect().x; dragPos = offsetX < dragEl.clientWidth / 2 ? 'left' : 'right'; } else { const offsetY = evnt.clientY - dragEl.getBoundingClientRect().y; dragPos = offsetY < dragEl.clientHeight / 2 ? 'top' : 'bottom'; } if (dragIndex === currIndex) { showDropTip(evnt, dragEl, dragPos); return; } showDropTip(evnt, dragEl, dragPos); internalData.prevDragIndex = currIndex; internalData.prevDragPos = dragPos; }; const handleDragSortDragendEvent = (evnt) => { const { fileList, dragIndex } = reactData; const { prevDragIndex, prevDragPos } = internalData; const oldIndex = dragIndex; const targetIndex = prevDragIndex; const dragOffsetIndex = prevDragPos === 'bottom' || prevDragPos === 'right' ? 1 : 0; const oldItem = fileList[oldIndex]; const newItem = fileList[targetIndex]; if (oldItem && newItem) { fileList.splice(oldIndex, 1); const ptfIndex = XEUtils.findIndexOf(fileList, item => newItem === item); const nIndex = ptfIndex + dragOffsetIndex; fileList.splice(nIndex, 0, oldItem); dispatchEvent('sort-dragend', { oldItem: oldItem, newItem: newItem, dragPos: prevDragPos, offsetIndex: dragOffsetIndex, _index: { newIndex: nIndex, oldIndex: oldIndex } }, evnt); } hideDropTip(); reactData.dragIndex = -1; }; const handleItemMousedownEvent = (evnt) => { if ($xeTable) { evnt.stopPropagation(); } reactData.isActivated = true; }; const handleGlobalPasteEvent = (evnt) => { const { pasteToUpload } = props; const { isActivated } = reactData; if (!isActivated || !pasteToUpload) { return; } const clipboardData = evnt.clipboardData || evnt.originalEvent.clipboardData; if (!clipboardData) { return; } const { items } = clipboardData; if (!items) { return; } const files = handleTransferFiles(items); if (files.length) { evnt.preventDefault(); uploadTransferFileEvent(evnt, files); } }; const handleGlobalMousedownEvent = (evnt) => { const el = refElem.value; const popupEl = refPopupElem.value; let isActivated = getEventTargetNode(evnt, el).flag; if (!isActivated && popupEl) { const parentEl = popupEl.parentElement || popupEl; const modalEl = parentEl ? parentEl.parentElement : parentEl; isActivated = getEventTargetNode(evnt, modalEl).flag; } reactData.isActivated = isActivated; }; const handleGlobalBlurEvent = () => { reactData.isActivated = false; }; const uploadMethods = { dispatchEvent, choose() { return handleChoose(null); } }; const uploadPrivateMethods = {}; Object.assign($xeUpload, uploadMethods, uploadPrivateMethods); const renderFileItemList = (currList, isMoreView) => { const { showRemoveButton, showDownloadButton, showProgress, progressText, showPreview, showErrorStatus, dragSort } = props; const { fileCacheMaps } = reactData; const isDisabled = computeIsDisabled.value; const formReadonly = computeFormReadonly.value; const nameProp = computeNameProp.value; const typeProp = computeTypeProp.value; const cornerSlot = slots.corner; const ons = {}; if (dragSort && currList.length > 1) { ons.onDragstart = handleDragSortDragstartEvent; ons.onDragover = handleDragSortDragoverEvent; ons.onDragend = handleDragSortDragendEvent; } return currList.map((item, index) => { const fileKey = getFieldKey(item); const cacheItem = fileCacheMaps[fileKey]; const isLoading = cacheItem && cacheItem.loading; const isError = cacheItem && cacheItem.status === 'error'; return h('div', Object.assign({ key: dragSort ? fileKey : index, class: ['vxe-upload--file-item', { 'is--preview': showPreview, 'is--loading': isLoading, 'is--error': isError }], fileid: fileKey, draggable: dragSort ? true : null }, ons), [ h('div', { class: 'vxe-upload--file-item-icon' }, [ h('i', { class: getIcon()[`UPLOAD_FILE_TYPE_${`${item[typeProp]}`.toLocaleUpperCase()}`] || getIcon().UPLOAD_FILE_TYPE_DEFAULT }) ]), h('div', { class: 'vxe-upload--file-item-name', onClick(evnt) { if (!isLoading && !isError) { handlePreviewFileEvent(evnt, item); } } }, `${item[nameProp] || ''}`), isLoading ? h('div', { class: 'vxe-upload--file-item-loading-icon' }, [ h('i', { class: getIcon().UPLOAD_LOADING }) ]) : createCommentVNode(), showProgress && isLoading && cacheItem ? h('div', { class: 'vxe-upload--file-item-loading-text' }, progressText ? XEUtils.toFormatString(`${XEUtils.isFunction(progressText) ? progressText({}) : progressText}`, { percent: cacheItem.percent }) : getI18n('vxe.upload.uploadProgress', [cacheItem.percent])) : createCommentVNode(), showErrorStatus && isError ? h('div', { class: 'vxe-upload--image-item-error' }, [ h(VxeButtonComponent, { icon: getIcon().UPLOAD_IMAGE_RE_UPLOAD, mode: 'text', status: 'primary', content: getI18n('vxe.upload.reUpload'), onClick() { handleReUpload(item); } }) ]) : createCommentVNode(), h('div', { class: 'vxe-upload--file-item-btn-wrapper' }, [ cornerSlot ? h('div', { class: 'vxe-upload--file-item-corner' }, getSlotVNs(cornerSlot({ option: item, isMoreView, readonly: formReadonly }))) : createCommentVNode(), showDownloadButton && !isLoading ? h('div', { class: 'vxe-upload--file-item-download-btn', onClick(evnt) { downloadFileEvent(evnt, item); } }, [ h('i', { class: getIcon().UPLOAD_FILE_DOWNLOAD }