daShangDao_miniProgram/components/CameraUpload.vue
2025-11-24 10:25:20 +08:00

681 lines
18 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="update">
<view class="upload-container">
<view class="image-list">
<!-- 已拍摄图片预览 - 不显示hidden的图片 -->
<view v-for="(file, index) in visibleFileList" :key="index" class="image-item" @click="previewImage(index)">
<image :src="file.url" class="preview-img" mode="aspectFill" />
<view class="delete-btn" @click.stop="handleDelete(index)">×</view>
<!-- 添加上传状态指示器 -->
<view v-if="file.status === 'uploading'" class="upload-status uploading">上传中...</view>
<view v-if="file.status === 'error'" class="upload-status error">上传失败</view>
</view>
<!-- 拍照图标,未达上限时显示 -->
<view v-if="fileList.length < maxCount" class="camera-icon" @click="handleOpenCamera"
hover-class="none">
<image src="/static/camera.png" class="icon-img" />
</view>
</view>
</view>
<!-- bcode-camera组件 -->
<bcode-camera v-if="showCamera" :tipsText="tipsText" :currentCount="visibleFileList.length" :fileList="visibleFileList"
@onConfirm="handleCameraConfirm" @onCancel="handleCameraCancel" @onUpload="onUpload"
@onDeleteImage="handleDeleteImage"></bcode-camera>
</view>
</template>
<script>
// 导入会员书籍数量检查工具
import { checkMemberBooksCount } from '@/components/MemberBookCheck.js';
export default {
name: 'CameraUpload',
props: {
// 初始文件列表
value: {
type: Array,
default: () => []
},
// 最大上传数量
maxCount: {
type: Number,
default: 9
},
uploadUrl: {
type: String,
required: true, // 必须传递有效 URL如 https://xxx.com
default: '' // 可选默认值
},
// 新增的ISBN和书名props
isbn: {
type: String,
default: ''
},
bookName: {
type: String,
default: ''
}
},
data() {
return {
showCamera: false,
fileList: this.value,
uploadQueue: [], // 上传队列
isUploading: false, // 是否正在上传
processedUrls: new Set() // 新增用于记录已处理的图片URL
};
},
computed: {
tipsText() {
return `已拍摄 ${this.fileList.length} 张,最多可拍 ${this.maxCount}`;
},
visibleFileList() {
return this.fileList.filter(file => !file.hidden);
}
},
watch: {
value(newVal) {
this.fileList = newVal;
},
// 新增:监听上传队列变化
uploadQueue: {
handler(newQueue) {
if (newQueue.length > 0 && !this.isUploading) {
this.processUploadQueue();
}
},
deep: true
},
// 监听文件列表变化,检查上传状态
fileList: {
handler(newFileList) {
// 检查是否有文件正在上传中
const isUploading = newFileList.some(file => file.status === 'uploading');
// 通知父组件上传状态
this.$emit('upload-status-change', isUploading);
},
deep: true
}
},
methods: {
// 新增:检查文件是否已处理
isProcessed(url) {
return this.processedUrls.has(url);
},
// 新增:标记文件为已处理
markAsProcessed(url) {
this.processedUrls.add(url);
},
// 新增:检查是否有文件正在上传中
checkUploadingStatus() {
const isUploading = this.fileList.some(file => file.status === 'uploading');
// 通知父组件上传状态
this.$emit('upload-status-change', isUploading);
return isUploading;
},
// 修改:处理新图片
async processNewImage(imageUrl) {
if (this.isProcessed(imageUrl)) {
console.log('图片已处理过,跳过:', imageUrl);
return;
}
// 获取已使用的编号包括2因为它预留给识图照片
const usedNums = new Set(["2"]);
// 收集当前文件列表中已使用的编号,并确保不会重复使用已上传文件的编号
this.fileList.forEach(file => {
if (file.num) {
usedNums.add(file.num);
}
});
// 找到下一个可用的编号从1开始
let nextNum = 1;
while (usedNums.has(nextNum.toString())) {
nextNum++;
}
// 添加到文件列表
const newFile = {
url: imageUrl,
status: "ready",
message: "待上传",
num: nextNum.toString()
};
// 在添加新文件之前,检查是否存在相同编号的文件
const existingFileIndex = this.fileList.findIndex(f => f.num === nextNum.toString());
if (existingFileIndex !== -1) {
// 如果存在相同编号的文件,为新文件找一个新的编号
nextNum = Math.max(...Array.from(this.fileList.map(f => parseInt(f.num) || 0))) + 1;
newFile.num = nextNum.toString();
}
this.fileList.push(newFile);
// 重新排序文件列表,但保持识图照片的位置
this.fileList.sort((a, b) => {
// 如果其中一个是识图照片,保持其位置
if (a.name?.startsWith('识图-')) return 1;
if (b.name?.startsWith('识图-')) return -1;
const numA = parseInt(a.num) || 999;
const numB = parseInt(b.num) || 999;
return numA - numB;
});
// 添加到上传队列
await this.addToUploadQueue(newFile, this.fileList.indexOf(newFile));
// 如果这是第一张普通照片num="1"),检查并上传识图照片
if (newFile.num === "1") {
const ocrPhoto = this.fileList.find(file =>
file.name?.startsWith('识图-') &&
file.status === 'ready' &&
file.url.startsWith('wxfile://tmp') || file.url.startsWith('http://tmp') // 检查是否为临时路径
);
if (ocrPhoto) {
console.log('发现待上传的识图照片,准备上传:', ocrPhoto);
// 更新识图照片的hidden属性为false
const ocrIndex = this.fileList.indexOf(ocrPhoto);
const updatedOcrPhoto = {
...ocrPhoto,
hidden: false,
num: "2" // 确保编号为2
};
this.fileList.splice(ocrIndex, 1, updatedOcrPhoto);
// 立即上传识图照片
await this.addToUploadQueue(updatedOcrPhoto, ocrIndex);
// 通知父组件更新
this.$emit('input', this.fileList);
console.log('已将识图照片设置为第二张并添加到上传队列');
}
}
// 标记为已处理
this.markAsProcessed(imageUrl);
// 通知父组件更新
this.$emit('input', this.fileList);
console.log(`新照片已添加,编号为: ${nextNum}`);
console.log('当前文件列表:', this.fileList.map(f => ({
url: f.url,
num: f.num,
name: f.name,
hidden: f.hidden,
status: f.status
})));
},
// 修改onUpload 处理程序
async onUpload(data) {
if (!data || !data.url) {
console.error('无效的图片URL');
return;
}
console.log('接收到新拍摄的照片:', data);
await this.processNewImage(data.url);
},
// 修改handleCameraConfirm 处理程序
async handleCameraConfirm(data) {
console.log('相机确认完成,接收到数据:', data);
// 关闭相机
this.showCamera = false;
this.$emit('camera-status-change', false);
// 检查是否有图片数据
if (!data || (!data.images && !data.url)) {
console.log('没有新的图片需要处理');
return;
}
try {
// 处理图片数组
if (data.images && data.images.length > 0) {
console.log(`处理${data.images.length}张新图片`);
for (const imageUrl of data.images) {
await this.processNewImage(imageUrl);
}
}
// 处理单张图片
else if (data.url) {
await this.processNewImage(data.url);
// 检查是否有识图照片需要上传
const ocrPhoto = this.fileList.find(file => file.name?.startsWith('识图-') && file.status === 'ready');
if (ocrPhoto) {
console.log('发现待上传的识图照片,准备上传');
// 更新识图照片的hidden属性为false使其显示出来
const ocrIndex = this.fileList.indexOf(ocrPhoto);
this.fileList.splice(ocrIndex, 1, {
...ocrPhoto,
hidden: false,
num: "2" // 设置为第二张照片
});
// 将识图照片添加到上传队列
this.addToUploadQueue(ocrPhoto, ocrIndex);
// 通知父组件更新
this.$emit('input', this.fileList);
}
}
} catch (error) {
console.error('处理照片失败:', error);
uni.showToast({
title: '处理照片失败',
icon: 'none',
duration: 1500
});
}
},
// 修改handleCameraCancel 处理程序(处理识图照片)
async handleCameraCancel(data) {
if (!data || !data.url) {
this.showCamera = false;
this.$emit('camera-status-change', false);
return;
}
try {
console.log('收到识图照片数据:', data);
// 添加识图照片hidden为true等待第一张照片上传后再显示
const file = {
url: data.url,
status: "ready",
message: "待上传",
name: `识图-${Date.now()}.jpg`,
num: "2", // 预设为第二张照片
hidden: true // 初始设置为隐藏
};
if (!this.isProcessed(data.url)) {
// 如果已经有识图照片,替换它
const existingOcrIndex = this.fileList.findIndex(f => f.name?.startsWith('识图-'));
if (existingOcrIndex !== -1) {
this.fileList.splice(existingOcrIndex, 1, file);
} else {
this.fileList.push(file);
}
// 标记为已处理
this.markAsProcessed(data.url);
// 通知父组件更新
this.$emit('input', this.fileList);
console.log('识图照片已保存,等待第一张照片上传后再显示');
}
} catch (error) {
console.error('处理识图照片失败:', error);
uni.showToast({
title: '处理照片失败',
icon: 'none',
duration: 1500
});
} finally {
this.showCamera = false;
this.$emit('camera-status-change', false);
}
},
// 新增:单个文件上传方法
async uploadSingleFile(file, index) {
const parentPage = this.getParentPage();
if (!parentPage || typeof parentPage.uploadFilePromise !== 'function') {
throw new Error('上传功能不可用');
}
try {
// 更新状态为上传中
this.updateFileStatus(index, 'uploading', '上传中');
// 上传文件
const result = await parentPage.uploadFilePromise(file.url, index);
// 更新状态为成功
this.updateFileStatus(index, 'success', '', result);
console.log(`图片 ${index + 1} 上传成功:`, result);
} catch (error) {
console.error(`图片 ${index + 1} 上传失败:`, error);
this.updateFileStatus(index, 'error', '上传失败');
throw error;
}
},
// 新增:更新文件状态的辅助方法
updateFileStatus(index, status, message, newUrl = null) {
const file = this.fileList[index];
if (file) {
const updatedFile = {
...file,
status: status,
message: message
};
if (newUrl) {
updatedFile.url = newUrl;
// 保留原有的num和name属性
updatedFile.num = file.num;
updatedFile.name = file.name;
}
this.fileList.splice(index, 1, updatedFile);
this.$emit('input', this.fileList);
}
},
// 新增:检查文件是否已存在
isFileExists(url) {
return this.fileList.some(file => file.url === url);
},
// 修改:添加到上传队列的方法
async addToUploadQueue(file, index) {
// 检查是否已在队列中
const isInQueue = this.uploadQueue.some(item => item.file.url === file.url);
if (!isInQueue) {
console.log('添加新文件到上传队列:', file.url);
// 获取父页面实例
const parentPage = this.getParentPage();
if (!parentPage || typeof parentPage.uploadFilePromise !== 'function') {
console.error('上传功能不可用');
return;
}
try {
// 更新状态为上传中
const fileIndex = this.fileList.indexOf(file);
if (fileIndex !== -1) {
this.fileList.splice(fileIndex, 1, {
...file,
status: 'uploading',
message: '上传中'
});
this.$emit('input', this.fileList);
// 检查上传状态并通知父组件
this.checkUploadingStatus();
}
// 上传文件
const result = await parentPage.uploadFilePromise(file.url, index);
// 更新状态为成功
if (fileIndex !== -1) {
this.fileList.splice(fileIndex, 1, {
...file,
status: 'success',
message: '上传成功',
url: result, // 使用服务器返回的URL
num: file.num, // 保留原有编号
name: file.name // 保留原有名称
});
this.$emit('input', this.fileList);
// 检查上传状态并通知父组件
this.checkUploadingStatus();
// 如果是识图照片确保保留name属性
if (file.name && file.name.startsWith('识图-')) {
console.log(`识图照片上传成功保留name属性: ${file.name}`);
}
}
console.log(`文件上传成功: ${file.url} -> ${result}`);
} catch (error) {
console.error('文件上传失败:', error);
// 更新状态为失败
const fileIndex = this.fileList.indexOf(file);
if (fileIndex !== -1) {
this.fileList.splice(fileIndex, 1, {
...file,
status: 'error',
message: '上传失败'
});
this.$emit('input', this.fileList);
// 检查上传状态并通知父组件
this.checkUploadingStatus();
}
}
} else {
console.log('文件已在上传队列中,跳过:', file.url);
}
},
previewImage(index) {
// 获取可见文件的URL列表
const visibleUrls = this.visibleFileList.map(item => item.url);
uni.previewImage({
current: this.visibleFileList[index].url,
urls: visibleUrls,
indicator: 'number', // 显示数字指示器
loop: true
});
},
async handleOpenCamera() {
// 检查用户是否可以上传书籍
const canUpload = await checkMemberBooksCount();
if (!canUpload) {
// 如果不能上传,则直接返回,不继续执行
return;
}
// 检查ISBN和书名是否存在
if (!this.isbn || !this.bookName) {
uni.showToast({
title: '请先填写ISBN和书名',
icon: 'none'
});
return;
}
if (this.fileList.length >= this.maxCount) {
uni.showToast({
title: `最多只能上传 ${this.maxCount} 张照片`,
icon: 'none'
});
return;
}
this.showCamera = true;
this.$emit('camera-status-change', true);
},
// 获取父组件实例的方法
getParentPage() {
let parent = this.$parent;
while (parent) {
if (parent.uploadFilePromise) {
return parent;
}
parent = parent.$parent;
}
return null;
},
handleDelete(index) {
// 获取要删除的可见文件
const fileToDelete = this.visibleFileList[index];
// 在完整fileList中找到对应索引并删除
const actualIndex = this.fileList.findIndex(file => file === fileToDelete);
if (actualIndex !== -1) {
this.fileList.splice(actualIndex, 1);
this.$emit('input', this.fileList);
}
},
handleDeleteImage(event) {
const index = event.index;
// 获取可见文件列表中的文件
if (index >= 0 && index < this.visibleFileList.length) {
const fileToDelete = this.visibleFileList[index];
// 在完整fileList中找到对应索引并删除
const actualIndex = this.fileList.findIndex(file => file === fileToDelete);
if (actualIndex !== -1) {
this.fileList.splice(actualIndex, 1);
this.$emit('input', this.fileList);
uni.showToast({
title: '已删除图片',
icon: 'none',
duration: 1000
});
}
}
},
// 添加一个新方法,专门用于处理文件变化
handleFileChange(newFileList) {
console.log('CameraUpload组件收到文件变化:', newFileList);
// 检查是否有识图照片
const ocrPhoto = newFileList.find(file => file.name && file.name.startsWith('识图-'));
if (ocrPhoto) {
console.log('发现识图照片:', ocrPhoto);
// 确保识图照片的num为2
const updatedOcrPhoto = {
...ocrPhoto,
num: "2"
};
// 找出识图照片在列表中的位置
const ocrIndex = newFileList.findIndex(file => file.name && file.name.startsWith('识图-'));
// 更新列表中的识图照片
if (ocrIndex !== -1) {
newFileList.splice(ocrIndex, 1, updatedOcrPhoto);
}
}
// 更新文件列表
this.fileList = [...newFileList];
// 触发输入事件
this.$emit('input', this.fileList);
}
}
};
</script>
<style scoped>
.update {
margin-top: 2rpx;
background-color: #3f51b530;
border-radius: 12rpx;
padding: 20rpx;
}
.image-list {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16rpx;
}
.image-item {
position: relative;
width: 150rpx;
height: 150rpx;
border-radius: 8rpx;
overflow: hidden;
}
.preview-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border-radius: 50%;
width: 50rpx;
height: 50rpx;
text-align: center;
line-height: 50rpx;
font-size: 50rpx;
z-index: 2;
}
.camera-icon {
width: 150rpx;
height: 150rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8rpx;
cursor: pointer;
outline: none !important;
-webkit-tap-highlight-color: transparent;
}
.icon-img {
width: 60rpx;
height: 60rpx;
outline: none !important;
-webkit-tap-highlight-color: transparent;
}
/* 新增:上传状态样式 */
.upload-status {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 4rpx;
font-size: 20rpx;
color: #fff;
text-align: center;
z-index: 2;
}
.uploading {
background-color: rgba(0, 122, 255, 0.7);
}
.error {
background-color: rgba(255, 59, 48, 0.7);
}
/* 添加提示文字样式 */
.photo-tip {
position: fixed;
top: 20rpx;
left: 0;
right: 0;
z-index: 10000;
text-align: center;
padding: 10rpx;
}
.photo-tip text {
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 10rpx 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
}
</style>