-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d225f20
commit bb0a808
Showing
9 changed files
with
564 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
import React, { useEffect, useState } from 'react' | ||
import cn from 'classnames' | ||
import defaultRequest from './request' | ||
import getUid from './uid' | ||
import { AtFile, ParsedFileInfo } from './interface' | ||
import './style/index.scss' | ||
|
||
export interface UploadFile { | ||
url: string | ||
uid: string | ||
progress: number | ||
done: boolean | ||
} | ||
|
||
export interface IProps { | ||
id: string | ||
style?: React.CSSProperties | ||
accept?: string | ||
action?: string | ||
data?: any | ||
filename?: string | ||
onBeforeUpload?: (file: AtFile, fileList: AtFile[]) => void | ||
fileList?: UploadFile[] | ||
multiple?: boolean | ||
maxCount?: number | ||
method?: 'post' | 'get' | 'put' | 'patch' | ||
onPreview?: (url: string) => void | ||
onProgress?: () => void | ||
onSuccess?: (fileList: UploadFile[]) => void | ||
onError?: (error: Error) => void | ||
listType?: 'picture-card' | ||
className?: string | ||
} | ||
|
||
export interface IOssData { | ||
accessId: string | ||
policy: string | ||
signature: string | ||
dir: string | ||
host: string | ||
expire: number | ||
} | ||
|
||
const Upload: React.FC<IProps> = props => { | ||
const { | ||
id, | ||
className, | ||
fileList = [], | ||
onPreview, | ||
data, | ||
accept, | ||
method, | ||
multiple, | ||
filename, | ||
maxCount = 1, | ||
onBeforeUpload, | ||
onError, | ||
onSuccess, | ||
...otherProps | ||
} = props | ||
|
||
const [uid, setUid] = useState(getUid()) | ||
|
||
const [isHideSelectEle, setHideSelectEle] = useState(false) | ||
|
||
const [totalFileList, setTotalFileList] = useState<UploadFile[]>(fileList) | ||
|
||
const reqs: any = {} | ||
|
||
const { host: action, dir } = data | ||
|
||
const classes = cn('x-upload', className) | ||
|
||
const onFileChange = (e: any) => { | ||
const { files = [] } = e.target | ||
const isGltMaxCount = checkFileCount(files) | ||
|
||
if (isGltMaxCount) { | ||
onError && onError(new Error('超出最大数量')) | ||
return | ||
} | ||
|
||
uploadFiles(files) | ||
|
||
reset() | ||
} | ||
|
||
const getExtraData = (file: AtFile) => { | ||
const [name, suffix] = file.name.split('.') | ||
return { | ||
key: `${dir}${filename ? filename : name}-${file.uid}.${suffix}`, | ||
OSSAccessKeyId: data.accessId, | ||
policy: data.policy, | ||
Signature: data.signature, | ||
} | ||
} | ||
|
||
const checkFileCount = (files: any): boolean => { | ||
// 用于提示错误,当可以多选择时一次性选择的数量+已经上传的数量超出了最大数量则提示错误 | ||
return files.length + fileList.length > maxCount ? true : false | ||
} | ||
|
||
const uploadFiles = (files: File[]) => { | ||
const originFiles = [...files] as AtFile[] | ||
const postFiles = originFiles.map((file: AtFile & { uid?: string }) => { | ||
file.uid = getUid() | ||
return processFile(file, originFiles) | ||
}) | ||
|
||
Promise.all(postFiles).then(fileList => fileList.forEach(file => post(file))) | ||
} | ||
|
||
const processFile = async (file: AtFile, fileList: AtFile[]): Promise<any> => { | ||
if (onBeforeUpload) { | ||
try { | ||
await onBeforeUpload(file, fileList) | ||
} catch (e) { | ||
return { | ||
file, | ||
data: null, | ||
error: e, | ||
} | ||
} | ||
} | ||
|
||
const data = getExtraData(file) | ||
|
||
setTotalFileList(f => { | ||
f.push({ | ||
url: `${action}/${data.key}`, | ||
uid: file.uid, | ||
progress: 0, | ||
done: false, | ||
}) | ||
return f | ||
}) | ||
|
||
return { | ||
file, | ||
data, | ||
} | ||
} | ||
|
||
const post = ({ file, data, error }: ParsedFileInfo) => { | ||
const { uid } = file | ||
const request = defaultRequest | ||
|
||
if (!data) { | ||
onError && onError(error) | ||
return | ||
} | ||
|
||
const requestOption = { | ||
action, | ||
filename: 'file', | ||
data, | ||
file, | ||
method: method || 'post', | ||
onSuccess: () => { | ||
onSuccess && onSuccess(updateSuccessFile(uid)) | ||
delete reqs[uid] | ||
}, | ||
onError: (err: any) => { | ||
onError && onError(err) | ||
delete reqs[uid] | ||
}, | ||
onProgress: (e: any) => { | ||
updateFileProgress(e, uid) | ||
}, | ||
} | ||
|
||
checkCurrentMaxCount() | ||
|
||
reqs[uid] = request(requestOption) | ||
} | ||
|
||
const updateSuccessFile = (uid: string) => { | ||
const currentFileIndex = totalFileList.findIndex((item: UploadFile) => item.uid === uid) | ||
if (currentFileIndex === -1) { | ||
return totalFileList | ||
} | ||
|
||
const total = [...totalFileList] | ||
total[currentFileIndex].done = true | ||
setTotalFileList(total) | ||
|
||
return totalFileList | ||
} | ||
|
||
const checkCurrentMaxCount = () => { | ||
if (totalFileList.length >= maxCount) { | ||
setHideSelectEle(true) | ||
} | ||
} | ||
|
||
const updateFileProgress = (e: any, uid: string) => { | ||
const currentFileIndex = totalFileList.findIndex((item: UploadFile) => item.uid === uid) | ||
if (currentFileIndex === -1) { | ||
return | ||
} | ||
|
||
const total = [...totalFileList] | ||
total[currentFileIndex].progress = e.precent | ||
|
||
setTotalFileList(total) | ||
} | ||
|
||
const reset = () => { | ||
setUid(getUid()) | ||
} | ||
|
||
const abort = (file?: any) => { | ||
if (file) { | ||
const uid = file.uid ? file.uid : file | ||
if (reqs[uid] && reqs[uid].abort) { | ||
reqs[uid].abort() | ||
} | ||
|
||
delete reqs[uid] | ||
} else { | ||
Object.keys(reqs).forEach(uid => { | ||
if (reqs[uid] && reqs[uid].abort) { | ||
reqs[uid].abort() | ||
} | ||
|
||
delete reqs[uid] | ||
}) | ||
} | ||
} | ||
|
||
const onFileItemDelete = (uid: string, e: React.MouseEvent) => { | ||
e.stopPropagation() | ||
|
||
const currentFileIndex = totalFileList.findIndex((item: UploadFile) => item.uid === uid) | ||
|
||
if (currentFileIndex === -1) { | ||
return | ||
} | ||
|
||
totalFileList.splice(currentFileIndex, 1) | ||
|
||
if (totalFileList.length < maxCount) { | ||
setHideSelectEle(false) | ||
} | ||
|
||
onSuccess && onSuccess(totalFileList) | ||
} | ||
|
||
useEffect(() => { | ||
return () => { | ||
abort() | ||
} | ||
}, []) | ||
|
||
return ( | ||
<div className={classes}> | ||
<div className="x-upload__inner"> | ||
{totalFileList.map((file: UploadFile) => ( | ||
<div className="x-upload--file" key={file.uid}> | ||
<div className="x-upload--filecontent">{file.done && <img src={file.url} />}</div> | ||
<div className="x-upload--handleicon" onClick={e => onFileItemDelete(file.uid, e)} /> | ||
|
||
{file.progress !== 100 && ( | ||
<div className="x-upload--progress"> | ||
<h6>上传中</h6> | ||
<div className="p"> | ||
<div className="i" style={{ width: `${file.progress}% ` }} /> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
))} | ||
|
||
{!isHideSelectEle && ( | ||
<div className="x-upload--file x-upload--select"> | ||
<input | ||
accept={accept} | ||
id={id} | ||
key={uid} | ||
multiple={multiple} | ||
onClick={e => e.stopPropagation()} | ||
type="file" | ||
onChange={onFileChange} | ||
{...otherProps} | ||
/> | ||
<span> | ||
<svg | ||
viewBox="64 64 896 896" | ||
focusable="false" | ||
data-icon="plus" | ||
width="1em" | ||
height="1em" | ||
fill="currentColor" | ||
aria-hidden="true" | ||
> | ||
<path d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z" /> | ||
<path d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z" /> | ||
</svg> | ||
</span> | ||
<p>上传</p> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
Upload.defaultProps = { | ||
method: 'post', | ||
listType: 'picture-card', | ||
maxCount: 1, | ||
fileList: [], | ||
multiple: false, | ||
accept: 'image/*', | ||
} | ||
export default Upload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Upload 上传 | ||
|
||
## 基本使用 | ||
|
||
code=[基本使用](upload) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export type BeforeUploadFileType = File | Blob | boolean | string | ||
|
||
export type Action = string | ((file: AtFile) => string | PromiseLike<string>) | ||
|
||
export interface AtFile extends File { | ||
uid: string | ||
} | ||
|
||
export interface ParsedFileInfo { | ||
file: AtFile | ||
data: object | ||
error: Error | ||
} |
Oops, something went wrong.