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>
|
|
|
|
|
|
|
2025-08-25 16:53:48 +08:00
|
|
|
|
<!-- <!– 错误提示 –>-->
|
|
|
|
|
|
<!-- <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>
|