| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- <template>
- <div class="page_dialog">
- <el-dialog
- title="重新识别"
- class="page_dialog"
- :model-value="showDialog"
- width="728px"
- @close="handleClose"
- append-to-body
- >
- <div class="dialog_center padding_20">
- <div class="re_identify">
- <div class="identify_message">
- 已扫描{{ reIdentifyTotal }}张,点击"开始识别"进行重新识别。
- <span style="color:#FF0000;">识别中过程不能关闭弹窗或离开页面。</span>
- </div>
- <div class="identify_center">
- <div class="center_number_1">
- <div class="center_title">
- 需识别张数<br>
- <span class="span_number">{{ reIdentifyNumber }}</span>
- </div>
- </div>
- <div class="center_number_1">
- <div class="center_title">
- 已识别张数<br>
- <span class="span_number">{{ recognizedNumber }}</span>
- </div>
- </div>
- </div>
- <div class="identify_progress">
- <span>识别进度:</span>
- <el-progress
- :percentage="reIdentifyProgress"
- :stroke-width="16"
- :color="customColor"
- :show-text="true"
- ></el-progress>
- </div>
- <div class="identify_button">
- <el-button
- type="primary"
- @click="StartReIdentify"
- :loading="reIdentifyLoading"
- >
- {{ reIdentifyText }}
- </el-button>
- </div>
- </div>
- </div>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
- // 1. 引入 Pinia store
- import { useUserStore } from '@/store/user'
- import { useExamStore } from '@/store/exam'
- import WebSocketManager from '@/utils/webSocket.ts';
- import { recognizeAgain } from '@/api/exam';
- import { ElMessage } from 'element-plus';
- // ==================== Props 定义 ====================
- interface SchoolInfo {
- schoolId: string; // 学校ID
- schoolName: string; // 学校名称
- }
- const props = defineProps({
- /**
- * 控制弹窗显示/隐藏(v-model)
- */
- modelValue: {
- type: Boolean,
- required: true,
- },
- /**
- * 需要重新识别的总张数
- */
- reIdentifyTotal: {
- type: [String, Number],
- default: '0'
- },
- /**
- * 是否只识别客观题
- * 0 - 否(全部识别)
- * 1 - 是(仅客观题)
- */
- justObjective: {
- type: [String, Number],
- default: 1
- },
- /**
- * 学校信息对象
- */
- schoolInfo: {
- type: Object as () => SchoolInfo,
- default: () => ({
- schoolId: '',
- schoolName: ''
- })
- }
- });
- // ==================== Emits 定义 ====================
- const emit = defineEmits([
- 'update:modelValue', // v-model 更新事件
- 'ReIdentifyComplete' // 重新识别完成事件
- ]);
- // ==================== Store 实例化 ====================
- const userStore = useUserStore()
- const examStore = useExamStore()
- // ==================== 响应式数据 ====================
- // --- 上传相关(当前组件未使用,已注释)---
- // const loading = ref(false); // 上传加载状态
- // const selectedFiles = ref<any[]>([]); // 选择的文件列表
- // const previewImageList = ref<any[]>([]); // 预览弹窗的图片列表
- // const batchNumber = ref(''); // 批次编号
- // const batchNOID = ref(''); // 批次号ID
- // const sensitive = ref('165'); // 灵敏度参数(0-255之间的奇数)
- // const onnx = ref(0); // 算法类型:0-正常算法,1-废资源算法
- // const isUploading = ref(false); // 是否正在上传(防止重复点击)
- // const debounceTimer = ref<any>(null); // 防抖定时器
- // const scanLoading = ref(false); // 扫描按钮loading状态
- // --- 学校信息 ---
- // const schoolId = ref(''); // 当前学校ID
- const schoolName = ref(''); // 当前学校名称(带冒号后缀)
- // --- 重新识别相关 ---
- const reIdentifyUserId = ref(''); // 重新识别的用户ID(WebSocket连接标识)
- const isReIdentify = ref(false); // 是否处于重新识别状态
- const reIdentifyNumber = ref(0); // 需要识别的张数(剩余)
- const recognizedNumber = ref(0); // 已识别的张数
- const reIdentifyProgress = ref(0); // 重新识别进度百分比(0-100)
- const reIdentifyText = ref('开始识别'); // 重新识别按钮文案
- const reIdentifyLoading = ref(false); // 重新识别按钮loading状态
- // --- WebSocket 相关 ---
- const websocketUrl = ref(import.meta.env.VUE_APP_WEB_SOCKET_URL || ''); // WebSocket链接地址
- const socket = ref<any>(null); // WebSocket实例对象
- // --- 进度条配置 ---
- /**
- * 自定义进度条颜色配置
- * 不同进度阶段显示不同颜色(当前统一为蓝色 #2E64FA)
- */
- const customColor = [
- { color: '#2E64FA', percentage: 20 },
- { color: '#2E64FA', percentage: 40 },
- { color: '#2E64FA', percentage: 60 },
- { color: '#2E64FA', percentage: 80 },
- { color: '#2E64FA', percentage: 100 }
- ];
- // ==================== 计算属性 ====================
- /**
- * 弹窗显示状态(双向绑定)
- */
- const showDialog = computed({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val)
- });
- /**
- * 当前考试科目ID
- * 从 Vuex Store 中获取
- */
- const examSubjectId = computed(() => {
- return examStore.currentExam?.id
- })//计算属性
- const schoolId = computed(() => {
- return userStore.userInfo.schoolId;
- });
- /**
- * 剩余未上传完成的文件数量
- * 当前组件未使用,已注释
- */
- // const remainCount = computed(() => {
- // return selectedFiles.value.filter(file => file.uploadStatus !== 0).length;
- // });
- /**
- * 账号学校类型
- * 0 - 单校版
- * 1 - 联校版
- * 当前组件未使用,已注释
- */
- // const schoolType = computed(() => {
- // return store.state.user?.schoolType;
- // });
- /**
- * 考试学校类型
- * 0 - 单校版
- * 1 - 联校版
- * 当前组件未使用,已注释
- */
- // const examSchoolType = computed(() => {
- // return store.state.makeTemplate?.examDetail?.schoolType;
- // });
- // ==================== 监听器 ====================
- /**
- * 监听 reIdentifyTotal 变化
- * 当父组件传入的总张数变化时,同步更新本地变量
- */
- watch(
- () => props.reIdentifyTotal,
- (newVal) => {
- reIdentifyNumber.value = Number(newVal);
- }
- );
- /**
- * 监听 schoolInfo 变化
- * 当学校信息变化时,更新本地的 schoolId 和 schoolName
- */
- watch(
- () => props.schoolInfo,
- (newVal) => {
- console.log("监听到学校信息的改变了", newVal);
- if (newVal) {
- // schoolId.value = newVal.schoolId || '';
- schoolName.value = newVal.schoolName ? newVal.schoolName + ':' : '';
- }
- },
- { deep: true, immediate: true }
- );
- // ==================== 生命周期 ====================
- /**
- * 组件挂载时执行
- * 初始化重新识别张数
- */
- onMounted(() => {
- console.log("打印学校id", props.schoolInfo);
- reIdentifyNumber.value = Number(props.reIdentifyTotal);
- });
- /**
- * 组件卸载前执行
- * 清理资源:关闭WebSocket连接、清除定时器
- */
- onBeforeUnmount(() => {
- // 清理 WebSocket 连接
- if (socket.value && socket.value.getSocketStatus() === WebSocket.OPEN) {
- socket.value.closeWebSocket();
- }
- // 清理定时器(当前未使用,保留以防后续扩展)
- // if (debounceTimer.value) {
- // clearTimeout(debounceTimer.value);
- // }
- });
- // ==================== 方法定义 ====================
- /**
- * 建立重新识别的 WebSocket 连接
- * @description 用于接收后端实时推送的识别进度消息
- */
- const ReIdentifyWebSocketLink = () => {
- // 如果已有连接且处于打开状态,先关闭
- if (socket.value && socket.value.getSocketStatus() === WebSocket.OPEN) {
- socket.value.closeWebSocket();
- }
- // 构建 WebSocket 用户标识:AGAIN_ + 科目ID
- let userCourseId = '';
- console.log("打印用户信息", userStore.userInfo);
- userCourseId = "AGAIN_" + examSubjectId.value;
- reIdentifyUserId.value = userCourseId;
- console.log("打印重新识别的userCourseId", userCourseId);
- console.log("import.meta.env.NODE_ENV", import.meta.env.NODE_ENV);
- // 根据环境设置 WebSocket URL
- if (import.meta.env.MODE === 'development') {
- // 开发环境使用固定地址
- websocketUrl.value = 'wss://dev3.k12100.net/teaching/api/v1/teaching/websocket/';
- } else if (import.meta.env.MODE === 'production') {
- // 生产环境使用环境变量
- websocketUrl.value = import.meta.env.VUE_APP_WEB_SOCKET_URL || '';
- }
- // 拼接完整的 WebSocket URL
- const url = websocketUrl.value + userCourseId;
- console.log("打印websocket链接地址url", url);
-
- // 创建并启动 WebSocket 连接
- socket.value = new WebSocketManager(url, MessageBack);
- socket.value.start();
- };
- /**
- * 开始重新识别
- * @description 调用后端接口触发重新识别,并建立 WebSocket 连接接收进度
- */
- const StartReIdentify = () => {
- // 如果已经识别完成,点击按钮则关闭弹窗并重置状态
- if (reIdentifyText.value === '识别完成') {
- showDialog.value = false;
- reIdentifyLoading.value = false;
- reIdentifyProgress.value = 0;
- reIdentifyText.value = '开始识别';
- return;
- }
- // 重置进度状态
- reIdentifyProgress.value = 0;
- reIdentifyLoading.value = true;
- reIdentifyText.value = '识别中';
-
- // 建立 WebSocket 连接
- ReIdentifyWebSocketLink();
- // 构建请求参数
- const param = {
- examSubjectId: examSubjectId.value, // 考试科目ID
- recognizeNumber: props.reIdentifyTotal, // 需要识别的总张数
- justObjective: props.justObjective, // 是否只识别客观题
- schoolId: schoolId.value // 学校ID
- };
- // 调用重新识别接口
- recognizeAgain(param).then(res => {
- console.log("重新识别结果", res);
- // reIdentifyLoading.value = false;
- if (res.code == 200) {
- // ElMessage.success("重新识别完成!");
- console.log("开始重新识别");
- }
- else
- {
-
- ElMessage.error("重新识别失败!" + res.msg);
- }
- }).catch(err => {
- console.error("重新识别请求失败", err);
- ElMessage.error("网络请求失败");
- });
- };
- /**
- * WebSocket 消息回调处理
- * @param data - WebSocket 返回的消息字符串(JSON格式)
- * @description 接收后端推送的识别进度,更新UI显示
- */
- const MessageBack = (data: string) => {
- const msg = JSON.parse(data);
- console.log("Websocket消息返回处理", msg);
- // code: 0-建立成功, 200-业务成功, 400-业务失败/异常
- if (msg.code === 200) {
- // 已识别张数 +1
- recognizedNumber.value++;
- // 待识别张数 -1
- reIdentifyNumber.value--;
-
- // 计算进度百分比
- const process = Math.round((Number(recognizedNumber.value) / Number(props.reIdentifyTotal)) * 100);
- reIdentifyProgress.value = Math.min(process, 100); // 确保不超过100%
- // 如果进度达到100%,表示识别完成
- if (reIdentifyProgress.value === 100) {
- ElMessage.success("重新识别完成!");
-
- // 重置所有状态
- reIdentifyNumber.value = 0;
- recognizedNumber.value = 0;
- reIdentifyProgress.value = 0;
- reIdentifyText.value = '识别完成';
- reIdentifyLoading.value = false;
-
- // 关闭弹窗
- showDialog.value = false;
-
- // 通知父组件识别完成
- emit('ReIdentifyComplete');
- }
- }
- };
- /**
- * 格式化文件名
- * @param fileName - 原始文件名
- * @returns 格式化后的文件名(超过17位时显示前5位+...+后12位)
- * @description 当前组件未使用,保留以备后续扩展
- */
- // const FormatFileName = (fileName: string): string => {
- // if (fileName.length <= 17) {
- // return fileName;
- // }
- // const prefix = fileName.substring(0, 5);
- // const suffix = fileName.substring(fileName.length - 12);
- // return prefix + '...' + suffix;
- // };
- /**
- * 检查文件是否为图片
- * @param fileName - 文件名
- * @returns 是否为图片文件
- * @description 当前组件未使用,保留以备后续扩展
- */
- // const isImageFile = (fileName: string): boolean => {
- // const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
- // const lowerFileName = fileName.toLowerCase();
- // return imageExtensions.some(ext => lowerFileName.endsWith(ext));
- // };
- /**
- * 获取文件预览URL
- * @param file - 文件对象
- * @returns 预览URL
- * @description 当前组件未使用,保留以备后续扩展
- */
- // const getFilePreviewUrl = (file: File): string => {
- // if (window.URL && file) {
- // return window.URL.createObjectURL(file);
- // }
- // return '';
- // };
- /**
- * 关闭弹窗
- * @description 重置状态并通知父组件
- */
- const handleClose = () => {
- showDialog.value = false;
- // selectedFiles.value = []; // 当前未使用
- emit('close');
- };
- /**
- * 取消上传
- * @description 当前组件未使用,保留以备后续扩展
- */
- // const CancelUpload = () => {
- // selectedFiles.value = [];
- // emit('CancelUpload', batchNumber.value);
- // showDialog.value = false;
- // };
- // ==================== 暴露方法 ====================
- defineExpose({
- handleClose
- // CancelUpload // 当前未使用
- });
- </script>
- <style lang="scss" scoped>
- // 样式保持不变
- </style>
|