diff --git a/components/vc-upload/index.js b/components/vc-upload/index.js new file mode 100644 index 000000000..e6c4df13a --- /dev/null +++ b/components/vc-upload/index.js @@ -0,0 +1,3 @@ +import upload from './src' + +export default upload diff --git a/components/vc-upload/src/AjaxUploader.jsx b/components/vc-upload/src/AjaxUploader.jsx new file mode 100644 index 000000000..ab329ab06 --- /dev/null +++ b/components/vc-upload/src/AjaxUploader.jsx @@ -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 ( + + + {this.$slots.default} + + ) + }, +} + +export default AjaxUploader diff --git a/components/vc-upload/src/IframeUploader.jsx b/components/vc-upload/src/IframeUploader.jsx new file mode 100644 index 000000000..ac1f3d598 --- /dev/null +++ b/components/vc-upload/src/IframeUploader.jsx @@ -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}";` + domainInput = `` + } + return ` + + + + + + ${domainScript} + + +
+ + ${domainInput} + +
+ + + ` + }, + 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 ( + +