From c01a29bf3fa72977d0e392db436a02cbdf7bda72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E4=BA=86=E4=B8=AA=E7=99=BD?= <543593352@qq.com> Date: Tue, 17 Dec 2024 17:20:30 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=B9=A0=E9=A2=98=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=EF=BC=9A=E6=89=AB=E6=8F=8F=E8=AF=86=E5=88=ABurl=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../questionUpload/ocrImg2ExamQues.js | 196 ++++++++++-------- 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js index 5e72245..b3a8c7a 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js +++ b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js @@ -1,6 +1,7 @@ import { ElMessageBox, ElMessage } from "element-plus"; import qs from "qs"; import axios from 'axios' +import request from '@/utils/request' import { pyOCRAPI } from "@/api/education/entpcoursework"; @@ -9,13 +10,20 @@ const baidubceConfig = { // Header 'Content-Type': "application/x-www-form-urlencoded", // 格式 - 'Accept' : 'application/json', + 'Accept': 'application/json', // id(临时测试) 'client_id': "U0DrGBE6X92IXgV6cJMNON8F", // 密钥(临时测试) 'client_secret': 'oWb0M0YWMmZPMQIhIUkJX99ddr7h61qf', }; +export function getOcrContent(data) { + return request({ + url: '/ocr/exam', + method: 'post', + data: data + }) +} /** @@ -34,8 +42,8 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' let regex = null; // 识别内容拼接 let ocrTxt = '' - - if(isLocalTest) { + + if (isLocalTest) { // 临时本地测试(json格式跟百度ocr一致) const response = await fetch('/cropImgTest/single.json'); const resOcr = await response.json(); @@ -59,7 +67,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' } else { const tmp = await ocrImg2Json(imgBase64); - if(!tmp?.data) { + if (!tmp?.data) { return examItem; } ocrJson = tmp.data.results; @@ -69,12 +77,12 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' }); } - if(ocrJson == '') { + if (ocrJson == '') { ElMessage.error('[人工录入-单项]识别的图片为空, 识别失败, 请检查重试!'); return examItem; } - if(ocrTxt == '') { + if (ocrTxt == '') { ElMessage.error('[人工录入-单项]识别内容拼接失败, 请检查重试!'); return examItem; } @@ -87,7 +95,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' ocrTxt = ocrTxt.replace(regex, '').replace(/
/g, ''); examItem = ocrTxt; } - else if (curItem === 'workdesc') { + else if (curItem === 'workdesc') { // 该类型下无需[判断题]和[主观题]处理 if (examType.includes('复合题')) { // 因[题目+选项]分离正则匹配需要, 故需开头手动拼一个
@@ -96,13 +104,13 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' worktype: '单选题', params: [], } - mutiParams.arrWorkDesc.forEach( item => { + mutiParams.arrWorkDesc.forEach(item => { const obj = { title: item.title, workanswer: '', checkAnswer: [], type: item.type, - options: item.options.map(element => {return {text: element.replace(/
/g, '')}}), + options: item.options.map(element => { return { text: element.replace(/
/g, '') } }), } examItem.params.push(obj); }); @@ -114,7 +122,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' // 先判断是否存在选项标识, 且存在2个及以上(A.---1.---(1)---(1)) regex = /\s*[A-H][..。]/g; const matches = ocrTxt.match(regex); - if (matches==null || matches.length < 2){ + if (matches == null || matches.length < 2) { ElMessage.error('[人工录入-单项]识别[选项]失败, 请检查重试!'); return examItem; } @@ -138,7 +146,7 @@ export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '' examItem.push(obj); return examItem; } - + } else if (curItem === 'workanswer') { // 该类型下只做[主观题]和[复合题]的处理 @@ -163,8 +171,8 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = '' let ocrJson = ''; // 识别内容拼接 let ocrTxt = ''; - - if(isLocalTest) { + + if (isLocalTest) { // 临时本地测试(json格式跟百度ocr一致) const response = await fetch('/cropImgTest/single.json'); const resOcr = await response.json(); @@ -173,7 +181,7 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = '' ocrJson.forEach(ele => { ocrTxt += `${ele.words.word}
`; }); - + //-------------------------------------------------------------- // 备用ocr识别服务 (python的一个识别服务) // const response = await ocrImgPyJson(imgBase64); @@ -186,7 +194,7 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = '' // }); } else { const tmp = await ocrImg2Json(imgBase64); - if(!tmp?.data) { + if (!tmp?.data) { return examQues; } ocrJson = tmp.data.results; @@ -195,20 +203,20 @@ export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = '' }); } - if(ocrJson == '') { + if (ocrJson == '') { ElMessage.error('[人工录入-整题]图片识别内容为空, 识别失败, 请重试!'); return examQues; } - - if(ocrTxt == '') { + + if (ocrTxt == '') { ElMessage.error('[人工录入-整题]识别内容拼接失败, 请重试!'); return examQues; } // 识别内容转为试题结构 examQues = assembleExam(ocrTxt); - if(examQues.err != '') { + if (examQues.err != '') { ElMessage.error(`[人工录入-整题]${examQues.err}, 请重试!`); examQues = {}; } @@ -226,30 +234,36 @@ const ocrImg2Json = async (urlBase64) => { ElMessage.error("未检测到截图图片, 请截取图片后再识别"); return null; } - const resToken = await bdyAPI_getToken(); - if (resToken.status !== 200) { - ElMessage.error("百度智能云用户标识有误"); - return null; - } - - const token = resToken.data?.access_token; let base64Code = urlBase64.split(",")[1]; - const query = { - image: base64Code, //图片地址(base64) - line_probability: false, //是否返回每行识别结果的置信度。默认为false - disp_line_poly: false, //是否返回每行的四角点坐标。默认为false - words_type: 'handprint_mix', //文字类型。 默认:印刷文字识别 = handwring_only:手写文字识别 = handprint_mix: 手写印刷混排识别 - layout_analysis: false, //是否分析文档版面:包括layout(图、表、标题、段落、目录);attribute(栏、页眉、页脚、页码、脚注)的分析输出 - recg_long_division: false, //是否检测并识别手写竖式 - recg_formula: true, //控制是否检测并识别公式,默认为false - } - - - const resOcr = await bdyAPI_getOcrContent(token, base64Code, query); - if (resOcr.status !== 200) { - ElMessage.error("百度智能云图片识别错误"); + const resOcr = await getOcrContent({ base64Code: base64Code }); + if (resOcr.code !== 200) { + ElMessage.error("图片识别错误"); return null; } + // const resToken = await bdyAPI_getToken(); + // if (resToken.status !== 200) { + // ElMessage.error("百度智能云用户标识有误"); + // return null; + // } + + // const token = resToken.data?.access_token; + // let base64Code = urlBase64.split(",")[1]; + // const query = { + // image: base64Code, //图片地址(base64) + // line_probability: false, //是否返回每行识别结果的置信度。默认为false + // disp_line_poly: false, //是否返回每行的四角点坐标。默认为false + // words_type: 'handprint_mix', //文字类型。 默认:印刷文字识别 = handwring_only:手写文字识别 = handprint_mix: 手写印刷混排识别 + // layout_analysis: false, //是否分析文档版面:包括layout(图、表、标题、段落、目录);attribute(栏、页眉、页脚、页码、脚注)的分析输出 + // recg_long_division: false, //是否检测并识别手写竖式 + // recg_formula: true, //控制是否检测并识别公式,默认为false + // } + + + // const resOcr = await bdyAPI_getOcrContent(token, base64Code, query); + // if (resOcr.status !== 200) { + // ElMessage.error("百度智能云图片识别错误"); + // return null; + // } return resOcr; } @@ -264,7 +278,7 @@ const ocrImgPyJson = async (urlBase64) => { ElMessage.error("未检测到截图图片, 请截取图片后再识别"); return null; } - + const resOcr = await pyOCRAPI(urlBase64); if (resOcr.status !== 200) { ElMessage.error("图片识别错误"); @@ -351,7 +365,7 @@ const assembleExam = (eachSub) => { let regex = null; let titleAndWorkDesc = '', - answer = ''; + answer = ''; // 获取[题源] - 格式化 @@ -361,7 +375,7 @@ const assembleExam = (eachSub) => { subObj.worktag = workTag[0].replace(/^\d*[..。]/g, ''); subObj.worktag = subObj.worktag.replace('(', '(').replace(')', ')'); } - + // 去掉开头的序号和题源 eachSub = eachSub.replace(regex, ''); // 先判断是否存在答案 @@ -370,7 +384,7 @@ const assembleExam = (eachSub) => { if (!hasAnswer) { // 不存在答案, 仅处理[题干+选项] titleAndWorkDesc = eachSub; - }else { + } else { // 存在答案, 需处理[题干+选项]和[答案+解析] regex = /(
?\s*[【\[].*?[】\]])/g; let tmpList = eachSub.split(regex); @@ -386,10 +400,10 @@ const assembleExam = (eachSub) => { // 第二部分[分析-答案] 处理 let answerAndAnswer = {}; // 将第二部分的内容做key-value绑定 - 键为【分析】、【讨论】、【方法】等. 值为随之分隔的内容 - for (let i=1; i|【|】|\[|\]/g, ''); - let value = tmpList[i+1]; + let value = tmpList[i + 1]; value = value.replace(/^
+|
+$/g, ''); answerAndAnswer[key] = value; } @@ -430,12 +444,12 @@ const assembleExam = (eachSub) => { // [答案] - 初步初始化 --- 根据答案判断试题大分类: 复合题(实际为大题) 或 其他基础题型(单选,多选,填空,判断) answer = answerAndAnswer['答案'].trim(); - if(!answer) { + if (!answer) { answer = answerAndAnswer['答案及评分参考'].trim(); answer = answer.replace(/^\d+[\u4e00-\u9fa5][..。]\s*
/, ''); // 去掉 - 有些开头会有[xx分。] } // 将多余的空格替换为固定的4个空格 - answer = answer.replaceAll("\\s{3,}"," "); + answer = answer.replaceAll("\\s{3,}", " "); if (answer == null | answer == '') { subObj.err = '题目缺少[答案]'; return subObj; @@ -455,7 +469,7 @@ const assembleExam = (eachSub) => { let answerFind = regex.test(answer); regex = /(\d+[..。]|\(\d+\)|(\d+))/; let titleFind = regex.test(titleAndWorkDesc); - if(titleFind && answerFind){ + if (titleFind && answerFind) { /** * [复合题] - 处理逻辑 */ @@ -471,7 +485,7 @@ const assembleExam = (eachSub) => { if (tmpExam) { // 错误信息 - if(tmpExam.errMsg !== '') { + if (tmpExam.errMsg !== '') { subObj.err = tmpExam.err; return subObj; } @@ -484,7 +498,7 @@ const assembleExam = (eachSub) => { subObj.workanswer = JSON.stringify(tmpExam.arrWorkAnswer); } } - + return subObj; } @@ -508,7 +522,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) { let matcher = null; /** [判断题]的处理逻辑, resp: -1-未找到 0-*为对应匹配的index */ - let judgedStatus = answer!=='' ? containsExactMatch(answer) : -1; + let judgedStatus = answer !== '' ? containsExactMatch(answer) : -1; /** 其他基础题型(单选,多选,填空,判断)的处理逻辑 */ // 先去掉开头的试题序号 @@ -524,7 +538,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) { answer = answer.replace("
", "").trim(); // [题型] - 格式化 - 根据答案字符个数区分[单选]或[多选] - examSingle.workType = answer==='' ? '单选题' : answer.length == 1 ? "单选题" : "多选题"; + examSingle.workType = answer === '' ? '单选题' : answer.length == 1 ? "单选题" : "多选题"; // 切分题干+选项 regex = /
*\s*[A-H][..。]/g; @@ -535,12 +549,12 @@ const processExamSingle = function (titleAndWorkDesc, answer) { // [选项]-处理 --- ['ABC123','ABC123'] for (let i = 1; i < tmpSplit.length; i++) { - let option = tmpSplit[i].replace("
", "").trim(); - //option = option.replace("_", ""); - // [选项] - 格式化 - examSingle.arrWorkDesc.push(option); + let option = tmpSplit[i].replace("
", "").trim(); + //option = option.replace("_", ""); + // [选项] - 格式化 + examSingle.arrWorkDesc.push(option); } - + // [题目答案] --- ['0'] | ['0','1'] if (answer !== '') { // 答案为空时, 置空后直接返回 @@ -570,7 +584,7 @@ const processExamSingle = function (titleAndWorkDesc, answer) { examSingle.arrWorkAnswer = answer.split(" "); } } - else if( judgedStatus != -1 ) { + else if (judgedStatus != -1) { /** * 判断题 */ @@ -635,48 +649,48 @@ const processExamMulti = function (titleAndWorkDesc, answer) { // 先确定当前是以什么形式的小题序号来切分 --- 需要全部独立判断, 避免出现复合题中, 每小题内还包含小题的情况--- 1.回答以下问题 (1)***** (2)****** let cliceSucc = false; let arrAnswer = [] - if(!cliceSucc){ + if (!cliceSucc) { regex = /
\s*\d+[..。]\s*/; if (regex.test(titleAndWorkDesc)) { // 再次以答案中的序号同步匹配一次 regex = /^\s*\d+[..。]\s*/; - if(answer === '' || regex.test(answer)){ - regex = /
\s*\d+[..。]\s*/g; - tmpSplit = titleAndWorkDesc.split(regex); - if (answer !== '') { - // 存在答案时, 再校验 - regex = /^\s*\d+[..。]\s*|
\s*\d+[..。]\s*|\s+\d+[..。]\s*/g; - arrAnswer = answer.split(regex); - } + if (answer === '' || regex.test(answer)) { + regex = /
\s*\d+[..。]\s*/g; + tmpSplit = titleAndWorkDesc.split(regex); + if (answer !== '') { + // 存在答案时, 再校验 + regex = /^\s*\d+[..。]\s*|
\s*\d+[..。]\s*|\s+\d+[..。]\s*/g; + arrAnswer = answer.split(regex); + } - cliceSucc = true; + cliceSucc = true; } } } - if (!cliceSucc){ + if (!cliceSucc) { regex = /
\s*(\d+)\s*/; if (regex.test(titleAndWorkDesc)) { // 再次以答案中的序号同步匹配一次 regex = /\s*(\d+)\s*/; - if(answer === '' || regex.test(answer)){ - regex = /
\s*(\d+)\s*/g; - tmpSplit = titleAndWorkDesc.split(regex); - if (answer !== '') { - // 存在答案时, 再校验 - regex = /^\s*(\d+)\s*|
\s*(\d+)\s*|\s+(\d+)\s*/g; - arrAnswer = answer.split(regex); - } + if (answer === '' || regex.test(answer)) { + regex = /
\s*(\d+)\s*/g; + tmpSplit = titleAndWorkDesc.split(regex); + if (answer !== '') { + // 存在答案时, 再校验 + regex = /^\s*(\d+)\s*|
\s*(\d+)\s*|\s+(\d+)\s*/g; + arrAnswer = answer.split(regex); + } - cliceSucc = true; + cliceSucc = true; } } } - if (!cliceSucc){ + if (!cliceSucc) { regex = /
\s*\(\d+\)\s*/; if (regex.test(titleAndWorkDesc)) { // 再次以答案中的序号同步匹配一次 regex = /^\s*\(\d+\)\s*/; - if(answer === '' || regex.test(answer)){ + if (answer === '' || regex.test(answer)) { regex = /
\s*\(\d+\)\s*/g; tmpSplit = titleAndWorkDesc.split(regex); if (answer !== '') { @@ -689,19 +703,19 @@ const processExamMulti = function (titleAndWorkDesc, answer) { } } } - if (!cliceSucc){ + if (!cliceSucc) { examMulti.errMsg = '[复合题]小题与答案序号[不匹配]'; return examMulti; } - if (tmpSplit.length < 2){ + if (tmpSplit.length < 2) { examMulti.errMsg = '[复合题]题干与小题[切分失败]'; return examMulti; } - if (answer !== '' && arrAnswer.length < 2){ + if (answer !== '' && arrAnswer.length < 2) { examMulti.errMsg = '[复合题]答案切分小题失败'; return examMulti; } - if (answer !== '' && tmpSplit.length != arrAnswer.length){ + if (answer !== '' && tmpSplit.length != arrAnswer.length) { examMulti.errMsg = '[复合题]小题个数与答案个数[不一致]'; return examMulti; } @@ -710,13 +724,13 @@ const processExamMulti = function (titleAndWorkDesc, answer) { examMulti.title = tmpSplit[0].trim(); // [选项]+[答案] - 逻辑处理 - for (let i=1; i Date: Tue, 17 Dec 2024 17:20:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=9A=E4=B9=A0=E9=A2=98=E4=B8=8A=E4=BC=A0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/router/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/router/index.js b/src/renderer/src/router/index.js index 470b3b9..7c1b13a 100644 --- a/src/renderer/src/router/index.js +++ b/src/renderer/src/router/index.js @@ -83,7 +83,7 @@ export const constantRoutes = [ path: 'questionUpload', component: () => import('@/views/classTask/newClassTaskAssign/questionUpload/index.vue'), name: 'questionUpload', - meta: { title: '习题上传' } + meta: { title: '习题上传', showBread: true } }, { path: 'aiKolors', From de9235751f649a2e0f8df70f9f11e9cede07b08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E4=BA=86=E4=B8=AA=E7=99=BD?= <543593352@qq.com> Date: Wed, 18 Dec 2024 09:51:44 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=9A=E4=B9=A0=E9=A2=98=E4=B8=8A=E4=BC=A0=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E8=BF=9B=E5=85=A5=20=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E9=A2=98=E5=BA=93=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/store/modules/classTask.js | 1 + .../classTask/newClassTaskAssign/index.vue | 26 +++++++++++++++++++ .../questionUpload/index.vue | 17 +++++++++--- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/store/modules/classTask.js b/src/renderer/src/store/modules/classTask.js index c5f8b31..05a9491 100644 --- a/src/renderer/src/store/modules/classTask.js +++ b/src/renderer/src/store/modules/classTask.js @@ -5,6 +5,7 @@ import { JYApiListCT, JYApiListOriginYear, JYApiListSO} from "@/utils/examQuesti const useClassTaskStore = defineStore('classTask',{ state: () => ({ + isOpenQuestUploadView: false, // 是否打开习题上传的页面 classListIds: [], entpCourseWorkTypeList: [ {value: 0, label: "不限"}, diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/index.vue b/src/renderer/src/views/classTask/newClassTaskAssign/index.vue index e8e9f7d..cf07db2 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/index.vue +++ b/src/renderer/src/views/classTask/newClassTaskAssign/index.vue @@ -149,10 +149,14 @@ import { useGetHomework } from '@/hooks/useGetHomework' import { sessionStore } from '@/utils/store' import { useRouter, useRoute } from 'vue-router' import useUserStore from '@/store/modules/user' +import useClassTaskStore from '@/store/modules/classTask' + const userStore = useUserStore().user const route = useRoute(); const router = useRouter() const { proxy } = getCurrentInstance() +const useClassTaskStores = useClassTaskStore(); + const props = defineProps({ currentCourse: Object, }) @@ -189,6 +193,7 @@ const boardLoading = ref(false); const fileLoading = ref(false); // 常规作业loading onMounted(() => { + console.log("----onMounted-------") currentRow.value = {id:0}; if(propsQueryCourseObj){ if(JSON.parse(propsQueryCourseObj)){ @@ -216,7 +221,28 @@ onMounted(() => { } } initHomeWork(); + isInToMyQuestion(); // 如果是上传习题后返回的,跳转到个人题库 }) +// 是否进入个人题库 +const isInToMyQuestion = () => { + console.log('isOpenQuestUploadView',useClassTaskStores.isOpenQuestUploadView); + if(useClassTaskStores.isOpenQuestUploadView){ + useClassTaskStores.isOpenQuestUploadView = false; + + currentRow.value = {id:1}; // 作业设计 + activeAptTab.value = "个人题库"; + //提交内容清空 重置 + classWorkForm.id = 0; + classWorkForm.uniquekey = ""; // 作业唯一标识 作业名称 + classWorkForm.worktype = "习题训练"; //作业类型 + classWorkForm.title = ""; // 作业说明 + classWorkForm.quizlist = []; // 作业习题列表内容 + classWorkForm.chooseWorkLists = []; // 作业框架梳理list + classWorkForm.fileHomeworkList = []; // 常规作业文件列表 + classWorkForm.whiteboardObj = ""; // 作业资源 - 课堂展示 白板 + classWorkForm.question = ""; // 作业资源 - 课堂展示 输入的问题 + } +} watch(() => props.currentCourse, (newVal, oldVal) => { if(newVal){ courseObj.textbookId = newVal.textbookId // 版本 diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue index 46b94b8..b8c192b 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue +++ b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue @@ -74,7 +74,7 @@