348 lines
8.0 KiB
Vue
348 lines
8.0 KiB
Vue
<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>
|
||
|
||
<!-- <!– 错误提示 –>-->
|
||
<!-- <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()
|
||
}
|
||
|
||
// 生成唯一文件名
|
||
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>
|