vxe-pc-ui
Version:
A vue based PC component library
1,371 lines (1,289 loc) • 62.9 kB
text/typescript
import { defineComponent, ref, h, reactive, watch, computed, TransitionGroup, PropType, 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'
import type { VxeUploadDefines, VxeUploadPropTypes, UploadReactData, UploadInternalData, UploadPrivateMethods, UploadMethods, VxeUploadEmits, UploadPrivateRef, VxeUploadPrivateComputed, VxeUploadConstructor, VxeUploadPrivateMethods, VxeFormDefines, VxeFormConstructor, VxeFormPrivateMethods, ValueOf } from '../../../types'
import type { VxeTableConstructor, VxeTablePrivateMethods } from '../../../types/components/table'
export default defineComponent({
name: 'VxeUpload',
props: {
modelValue: [Array, String, Object] as PropType<VxeUploadPropTypes.ModelValue>,
showList: {
type: Boolean as PropType<VxeUploadPropTypes.ShowList>,
default: () => getConfig().upload.showList
},
moreConfig: Object as PropType<VxeUploadPropTypes.MoreConfig>,
readonly: {
type: Boolean as PropType<VxeUploadPropTypes.Readonly>,
default: null
},
disabled: {
type: Boolean as PropType<VxeUploadPropTypes.Disabled>,
default: null
},
mode: {
type: String as PropType<VxeUploadPropTypes.Mode>,
default: () => getConfig().upload.mode
},
imageTypes: {
type: Array as PropType<VxeUploadPropTypes.ImageTypes>,
default: () => XEUtils.clone(getConfig().upload.imageTypes, true)
},
imageConfig: {
type: Object as PropType<VxeUploadPropTypes.ImageConfig>,
default: () => XEUtils.clone(getConfig().upload.imageConfig, true)
},
/**
* 已废弃,被 image-config 替换
* @deprecated
*/
imageStyle: {
type: Object as PropType<VxeUploadPropTypes.ImageStyle>,
default: () => XEUtils.clone(getConfig().upload.imageStyle, true)
},
fileTypes: {
type: Array as PropType<VxeUploadPropTypes.FileTypes>,
default: () => XEUtils.clone(getConfig().upload.fileTypes, true)
},
dragSort: Boolean as PropType<VxeUploadPropTypes.DragSort>,
dragToUpload: {
type: Boolean as PropType<VxeUploadPropTypes.DragToUpload>,
default: () => XEUtils.clone(getConfig().upload.dragToUpload, true)
},
pasteToUpload: {
type: Boolean as PropType<VxeUploadPropTypes.PasteToUpload>,
default: () => XEUtils.clone(getConfig().upload.pasteToUpload, true)
},
keyField: String as PropType<VxeUploadPropTypes.KeyField>,
singleMode: Boolean as PropType<VxeUploadPropTypes.SingleMode>,
urlMode: Boolean as PropType<VxeUploadPropTypes.UrlMode>,
multiple: Boolean as PropType<VxeUploadPropTypes.Multiple>,
limitSize: {
type: [String, Number] as PropType<VxeUploadPropTypes.LimitSize>,
default: () => getConfig().upload.limitSize
},
showLimitSize: {
type: Boolean as PropType<VxeUploadPropTypes.ShowLimitSize>,
default: () => getConfig().upload.showLimitSize
},
limitSizeText: {
type: [String, Number, Function] as PropType<VxeUploadPropTypes.LimitSizeText>,
default: () => getConfig().upload.limitSizeText
},
limitCount: {
type: [String, Number] as PropType<VxeUploadPropTypes.LimitCount>,
default: () => getConfig().upload.limitCount
},
showLimitCount: {
type: Boolean as PropType<VxeUploadPropTypes.ShowLimitCount>,
default: () => getConfig().upload.showLimitCount
},
limitCountText: {
type: [String, Number, Function] as PropType<VxeUploadPropTypes.LimitCountText>,
default: () => getConfig().upload.limitCountText
},
nameField: {
type: String as PropType<VxeUploadPropTypes.NameField>,
default: () => getConfig().upload.nameField
},
typeField: {
type: String as PropType<VxeUploadPropTypes.TypeField>,
default: () => getConfig().upload.typeField
},
urlField: {
type: String as PropType<VxeUploadPropTypes.UrlField>,
default: () => getConfig().upload.urlField
},
sizeField: {
type: String as PropType<VxeUploadPropTypes.SizeField>,
default: () => getConfig().upload.sizeField
},
showErrorStatus: {
type: Boolean as PropType<VxeUploadPropTypes.ShowErrorStatus>,
default: () => getConfig().upload.showErrorStatus
},
showProgress: {
type: Boolean as PropType<VxeUploadPropTypes.ShowProgress>,
default: () => getConfig().upload.showProgress
},
progressText: {
type: [String, Number, Function] as PropType<VxeUploadPropTypes.ProgressText>,
default: () => getConfig().upload.progressText
},
autoHiddenButton: {
type: Boolean as PropType<VxeUploadPropTypes.AutoHiddenButton>,
default: () => getConfig().upload.autoHiddenButton
},
showUploadButton: {
type: Boolean as PropType<VxeUploadPropTypes.ShowUploadButton>,
default: () => getConfig().upload.showUploadButton
},
buttonText: {
type: [String, Number, Function] as PropType<VxeUploadPropTypes.ButtonText>,
default: () => getConfig().upload.buttonText
},
buttonIcon: {
type: String as PropType<VxeUploadPropTypes.ButtonIcon>,
default: () => getConfig().upload.buttonIcon
},
showButtonText: {
type: Boolean as PropType<VxeUploadPropTypes.ShowButtonText>,
default: () => getConfig().upload.showButtonText
},
showButtonIcon: {
type: Boolean as PropType<VxeUploadPropTypes.ShowButtonIcon>,
default: () => getConfig().upload.showButtonIcon
},
showRemoveButton: {
type: Boolean as PropType<VxeUploadPropTypes.ShowRemoveButton>,
default: () => getConfig().upload.showRemoveButton
},
showDownloadButton: {
type: Boolean as PropType<VxeUploadPropTypes.ShowDownloadButton>,
default: () => getConfig().upload.showDownloadButton
},
showPreview: {
type: Boolean as PropType<VxeUploadPropTypes.ShowPreview>,
default: () => getConfig().upload.showPreview
},
showTip: {
type: Boolean as PropType<VxeUploadPropTypes.ShowTip>,
default: () => null
},
tipText: [String, Number, Function] as PropType<VxeUploadPropTypes.TipText>,
hintText: String as PropType<VxeUploadPropTypes.HintText>,
previewMethod: Function as PropType<VxeUploadPropTypes.PreviewMethod>,
uploadMethod: Function as PropType<VxeUploadPropTypes.UploadMethod>,
beforeRemoveMethod: Function as PropType<VxeUploadPropTypes.BeforeRemoveMethod>,
removeMethod: Function as PropType<VxeUploadPropTypes.RemoveMethod>,
beforeDownloadMethod: Function as PropType<VxeUploadPropTypes.BeforeDownloadMethod>,
downloadMethod: Function as PropType<VxeUploadPropTypes.DownloadMethod>,
getUrlMethod: Function as PropType<VxeUploadPropTypes.GetUrlMethod>,
getThumbnailUrlMethod: Function as PropType<VxeUploadPropTypes.GetThumbnailUrlMethod>,
size: {
type: String as PropType<VxeUploadPropTypes.Size>,
default: () => getConfig().upload.size || getConfig().size
}
},
emits: [
'update:modelValue',
'add',
'remove',
'remove-fail',
'download',
'download-fail',
'upload-success',
'upload-error',
'sort-dragend'
] as VxeUploadEmits,
setup (props, context) {
const { emit, slots } = context
const $xeForm = inject<VxeFormConstructor & VxeFormPrivateMethods | null>('$xeForm', null)
const formItemInfo = inject<VxeFormDefines.ProvideItemInfo | null>('xeFormItemInfo', null)
const $xeTable = inject<(VxeTableConstructor & VxeTablePrivateMethods) | null>('$xeTable', null)
const xID = XEUtils.uniqueId()
const { computeSize } = useSize(props)
const refElem = ref<HTMLDivElement>()
const refPopupElem = ref<HTMLDivElement>()
const refDragLineElem = ref<HTMLDivElement>()
const refModalDragLineElem = ref<HTMLDivElement>()
const reactData = reactive<UploadReactData>({
isDragUploadStatus: false,
showMorePopup: false,
isActivated: false,
fileList: [],
fileCacheMaps: {},
isDragMove: false,
dragIndex: -1,
dragTipText: ''
})
const internalData: UploadInternalData = {
imagePreviewTypes: ['jpg', 'jpeg', 'png', 'gif'],
prevDragIndex: -1
// prevDragPos: ''
}
const refMaps: UploadPrivateRef = {
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: string[] = []
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: Record<string, string> = {}
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: VxeUploadPrivateComputed = {
}
const $xeUpload = {
xID,
props,
context,
reactData,
internalData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
} as unknown as VxeUploadConstructor & VxeUploadPrivateMethods
const getUniqueKey = () => {
return XEUtils.uniqueId()
}
const getFieldKey = (item: VxeUploadDefines.FileObjItem) => {
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: string) => {
return decodeURIComponent(`${url || ''}`).split('/').pop() || ''
}
const parseFileType = (name: string) => {
// 这里不用split('.').pop()因为没有后缀时会返回自身
const index = name.lastIndexOf('.')
if (index > 0) {
return name.substring(index + 1).toLowerCase()
}
return ''
}
const dispatchEvent = (type: ValueOf<VxeUploadEmits>, params: Record<string, any>, evnt: Event | null) => {
emit(type, createEvent(evnt, { $upload: $xeUpload }, params))
}
const handleChange = (value: VxeUploadDefines.FileObjItem[]) => {
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: VxeUploadDefines.FileObjItem) => {
const getThumbnailUrlFn = props.getThumbnailUrlMethod || getConfig().upload.getThumbnailUrlMethod
if (getThumbnailUrlFn) {
return getThumbnailUrlFn({
$upload: $xeUpload,
option: item
})
}
return getFileUrl(item)
}
const getFileUrl = (item: VxeUploadDefines.FileObjItem) => {
const getUrlFn = props.getUrlMethod || getConfig().upload.getUrlMethod
const urlProp = computeUrlProp.value
return getUrlFn
? getUrlFn({
$upload: $xeUpload,
option: item
})
: item[urlProp]
}
const handleDefaultFilePreview = (item: VxeUploadDefines.FileObjItem) => {
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: MouseEvent, item: VxeUploadDefines.FileObjItem) => {
const previewFn = props.previewMethod || getConfig().upload.previewMethod
if (props.showPreview) {
if (previewFn) {
previewFn({
$upload: $xeUpload,
option: item
})
} else {
handleDefaultFilePreview(item)
}
}
}
const handlePreviewImageEvent = (evnt: MouseEvent, item: VxeUploadDefines.FileObjItem, index: number) => {
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: VxeUploadDefines.FileObjItem, file: 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: VxeUploadDefines.FileObjItem) => {
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: File[], evnt: Event | null) => {
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: any[] = []
selectFiles.forEach(file => {
const { name } = file
const fileKey = getUniqueKey()
const fileObj: VxeUploadDefines.FileObjItem = {
[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 as any, formItemInfo.itemConfig.field, newFileList)
}
})
}
const handleChoose = (evnt: MouseEvent | null) => {
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: MouseEvent) => {
handleChoose(evnt).catch(() => {
// 错误文件类型
})
}
const handleRemoveEvent = (evnt: MouseEvent, item: VxeUploadDefines.FileObjItem, index: number) => {
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: MouseEvent, item: VxeUploadDefines.FileObjItem, index: number) => {
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: MouseEvent, item: VxeUploadDefines.FileObjItem) => {
dispatchEvent('download', { option: item }, evnt)
}
const downloadFileEvent = (evnt: MouseEvent, item: VxeUploadDefines.FileObjItem) => {
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: DragEvent) => {
const targetElem = evnt.currentTarget as HTMLDivElement
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: DragEvent) => {
const dataTransfer = evnt.dataTransfer
if (dataTransfer) {
const { items } = dataTransfer
if (items && items.length) {
evnt.preventDefault()
reactData.isDragUploadStatus = true
}
}
}
const uploadTransferFileEvent = (evnt: Event, files: File[]) => {
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: DragEvent) => {
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: DataTransferItemList) => {
const files: File[] = []
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: Record<string, any> = {}
if (dragToUpload && dragIndex === -1) {
ons.onDragover = handleUploadDragoverEvent
ons.onDragleave = handleUploadDragleaveEvent
ons.onDrop = handleUploadDropEvent
}
return h('div', {
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: DragEvent, dragEl: HTMLElement, dragPos: string) => {
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: DragEvent) => {
evnt.stopPropagation()
if (evnt.dataTransfer) {
evnt.dataTransfer.setDragImage(getTpImg(), 0, 0)
}
const dragEl = evnt.currentTarget as HTMLElement
const parentEl = dragEl.parentElement as HTMLDivElement
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: DragEvent) => {
evnt.stopPropagation()
evnt.preventDefault()
const { dragIndex } = reactData
if (dragIndex === -1) {
return
}
const isImage = computeIsImage.value
const dragEl = evnt.currentTarget as HTMLElement
const parentEl = dragEl.parentElement as HTMLDivElement
const currIndex = XEUtils.findIndexOf(Array.from(parentEl.children), item => dragEl === item)
let dragPos: 'top' | 'bottom' | 'left' | 'right' | '' = ''
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: DragEvent) => {
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 as any,
offsetIndex: dragOffsetIndex,
_index: {
newIndex: nIndex,
oldIndex: oldIndex
}
}, evnt)
}
hideDropTip()
reactData.dragIndex = -1
}
const handleItemMousedownEvent = (evnt: MouseEvent) => {
if ($xeTable) {
evnt.stopPropagation()
}
reactData.isActivated = true
}
const handleGlobalPasteEvent = (evnt: ClipboardEvent) => {
const { pasteToUpload } = props
const { isActivated } = reactData
if (!isActivated || !pasteToUpload) {
return
}
const clipboardData: DataTransfer = evnt.clipboardData || (evnt as any).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: MouseEvent) => {
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: UploadMethods = {
dispatchEvent,
choose () {
return handleChoose(null)
}
}
const uploadPrivateMethods: UploadPrivateMethods = {
}
Object.assign($xeUpload, uploadMethods, uploadPrivateMethods)
const renderFileItemList = (currList: VxeUploadDefines.FileObjItem[], isMoreView: boolean) => {
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: Record<string, any> = {}
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', {
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() as 'DEFAULT'}`] || 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: MouseEvent) {
downloadFileEvent(evnt, item)
}
}, [
h('i', {
class: getIcon().UPLOAD_FILE_DOWNLOAD
})
])
: createCommentVNode(),
showRemoveButton && !formReadonly && !isDisabled && !isLoading
? h('div', {
class: 'vxe-upload--file-item-remove-btn',
onClick (evnt: MouseEvent) {
removeFileEvent(evnt, item, index)
}
}, [
h('i', {
class: getIcon().UPLOAD_FILE_REMOVE
})
])
: createCommentVNode()
])
])
})
}
const renderFileAction = (isMoreView: boolean) => {
const { showUploadButton, buttonText, buttonIcon, showButtonText, showButtonIcon, autoHiddenButton } = props
const isDisabled = computeIsDisabled.value
const formReadonly = computeFormReadonly.value
const showTipText = computedShowTipText.value
const defTipText = computedDefTipText.value
const overCount = computeOverCount.value
const defaultSlot = slots.default
const tipSlot = slots.tip || slots.hint
if (formReadonly || !showUploadButton) {
return createCommentVNode()
}
return h('div', {
class: 'vxe-upload--file-action'
}, [
autoHiddenButton && overCount
? createCommentVNode()
: h('div', {
class: 'vxe-upload--file-action-btn',
onClick: clickEvent
}, defaultSlot
? getSlotVNs(defaultSlot({ $upload: $xeUpload }))
: [
h(VxeButtonComponent, {
class: 'vxe-upload--file-action-button',
content: (isMoreView || showButtonText) ? (buttonText ? `${XEUtils.isFunction(buttonText) ? buttonText({}) : buttonText}` : getI18n('vxe.upload.fileBtnText')) : '',
icon: showButtonIcon ? (buttonIcon || getIcon().UPLOAD_FILE_ADD) : '',
disabled: isDisabled
})
]),
showTipText && (defTipText || tipSlot)
? h('div', {
class: 'vxe-upload--file-action-tip'
}, tipSlot ? getSlotVNs(tipSlot({ $upload: $xeUpload })) : `${defTipText}`)
: createCommentVNode()
])
}
const renderAllMode = () => {
const { showList, moreConfig, dragSort } = props
const { fileList, isDragMove } = reactData
const moreOpts = computeMoreOpts.value
const { maxCount, showMoreButton, layout } = moreOpts
const isHorizontal = layout === 'horizontal'
let currList = fileList
let overMaxNum = 0
if (maxCount && fileList.length > maxCount) {
overMaxNum = fileList.length - maxCount
currList = fileList.slice(0, maxCount)
}
return h('div', {
key: 'all',
class: 'vxe-upload--file-wrapper'
}, showList
? [
showMoreButton && moreConfig && isHorizontal
? createCommentVNode()
: renderFileAction(true),
(currList.length || (showMoreButton && isHorizontal))
? h('div', {
class: ['vxe-upload--file-list-wrapper', {
'is--horizontal': isHorizontal
}]
}, [
currList.length
? (
dragSort
? h(TransitionGroup, {
name: `vxe-upload--drag-list${isDragMove ? '' : '-disabled'}`,
tag: 'div',
class: 'vxe-upload--file-list'
}, {
default: () => renderFileItemList(currList, false)
})
: h('div', {
class: 'vxe-upload--file-list'
}, renderFileItemList(currList, false))
)
: createCommentVNode(),
showMoreButton && overMaxNum
? h('div',