wangxueliang
7 years ago
8 changed files with 699 additions and 0 deletions
@ -0,0 +1,3 @@
|
||||
import upload from './src' |
||||
|
||||
export default upload |
@ -0,0 +1,200 @@
|
||||
import PropTypes from '../../_util/vue-types' |
||||
import BaseMixin from '../../_util/BaseMixin' |
||||
import classNames from 'classnames' |
||||
import defaultRequest from './request' |
||||
import getUid from './uid' |
||||
import attrAccept from './attr-accept' |
||||
|
||||
const upLoadPropTypes = { |
||||
component: PropTypes.string, |
||||
// style: PropTypes.object, |
||||
prefixCls: PropTypes.string, |
||||
// className: PropTypes.string, |
||||
multiple: PropTypes.bool, |
||||
disabled: PropTypes.bool, |
||||
accept: PropTypes.string, |
||||
// children: PropTypes.any, |
||||
// onStart: PropTypes.func, |
||||
data: PropTypes.oneOfType([ |
||||
PropTypes.object, |
||||
PropTypes.func, |
||||
]), |
||||
headers: PropTypes.object, |
||||
beforeUpload: PropTypes.func, |
||||
customRequest: PropTypes.func, |
||||
// onProgress: PropTypes.func, |
||||
withCredentials: PropTypes.bool, |
||||
} |
||||
|
||||
const AjaxUploader = { |
||||
mixins: [BaseMixin], |
||||
props: upLoadPropTypes, |
||||
data () { |
||||
this.reqs = {} |
||||
return { |
||||
uid: getUid(), |
||||
} |
||||
}, |
||||
methods: { |
||||
onChange (e) { |
||||
const files = e.target.files |
||||
this.uploadFiles(files) |
||||
this.reset() |
||||
}, |
||||
onClick () { |
||||
const el = this.$refs.fileInputRef |
||||
if (!el) { |
||||
return |
||||
} |
||||
el.click() |
||||
}, |
||||
onKeyDown (e) { |
||||
if (e.key === 'Enter') { |
||||
this.onClick() |
||||
} |
||||
}, |
||||
onFileDrop (e) { |
||||
if (e.type === 'dragover') { |
||||
e.preventDefault() |
||||
return |
||||
} |
||||
const files = Array.prototype.slice.call(e.dataTransfer.files).filter( |
||||
file => attrAccept(file, this.accept) |
||||
) |
||||
this.uploadFiles(files) |
||||
|
||||
e.preventDefault() |
||||
}, |
||||
uploadFiles (files) { |
||||
const postFiles = Array.prototype.slice.call(files) |
||||
postFiles.forEach((file) => { |
||||
file.uid = getUid() |
||||
this.upload(file, postFiles) |
||||
}) |
||||
}, |
||||
upload (file, fileList) { |
||||
if (!this.beforeUpload) { |
||||
// always async in case use react state to keep fileList |
||||
return setTimeout(() => this.post(file), 0) |
||||
} |
||||
|
||||
const before = this.beforeUpload(file, fileList) |
||||
if (before && before.then) { |
||||
before.then((processedFile) => { |
||||
const processedFileType = Object.prototype.toString.call(processedFile) |
||||
if (processedFileType === '[object File]' || processedFileType === '[object Blob]') { |
||||
this.post(processedFile) |
||||
} else { |
||||
this.post(file) |
||||
} |
||||
}).catch(e => { |
||||
console && console.log(e); // eslint-disable-line |
||||
}) |
||||
} else if (before !== false) { |
||||
setTimeout(() => this.post(file), 0) |
||||
} |
||||
}, |
||||
post (file) { |
||||
if (!this._isMounted) { |
||||
return |
||||
} |
||||
let { data } = this.$props |
||||
if (typeof data === 'function') { |
||||
data = data(file) |
||||
} |
||||
const { uid } = file |
||||
const request = this.customRequest || defaultRequest |
||||
this.reqs[uid] = request({ |
||||
action: this.action, |
||||
filename: this.name, |
||||
file, |
||||
data, |
||||
headers: this.headers, |
||||
withCredentials: this.withCredentials, |
||||
onProgress: e => { |
||||
this.$emit('progress', e, file) |
||||
}, |
||||
onSuccess: (ret, xhr) => { |
||||
delete this.reqs[uid] |
||||
this.$emit('success', ret, file, xhr) |
||||
}, |
||||
onError: (err, ret) => { |
||||
delete this.reqs[uid] |
||||
this.$emit('error', err, ret, file) |
||||
}, |
||||
}) |
||||
this.$emit('start', file) |
||||
}, |
||||
reset () { |
||||
this.setState({ |
||||
uid: getUid(), |
||||
}) |
||||
}, |
||||
abort (file) { |
||||
const { reqs } = this |
||||
if (file) { |
||||
let uid = file |
||||
if (file && file.uid) { |
||||
uid = file.uid |
||||
} |
||||
if (reqs[uid]) { |
||||
reqs[uid].abort() |
||||
delete reqs[uid] |
||||
} |
||||
} else { |
||||
Object.keys(reqs).forEach((uid) => { |
||||
if (reqs[uid]) { |
||||
reqs[uid].abort() |
||||
} |
||||
|
||||
delete reqs[uid] |
||||
}) |
||||
} |
||||
}, |
||||
}, |
||||
mounted () { |
||||
this.$nextTick(() => { |
||||
this._isMounted = true |
||||
}) |
||||
}, |
||||
beforeDestroy () { |
||||
this._isMounted = false |
||||
this.abort() |
||||
}, |
||||
render () { |
||||
const { |
||||
component: Tag, prefixCls, disabled, multiple, accept, |
||||
} = this.$props |
||||
const cls = classNames({ |
||||
[prefixCls]: true, |
||||
[`${prefixCls}-disabled`]: disabled, |
||||
}) |
||||
const events = disabled ? {} : { |
||||
onClick: this.onClick, |
||||
onKeydown: this.onKeyDown, |
||||
onDrop: this.onFileDrop, |
||||
onDragover: this.onFileDrop, |
||||
tabIndex: '0', |
||||
} |
||||
return ( |
||||
<Tag |
||||
{...events} |
||||
class={cls} |
||||
role='button' |
||||
> |
||||
<input |
||||
type='file' |
||||
ref='fileInputRef' |
||||
key={this.uid} |
||||
style={{ display: 'none' }} |
||||
accept={accept} |
||||
multiple={multiple} |
||||
onChange={this.onChange} |
||||
/> |
||||
{this.$slots.default} |
||||
</Tag> |
||||
) |
||||
}, |
||||
} |
||||
|
||||
export default AjaxUploader |
@ -0,0 +1,272 @@
|
||||
import PropTypes from '../../_util/vue-types' |
||||
import BaseMixin from '../../_util/BaseMixin' |
||||
import classNames from 'classnames' |
||||
import getUid from './uid' |
||||
import warning from '../../_util/warning' |
||||
|
||||
const IFRAME_STYLE = { |
||||
position: 'absolute', |
||||
top: 0, |
||||
opacity: 0, |
||||
filter: 'alpha(opacity=0)', |
||||
left: 0, |
||||
zIndex: 9999, |
||||
} |
||||
|
||||
// diferent from AjaxUpload, can only upload on at one time, serial seriously |
||||
const IframeUploader = { |
||||
mixins: [BaseMixin], |
||||
props: { |
||||
component: PropTypes.string, |
||||
// style: PropTypes.object, |
||||
disabled: PropTypes.bool, |
||||
prefixCls: PropTypes.string, |
||||
// className: PropTypes.string, |
||||
accept: PropTypes.string, |
||||
// onStart: PropTypes.func, |
||||
multiple: PropTypes.bool, |
||||
// children: PropTypes.any, |
||||
data: PropTypes.oneOfType([ |
||||
PropTypes.object, |
||||
PropTypes.func, |
||||
]), |
||||
action: PropTypes.string, |
||||
name: PropTypes.string, |
||||
}, |
||||
data () { |
||||
this.file = {} |
||||
return { |
||||
uploading: false, |
||||
} |
||||
}, |
||||
methods: { |
||||
onLoad () { |
||||
if (!this.uploading) { |
||||
return |
||||
} |
||||
const { file } = this |
||||
let response |
||||
try { |
||||
const doc = this.getIframeDocument() |
||||
const script = doc.getElementsByTagName('script')[0] |
||||
if (script && script.parentNode === doc.body) { |
||||
doc.body.removeChild(script) |
||||
} |
||||
response = doc.body.innerHTML |
||||
this.$emit('success', response, file) |
||||
} catch (err) { |
||||
warning(false, 'cross domain error for Upload. Maybe server should return document.domain script. see Note from https://github.com/react-component/upload') |
||||
response = 'cross-domain' |
||||
this.$emit('error', err, null, file) |
||||
} |
||||
this.endUpload() |
||||
}, |
||||
onChange () { |
||||
const target = this.getFormInputNode() |
||||
// ie8/9 don't support FileList Object |
||||
// http://stackoverflow.com/questions/12830058/ie8-input-type-file-get-files |
||||
const file = this.file = { |
||||
uid: getUid(), |
||||
name: target.value, |
||||
} |
||||
this.startUpload() |
||||
const { $props: props } = this |
||||
if (!props.beforeUpload) { |
||||
return this.post(file) |
||||
} |
||||
const before = props.beforeUpload(file) |
||||
if (before && before.then) { |
||||
before.then(() => { |
||||
this.post(file) |
||||
}, () => { |
||||
this.endUpload() |
||||
}) |
||||
} else if (before !== false) { |
||||
this.post(file) |
||||
} else { |
||||
this.endUpload() |
||||
} |
||||
}, |
||||
getIframeNode () { |
||||
return this.$refs.iframeRef |
||||
}, |
||||
getIframeDocument () { |
||||
return this.getIframeNode().contentDocument |
||||
}, |
||||
getFormNode () { |
||||
return this.getIframeDocument().getElementById('form') |
||||
}, |
||||
getFormInputNode () { |
||||
return this.getIframeDocument().getElementById('input') |
||||
}, |
||||
getFormDataNode () { |
||||
return this.getIframeDocument().getElementById('data') |
||||
}, |
||||
getFileForMultiple (file) { |
||||
return this.multiple ? [file] : file |
||||
}, |
||||
getIframeHTML (domain) { |
||||
let domainScript = '' |
||||
let domainInput = '' |
||||
if (domain) { |
||||
const script = 'script' |
||||
domainScript = `<${script}>document.domain="${domain}";</${script}>` |
||||
domainInput = `<input name="_documentDomain" value="${domain}" />` |
||||
} |
||||
return ` |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> |
||||
<style> |
||||
body,html {padding:0;margin:0;border:0;overflow:hidden;} |
||||
</style> |
||||
${domainScript} |
||||
</head> |
||||
<body> |
||||
<form method="post" |
||||
encType="multipart/form-data" |
||||
action="${this.action}" id="form" |
||||
style="display:block;height:9999px;position:relative;overflow:hidden;"> |
||||
<input id="input" type="file" |
||||
name="${this.name}" |
||||
style="position:absolute;top:0;right:0;height:9999px;font-size:9999px;cursor:pointer;"/> |
||||
${domainInput} |
||||
<span id="data"></span> |
||||
</form> |
||||
</body> |
||||
</html> |
||||
` |
||||
}, |
||||
initIframeSrc () { |
||||
if (this.domain) { |
||||
this.getIframeNode().src = `javascript:void((function(){ |
||||
var d = document; |
||||
d.open(); |
||||
d.domain='${this.domain}'; |
||||
d.write(''); |
||||
d.close(); |
||||
})())` |
||||
} |
||||
}, |
||||
initIframe () { |
||||
const iframeNode = this.getIframeNode() |
||||
let win = iframeNode.contentWindow |
||||
let doc |
||||
this.domain = this.domain || '' |
||||
this.initIframeSrc() |
||||
try { |
||||
doc = win.document |
||||
} catch (e) { |
||||
this.domain = document.domain |
||||
this.initIframeSrc() |
||||
win = iframeNode.contentWindow |
||||
doc = win.document |
||||
} |
||||
doc.open('text/html', 'replace') |
||||
doc.write(this.getIframeHTML(this.domain)) |
||||
doc.close() |
||||
this.getFormInputNode().onchange = this.onChange |
||||
}, |
||||
endUpload () { |
||||
if (this.uploading) { |
||||
this.file = {} |
||||
// hack avoid batch |
||||
this.uploading = false |
||||
this.setState({ |
||||
uploading: false, |
||||
}) |
||||
this.initIframe() |
||||
} |
||||
}, |
||||
startUpload () { |
||||
if (!this.uploading) { |
||||
this.uploading = true |
||||
this.setState({ |
||||
uploading: true, |
||||
}) |
||||
} |
||||
}, |
||||
updateIframeWH () { |
||||
const rootNode = this.$el |
||||
const iframeNode = this.getIframeNode() |
||||
iframeNode.style.height = `${rootNode.offsetHeight}px` |
||||
iframeNode.style.width = `${rootNode.offsetWidth}px` |
||||
}, |
||||
abort (file) { |
||||
if (file) { |
||||
let uid = file |
||||
if (file && file.uid) { |
||||
uid = file.uid |
||||
} |
||||
if (uid === this.file.uid) { |
||||
this.endUpload() |
||||
} |
||||
} else { |
||||
this.endUpload() |
||||
} |
||||
}, |
||||
post (file) { |
||||
const formNode = this.getFormNode() |
||||
const dataSpan = this.getFormDataNode() |
||||
let { data } = this.$props |
||||
if (typeof data === 'function') { |
||||
data = data(file) |
||||
} |
||||
const inputs = document.createDocumentFragment() |
||||
for (const key in data) { |
||||
if (data.hasOwnProperty(key)) { |
||||
const input = document.createElement('input') |
||||
input.setAttribute('name', key) |
||||
input.value = data[key] |
||||
inputs.appendChild(input) |
||||
} |
||||
} |
||||
dataSpan.appendChild(inputs) |
||||
formNode.submit() |
||||
dataSpan.innerHTML = '' |
||||
this.$emit('start', file) |
||||
}, |
||||
}, |
||||
mounted () { |
||||
this.$nextTick(() => { |
||||
this.updateIframeWH() |
||||
this.initIframe() |
||||
}) |
||||
}, |
||||
updated () { |
||||
this.$nextTick(() => { |
||||
this.updateIframeWH() |
||||
}) |
||||
}, |
||||
|
||||
render () { |
||||
const { |
||||
component: Tag, disabled, |
||||
prefixCls, |
||||
} = this.$props |
||||
const iframeStyle = { |
||||
...IFRAME_STYLE, |
||||
display: this.uploading || disabled ? 'none' : '', |
||||
} |
||||
const cls = classNames({ |
||||
[prefixCls]: true, |
||||
[`${prefixCls}-disabled`]: disabled, |
||||
}) |
||||
return ( |
||||
<Tag |
||||
className={cls} |
||||
style={{ position: 'relative', zIndex: 0 }} |
||||
> |
||||
<iframe |
||||
ref='iframeRef' |
||||
onLoad={this.onLoad} |
||||
style={iframeStyle} |
||||
/> |
||||
{this.$slots.default} |
||||
</Tag> |
||||
) |
||||
}, |
||||
} |
||||
|
||||
export default IframeUploader |
@ -0,0 +1,91 @@
|
||||
import PropTypes from '../../_util/vue-types' |
||||
import { initDefaultProps } from '../../_util/props-util' |
||||
import BaseMixin from '../../_util/BaseMixin' |
||||
import AjaxUpload from './AjaxUploader' |
||||
import IframeUpload from './IframeUploader' |
||||
|
||||
// function empty () { |
||||
// } |
||||
|
||||
const uploadProps = { |
||||
component: PropTypes.string, |
||||
prefixCls: PropTypes.string, |
||||
action: PropTypes.string, |
||||
name: PropTypes.string, |
||||
multipart: PropTypes.bool, |
||||
// onError: PropTypes.func, |
||||
// onSuccess: PropTypes.func, |
||||
// onProgress: PropTypes.func, |
||||
// onStart: PropTypes.func, |
||||
data: PropTypes.oneOfType([ |
||||
PropTypes.object, |
||||
PropTypes.func, |
||||
]), |
||||
headers: PropTypes.object, |
||||
accept: PropTypes.string, |
||||
multiple: PropTypes.bool, |
||||
disabled: PropTypes.bool, |
||||
beforeUpload: PropTypes.func, |
||||
customRequest: PropTypes.func, |
||||
// onReady: PropTypes.func, |
||||
withCredentials: PropTypes.bool, |
||||
supportServerRender: PropTypes.bool, |
||||
} |
||||
export default { |
||||
name: 'Upload', |
||||
mixins: [BaseMixin], |
||||
props: initDefaultProps(uploadProps, { |
||||
component: 'span', |
||||
prefixCls: 'rc-upload', |
||||
data: {}, |
||||
headers: {}, |
||||
name: 'file', |
||||
multipart: false, |
||||
// onReady: empty, |
||||
// onStart: empty, |
||||
// onError: empty, |
||||
// onSuccess: empty, |
||||
supportServerRender: false, |
||||
multiple: false, |
||||
beforeUpload: null, |
||||
customRequest: null, |
||||
withCredentials: false, |
||||
}), |
||||
data () { |
||||
return { |
||||
Component: null, |
||||
} |
||||
}, |
||||
mounted () { |
||||
this.$nextTick(() => { |
||||
if (this.supportServerRender) { |
||||
/* eslint react/no-did-mount-set-state:0 */ |
||||
this.setState({ |
||||
Component: this.getComponent(), |
||||
}, () => { |
||||
this.$emit('ready') |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
methods: { |
||||
getComponent () { |
||||
return typeof File !== 'undefined' ? AjaxUpload : IframeUpload |
||||
}, |
||||
abort (file) { |
||||
this.$refs.uploaderRef.abort(file) |
||||
}, |
||||
}, |
||||
|
||||
render () { |
||||
if (this.supportServerRender) { |
||||
const ComponentUploader = this.Component |
||||
if (ComponentUploader) { |
||||
return <ComponentUploader {...this.$props} ref='uploaderRef' /> |
||||
} |
||||
return null |
||||
} |
||||
const ComponentUploader = this.getComponent() |
||||
return <ComponentUploader {...this.$props} ref='uploaderRef' /> |
||||
}, |
||||
} |
@ -0,0 +1,26 @@
|
||||
function endsWith (str, suffix) { |
||||
return str.indexOf(suffix, str.length - suffix.length) !== -1 |
||||
} |
||||
|
||||
export default (file, acceptedFiles) => { |
||||
if (file && acceptedFiles) { |
||||
const acceptedFilesArray = Array.isArray(acceptedFiles) |
||||
? acceptedFiles |
||||
: acceptedFiles.split(',') |
||||
const fileName = file.name || '' |
||||
const mimeType = file.type || '' |
||||
const baseMimeType = mimeType.replace(/\/.*$/, '') |
||||
|
||||
return acceptedFilesArray.some(type => { |
||||
const validType = type.trim() |
||||
if (validType.charAt(0) === '.') { |
||||
return endsWith(fileName.toLowerCase(), validType.toLowerCase()) |
||||
} else if (/\/\*$/.test(validType)) { |
||||
// This is something like a image/* mime type
|
||||
return baseMimeType === validType.replace(/\/.*$/, '') |
||||
} |
||||
return mimeType === validType |
||||
}) |
||||
} |
||||
return true |
||||
} |
@ -0,0 +1,4 @@
|
||||
// export this package's api
|
||||
import Upload from './Upload' |
||||
|
||||
export default Upload |
@ -0,0 +1,97 @@
|
||||
function getError (option, xhr) { |
||||
const msg = `cannot post ${option.action} ${xhr.status}'` |
||||
const err = new Error(msg) |
||||
err.status = xhr.status |
||||
err.method = 'post' |
||||
err.url = option.action |
||||
return err |
||||
} |
||||
|
||||
function getBody (xhr) { |
||||
const text = xhr.responseText || xhr.response |
||||
if (!text) { |
||||
return text |
||||
} |
||||
|
||||
try { |
||||
return JSON.parse(text) |
||||
} catch (e) { |
||||
return text |
||||
} |
||||
} |
||||
|
||||
// option {
|
||||
// onProgress: (event: { percent: number }): void,
|
||||
// onError: (event: Error, body?: Object): void,
|
||||
// onSuccess: (body: Object): void,
|
||||
// data: Object,
|
||||
// filename: String,
|
||||
// file: File,
|
||||
// withCredentials: Boolean,
|
||||
// action: String,
|
||||
// headers: Object,
|
||||
// }
|
||||
export default function upload (option) { |
||||
const xhr = new window.XMLHttpRequest() |
||||
|
||||
if (option.onProgress && xhr.upload) { |
||||
xhr.upload.onprogress = function progress (e) { |
||||
if (e.total > 0) { |
||||
e.percent = e.loaded / e.total * 100 |
||||
} |
||||
option.onProgress(e) |
||||
} |
||||
} |
||||
|
||||
const formData = new window.FormData() |
||||
|
||||
if (option.data) { |
||||
Object.keys(option.data).map(key => { |
||||
formData.append(key, option.data[key]) |
||||
}) |
||||
} |
||||
|
||||
formData.append(option.filename, option.file) |
||||
|
||||
xhr.onerror = function error (e) { |
||||
option.onError(e) |
||||
} |
||||
|
||||
xhr.onload = function onload () { |
||||
// allow success when 2xx status
|
||||
// see https://github.com/react-component/upload/issues/34
|
||||
if (xhr.status < 200 || xhr.status >= 300) { |
||||
return option.onError(getError(option, xhr), getBody(xhr)) |
||||
} |
||||
|
||||
option.onSuccess(getBody(xhr), xhr) |
||||
} |
||||
|
||||
xhr.open('post', option.action, true) |
||||
|
||||
// Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
|
||||
if (option.withCredentials && 'withCredentials' in xhr) { |
||||
xhr.withCredentials = true |
||||
} |
||||
|
||||
const headers = option.headers || {} |
||||
|
||||
// when set headers['X-Requested-With'] = null , can close default XHR header
|
||||
// see https://github.com/react-component/upload/issues/33
|
||||
if (headers['X-Requested-With'] !== null) { |
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') |
||||
} |
||||
|
||||
for (const h in headers) { |
||||
if (headers.hasOwnProperty(h) && headers[h] !== null) { |
||||
xhr.setRequestHeader(h, headers[h]) |
||||
} |
||||
} |
||||
xhr.send(formData) |
||||
|
||||
return { |
||||
abort () { |
||||
xhr.abort() |
||||
}, |
||||
} |
||||
} |
Loading…
Reference in new issue