未验证 提交 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 @@ ...@@ -182,10 +182,13 @@
| Attribute | Description | Type | Accepted Values | Demo | | 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' } | | action | upload action config | String/Object | '' | { target: '/upload' } |
| max | max upload files number | Number | 10 | - | | max | max upload files number | Number | 10 | - |
| auto | whether auto start upload | Boolean | true | - | | auto | whether auto start upload | Boolean | true | - |
| simultaneousUploads | the number of simultaneous uploads | Number | 1 | - | | 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) } | - | | processFile | process the original file | Function | function (file, next) { next(file) } | - |
* `action` sub configuration * `action` sub configuration
...@@ -194,15 +197,15 @@ If `action` is a string, it will be transformed into `{ target: action }`. ...@@ -194,15 +197,15 @@ If `action` is a string, it will be transformed into `{ target: action }`.
| Attribute | Description | Type | Default | | 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' | | fileName | the name of the multipart POST parameter | String | 'file' |
| prop | which property in file object will be uploaded | String | 'file' | | prop | which property in file object will be uploaded | String | 'file' |
| headers | extra headers 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 | Object | {} | | 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 | | 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 | | timeout | upload request timeout value | Number | 0 |
| progressInterval | The time interval between progress reports (Unit: ms) | Number | 100 | | 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 * `processFile` sub configuration
......
...@@ -193,6 +193,8 @@ ...@@ -193,6 +193,8 @@
| max | 最大上传文件个数 | Number | 10 | - | | max | 最大上传文件个数 | Number | 10 | - |
| auto | 是否自动上传,即选择完文件后自动开始上传 | Boolean | true | - | | auto | 是否自动上传,即选择完文件后自动开始上传 | Boolean | true | - |
| simultaneousUploads | 并发上传数 | Number | 1 | - | | simultaneousUploads | 并发上传数 | Number | 1 | - |
| multiple | 是否多选 | Boolean | true | - |
| accept | input 的 accept 属性值 | String | image/* | - |
| processFile | 处理原始文件函数 | Function | function (file, next) { next(file) } | - | | processFile | 处理原始文件函数 | Function | function (file, next) { next(file) } | - |
* `action` 子配置项 * `action` 子配置项
...@@ -201,15 +203,15 @@ ...@@ -201,15 +203,15 @@
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| - | - | - | - | | - | - | - | - |
| target | 上传目标 URL | String | - | | target | 上传目标 URL,如果为函数,则传入当前文件对象调用得到目标 URL | String/Function<sup>1.11.0+</sup> | - |
| fileName | 上传文件时文件的参数名 | String | 'file' | | fileName | 上传文件时文件的参数名 | String | 'file' |
| prop | 上传的时候使用文件对象的 prop 属性所对应的值 | String | 'file' | | prop | 上传的时候使用文件对象的 prop 属性所对应的值 | String | 'file' |
| headers | 自定义请求头 | Object | {} | | headers | 自定义请求头,如果为函数,则传入当前文件对象调用得到 headers | Object/Function<sup>1.11.0+</sup> | {} |
| data | 上传需要附加数据 | Object | {} | | data | 上传需要附加数据,如果为函数,则传入当前文件对象调用得到 data | Object/Function<sup>1.11.0+</sup> | {} |
| withCredentials | 标准的 CORS 请求是不会带上 cookie 的,如果想要带的话需要设置 withCredentials 为 true | Boolean | false | | withCredentials | 标准的 CORS 请求是不会带上 cookie 的,如果想要带的话需要设置 withCredentials 为 true | Boolean | false |
| timeout | 请求超时时间 | Number | 0 | | | timeout | 请求超时时间 | Number | 0 | |
| progressInterval | 进度回调间隔(单位:ms) | Number | 100 | | 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` 子配置项 * `processFile` 子配置项
......
import { import {
evalOpts,
STATUS_SUCCESS, STATUS_SUCCESS,
STATUS_UPLOADING, STATUS_UPLOADING,
STATUS_ERROR STATUS_ERROR
...@@ -17,6 +18,8 @@ export default function ajaxUpload(file, options, changeHandler) { ...@@ -17,6 +18,8 @@ export default function ajaxUpload(file, options, changeHandler) {
checkSuccess = function () { return true } checkSuccess = function () { return true }
} = options } = options
const realTarget = evalOpts(target, file)
file.progress = 0 file.progress = 0
file.status = STATUS_UPLOADING file.status = STATUS_UPLOADING
...@@ -50,8 +53,9 @@ export default function ajaxUpload(file, options, changeHandler) { ...@@ -50,8 +53,9 @@ export default function ajaxUpload(file, options, changeHandler) {
} }
const formData = new window.FormData() const formData = new window.FormData()
Object.keys(data).forEach((key) => { const realData = evalOpts(data, file)
formData.append(key, data[key]) Object.keys(realData).forEach((key) => {
formData.append(key, realData[key])
}) })
formData.append(fileName, file[prop]) formData.append(fileName, file[prop])
...@@ -67,8 +71,15 @@ export default function ajaxUpload(file, options, changeHandler) { ...@@ -67,8 +71,15 @@ export default function ajaxUpload(file, options, changeHandler) {
file.response = response file.response = response
file.responseHeaders = xhr.getAllResponseHeaders() file.responseHeaders = xhr.getAllResponseHeaders()
const isSuccess = checkSuccess(response) 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) setStatus(isSuccess ? STATUS_SUCCESS : STATUS_ERROR)
})
}
} }
xhr.onerror = function () { xhr.onerror = function () {
setStatus(STATUS_ERROR) setStatus(STATUS_ERROR)
...@@ -77,12 +88,13 @@ export default function ajaxUpload(file, options, changeHandler) { ...@@ -77,12 +88,13 @@ export default function ajaxUpload(file, options, changeHandler) {
setStatus(STATUS_ERROR) setStatus(STATUS_ERROR)
} }
xhr.open('POST', target, true) xhr.open('POST', realTarget, true)
if (withCredentials) { if (withCredentials) {
xhr.withCredentials = true xhr.withCredentials = true
} }
Object.keys(headers).forEach((key) => { const realHeaders = evalOpts(headers, file)
xhr.setRequestHeader(key, headers[key]) Object.keys(realHeaders).forEach((key) => {
xhr.setRequestHeader(key, realHeaders[key])
}) })
if (timeout > 0) { if (timeout > 0) {
xhr.timeout = timeout xhr.timeout = timeout
......
export default {
props: {
multiple: {
type: Boolean,
default: true
},
accept: {
type: String,
default: 'image/*'
}
}
}
...@@ -7,20 +7,13 @@ ...@@ -7,20 +7,13 @@
</div> </div>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import btnMixin from './btn-mixin'
const COMPONENT_NAME = 'cube-upload-btn' const COMPONENT_NAME = 'cube-upload-btn'
export default { export default {
name: COMPONENT_NAME, name: COMPONENT_NAME,
props: { mixins: [btnMixin],
multiple: {
type: Boolean,
default: true
},
accept: {
type: String,
default: 'image/*'
}
},
methods: { methods: {
changeHandler(e) { changeHandler(e) {
const fileEle = e.currentTarget const fileEle = e.currentTarget
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<slot> <slot>
<div class="cube-upload-def clear-fix"> <div class="cube-upload-def clear-fix">
<upload-file v-for="(file, i) in files" :file="file" :key="i" @click="fileClick"></upload-file> <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> </div>
</slot> </slot>
</div> </div>
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
import UploadBtn from './btn.vue' import UploadBtn from './btn.vue'
import UploadFile from './file.vue' import UploadFile from './file.vue'
import ajaxUpload from './ajax' import ajaxUpload from './ajax'
import btnMixin from './btn-mixin'
import { import {
processFiles, processFiles,
newFile, newFile,
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
export default { export default {
name: COMPONENT_NAME, name: COMPONENT_NAME,
mixins: [btnMixin],
props: { props: {
value: { value: {
type: Array, type: Array,
......
...@@ -51,3 +51,10 @@ function createURL(file) { ...@@ -51,3 +51,10 @@ function createURL(file) {
} }
return '' return ''
} }
export function evalOpts(data, ...args) {
if (typeof data === 'function') {
return data.apply(this, args)
}
return data
}
...@@ -45,7 +45,17 @@ describe('Upload.vue', () => { ...@@ -45,7 +45,17 @@ describe('Upload.vue', () => {
it('should add files & upload', function (done) { it('should add files & upload', function (done) {
this.timeout(1000) 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 // check data
expect(vm.files.length) expect(vm.files.length)
.to.equal(2) .to.equal(2)
...@@ -76,6 +86,8 @@ describe('Upload.vue', () => { ...@@ -76,6 +86,8 @@ describe('Upload.vue', () => {
// check data state // check data state
expect(vm.files[0]._xhr) expect(vm.files[0]._xhr)
.not.to.be.null .not.to.be.null
expect(vm.files[0]._xhr.url)
.to.equal('/upload2')
expect(vm.files[0].status) expect(vm.files[0].status)
.to.equal('uploading') .to.equal('uploading')
expect(vm.files[0].progress) expect(vm.files[0].progress)
...@@ -102,6 +114,8 @@ describe('Upload.vue', () => { ...@@ -102,6 +114,8 @@ describe('Upload.vue', () => {
.to.equal('0.67') .to.equal('0.67')
// success // success
vm.files[0]._xhr.triggerSuccess() vm.files[0]._xhr.triggerSuccess()
// need to wait checkSuccess
setTimeout(() => {
expect(vm.files[0].progress) expect(vm.files[0].progress)
.to.equal(1) .to.equal(1)
expect(vm.files[0].status) expect(vm.files[0].status)
...@@ -132,6 +146,7 @@ describe('Upload.vue', () => { ...@@ -132,6 +146,7 @@ describe('Upload.vue', () => {
done() done()
}) })
}) })
})
}, 100) }, 100)
}) })
}, 200) }, 200)
...@@ -265,7 +280,7 @@ describe('Upload.vue', () => { ...@@ -265,7 +280,7 @@ describe('Upload.vue', () => {
return vm return vm
} }
function createFilesUpload(max = 2, events = {}) { function createFilesUpload(max = 2, events = {}, opts = {}) {
const vm = createUpload({ const vm = createUpload({
action: { action: {
target: '/upload', target: '/upload',
...@@ -275,7 +290,8 @@ describe('Upload.vue', () => { ...@@ -275,7 +290,8 @@ describe('Upload.vue', () => {
}, },
headers: { headers: {
'my-header': 'my-header' 'my-header': 'my-header'
} },
...opts
}, },
max max
}, events) }, events)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册