613 lines
16 KiB
Vue
613 lines
16 KiB
Vue
<template>
|
||
<view class="tui-upload__container">
|
||
<view class="tui-upload-box">
|
||
<view class="tui-image-item" :style="{width:width+'rpx',height:height+'rpx',borderRadius:radius+'rpx'}"
|
||
v-for="(item,index) in imageList" :key="index">
|
||
<image :src="item" class="tui-item-img"
|
||
:style="{width:width+'rpx',height:height+'rpx',borderRadius:radius+'rpx'}"
|
||
@tap.stop="previewImage(index)" mode="aspectFill"></image>
|
||
<view v-if="!forbidDel" class="tui-img-del" :style="{background:getDelColor}"
|
||
@tap.stop="delImage(index)">
|
||
</view>
|
||
<view v-if="statusArr[index]!=1" class="tui-upload-mask">
|
||
<view class="tui-upload-loading" v-if="statusArr[index]==2"></view>
|
||
<text class="tui-tips">{{statusArr[index]==2?'上传中...':'上传失败'}}</text>
|
||
<view class="tui-mask-btn" v-if="statusArr[index]==3" @tap.stop="reUpLoad(index)"
|
||
hover-class="tui-btn-hover" :hover-stay-time="150">重新上传</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="isShowAdd" class="tui-upload-add"
|
||
:class="[borderColor!=='transparent'?'tui-upload__border':'tui-upload__unborder']"
|
||
:style="{width:width+'rpx',height:height+'rpx',background:background,borderRadius:radius+'rpx',borderColor:borderColor,borderStyle:borderSytle}"
|
||
@tap="chooseImage">
|
||
<slot>
|
||
<view class="tui-upload-icon tui-icon-plus" :style="{color:addColor,fontSize:addSize+'rpx'}"></view>
|
||
</slot>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'tuiUpload',
|
||
emits: ['remove', 'complete', 'reupload'],
|
||
props: {
|
||
//展示图片宽度
|
||
width: {
|
||
type: [Number, String],
|
||
default: 218
|
||
},
|
||
//展示图片高度
|
||
height: {
|
||
type: [Number, String],
|
||
default: 218
|
||
},
|
||
//初始化图片路径
|
||
value: {
|
||
type: Array,
|
||
default () {
|
||
return []
|
||
}
|
||
},
|
||
//2.3.0+
|
||
radius: {
|
||
type: [Number, String],
|
||
default: 0
|
||
},
|
||
//2.3.0+
|
||
background: {
|
||
type: String,
|
||
default: '#F7F7F7'
|
||
},
|
||
//2.3.0+
|
||
borderColor: {
|
||
type: String,
|
||
default: 'transparent'
|
||
},
|
||
//2.3.0+
|
||
//solid、dashed、dotted
|
||
borderSytle: {
|
||
type: String,
|
||
default: 'dashed'
|
||
},
|
||
//2.3.0+
|
||
delColor: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
//删除图片前是否弹框确认
|
||
delConfirm: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
//禁用删除
|
||
forbidDel: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
//V2.9.6+ 删除图片是否触发 @complete 事件
|
||
delTrigger:{
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
//2.3.0+
|
||
addColor: {
|
||
type: String,
|
||
default: '#888'
|
||
},
|
||
//2.3.0+
|
||
addSize: {
|
||
type: [Number, String],
|
||
default: 68
|
||
},
|
||
//禁用添加
|
||
forbidAdd: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
//服务器接口地址。当接口地址为空时,直接返回本地图片地址
|
||
serverUrl: {
|
||
type: String,
|
||
default: ""
|
||
},
|
||
//限制数
|
||
limit: {
|
||
type: Number,
|
||
default: 9
|
||
},
|
||
//original 原图,compressed 压缩图,默认二者都有
|
||
sizeType: {
|
||
type: Array,
|
||
default () {
|
||
return ['original', 'compressed']
|
||
}
|
||
},
|
||
//album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
|
||
sourceType: {
|
||
type: Array,
|
||
default () {
|
||
return ['album', 'camera']
|
||
}
|
||
},
|
||
//可上传图片类型,默认为空,不限制 Array<String> ['jpg','png','gif']
|
||
imageFormat: {
|
||
type: Array,
|
||
default () {
|
||
return []
|
||
}
|
||
},
|
||
//单张图片大小限制 MB
|
||
size: {
|
||
type: Number,
|
||
default: 4
|
||
},
|
||
//文件对应的key,默认为 file
|
||
fileKeyName: {
|
||
type: String,
|
||
default: "file"
|
||
},
|
||
//HTTP 请求 Header, header 中不能设置 Referer。
|
||
header: {
|
||
type: Object,
|
||
default () {
|
||
return {}
|
||
}
|
||
},
|
||
//HTTP 请求中其他额外的 form data
|
||
formData: {
|
||
type: Object,
|
||
default () {
|
||
return {}
|
||
}
|
||
},
|
||
//自定义参数
|
||
params: {
|
||
type: [Number, String],
|
||
default: 0
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
//图片地址
|
||
imageList: [],
|
||
tempFiles: [],
|
||
//上传状态:1-上传成功 2-上传中 3-上传失败
|
||
statusArr: [],
|
||
//传入回调函数上传
|
||
callUpload: false
|
||
}
|
||
},
|
||
created() {
|
||
this.initImages()
|
||
},
|
||
watch: {
|
||
value(val) {
|
||
if (val) {
|
||
this.initImages()
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
isShowAdd() {
|
||
let isShow = true;
|
||
if (this.forbidAdd || (this.limit && this.imageList.length >= this.limit)) {
|
||
isShow = false;
|
||
}
|
||
return isShow
|
||
},
|
||
getDelColor() {
|
||
return this.delColor || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909';
|
||
}
|
||
},
|
||
methods: {
|
||
initImages() {
|
||
this.statusArr = [];
|
||
this.imageList = [...this.value];
|
||
let tempFiles = []
|
||
for (let item of this.imageList) {
|
||
this.statusArr.push("1")
|
||
tempFiles.push({
|
||
path: item
|
||
})
|
||
}
|
||
this.tempFiles = tempFiles;
|
||
},
|
||
// 重新上传
|
||
reUpLoad(index) {
|
||
this.$set(this.statusArr, index, "2")
|
||
this.$emit('reupload', {
|
||
index
|
||
})
|
||
if (!this.callUpload) {
|
||
this.uploadImage(index, this.imageList[index]).then(() => {
|
||
this.change()
|
||
}).catch(() => {
|
||
this.change()
|
||
})
|
||
}
|
||
},
|
||
/**
|
||
* @param manual 是否手动上传
|
||
**/
|
||
change(manual = false) {
|
||
let status = ~this.statusArr.indexOf("2") ? 2 : 1
|
||
if (status != 2 && ~this.statusArr.indexOf("3")) {
|
||
// 上传失败
|
||
status = 3
|
||
}
|
||
this.$emit('complete', {
|
||
status: status,
|
||
imgArr: this.imageList,
|
||
params: this.params,
|
||
manual: manual
|
||
})
|
||
},
|
||
toast(text) {
|
||
text && uni.showToast({
|
||
title: text,
|
||
icon: "none"
|
||
});
|
||
},
|
||
chooseImage: function() {
|
||
let _this = this;
|
||
uni.chooseImage({
|
||
count: _this.limit - _this.imageList.length,
|
||
sizeType: _this.sizeType,
|
||
sourceType: _this.sourceType,
|
||
success: function(e) {
|
||
let imageArr = [];
|
||
for (let i = 0; i < e.tempFiles.length; i++) {
|
||
let len = _this.imageList.length;
|
||
if (len >= _this.limit) {
|
||
_this.toast(`最多可上传${_this.limit}张图片`);
|
||
break;
|
||
}
|
||
//过滤图片类型
|
||
let path = e.tempFiles[i].path;
|
||
|
||
if (_this.imageFormat.length > 0) {
|
||
let format = ""
|
||
// #ifdef H5
|
||
let type = e.tempFiles[i].type;
|
||
format = type.split('/')[1]
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
format = path.split(".")[(path.split(".")).length - 1];
|
||
// #endif
|
||
|
||
if (_this.imageFormat.indexOf(format) == -1) {
|
||
let text = `只能上传 ${_this.imageFormat.join(',')} 格式图片!`
|
||
_this.toast(text);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
//过滤超出大小限制图片
|
||
let size = e.tempFiles[i].size;
|
||
|
||
if (_this.size * 1024 * 1024 < size) {
|
||
let err = `单张图片大小不能超过:${_this.size}MB`
|
||
_this.toast(err);
|
||
continue;
|
||
}
|
||
imageArr.push(path)
|
||
_this.imageList.push(path)
|
||
_this.tempFiles.push(e.tempFiles[i])
|
||
_this.statusArr.push("2")
|
||
}
|
||
_this.change()
|
||
|
||
let start = _this.imageList.length - imageArr.length
|
||
for (let j = 0; j < imageArr.length; j++) {
|
||
let index = start + j
|
||
//服务器地址
|
||
if (_this.serverUrl) {
|
||
_this.uploadImage(index, imageArr[j]).then(() => {
|
||
_this.change()
|
||
}).catch(() => {
|
||
_this.change()
|
||
})
|
||
} else {
|
||
//无服务器地址则直接返回成功
|
||
_this.$set(_this.statusArr, index, "1")
|
||
_this.change()
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
uploadImage: function(index, url, serverUrl) {
|
||
let _this = this;
|
||
return new Promise((resolve, reject) => {
|
||
uni.uploadFile({
|
||
url: this.serverUrl || serverUrl,
|
||
name: this.fileKeyName,
|
||
header: this.header,
|
||
formData: this.formData,
|
||
filePath: url,
|
||
success: function(res) {
|
||
if (res.statusCode == 200) {
|
||
//返回结果 此处需要按接口实际返回进行修改
|
||
let d = JSON.parse(res.data.replace(/\ufeff/g, "") || "{}")
|
||
//判断code,以实际接口规范判断
|
||
if (d.code % 100 === 0) {
|
||
// 上传成功 d.url 为上传后图片地址,以实际接口返回为准
|
||
d.url && (_this.imageList[index] = d.url)
|
||
_this.$set(_this.statusArr, index, d.url ? "1" : "3")
|
||
} else {
|
||
// 上传失败
|
||
_this.$set(_this.statusArr, index, "3")
|
||
}
|
||
resolve(index)
|
||
} else {
|
||
_this.$set(_this.statusArr, index, "3")
|
||
reject(index)
|
||
}
|
||
},
|
||
fail: function(res) {
|
||
_this.$set(_this.statusArr, index, "3")
|
||
reject(index)
|
||
}
|
||
})
|
||
})
|
||
|
||
},
|
||
delImage: function(index) {
|
||
let that = this
|
||
if (this.delConfirm) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确认删除该图片吗?',
|
||
showCancel: true,
|
||
cancelColor: "#555",
|
||
confirmColor: "#eb0909",
|
||
confirmText: "确定",
|
||
success(res) {
|
||
if (res.confirm) {
|
||
that.imageList.splice(index, 1)
|
||
that.tempFiles.splice(index, 1)
|
||
that.statusArr.splice(index, 1)
|
||
that.$emit("remove", {
|
||
index: index,
|
||
params: that.params
|
||
})
|
||
that.delTrigger && that.change()
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
that.imageList.splice(index, 1)
|
||
that.tempFiles.splice(index, 1)
|
||
that.statusArr.splice(index, 1)
|
||
that.$emit("remove", {
|
||
index: index,
|
||
params: that.params
|
||
})
|
||
that.delTrigger && that.change()
|
||
}
|
||
},
|
||
previewImage: function(index) {
|
||
if (!this.imageList.length) return;
|
||
uni.previewImage({
|
||
current: this.imageList[index],
|
||
loop: true,
|
||
urls: this.imageList
|
||
})
|
||
},
|
||
/**
|
||
* 当属性serverUrl传空时,父级调用该方法一次性上传所有图片
|
||
* @param serverUrl 服务器接口地址
|
||
**/
|
||
uploadAllImage(serverUrl) {
|
||
if (!serverUrl) {
|
||
this.toast('服务器接口地址不能为空!');
|
||
return;
|
||
}
|
||
let imageArr = [...this.imageList]
|
||
const len = imageArr.length
|
||
for (let i = 0; i < len; i++) {
|
||
//如果是服务器地址图片则无需再次上传
|
||
if (imageArr[i].startsWith('https')) {
|
||
continue;
|
||
} else {
|
||
this.$set(this.statusArr, i, "2")
|
||
this.uploadImage(i, imageArr[i], serverUrl).then(() => {
|
||
if (i === len - 1) {
|
||
this.change(true)
|
||
}
|
||
}).catch(() => {
|
||
if (i === len - 1) {
|
||
this.change(true)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
},
|
||
upload(callback, index) {
|
||
// 传入一个返回Promise的文件上传的函数
|
||
//上传状态:1-上传成功 2-上传中 3-上传失败
|
||
this.callUpload = true;
|
||
if (index === undefined || index === null) {
|
||
let urls = [...this.imageList]
|
||
const len = urls.length
|
||
for (let i = 0; i < len; i++) {
|
||
if (urls[i].startsWith('https')) {
|
||
continue;
|
||
} else {
|
||
this.$set(this.statusArr, i, "2")
|
||
if (typeof callback === 'function') {
|
||
callback(this.tempFiles[i]).then(res => {
|
||
this.$set(this.statusArr, i, '1')
|
||
this.imageList[i] = res
|
||
this.change(true)
|
||
}).catch(err => {
|
||
this.$set(this.statusArr, i, '3')
|
||
})
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
//如果传入index,则是重新上传时调用
|
||
this.$set(this.statusArr, index, "2")
|
||
if (typeof callback === 'function') {
|
||
callback(this.tempFiles[index]).then(res => {
|
||
this.$set(this.statusArr, index, '1')
|
||
this.imageList[index] = res
|
||
this.change(true)
|
||
}).catch(err => {
|
||
this.$set(this.statusArr, index, '3')
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
@font-face {
|
||
font-family: 'tuiUpload';
|
||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAATcAA0AAAAAByQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEwAAAABoAAAAciR52BUdERUYAAASgAAAAHgAAAB4AKQALT1MvMgAAAaAAAABCAAAAVjxvR/tjbWFwAAAB+AAAAEUAAAFK5ibpuGdhc3AAAASYAAAACAAAAAj//wADZ2x5ZgAAAkwAAADXAAABAAmNjcZoZWFkAAABMAAAAC8AAAA2FpiS+WhoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAARAAAAEgwAACBsb2NhAAACQAAAAAwAAAAMAEoAgG1heHAAAAGAAAAAHwAAACABEgA2bmFtZQAAAyQAAAFJAAACiCnmEVVwb3N0AAAEcAAAACgAAAA6OMUs4HjaY2BkYGAAYo3boY/i+W2+MnCzMIDAzb3qdQj6fwPzf+YGIJeDgQkkCgA/KAtvAHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBl0GJgZgABJiDmAkIGhv9gPgMADTABSQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ9xMjf8b2CIYW5gaAAKM4LkANt9C+UAAHjaY2GAABYIVmBgAAAA+gAtAAAAeNpjYGBgZoBgGQZGBhBwAfIYwXwWBg0gzQakGRmYnjE+4/z/n4EBQksxSf6GqgcCRjYGOIeRCUgwMaACRoZhDwCiLwmoAAAAAAAAAAAAAAAASgCAeNpdjkFKw0AARf/vkIR0BkPayWRKQZtYY90ohJju2kOIbtz0KD1HVm50UfEmWXoAr9ADOHFARHHzeY//Fx8Ci+FJfIgdJFa4AhgiMshbrCuIsLxhFJZVs+Vl1bT1GddtbXTC3OhohN4dg4BJ3zMJAnccyfm468ZzHXddrH9ZKbHzdf9n/vkY/xv9sPQXgGEvBrHHwst5kTbXLE+YpYVPkxepPmW94W16UbdNJd6f3SAzo5W7m1jaKd+8ZZIvk5nlKw9SK6Wle7BLS3f/bTzQLmfAF2T1NsQAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMxsKak5qSWpbFmZiRmJ+QAmgAUIAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABAABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9M296nUwGgA+8QYgAAA=) format('woff');
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
}
|
||
|
||
.tui-upload-icon {
|
||
font-family: "tuiUpload" !important;
|
||
font-style: normal;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
padding: 10rpx;
|
||
}
|
||
|
||
.tui-icon-delete:before {
|
||
content: "\e601";
|
||
}
|
||
|
||
.tui-icon-plus:before {
|
||
content: "\e609";
|
||
}
|
||
|
||
.tui-upload-box {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.tui-upload-add {
|
||
font-weight: 100;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
/* #ifdef H5 */
|
||
cursor: pointer;
|
||
/* #endif */
|
||
}
|
||
|
||
.tui-upload__unborder {
|
||
border-width: 0;
|
||
}
|
||
|
||
.tui-upload__border {
|
||
border-width: 1px;
|
||
}
|
||
|
||
.tui-image-item {
|
||
position: relative;
|
||
margin-right: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tui-item-img {
|
||
display: block;
|
||
}
|
||
|
||
.tui-img-del {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
position: absolute;
|
||
right: -12rpx;
|
||
top: -12rpx;
|
||
border-radius: 50%;
|
||
color: white;
|
||
font-size: 34rpx;
|
||
z-index: 5;
|
||
/* #ifdef H5 */
|
||
cursor: pointer;
|
||
/* #endif */
|
||
}
|
||
|
||
.tui-img-del::before {
|
||
content: '';
|
||
width: 16rpx;
|
||
height: 1px;
|
||
position: absolute;
|
||
left: 10rpx;
|
||
top: 18rpx;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.tui-upload-mask {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40rpx 0;
|
||
box-sizing: border-box;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
z-index: 3;
|
||
}
|
||
|
||
.tui-upload-loading {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
border-radius: 50%;
|
||
border: 2px solid;
|
||
border-color: #B2B2B2 #B2B2B2 #B2B2B2 #fff;
|
||
animation: tui-rotate 0.7s linear infinite;
|
||
}
|
||
|
||
@keyframes tui-rotate {
|
||
0% {
|
||
transform: rotate(0);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.tui-tips {
|
||
font-size: 26rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
.tui-mask-btn {
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 40rpx;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: #fff;
|
||
border: 1px solid #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
margin-top: 26rpx;
|
||
}
|
||
|
||
.tui-btn-hover {
|
||
opacity: 0.8;
|
||
}
|
||
</style> |