HomeLease/components/image-uploader/image-uploader.vue

348 lines
8.0 KiB
Vue
Raw Normal View History

2025-08-25 16:26:57 +08:00
<template>
<view class="image-uploader">
<!-- 上传区域 -->
<view class="upload-area" @click="chooseImage">
<view v-if="!imageUrl" class="upload-placeholder">
<text class="upload-icon">📷</text>
<!-- <text class="upload-text">{{ placeholder || '点击选择图片' }}</text>-->
<!-- <text class="upload-hint">{{ hint || '支持 JPG、PNG 格式' }}</text>-->
</view>
<image v-else :src="imageUrl" class="preview-image" mode="aspectFit"></image>
</view>
<!-- &lt;!&ndash; 错误提示 &ndash;&gt;-->
<!-- <view v-if="errorMessage" class="error-message">-->
<!-- <text class="error-text">{{ errorMessage }}</text>-->
<!-- </view>-->
2025-08-25 16:26:57 +08:00
</view>
</template>
<script>
import { getQiniuUploadToken, uploadToQiniu } from '@/api/upload.js'
export default {
name: 'ImageUploader',
props: {
// 占位符文本
placeholder: {
type: String,
default: '点击选择图片',
},
// 提示文本
hint: {
type: String,
default: '支持 JPG、PNG 格式',
},
// 是否显示删除按钮
showDelete: {
type: Boolean,
default: true,
},
// 上传区域高度
height: {
type: String,
default: '400rpx',
},
width: {
type: String,
default: '400rpx',
},
// 是否自动上传
autoUpload: {
type: Boolean,
default: true,
},
// 最大文件大小MB
maxSize: {
type: Number,
default: 10,
},
},
data() {
return {
selectedImagePath: '', // 选择的图片路径
imageUrl: '', // 预览图片URL
uploadResult: '', // 上传成功后的完整URL
uploading: false, // 上传状态
errorMessage: '', // 错误信息
qiniuToken: '', // 七牛云上传token
}
},
mounted() {
this.getQiniuToken()
},
methods: {
// 选择图片
chooseImage() {
if (this.uploading) return
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: res => {
const filePath = res.tempFilePaths[0]
// 检查文件大小
if (!this.checkFileSize(filePath)) {
return
}
this.selectedImagePath = filePath
this.imageUrl = filePath
this.uploadResult = ''
this.errorMessage = ''
// 自动上传
if (this.autoUpload) {
this.uploadImage()
}
// 触发选择事件
this.$emit('select', {
filePath,
size: res.tempFiles[0].size,
})
},
fail: err => {
console.error('选择图片失败:', err)
this.errorMessage = '选择图片失败'
this.$emit('error', err)
},
})
},
// 检查文件大小
checkFileSize(filePath) {
return new Promise(resolve => {
uni.getFileInfo({
filePath,
success: res => {
const sizeInMB = res.size / (1024 * 1024)
if (sizeInMB > this.maxSize) {
this.errorMessage = `文件大小不能超过 ${this.maxSize}MB`
uni.showToast({
title: `文件大小不能超过 ${this.maxSize}MB`,
icon: 'none',
})
resolve(false)
} else {
resolve(true)
}
},
fail: () => {
resolve(true) // 如果获取文件信息失败,默认允许上传
},
})
})
},
// 上传图片
async uploadImage() {
if (!this.selectedImagePath) {
this.errorMessage = '请先选择图片'
return
}
this.uploading = true
this.errorMessage = ''
try {
// 确保有token
if (!this.qiniuToken) {
await this.getQiniuToken()
}
// 生成唯一文件名
const key = `uploads/${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`
// 上传到七牛云
const result = await uploadToQiniu(this.selectedImagePath, this.qiniuToken, key)
// 构建完整的图片URL
this.uploadResult = `https://api.ccttiot.com/${result.key}`
// 触发上传成功事件
this.$emit('success', {
url: this.uploadResult,
key: result.key,
originalPath: this.selectedImagePath,
})
// 显示成功提示
uni.showToast({
title: '上传成功',
icon: 'success',
})
} catch (error) {
console.error('上传失败:', error)
this.errorMessage = error.message || '上传失败'
this.$emit('error', error)
} finally {
this.uploading = false
}
},
// 删除图片
deleteImage() {
uni.showModal({
title: '确认删除',
content: '确定要删除这张图片吗?',
success: res => {
if (res.confirm) {
this.selectedImagePath = ''
this.imageUrl = ''
this.uploadResult = ''
this.errorMessage = ''
this.$emit('delete')
}
},
})
},
// 获取七牛云token
async getQiniuToken() {
try {
const res = await getQiniuUploadToken()
console.log('Token响应:', res)
if (res.code === 200) {
// 尝试不同的可能字段名
const token = res.data
if (token) {
this.qiniuToken = token
console.log('Token获取成功:', token.substring(0, 20) + '...')
} else {
throw new Error('Token字段不存在')
}
} else {
throw new Error(res.msg || '获取Token失败')
}
} catch (error) {
console.error('获取Token失败:', error)
this.errorMessage = `获取上传凭证失败: ${error.message}`
this.$emit('error', error)
}
},
// 手动上传方法(供外部调用)
manualUpload() {
if (!this.autoUpload) {
this.uploadImage()
}
},
// 获取上传结果
getUploadResult() {
return {
url: this.uploadResult,
localPath: this.selectedImagePath,
isUploaded: !!this.uploadResult,
}
},
// 重置组件
reset() {
this.selectedImagePath = ''
this.imageUrl = ''
this.uploadResult = ''
this.uploading = false
this.errorMessage = ''
},
},
}
</script>
<style lang="scss" scoped>
.image-uploader {
.upload-area {
width: v-bind(width);
height: v-bind(height);
background: #fff;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx dashed #ddd;
position: relative;
overflow: hidden;
.upload-placeholder {
text-align: center;
.upload-icon {
display: block;
font-size: 60rpx;
margin-bottom: 20rpx;
}
.upload-text {
display: block;
font-size: 32rpx;
color: #333;
margin-bottom: 10rpx;
}
.upload-hint {
display: block;
font-size: 24rpx;
color: #999;
}
}
.preview-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.delete-btn {
position: absolute;
top: 16rpx;
right: 16rpx;
width: 60rpx;
height: 60rpx;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.delete-icon {
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
}
}
.upload-status {
margin-top: 16rpx;
text-align: center;
.status-text {
font-size: 26rpx;
color: #666;
}
}
.error-message {
margin-top: 16rpx;
padding: 16rpx;
background: #fff2f0;
border: 1rpx solid #ffccc7;
border-radius: 8rpx;
.error-text {
font-size: 26rpx;
color: #ff4d4f;
line-height: 1.4;
}
}
}
</style>