You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

595 lines
12 KiB

9 months ago
<template>
<view ref="lsjFile" class="lsj-file" :style="[getStyles]">
<!-- #ifdef APP-PLUS -->
<view>
<slot>
<view class="defview" :style="[getStyles]">附件上传</view>
</slot>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view ref="hFile" class="hFile" :style="[getStyles]">
<slot>
<view class="defview" :style="[getStyles]">附件上传</view>
</slot>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view class="hFile" :style="[getStyles]" @click="wxChooseFile">
<slot>
<view class="defview" :style="[getStyles]">附件上传</view>
</slot>
</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: [String, Number],
default: ''
},
option: {
type: Object,
required: true
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '80rpx'
},
size: {
type: Number,
default: 10
},
// #ifdef APP-NVUE
position: {
type: String,
default: 'absolute'
},
// #endif
// #ifndef APP-NVUE
position: {
type: String,
default: 'static'
},
// #endif
childId: {
type: String,
default: 'lsjUpload'
},
manual: {
type: Boolean,
default: false
},
top: {
type: [String, Number],
default: ''
},
left: {
type: [String, Number],
default: ''
},
bottom: {
type: [String, Number],
default: ''
},
right: {
type: [String, Number],
default: ''
}
},
data() {
this.xmlRequest = ''
this.wxParam = {}
this.wv = {}
return {
fileDom: '',
disabled: false,
param: {},
currCreate: false
}
},
computed: {
getStyles() {
let styles = {
width: this.width,
height: this.height
}
if (this.position == 'absolute') {
styles['top'] = this.top
styles['bottom'] = this.bottom
styles['left'] = this.left
styles['right'] = this.right
styles['position'] = 'fixed'
}
return styles
}
},
watch: {
option: {
immediate: true,
handler(param) {
this.param = param
if (param.url) {
setTimeout(() => {
this.show()
}, 500)
} else {
this.hide()
}
}
}
},
mounted() {
this._manual = this.manual
},
updated() {
if (this.isShow) {
this.show()
}
},
methods: {
submit(data) {
this._manual && this.wv.evalJS(`vm.submit('${JSON.stringify(data)}')`)
},
show() {
// #ifdef APP-PLUS
if (this.isShow) {
this.getDomStyles(styles => {
this.wv.setStyle(styles)
})
return
}
// #endif
this.refresh()
},
hide() {
// #ifdef APP-PLUS
if (plus.webview.getWebviewById(this.childId)) {
// #ifndef APP-NVUE
this.wv.removeFromParent()
// #endif
plus.webview.close(this.childId, 'none', 0)
}
// #endif
// #ifdef H5
if (this.fileDom) {
this.$refs.hFile.$el.removeChild(this.fileDom)
this.fileDom = ''
}
// #endif
// #ifdef MP-WEIXIN
this.disabled = true
// #endif
this.isShow = false
},
refresh() {
let param = this.param
if (!param.url) {
console.error('url为必传参数')
return
}
// #ifdef APP-PLUS
if (!param.cuWebview) {
console.error('cuWebview为必传参数')
return
}
this.hide()
this.getDomStyles(styles => {
this.createAppFile(styles, param)
})
// #endif
// #ifdef MP-WEIXIN
this.wxParam = param
this.disabled = false
// #endif
// #ifdef H5
this.createH5File(param)
// #endif
this.isShow = true
},
getDomStyles(callback) {
// #ifndef APP-NVUE
let view = uni
.createSelectorQuery()
.in(this)
.select('.lsj-file')
view.fields({
size: true,
rect: true
},
({
height,
width,
top,
left,
right,
bottom
}) => {
uni.createSelectorQuery()
.selectViewport()
.scrollOffset(({
scrollTop
}) => {
return callback({
top: parseInt(top) + parseInt(scrollTop) + 'px',
left: parseInt(left) + 'px',
width: parseInt(width) + 'px',
height: parseInt(height) + 'px'
})
})
.exec()
}
).exec()
// #endif
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
dom.getComponentRect(this.$refs.lsjFile, ({
size: {
height,
width,
top,
left,
right,
bottom
}
}) => {
return callback({
top: parseInt(top) + 'px',
left: parseInt(left) + 'px',
width: parseInt(width) + 'px',
height: parseInt(height) + 'px',
right: parseInt(right) + 'px',
bottom: parseInt(bottom) + 'px'
})
})
// #endif
},
toast(title = '', {
duration = 2000,
icon = 'none'
} = {}) {
uni.showToast({
title,
duration,
icon
})
},
getRequest(url) {
let theRequest = new Object()
let index = url.indexOf('?')
if (index != -1) {
let str = url.substring(index + 1)
let strs = str.split('&')
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
}
}
return theRequest
},
wxChooseFile() {
// #ifdef MP-WEIXIN
if (this.disabled) {
return
}
wx.chooseMessageFile({
count: 1,
type: 'file',
success: ({
tempFiles
}) => {
this.handleWXUpload(tempFiles[0])
},
fail: () => {
this.$emit('callback', {
success: false,
status: -100,
msg: '文件选择失败'
})
}
})
// #endif
},
handleWXUpload(file) {
let {
debug = false, url, name = 'file', header = {}, formData = {}
} = this.wxParam
if (!url) {
console.error('url为必传参数')
return
}
if (file.size > 1024 * 1024 * Math.abs(this.size)) {
this.toast(`附件大小请勿超过${this.size}M`)
return
}
formData['fileName'] = file.name
let opt = {
url,
name,
header,
formData,
filePath: file.path
}
debug &&
console.log(`
上传接口地址:${url}\n
附件key:${name}\n
附件名称:${file.name}\n
附件大小:${file.size}\n
请求头:${JSON.stringify(header)}\n
参数:${JSON.stringify(formData)}
`)
opt['fail'] = ({
errMsg = ''
}) => {
this.disabled = false
console.error('--ERROR--' + errMsg)
this.$emit('callback', {
success: false,
status: 500,
msg: '上传失败'
})
}
opt['success'] = res => {
this.disabled = false
if (res.statusCode == 200) {
this.$emit('callback', {
success: true,
fileName: file.name,
responseText: res.data,
status: res.statusCode,
msg: '上传成功'
})
return
}
this.$emit('callback', {
success: false,
status: res.statusCode,
msg: '上传失败'
})
}
this.disabled = true
this.xmlRequest = uni.uploadFile(opt)
this.xmlRequest &&
this.xmlRequest.onProgressUpdate(({
progress = 0
}) => {
if (progress <= 100) {
this.$emit('input', progress)
this.$forceUpdate()
}
})
},
createH5File(param) {
this.hide();
if (!this.fileDom) {
this.fileDom = document.createElement('input');
}
this.fileDom.type = 'file'
this.fileDom.value = ''
this.fileDom.style.height = this.height
this.fileDom.style.width = this.width
this.fileDom.style.position = 'absolute'
this.fileDom.style.top = 0
this.fileDom.style.left = 0
this.fileDom.style.right = 0
this.fileDom.style.bottom = 0
this.fileDom.style.opacity = 0
this.fileDom.style.zIndex = 999
this.$refs.hFile.$el.appendChild(this.fileDom)
this.fileDom.onchange = event => {
let file = event.target.files[0]
if (file) {
// 限制文件小于10M,可自行修改
if (file.size > 1024 * 1024 * Math.abs(this.size)) {
this.toast(`附件大小请勿超过${this.size}M`)
return
}
this.uploadH5(file, param)
}
}
},
createAppFile(rect = {}, {
cuWebview,
...param
}) {
param.size = this.size
param.manual = this._manual
let wvPath = '/uni_modules/lsj-upload/hybrid/html/uploadFile.html'
let styles = {
position: this.position,
background: 'transparent'
}
// #ifdef APP-NVUE
styles.position = 'absolute'
// #endif
styles = Object.assign(rect, styles)
let wv = plus.webview.create(wvPath, this.childId, styles, param || {})
wv.loadURL(wvPath)
cuWebview.append(wv)
this.wv = wv
wv.overrideUrlLoading({
mode: 'reject'
}, e => {
let {
retype,
percent = '',
msg = '',
fileName = '',
responseText = '{}',
success = false,
status = 0,
files = []
} = this.getRequest(
e.url
)
switch (retype) {
case 'change':
files = unescape(files)
try {
files = JSON.parse(files)
} catch (e) {
return console.error('出错了,请检查代码')
}
this.$emit('change', files)
break
case 'percent':
percent = unescape(percent)
this.$emit('input', percent)
this.$forceUpdate()
break
case 'complete':
msg = unescape(msg)
fileName = unescape(fileName)
responseText = unescape(responseText)
this.$emit('callback', {
success,
fileName,
status,
responseText,
msg
})
break
default:
break
}
})
},
uploadH5(file, {
debug = false,
url,
name = 'file',
method = 'POST',
header = {},
formData: data = {}
}) {
if (!url) {
return
}
data['fileName'] = file.name
debug &&
console.log(`
上传接口地址:${url}\n
附件key:${name}\n
附件名称:${file.name}\n
附件大小:${file.size}\n
请求头:${JSON.stringify(header)}\n
参数:${JSON.stringify(data)}
`)
let formData = new FormData()
for (let keys in data) {
formData.append(keys, data[keys])
}
formData.append(name, file)
this.xmlRequest = new XMLHttpRequest()
this.xmlRequest.open(method, url, true)
for (let keys in header) {
this.xmlRequest.setRequestHeader(keys, header[keys])
}
this.xmlRequest.upload.addEventListener(
'progress',
event => {
if (event.lengthComputable) {
let percent = Math.ceil((event.loaded * 100) / event.total)
if (percent <= 100) {
this.$emit('input', percent)
this.$forceUpdate()
}
}
},
false
)
this.xmlRequest.ontimeout = () => {
this.disabled = false
this.fileDom.value = ''
this.$emit('callback', {
success: false,
status: 408,
msg: '请求超时'
})
}
this.xmlRequest.onreadystatechange = ev => {
if (this.xmlRequest.readyState == 4) {
this.disabled = false
this.fileDom.value = ''
if (this.xmlRequest.status == 200) {
debug && console.log('上传完成:' + this.xmlRequest.responseText)
this.$emit('callback', {
success: true,
fileName: file.name,
responseText: this.xmlRequest.responseText,
status: this.xmlRequest.status,
msg: '上传成功'
})
return
} else if (this.xmlRequest.status == 0) {
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配服务端已正确开启跨域并且nginx未拦截阻止请求')
}
console.error('--ERROR--status = ' + this.xmlRequest.status)
this.$emit('callback', {
success: false,
status: this.xmlRequest.status,
msg: '上传失败'
})
}
}
this.disabled = true
this.xmlRequest.send(formData)
}
}
}
</script>
<style scoped>
.lsj-file {
display: inline-block;
}
.defview {
background-color: #007aff;
color: #fff;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.hFile {
position: relative;
}
</style>