未验证 提交 32a594d5 编写于 作者: D doly mood 提交者: GitHub

Feat upload support dynamic target, headers, data (#277)

* feat(upload): target can be a function to get target URL

* feat(upload): checkSuccess support cb

* fix(upload): checkSuccess add the second param file

* test(upload): target and checkSuccess test

* docs(upload): target[function] and checkSuccess async docs

* feat(upload): headers and data support function too

* docs(upload): add headers & data function doc

* feat(upload): upload component add multiple & accept prop

* docs(upload): add multiple & accept doc
上级 a73db2d3
......@@ -182,10 +182,13 @@
| Attribute | Description | Type | Accepted Values | Demo |
| - | - | - | - | - |
| v-model | file list | Array | [] | [{ name, size, url, status: 'success', progress: 1 }] |
| action | upload action config | String/Object | '' | { target: '/upload' } |
| max | max upload files number | Number | 10 | - |
| auto | whether auto start upload | Boolean | true | - |
| simultaneousUploads | the number of simultaneous uploads | Number | 1 | - |
| multiple | multiple select | Boolean | true | - |
| accept | input accept | String | image/* | - |
| processFile | process the original file | Function | function (file, next) { next(file) } | - |
* `action` sub configuration
......@@ -194,15 +197,15 @@ If `action` is a string, it will be transformed into `{ target: action }`.
| Attribute | Description | Type | Default |
| - | - | - | - |
| target | the upload target URL for the multipart POST request | String | - |
| target | the upload target URL for the multipart POST request, if this value is a function, then it will be called with the file object as parameter and the returned value as the URL | String/Function<sup>1.11.0+</sup> | - |
| fileName | the name of the multipart POST parameter | String | 'file' |
| prop | which property in file object will be uploaded | String | 'file' |
| headers | extra headers to include in the multipart POST | Object | {} |
| data | extra data to include in the multipart POST | Object | {} |
| headers | extra headers to include in the multipart POST, if this value is a function, then it will be called with the file object as parameter and the returned value as headers | Object/Function<sup>1.11.0+</sup> | {} |
| data | extra data to include in the multipart POST, if this value is a function, then it will be called with the file object as parameter and the returned value as data | Object/Function<sup>1.11.0+</sup> | {} |
| withCredentials | Standard CORS requests would not send or set any cookies by default. In order to include cookies as part of the request, you need to set the withCredentials property to true | Boolean | false |
| timeout | upload request timeout value | Number | 0 |
| progressInterval | The time interval between progress reports (Unit: ms) | Number | 100 |
| checkSuccess | Check the response should be successful, the parameter is `response` object. If return true then it will be treated as successful | Function | function (res) { return true } |
| checkSuccess | Check the response should be successful, the parameters is `(response, file[, cb])` object. The `file` and optional `cb` parameters are avaliable after 1.11.0. If there are no `cb` then get this function as result `isSuccess`, otherwise the `cb(isSuccess)` parameter `isSuccess` as the result. If the result `isSuccess` is `true` then it will be treated as successful | Function | function (res) { return true } |
* `processFile` sub configuration
......
......@@ -193,6 +193,8 @@
| max | 最大上传文件个数 | Number | 10 | - |
| auto | 是否自动上传,即选择完文件后自动开始上传 | Boolean | true | - |
| simultaneousUploads | 并发上传数 | Number | 1 | - |
| multiple | 是否多选 | Boolean | true | - |
| accept | input 的 accept 属性值 | String | image/* | - |
| processFile | 处理原始文件函数 | Function | function (file, next) { next(file) } | - |
* `action` 子配置项
......@@ -201,15 +203,15 @@
| 参数 | 说明 | 类型 | 默认值 |
| - | - | - | - |
| target | 上传目标 URL | String | - |
| target | 上传目标 URL,如果为函数,则传入当前文件对象调用得到目标 URL | String/Function<sup>1.11.0+</sup> | - |
| fileName | 上传文件时文件的参数名 | String | 'file' |
| prop | 上传的时候使用文件对象的 prop 属性所对应的值 | String | 'file' |
| headers | 自定义请求头 | Object | {} |
| data | 上传需要附加数据 | Object | {} |
| headers | 自定义请求头,如果为函数,则传入当前文件对象调用得到 headers | Object/Function<sup>1.11.0+</sup> | {} |
| data | 上传需要附加数据,如果为函数,则传入当前文件对象调用得到 data | Object/Function<sup>1.11.0+</sup> | {} |
| withCredentials | 标准的 CORS 请求是不会带上 cookie 的,如果想要带的话需要设置 withCredentials 为 true | Boolean | false |
| timeout | 请求超时时间 | Number | 0 | |
| progressInterval | 进度回调间隔(单位:ms) | Number | 100 |
| checkSuccess | 校验是否成功函数,参数为服务端响应数据,返回值为 true 则代表成功 | Function | function (res) { return true } |
| checkSuccess | 校验是否成功函数,参数为`(服务端响应数据, 当前文件对象 [,cb 回调])`,注意第二个参数和第三个参数是 1.11.0 后新增的,而参数 `cb` 是可选的,异步场景可用,如果没有 `cb` 则取其返回值,如果结果值为 true 则代表成功 | Function | function (res, file) { return true } |
* `processFile` 子配置项
......
import {
evalOpts,
STATUS_SUCCESS,
STATUS_UPLOADING,
STATUS_ERROR
......@@ -17,6 +18,8 @@ export default function ajaxUpload(file, options, changeHandler) {
checkSuccess = function () { return true }
} = options
const realTarget = evalOpts(target, file)
file.progress = 0
file.status = STATUS_UPLOADING
......@@ -50,8 +53,9 @@ export default function ajaxUpload(file, options, changeHandler) {
}
const formData = new window.FormData()
Object.keys(data).forEach((key) => {
formData.append(key, data[key])
const realData = evalOpts(data, file)
Object.keys(realData).forEach((key) => {
formData.append(key, realData[key])
})
formData.append(fileName, file[prop])
......@@ -67,8 +71,15 @@ export default function ajaxUpload(file, options, changeHandler) {
file.response = response
file.responseHeaders = xhr.getAllResponseHeaders()
const isSuccess = checkSuccess(response)
setStatus(isSuccess ? STATUS_SUCCESS : STATUS_ERROR)
if (checkSuccess.length <= 2) {
const isSuccess = checkSuccess(response, file)
setStatus(isSuccess ? STATUS_SUCCESS : STATUS_ERROR)
} else {
// callback
checkSuccess(response, file, (isSuccess) => {
setStatus(isSuccess ? STATUS_SUCCESS : STATUS_ERROR)
})
}
}
xhr.onerror = function () {
setStatus(STATUS_ERROR)
......@@ -77,12 +88,13 @@ export default function ajaxUpload(file, options, changeHandler) {
setStatus(STATUS_ERROR)
}
xhr.open('POST', target, true)
xhr.open('POST', realTarget, true)
if (withCredentials) {
xhr.withCredentials = true
}
Object.keys(headers).forEach((key) => {
xhr.setRequestHeader(key, headers[key])
const realHeaders = evalOpts(headers, file)
Object.keys(realHeaders).forEach((key) => {
xhr.setRequestHeader(key, realHeaders[key])
})
if (timeout > 0) {
xhr.timeout = timeout
......
export default {
props: {
multiple: {
type: Boolean,
default: true
},
accept: {
type: String,
default: 'image/*'
}
}
}
......@@ -7,20 +7,13 @@
</div>
</template>
<script type="text/ecmascript-6">
import btnMixin from './btn-mixin'
const COMPONENT_NAME = 'cube-upload-btn'
export default {
name: COMPONENT_NAME,
props: {
multiple: {
type: Boolean,
default: true
},
accept: {
type: String,
default: 'image/*'
}
},
mixins: [btnMixin],
methods: {
changeHandler(e) {
const fileEle = e.currentTarget
......
......@@ -3,7 +3,7 @@
<slot>
<div class="cube-upload-def clear-fix">
<upload-file v-for="(file, i) in files" :file="file" :key="i" @click="fileClick"></upload-file>
<upload-btn v-show="isShowBtn"></upload-btn>
<upload-btn :multiple="multiple" :accept="accept" v-show="isShowBtn"></upload-btn>
</div>
</slot>
</div>
......@@ -12,6 +12,7 @@
import UploadBtn from './btn.vue'
import UploadFile from './file.vue'
import ajaxUpload from './ajax'
import btnMixin from './btn-mixin'
import {
processFiles,
newFile,
......@@ -33,6 +34,7 @@
export default {
name: COMPONENT_NAME,
mixins: [btnMixin],
props: {
value: {
type: Array,
......
......@@ -51,3 +51,10 @@ function createURL(file) {
}
return ''
}
export function evalOpts(data, ...args) {
if (typeof data === 'function') {
return data.apply(this, args)
}
return data
}
......@@ -45,7 +45,17 @@ describe('Upload.vue', () => {
it('should add files & upload', function (done) {
this.timeout(1000)
vm = createFilesUpload()
vm = createFilesUpload(2, {}, {
target() {
return '/upload2'
},
checkSuccess(response, file, cb) {
setTimeout(() => {
/* eslint-disable standard/no-callback-literal */
cb(true)
})
}
})
// check data
expect(vm.files.length)
.to.equal(2)
......@@ -76,6 +86,8 @@ describe('Upload.vue', () => {
// check data state
expect(vm.files[0]._xhr)
.not.to.be.null
expect(vm.files[0]._xhr.url)
.to.equal('/upload2')
expect(vm.files[0].status)
.to.equal('uploading')
expect(vm.files[0].progress)
......@@ -102,34 +114,37 @@ describe('Upload.vue', () => {
.to.equal('0.67')
// success
vm.files[0]._xhr.triggerSuccess()
expect(vm.files[0].progress)
.to.equal(1)
expect(vm.files[0].status)
.to.equal('success')
// need to wait checkSuccess
setTimeout(() => {
expect(allFiles[0].querySelector('.cube-upload-file-status').className)
.to.equal('cube-upload-file-status cubeic-right')
// next file
expect(vm.files[1]._xhr)
.not.to.be.null
expect(vm.files[1].status)
.to.equal('uploading')
expect(vm.files[1].progress)
.to.equal(0)
// error
vm.files[1]._xhr.triggerError()
expect(vm.files[1].progress)
expect(vm.files[0].progress)
.to.equal(1)
expect(vm.files[1].status)
.to.equal('error')
expect(vm.files[0].status)
.to.equal('success')
setTimeout(() => {
expect(allFiles[1].querySelector('.cube-upload-file-status').className)
.to.equal('cube-upload-file-status cubeic-warn')
done()
expect(allFiles[0].querySelector('.cube-upload-file-status').className)
.to.equal('cube-upload-file-status cubeic-right')
// next file
expect(vm.files[1]._xhr)
.not.to.be.null
expect(vm.files[1].status)
.to.equal('uploading')
expect(vm.files[1].progress)
.to.equal(0)
// error
vm.files[1]._xhr.triggerError()
expect(vm.files[1].progress)
.to.equal(1)
expect(vm.files[1].status)
.to.equal('error')
setTimeout(() => {
expect(allFiles[1].querySelector('.cube-upload-file-status').className)
.to.equal('cube-upload-file-status cubeic-warn')
done()
})
})
})
}, 100)
......@@ -265,7 +280,7 @@ describe('Upload.vue', () => {
return vm
}
function createFilesUpload(max = 2, events = {}) {
function createFilesUpload(max = 2, events = {}, opts = {}) {
const vm = createUpload({
action: {
target: '/upload',
......@@ -275,7 +290,8 @@ describe('Upload.vue', () => {
},
headers: {
'my-header': 'my-header'
}
},
...opts
},
max
}, events)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册