|
@@ -0,0 +1,569 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="page_dialog">
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ :title="schoolName + '导入扫描图片'"
|
|
|
|
|
+ :model-value="showDialog"
|
|
|
|
|
+ width="940px"
|
|
|
|
|
+ @close="HandleClose"
|
|
|
|
|
+ v-draggable
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="dialog_center">
|
|
|
|
|
+ <div class="center_header">
|
|
|
|
|
+ <div class="header_left">
|
|
|
|
|
+ <!-- 隐藏的文件输入框,用于选择文件夹 -->
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="file"
|
|
|
|
|
+ ref="fileInput"
|
|
|
|
|
+ style="display: none"
|
|
|
|
|
+ webkitdirectory
|
|
|
|
|
+ directory
|
|
|
|
|
+ multiple
|
|
|
|
|
+ @change="HandleFolderSelect" />
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="SelectFolder">
|
|
|
|
|
+ 选择文件夹
|
|
|
|
|
+ <i class='el-icon-folder el-icon--right'></i>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <div class="left_item">
|
|
|
|
|
+ <span class="remain_count">(剩余{{ remainCount }}个待上传)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="left_item" style="display: none;">
|
|
|
|
|
+ <span style="width: 80px;">灵敏度:</span>
|
|
|
|
|
+ <el-input v-model="sensitive" placeholder="0-255之间得奇数"></el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="left_item" style="display: none;">
|
|
|
|
|
+ 算法类型:
|
|
|
|
|
+ <el-select style="margin-right: 6px;" v-model="onnx" placeholder="算法类型" >
|
|
|
|
|
+ <el-option label="正常算法" :value="0"></el-option>
|
|
|
|
|
+ <el-option label="废资源算法" :value="1"></el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="header_right">
|
|
|
|
|
+ <div class="upload_info" v-if="selectedFiles.length > 0">
|
|
|
|
|
+ <p>已选择文件:{{ selectedFiles.length }}个</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图片预览列表区域 -->
|
|
|
|
|
+ <div class="center_pic_list" v-if="selectedFiles.length > 0" style="height: 400px !important; max-height: 400px !important;">
|
|
|
|
|
+ <div class="list_img_content">
|
|
|
|
|
+ <div class="list_img_item" v-for="(file, index) in selectedFiles" :key="index" v-show="file && file.uploadStatus !== undefined && file.uploadStatus !== 0">
|
|
|
|
|
+ <div class="item_batch_name">{{ FormatFileName(file.name) }} {{ file.uploadStatus }}</div>
|
|
|
|
|
+ <div class="item_batch_img">
|
|
|
|
|
+ <img :src="file.url" :alt="file.name">
|
|
|
|
|
+ <!-- 查看按钮,仅在非上传中状态显示 -->
|
|
|
|
|
+ <div class="button_group" v-if="file.uploadStatus != -1">
|
|
|
|
|
+ <div class="pic_button_view" @click="OpenViewPaper(file.url)">
|
|
|
|
|
+ <i class="iconfont icon_xianshimima"></i>查看
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 上传进度条 -->
|
|
|
|
|
+ <div class="item_batch_progress" v-if="file.uploadStatus == -1">
|
|
|
|
|
+ <div>上传中...</div>
|
|
|
|
|
+ <el-progress :percentage="file.uploadProgress"></el-progress>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 上传失败提示 -->
|
|
|
|
|
+ <div class="item_batch_failed" v-if="file.uploadStatus == 1">
|
|
|
|
|
+ <div style="color:#F56C6C;">上传失败</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 上传成功提示 -->
|
|
|
|
|
+ <div class="item_batch_success" v-if="file.uploadStatus == 0">
|
|
|
|
|
+ <div style="color:green;">上传成功</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="CancelUpload">取消</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ style="width: 96px;"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ @click="DebounceUpload()"
|
|
|
|
|
+ :disabled="selectedFiles.length === 0">
|
|
|
|
|
+ 确定
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ <!-- 图片预览弹窗组件 -->
|
|
|
|
|
+ <!-- <PaperPreview v-model="showPaperPreview" :imageList="previewImageList"></PaperPreview> -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, computed, watch, onMounted } from 'vue';
|
|
|
|
|
+
|
|
|
|
|
+import axios from 'axios';
|
|
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
|
|
+// import PaperPreview from '@/views/examDetailsEdit/components/ImagePreview.vue';
|
|
|
|
|
+import { useExamStore } from '@/store/exam'
|
|
|
|
|
+import {getCurrentBatchNo,} from '@/api/exam'
|
|
|
|
|
+// 实例化 Store
|
|
|
|
|
+const examStore = useExamStore()
|
|
|
|
|
+
|
|
|
|
|
+// 定义组件事件
|
|
|
|
|
+const emit = defineEmits(['update:modelValue', 'UploadComplete', 'UploadProgress', 'close', 'CancelUpload']);
|
|
|
|
|
+
|
|
|
|
|
+// 定义组件属性
|
|
|
|
|
+const props = defineProps({
|
|
|
|
|
+ modelValue: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ paperSchema: {
|
|
|
|
|
+ type: [String, Number],
|
|
|
|
|
+ default: '1' // 单双面 1单面 2双面
|
|
|
|
|
+ },//选择题判分都是单面
|
|
|
|
|
+ isfilter: {
|
|
|
|
|
+ type: [String, Number],
|
|
|
|
|
+ default: 1 // 是否过滤 1是 0否
|
|
|
|
|
+ },
|
|
|
|
|
+ // heightRatio: {
|
|
|
|
|
+ // type: [String, Number],
|
|
|
|
|
+ // default: 0.03 // 占打分框高度比例
|
|
|
|
|
+ // },
|
|
|
|
|
+ // schoolInfo: {
|
|
|
|
|
+ // type: Object,
|
|
|
|
|
+ // default: () => ({
|
|
|
|
|
+ // schoolId: '',
|
|
|
|
|
+ // schoolName: ''
|
|
|
|
|
+ // })
|
|
|
|
|
+ // },
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// --- 响应式状态定义 ---
|
|
|
|
|
+const loading = ref(false); // 加载状态
|
|
|
|
|
+const selectedFiles = ref([]); // 已选择的文件列表
|
|
|
|
|
+const showPaperPreview = ref(false); // 是否显示预览弹窗
|
|
|
|
|
+const previewImageList = ref([]); // 预览图片列表
|
|
|
|
|
+const batchNumber = ref(''); // 批次号
|
|
|
|
|
+const batchNOID = ref(''); // 批次ID
|
|
|
|
|
+const sensitive = ref('165'); // 灵敏度参数
|
|
|
|
|
+const onnx = ref(0); // 算法类型 0正常 1废资源
|
|
|
|
|
+const isUploading = ref(false); // 是否正在上传中
|
|
|
|
|
+const debounceTimer = ref(null); // 防抖定时器
|
|
|
|
|
+const fileInput = ref(null); // 文件输入框引用
|
|
|
|
|
+
|
|
|
|
|
+// 学校相关信息
|
|
|
|
|
+const schoolName = ref('');
|
|
|
|
|
+// 考试科目 ID
|
|
|
|
|
+const schoolId = computed(() => {
|
|
|
|
|
+ return examStore.currentExam?.schoolInfoId
|
|
|
|
|
+})//计算属性
|
|
|
|
|
+
|
|
|
|
|
+// --- 计算属性 ---
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 控制弹窗显示的双向绑定
|
|
|
|
|
+ */
|
|
|
|
|
+const showDialog = computed({
|
|
|
|
|
+ get: () => props.modelValue,
|
|
|
|
|
+ set: (val) => emit('update:modelValue', val)
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取当前考试科目ID
|
|
|
|
|
+ */
|
|
|
|
|
+// 考试科目 ID
|
|
|
|
|
+const examSubjectId = computed(() => {
|
|
|
|
|
+ return examStore.currentExam?.id
|
|
|
|
|
+})//计算属性
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 计算剩余待上传的文件数量
|
|
|
|
|
+ */
|
|
|
|
|
+const remainCount = computed(() => {
|
|
|
|
|
+ return selectedFiles.value.filter(file => file.uploadStatus !== 0).length;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// --- 监听器 ---
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 监听学校信息变化,更新本地学校名称和ID
|
|
|
|
|
+ */
|
|
|
|
|
+// watch(() => props.schoolInfo, (newVal) => {
|
|
|
|
|
+// if (newVal) {
|
|
|
|
|
+// schoolId.value = newVal.schoolId || '';
|
|
|
|
|
+// schoolName.value = (newVal.schoolName || '') ;
|
|
|
|
|
+// }
|
|
|
|
|
+// }, { immediate: true });
|
|
|
|
|
+
|
|
|
|
|
+// --- 生命周期 ---
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ console.log("UploadImage组件挂载,当前学校信息:", props.schoolInfo);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// --- 方法定义 ---
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 格式化文件名,只显示前5位和后12位
|
|
|
|
|
+ * @param {string} fileName - 原始文件名
|
|
|
|
|
+ * @returns {string} 格式化后的文件名
|
|
|
|
|
+ */
|
|
|
|
|
+const FormatFileName = (fileName) => {
|
|
|
|
|
+ if (fileName.length <= 17) {
|
|
|
|
|
+ return fileName;
|
|
|
|
|
+ }
|
|
|
|
|
+ const prefix = fileName.substring(0, 5);
|
|
|
|
|
+ const suffix = fileName.substring(fileName.length - 12);
|
|
|
|
|
+ return prefix + '...' + suffix;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 触发文件选择框点击事件
|
|
|
|
|
+ */
|
|
|
|
|
+const SelectFolder = () => {
|
|
|
|
|
+ const input = fileInput.value;
|
|
|
|
|
+ if (input) {
|
|
|
|
|
+ input.setAttribute('webkitdirectory', 'true');
|
|
|
|
|
+ input.setAttribute('directory', 'true');
|
|
|
|
|
+ input.click();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 处理文件夹选择后的文件流
|
|
|
|
|
+ * @param {Event} event - 输入框变更事件
|
|
|
|
|
+ */
|
|
|
|
|
+const HandleFolderSelect = (event) => {
|
|
|
|
|
+ const files = Array.from(event.target.files);
|
|
|
|
|
+ ProcessSelectedFiles(files);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 处理并过滤选中的文件,只保留图片并添加到列表
|
|
|
|
|
+ * @param {Array} files - 原始文件数组
|
|
|
|
|
+ */
|
|
|
|
|
+const ProcessSelectedFiles = (files) => {
|
|
|
|
|
+
|
|
|
|
|
+ //过滤出图片文件
|
|
|
|
|
+ const imageFiles = files.filter(file => IsImageFile(file.name));
|
|
|
|
|
+
|
|
|
|
|
+ if (imageFiles.length === 0) {
|
|
|
|
|
+ ElMessage.warning('未找到图片文件');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ imageFiles.forEach(file => {
|
|
|
|
|
+ // 去重检查
|
|
|
|
|
+ const exists = selectedFiles.value.some(f => f.name === file.name);
|
|
|
|
|
+ if (!exists) {
|
|
|
|
|
+ selectedFiles.value.push({
|
|
|
|
|
+ name: file.name,
|
|
|
|
|
+ url: GetFilePreviewUrl(file),
|
|
|
|
|
+ file: file,
|
|
|
|
|
+ uploadStatus: -2, // -2: 未上传, -1: 上传中, 0: 成功, 1: 失败
|
|
|
|
|
+ uploadProgress: 0,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ ElMessage.success(`成功选择 ${imageFiles.length} 张图片`);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 判断文件是否为图片格式
|
|
|
|
|
+ * @param {string} fileName - 文件名
|
|
|
|
|
+ * @returns {boolean} 是否为图片
|
|
|
|
|
+ */
|
|
|
|
|
+const IsImageFile = (fileName) => {
|
|
|
|
|
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
|
|
|
|
|
+ const lowerFileName = fileName.toLowerCase();
|
|
|
|
|
+ return imageExtensions.some(ext => lowerFileName.endsWith(ext));
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取文件的本地预览URL
|
|
|
|
|
+ * @param {File} file - 文件对象
|
|
|
|
|
+ * @returns {string} Blob URL
|
|
|
|
|
+ */
|
|
|
|
|
+const GetFilePreviewUrl = (file) => {
|
|
|
|
|
+ if (window.URL && file) {
|
|
|
|
|
+ return window.URL.createObjectURL(file);
|
|
|
|
|
+ }
|
|
|
|
|
+ return '';
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 防抖处理的上传触发函数
|
|
|
|
|
+ */
|
|
|
|
|
+const DebounceUpload = () => {
|
|
|
|
|
+ if (debounceTimer.value) {
|
|
|
|
|
+ clearTimeout(debounceTimer.value);
|
|
|
|
|
+ }
|
|
|
|
|
+ debounceTimer.value = setTimeout(() => {
|
|
|
|
|
+ EnterUpload();
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 执行具体的上传逻辑
|
|
|
|
|
+ */
|
|
|
|
|
+const EnterUpload = async () => {
|
|
|
|
|
+ if (isUploading.value) {
|
|
|
|
|
+ ElMessage.warning("正在导入图片中,请勿重复点击!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (selectedFiles.value.length === 0) {
|
|
|
|
|
+ ElMessage.warning('请先选择图片');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // // 校验灵敏度参数
|
|
|
|
|
+ // const sensVal = Number(sensitive.value);
|
|
|
|
|
+ // if (!sensitive.value || isNaN(sensVal) || sensVal <= 0 || sensVal >= 255 || sensVal % 2 === 0) {
|
|
|
|
|
+ // ElMessage.warning('灵敏度参数只能是0-255之间的奇数,不包含0和255');
|
|
|
|
|
+ // return;
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ // // 校验双面扫描图片数量
|
|
|
|
|
+ // if (props.paperSchema == 2 && selectedFiles.value.length % 2 != 0) {
|
|
|
|
|
+ // ElMessage.warning('图片数量不对!双面扫描的图片数量必须为偶数');
|
|
|
|
|
+ // return;
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ isUploading.value = true;
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取批次号
|
|
|
|
|
+ const param = {
|
|
|
|
|
+ examSubjectId: examSubjectId.value,
|
|
|
|
|
+ schoolId: schoolId.value,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const res = await getCurrentBatchNo(param);
|
|
|
|
|
+
|
|
|
|
|
+ batchNumber.value = res.data.batchNo;
|
|
|
|
|
+ batchNOID.value = res.data.id;
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件上传开始
|
|
|
|
|
+ emit('UploadComplete', batchNumber.value, selectedFiles.value.length, batchNOID.value);
|
|
|
|
|
+
|
|
|
|
|
+ const uploadResults = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 逐个上传文件
|
|
|
|
|
+ for (let i = 0; i < selectedFiles.value.length; i++) {
|
|
|
|
|
+ let fileItem = selectedFiles.value[i];
|
|
|
|
|
+ if (!fileItem) continue;
|
|
|
|
|
+ fileItem.uploadStatus = -1; // 标记为上传中
|
|
|
|
|
+
|
|
|
|
|
+ const result = await UploadSingleFile(fileItem.file, i + 1);
|
|
|
|
|
+ console.log("打印上传结果",result);
|
|
|
|
|
+ uploadResults.push(result);
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟进度条动画效果
|
|
|
|
|
+ let progress = 0;
|
|
|
|
|
+ const interval = setInterval(() => {
|
|
|
|
|
+ progress += 5;
|
|
|
|
|
+ if (progress >= 100) {
|
|
|
|
|
+ progress = 100;
|
|
|
|
|
+ clearInterval(interval);
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ fileItem.uploadProgress = 100;
|
|
|
|
|
+ fileItem.uploadStatus = 0;
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fileItem.uploadProgress = 0;
|
|
|
|
|
+ fileItem.uploadStatus = 1; // 失败状态
|
|
|
|
|
+ }
|
|
|
|
|
+ // fileItem.uploadProgress = progress;
|
|
|
|
|
+ // // 根据上传结果设置最终状态
|
|
|
|
|
+ // fileItem.uploadStatus = result.success ? 0 : 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fileItem.uploadProgress = progress;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件上传进度
|
|
|
|
|
+ emit('UploadProgress', batchNumber.value, uploadResults.length, batchNOID.value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 上传完成后的处理
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ HandleClose();
|
|
|
|
|
+ ElMessage.success(`成功上传 ${uploadResults.length} 张图片`);
|
|
|
|
|
+ }, 3000);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('上传过程中发生错误:', error);
|
|
|
|
|
+ ElMessage.error('上传过程中发生错误');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ isUploading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 上传单个文件到服务器
|
|
|
|
|
+ * @param {File} file - 文件对象
|
|
|
|
|
+ * @param {number} index - 文件序号
|
|
|
|
|
+ * @returns {Promise} 上传结果
|
|
|
|
|
+ */
|
|
|
|
|
+const UploadSingleFile = (file, index) => {
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ formData.append('file', file);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建 JSON 参数
|
|
|
|
|
+ let jsonParam = {
|
|
|
|
|
+ examSubjectId: examSubjectId.value,
|
|
|
|
|
+ batchNumber: batchNumber.value,
|
|
|
|
|
+ sensitive: sensitive.value,
|
|
|
|
|
+ filter: 0,
|
|
|
|
|
+ heightRatio: 0.03,
|
|
|
|
|
+ schoolId: schoolId.value,
|
|
|
|
|
+ };
|
|
|
|
|
+ if (onnx.value == 1) {
|
|
|
|
|
+ jsonParam.onnx = 1;//算法类型 1-表示废资源算法,也就是识别一张要花15S,不传的话是正常算法,放在页面让用户选择。
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ formData.append('jsonParam', JSON.stringify(jsonParam));
|
|
|
|
|
+ formData.append('seqNumber', index);
|
|
|
|
|
+ formData.append('paperSchema', props.paperSchema);
|
|
|
|
|
+
|
|
|
|
|
+ // 根据环境确定上传地址
|
|
|
|
|
+ let uploadUrl = '/api/v1/ai_exam_scan/upload_img';
|
|
|
|
|
+ if (import.meta.env.MODE === 'production') {
|
|
|
|
|
+ uploadUrl = '/teaching/api/v1/ai_exam_scan/upload_img';//线上环境
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ axios.post(uploadUrl, formData, {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Authorization': localStorage.getItem('token'),
|
|
|
|
|
+ 'Content-Type': 'multipart/form-data'
|
|
|
|
|
+ }
|
|
|
|
|
+ }).then(response => {
|
|
|
|
|
+ const data = response.data;
|
|
|
|
|
+ resolve({
|
|
|
|
|
+ fileName: file.name,
|
|
|
|
|
+ success: data.code === 200,
|
|
|
|
|
+ data: data,
|
|
|
|
|
+ file: file
|
|
|
|
|
+ });
|
|
|
|
|
+ }).catch(error => {
|
|
|
|
|
+ console.error(`文件 ${file.name} 上传失败:`, error);
|
|
|
|
|
+ resolve({
|
|
|
|
|
+ fileName: file.name,
|
|
|
|
|
+ success: false,
|
|
|
|
|
+ error: error.message,
|
|
|
|
|
+ file: file
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 打开图片预览弹窗
|
|
|
|
|
+ * @param {string} url - 图片URL
|
|
|
|
|
+ */
|
|
|
|
|
+const OpenViewPaper = (url) => {
|
|
|
|
|
+ showPaperPreview.value = true;
|
|
|
|
|
+ previewImageList.value = [{ url }];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 关闭弹窗并重置状态
|
|
|
|
|
+ */
|
|
|
|
|
+const HandleClose = () => {
|
|
|
|
|
+ showDialog.value = false;
|
|
|
|
|
+ selectedFiles.value = [];
|
|
|
|
|
+ if (fileInput.value) {
|
|
|
|
|
+ fileInput.value.value = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ emit('close');
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 取消上传并重置状态
|
|
|
|
|
+ */
|
|
|
|
|
+const CancelUpload = () => {
|
|
|
|
|
+ selectedFiles.value = [];
|
|
|
|
|
+ if (fileInput.value) {
|
|
|
|
|
+ fileInput.value.value = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ emit('CancelUpload', batchNumber.value);
|
|
|
|
|
+ showDialog.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+/* 保持原有样式布局 */
|
|
|
|
|
+.center_header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+.header_left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.list_img_content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.list_img_item {
|
|
|
|
|
+ width: 120px;
|
|
|
|
|
+ border: 1px solid #eee;
|
|
|
|
|
+ padding: 5px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+.item_batch_name {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+.item_batch_img {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 80px;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.button_group {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 80px; /* 与图片高度一致 */
|
|
|
|
|
+ background: rgba(0,0,0,0.5);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transition: opacity 0.3s;
|
|
|
|
|
+}
|
|
|
|
|
+.list_img_item:hover .button_group {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+}
|
|
|
|
|
+.item_batch_progress, .item_batch_failed, .item_batch_success {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: rgba(255,255,255,0.9);
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 2px 0;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|