<template>
<view>
<view class="upload-box">
<view class="upload-item" v-for="(item, index) in fileList" :key="index" @click="previewImage(item)">
<image :src="item.url" mode="aspectFill"></image>
<text class="delete" @click.stop="deleteImage(index)">×</text>
</view>
<view class="upload-btn" v-if="fileList.length < maxCount" @click="uploadShow = true">
<text class="plus">+</text>
<text>上传图片</text>
</view>
</view>
<text class="tip">最多上传{{ maxCount }}张图片</text>
<u-modal :show="uploadShow" @cancel="uploadShow = false" :showConfirmButton="false" :showCancelButton='false'
@close="uploadShow = false" :closeOnClickOverlay="true">
<view class="modal_view">
<view class="modal_photo" style="display:flex">
<view class="da" @click="photoUpload(1)">拍照上传</view>
<view class="da" @click="photoUpload(2)">从相册导入</view>
</view>
</view>
</u-modal>
</view>
</template>
<script>
import { getFilePrivate } from '@/api/需引入自己的上传接口'
export default {
name: 'UploadImage',
props: {
// 接收已存在的图片ID字符串,用逗号分隔
value: {
type: String,
default: ''
},
// 最大上传数量
maxCount: {
type: Number,
default: 3
}
},
data () {
return {
fileList: [], // 用于页面展示图片 { url: base64 }
imgUrls: [], // 存储图片ID数组
uploadShow: false, // 控制弹窗显示
baseUrl: uni.getStorageSync('baseUrl')
}
},
watch: {
value: {
handler (newVal) {
if (newVal) {
// 防止重复请求:如果当前 imgUrls 已经和传入的一致,则不重新请求
const newIds = newVal.split(',')
if (JSON.stringify(this.imgUrls) === JSON.stringify(newIds)) return
this.imgUrls = newIds
this.initFileList()
} else {
this.fileList = []
this.imgUrls = []
}
},
immediate: true
}
},
methods: {
// 初始化图片列表(将ID转为base64展示)
async initFileList () {
this.fileList = await Promise.all(
this.imgUrls.map(async (id) => {
const url = await this.getImgUrl(id)
return { url }
})
)
},
// 获取图片Base64
async getImgUrl (id) {
try {
let res = await getFilePrivate(id)
return 'data:image/jpeg;base64,' + uni.arrayBufferToBase64(res)
} catch (error) {
console.error('获取图片失败:', error)
return ''
}
},
// 选择图片
photoUpload (type) {
uni.chooseImage({
count: this.maxCount - this.fileList.length,
sourceType: type == 1 ? ['camera'] : ['album'],
success: async (res) => {
this.uploadShow = false
for (let filePath of res.tempFilePaths) {
await this.uploadImage(filePath)
}
}
})
},
// 上传单张图片到服务器
uploadImage (filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.baseUrl + '/filePrivate',
filePath: filePath,
header: {
token: uni.getStorageSync('token')
},
name: 'file',
success: async (res) => {
let obj = JSON.parse(res.data)
if (obj.data && obj.data.bcode === 0) {
const imgId = obj.data.bdata
this.imgUrls.push(imgId)
const base64Url = await this.getImgUrl(imgId)
this.fileList.push({ url: base64Url })
// 通知父组件数据更新
this.$emit('input', this.imgUrls.join(","))
}
resolve()
},
fail: (err) => {
reject(err)
}
})
})
},
// 预览图片
previewImage (item) {
uni.previewImage({
urls: [item.url]
})
},
// 删除图片
deleteImage (index) {
this.fileList.splice(index, 1)
this.imgUrls.splice(index, 1)
this.$emit('input', this.imgUrls.join(","))
}
}
}
</script>
<style lang="scss" scoped>
.upload-box {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
.upload-item {
width: 200rpx;
height: 200rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
position: relative;
image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.delete {
position: absolute;
right: -10rpx;
top: -10rpx;
width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align: center;
background-color: #f56c6c;
color: #fff;
border-radius: 50%;
}
}
.upload-btn {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #dcdfe6;
border-radius: 8rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.plus {
font-size: 60rpx;
color: #909399;
margin-bottom: 10rpx;
}
text {
font-size: 24rpx;
color: #909399;
}
}
}
.tip {
font-size: 24rpx;
color: #909399;
margin-top: 10rpx;
}
.modal_view {
padding: 20rpx;
width: 560rpx;
.modal_photo {
display: flex;
justify-content: space-between;
height: 90rpx;
width: 520rpx;
margin: 40rpx auto;
&>view {
height: 88rpx;
width: 246rpx;
justify-content: center;
border-radius: 45rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
&>view:nth-child(1) {
background-color: #225AE7;
color: #fff;
}
&>view:nth-child(2) {
border: 1rpx solid #666;
color: #666;
}
}
}
</style>
export function getFilePrivate(id) {
return new Promise((resolve, reject) => {
uni.request({
url: `xxxxxxx/${id}`,
responseType: 'arraybuffer',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'token': `${user.state().token}`
},
success: res => {
resolve(res.data)
},
fail: err => {
console.log('request失败fail', err);
reject(err)
}
})
})
}