图片上传优化版
This commit is contained in:
parent
5fd9b217ef
commit
df1709deab
347
components/image-uploader/image-uploader.vue
Normal file
347
components/image-uploader/image-uploader.vue
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
<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>
|
||||
|
|
@ -1,186 +1,38 @@
|
|||
<template>
|
||||
<view class="image-upload-page">
|
||||
<view class="content">
|
||||
<!-- 上传区域 -->
|
||||
<view class="upload-area" @click="chooseImage">
|
||||
<view v-if="!imageUrl" class="upload-placeholder">
|
||||
<u-icon color="#ccc" name="camera" size="60"></u-icon>
|
||||
<text class="upload-text">点击选择图片</text>
|
||||
<text class="upload-hint">支持 JPG、PNG 格式</text>
|
||||
</view>
|
||||
<image v-else :src="imageUrl" class="preview-image" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<button
|
||||
:disabled="!selectedImagePath || uploading"
|
||||
:loading="uploading"
|
||||
class="upload-btn"
|
||||
@click="uploadImage"
|
||||
>
|
||||
{{ uploading ? '上传中...' : '确认上传' }}
|
||||
</button>
|
||||
|
||||
<!-- 结果展示 -->
|
||||
<view v-if="uploadResult" class="result-section">
|
||||
<view class="result-item">
|
||||
<text class="result-label">上传状态:</text>
|
||||
<text class="result-value success">成功</text>
|
||||
</view>
|
||||
<view class="result-item">
|
||||
<text class="result-label">图片URL:</text>
|
||||
<text class="result-url" @click="copyUrl">{{ uploadResult }}</text>
|
||||
</view>
|
||||
<button class="copy-btn" @click="copyUrl">复制URL</button>
|
||||
</view>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<view v-if="errorMessage" class="error-section">
|
||||
<text class="error-text">{{ errorMessage }}</text>
|
||||
</view>
|
||||
<!-- 使用图片上传组件 -->
|
||||
<image-uploader
|
||||
ref="uploader"
|
||||
:height="'150rpx'"
|
||||
:width="'150rpx'"
|
||||
@error="handleUploadError"
|
||||
@success="handleUploadSuccess"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getQiniuUploadToken, uploadToQiniu } from '@/api/upload.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedImagePath: '', // 选择的图片路径
|
||||
imageUrl: '', // 预览图片URL
|
||||
uploadResult: '', // 上传成功后的完整URL
|
||||
uploading: false, // 上传状态
|
||||
errorMessage: '', // 错误信息
|
||||
qiniuToken: '', // 七牛云上传token
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.getQiniuToken()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: res => {
|
||||
this.selectedImagePath = res.tempFilePaths[0]
|
||||
this.imageUrl = res.tempFilePaths[0]
|
||||
this.uploadResult = ''
|
||||
this.errorMessage = ''
|
||||
},
|
||||
fail: err => {
|
||||
console.error('选择图片失败:', err)
|
||||
uni.showToast({
|
||||
title: '选择图片失败',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
// 上传成功回调
|
||||
handleUploadSuccess(result) {
|
||||
console.log('图片上传成功:', result.url)
|
||||
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success',
|
||||
})
|
||||
},
|
||||
|
||||
// 上传图片
|
||||
async uploadImage() {
|
||||
if (!this.selectedImagePath) {
|
||||
uni.showToast({
|
||||
title: '请先选择图片',
|
||||
icon: 'none',
|
||||
})
|
||||
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}`
|
||||
|
||||
// 先触发事件,再返回上一页
|
||||
console.log('触发图片上传成功事件:', this.uploadResult)
|
||||
uni.$emit('image-upload-success', this.uploadResult)
|
||||
|
||||
// 显示成功提示
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success',
|
||||
})
|
||||
|
||||
// 延迟返回,确保事件被处理
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
this.errorMessage = error.message || '上传失败'
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none',
|
||||
})
|
||||
} finally {
|
||||
this.uploading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取七牛云token
|
||||
async getQiniuToken() {
|
||||
try {
|
||||
const res = await getQiniuUploadToken()
|
||||
console.log('Token响应:', res)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 尝试不同的可能字段名
|
||||
const token = res.data?.token || res.data?.uploadToken || res.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}`
|
||||
uni.showToast({
|
||||
title: '获取上传凭证失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 复制URL
|
||||
copyUrl() {
|
||||
if (this.uploadResult) {
|
||||
uni.setClipboardData({
|
||||
data: this.uploadResult,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: 'URL已复制',
|
||||
icon: 'success',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
// 上传失败回调
|
||||
handleUploadError(error) {
|
||||
console.error('图片上传失败:', error)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -194,127 +46,5 @@ export default {
|
|||
.content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30rpx;
|
||||
border: 2rpx dashed #ddd;
|
||||
|
||||
.upload-placeholder {
|
||||
text-align: center;
|
||||
|
||||
.upload-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #f15a04;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:active {
|
||||
background: #ff5e00;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #ccc;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.result-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-right: 20rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 28rpx;
|
||||
|
||||
&.success {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.result-url {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-section {
|
||||
background: #fff2f0;
|
||||
border: 1rpx solid #ffccc7;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
|
||||
.error-text {
|
||||
font-size: 26rpx;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user