import type { PropType } from 'vue' ; import { defineComponent , provide , reactive , ref , toRefs , watch , computed , } from 'vue' ; import { getPrefixCls } from '../_utils/global-config' ; import { isFunction , isObject } from '../_utils/is' ; import type { CustomIcon , FileItem , ListType , RequestOption , UploadRequest , } from './interfaces' ; import { getDataURLFromFile , uploadRequest } from './utils' ; import UploadButton from './upload-button' ; import UploadList from './upload-list' ; import { uploadInjectionKey } from './context' ; import { Data , EmitType } from '../_utils/types' ; import { ImagePreviewGroup } from '../image' ; export default defineComponent ( { name : 'Upload' , components : { UploadButton , UploadList } , props : { zh en vModel fileList : { type : Array as PropType < FileItem [ ] > , default : undefined } , zh en defaultFileList : { type : Array as PropType < FileItem [ ] > , default : ( ) => [ ] } , zh en accept : String , zh en action : String , zh en disabled : { type : Boolean , default : false } , zh en multiple : { type : Boolean , default : false } , zh en directory : { type : Boolean , default : false } , zh en draggable : { type : Boolean , default : false } , zh en tip : String , zh en headers : Object , zh en data : { type : [ Object , Function ] as PropType < Record < string , unknown > | ( ( fileItem : FileItem ) => Record < string , unknown > ) > } , zh en name : { type : [ String , Function ] as PropType < string | ( ( fileItem : FileItem ) => string ) > } , zh en withCredentials : { type : Boolean , default : false } , zh en customRequest : { type : Function as PropType < ( option : RequestOption ) => UploadRequest > } , zh en limit : { type : Number , default : 0 } , zh en autoUpload : { type : Boolean , default : true } , zh en showFileList : { type : Boolean , default : true } , zh en version showRemoveButton : { type : Boolean , default : true } , zh en version showRetryButton : { type : Boolean , default : true } , zh en version showCancelButton : { type : Boolean , default : true } , zh en version showUploadButton : { type : [ Boolean , Object ] as PropType < boolean | { showOnExceedLimit : boolean ; } > , default : true } , zh en version download : { type : Boolean , default : false } , zh en version showLink : { type : Boolean , default : true } , zh en version imageLoading : { type : String as PropType < 'eager' | 'lazy' > } , zh en values listType : { type : String as PropType < ListType > , default : 'text' } , zh en responseUrlKey : { type : [ String , Function ] as PropType < string | ( ( fileItem : FileItem ) => string ) > } , zh en customIcon : { type : Object as PropType < CustomIcon > } , zh version imagePreview : { type : Boolean , default : false } , zh en onBeforeUpload : { type : Function as PropType < ( file : File ) => Promise < boolean > > } , zh en onBeforeRemove : { type : Function as PropType < ( fileItem : FileItem ) => Promise < boolean > > } , zh en onButtonClick : { type : Function as PropType < ( event : Event ) => Promise < FileList > | void > } , onChange : { type : [ Function , Array ] as PropType < EmitType < ( fileList : FileItem [ ] , fileItem : FileItem ) => void > > } , onProgress : { type : [ Function , Array ] as PropType < EmitType < ( fileItem : FileItem , event : ProgressEvent ) => void > > } , onExceedLimit : { type : [ Function , Array ] as PropType < EmitType < ( fileList : FileItem [ ] , files : File [ ] ) => void > > } , onPreview : { type : [ Function , Array ] as PropType < EmitType < ( fileItem : FileItem ) => void > > } , onSuccess : { type : [ Function , Array ] as PropType < EmitType < ( fileItem : FileItem ) => void > > } , onError : { type : [ Function , Array ] as PropType < EmitType < ( fileItem : FileItem ) => void > > } } , emits : [ 'update:fileList' , 'exceedLimit' , 'change' , 'progress' , 'preview' , 'success' , 'error' , ] , zh en slot binding binding zh en slot setup ( props , { emit , slots } ) { const { fileList , disabled , listType , customIcon , showRetryButton , showCancelButton , showRemoveButton , imageLoading , download , showLink , } = toRefs ( props ) ; const prefixCls = getPrefixCls ( 'upload' ) ; const _fileList = ref < FileItem [ ] > ( [ ] ) ; const fileMap = new Map < string , FileItem > ( ) ; const requestMap = new Map < string , UploadRequest > ( ) ; const isMax = computed ( ( ) => { return props . limit > 0 && _fileList . value . length >= props . limit ; } ) ; const checkFileList = ( fileList ? ) => { fileMap . clear ( ) ; const newFileList = fileList ?. map ( ( data , index ) => { const status = data . status ?? 'done' ; const fileItem = reactive ( { ... data , uid : data . uid ?? `${ Date . now ( ) }${ index }` , status , percent : data . percent ?? ( [ 'error' , 'init' ] . indexOf ( status ) > - 1 ? 0 : 1 ) } ) ; fileMap . set ( fileItem . uid , fileItem ) ; return fileItem ; } ) ; _fileList . value = newFileList ?? [ ] ; } ; checkFileList ( props . defaultFileList ) ; watch ( fileList , ( fileList ) => { if ( fileList ) { checkFileList ( fileList ) ; } } , { immediate : true , deep : true } ) ; const updateFileList = ( file ) => { emit ( 'update:fileList' , _fileList . value ) ; emit ( 'change' , _fileList . value , file ) ; } ; const uploadFile = ( fileItem ) => { const handleProgress = ( percent , event ? ) => { const file = fileMap . get ( fileItem . uid ) ; if ( file ) { file . status = 'uploading' ; file . percent = percent ; emit ( 'progress' , file , event ) ; updateFileList ( file ) ; } } ; const handleSuccess = ( response ) => { const file = fileMap . get ( fileItem . uid ) ; if ( file ) { file . status = 'done' ; file . percent = 1 ; file . response = response ; if ( props . responseUrlKey ) { if ( isFunction ( props . responseUrlKey ) ) { file . url = props . responseUrlKey ( file ) ; } else if ( response [ props . responseUrlKey ] ) { file . url = response [ props . responseUrlKey ] ; } } requestMap . delete ( file . uid ) ; emit ( 'success' , file ) ; updateFileList ( file ) ; } } ; const handleError = ( response ) => { const file = fileMap . get ( fileItem . uid ) ; if ( file ) { file . status = 'error' ; file . percent = 0 ; file . response = response ; requestMap . delete ( file . uid ) ; emit ( 'error' , file ) ; updateFileList ( file ) ; } } ; const option = { fileItem , action : props . action , name : props . name , data : props . data , headers : props . headers , withCredentials : props . withCredentials , onProgress : handleProgress , onSuccess : handleSuccess , onError : handleError } ; fileItem . status = 'uploading' ; fileItem . percent = 0 ; const request = isFunction ( props . customRequest ) ? props . customRequest ( option ) : uploadRequest ( option ) ; requestMap . set ( fileItem . uid , request ) ; updateFileList ( fileItem ) ; } ; const abort = ( fileItem ) => { const req = requestMap . get ( fileItem . uid ) ; if ( req ) { req . abort ?. ( ) ; requestMap . delete ( fileItem . uid ) ; const file = fileMap . get ( fileItem . uid ) ; if ( file ) { file . status = 'error' ; file . percent = 0 ; updateFileList ( file ) ; } } } ; const submit = ( fileItem ? ) => { if ( fileItem ) { const file = fileMap . get ( fileItem . uid ) ; if ( file ) { uploadFile ( file ) ; } } else { for ( const item of _fileList . value ) { if ( item . status === 'init' ) { uploadFile ( item ) ; } } } } ; const initUpload = async ( file , index ) => { const uid = `${ Date . now ( ) }${ index }` ; const dataURL = await getDataURLFromFile ( file ) ; const fileItem = reactive ( { uid , file , url : dataURL , name : file . name , status : 'init' , percent : 0 } ) ; fileMap . set ( uid , fileItem ) ; _fileList . value . push ( fileItem ) ; updateFileList ( fileItem ) ; if ( props . autoUpload ) { uploadFile ( fileItem ) ; } } ; const uploadFiles = ( files ) => { if ( props . limit > 0 && _fileList . value . length + files . length > props . limit ) { emit ( 'exceedLimit' , _fileList . value , files ) ; return ; } for ( let i = 0 ; i < files . length ; i ++ ) { const file = files [ i ] ; if ( isFunction ( props . onBeforeUpload ) ) { Promise . resolve ( props . onBeforeUpload ( file ) ) . then ( ( result ) => { if ( result ) { initUpload ( file , i ) ; } } ) . catch ( ( err ) => { console . error ( err ) ; } ) ; } else { initUpload ( file , i ) ; } } } ; const removeFile = ( fileItem ) => { _fileList . value . splice ( _fileList . value . indexOf ( fileItem ) , 1 ) ; updateFileList ( fileItem ) ; } ; const handleRemove = ( fileItem ) => { if ( isFunction ( props . onBeforeRemove ) ) { Promise . resolve ( props . onBeforeRemove ( fileItem ) ) . then ( ( result ) => { if ( result ) { removeFile ( fileItem ) ; } } ) . catch ( ( err ) => { console . error ( err ) ; } ) ; } else { removeFile ( fileItem ) ; } } ; const handlePreview = ( fileItem ) => { if ( props . imagePreview ) { const current = imageList . value . indexOf ( fileItem . url ) ; if ( current > - 1 ) { imagePreviewCurrent . value = current ; imagePreviewVisible . value = true ; } } emit ( 'preview' , fileItem ) ; } ; provide ( uploadInjectionKey , reactive ( { disabled , listType , iconCls : `${ prefixCls }-icon` , showRemoveButton , showRetryButton , showCancelButton , showLink , imageLoading , download , customIcon , onUpload : uploadFile , onAbort : abort , onRemove : handleRemove , onPreview : handlePreview } ) ) ; const mergedAccept = computed ( ( ) => { if ( props . accept ) { return props . accept ; } if ( props . listType === 'picture' || props . listType === 'picture-card' ) { return 'image/*' ; } return undefined ; } ) ; const renderButton = ( ) => { const button = ( < UploadButton key = "arco-upload-button" v - slots = { { default : slots [ 'upload-button' ] } } disabled = { props . disabled } draggable = { props . draggable } listType = { props . listType } uploadFiles = { uploadFiles } multiple = { props . multiple } directory = { props . directory } tip = { props . tip } hide = { ! props . showUploadButton || ( isMax . value && ! ( isObject ( props . showUploadButton ) && props . showUploadButton . showOnExceedLimit ) ) } accept = { mergedAccept . value } / > ) ; if ( props . tip && props . listType !== 'picture-card' && ! props . draggable ) { return ( < span > { button } < div class = { `${ prefixCls }-tip` } > { props . tip } < /div> < /span>); } return button ; } ; const imagePreviewVisible = ref ( false ) ; const imagePreviewCurrent = ref ( 0 ) ; const handleImagePreviewChange = ( current ) => { imagePreviewCurrent . value = current ; } ; const handleImagePreviewVisibleChange = ( visible ) => { imagePreviewVisible . value = visible ; } ; const imageList = computed ( ( ) => _fileList . value . filter ( ( item ) => Boolean ( item . url ) ) . map ( ( item ) => item . url ) ) ; const render = ( ) => { if ( ! props . showFileList ) { return props . showUploadButton && renderButton ( ) ; } return ( < div class = { [ `${ prefixCls }-wrapper` , `${ prefixCls }-wrapper-type-${ props . listType }` , ] } > { props . imagePreview && imageList . value . length > 0 && ( < ImagePreviewGroup srcList = { imageList . value } visible = { imagePreviewVisible . value } current = { imagePreviewCurrent . value } onChange = { handleImagePreviewChange } onVisibleChange = { handleImagePreviewVisibleChange } / > ) } { props . listType !== 'picture-card' && props . showUploadButton && renderButton ( ) } < UploadList v - slots = { { 'upload-button' : renderButton , 'upload-item' : slots [ 'upload-item' ] } } fileList = { _fileList . value } listType = { props . listType } / > < /div>); } ; return { prefixCls , render , innerSubmit : submit , innerAbort : abort } ; } , methods : { zh en public param { FileItem } fileItem submit ( fileItem ? ) { return this . innerSubmit ( fileItem ) ; } , zh en public param { FileItem } fileItem abort ( fileItem ) { return this . innerAbort ( fileItem ) ; } } , render ( ) { return this . render ( ) ; } } ) ;