HomeLease/components/image-uploader/image-uploader.vue
2025-09-10 15:00:34 +08:00

353 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>-->
</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()
uni.showToast({
title: '上传失败',
icon: 'error',
})
return
}
// 生成唯一文件名
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('七牛云oken字段不存在')
}
} 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>