From fcc881a2fa28c85a110a197c5af2e22b82e26b2b 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, 19 Nov 2024 15:49:53 +0800 Subject: [PATCH] 1 --- package.json | 1 + .../questionUpload/index.vue | 122 ++- .../questionUpload/ocrImg2ExamQues.js | 776 ++++++++++++++++++ 3 files changed, 894 insertions(+), 5 deletions(-) create mode 100644 src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js diff --git a/package.json b/package.json index 88ac5f2..b9a59d7 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "nanoid": "^5.0.7", "number-precision": "^1.6.0", "vue-cropper": "1.0.3", + "qs": "^6.12.0", "pptxgenjs": "^3.12.0", "pptxtojson": "^1.0.3", "prosemirror-commands": "^1.6.0", diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue index a4a2800..31731e6 100644 --- a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue +++ b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/index.vue @@ -3,7 +3,7 @@
-
+
获取剪贴板图片 整题识别
+ +
+

orc 使用说明

+

1、本地浏览

+

2、获取剪贴板图片

+

3、整题识别

+

+
@@ -52,9 +60,9 @@
@@ -165,7 +242,42 @@ const initHomeWork = async()=> { height: 100%; display: flex; flex-direction: column; + + .row-import-manual{ + height: 100%; + display: flex; + flex-direction: column; + + .import-manual-cropper{ + width: 100%; + // height: 560px; + min-height: 400px; + border: 1px solid #000; + } + + .import-manual-crop-menu{ + display: flex; + background-color: #e8e9eb; + + .manual-crop-menu-browse{ + margin-right: 10px; + } + .manual-crop-menu-whole{ + margin-left: auto; + } + } + .import-manual-explain { + text-align: left; + } + } } + + + + + + + } .page-right { width: 100%; diff --git a/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js new file mode 100644 index 0000000..4a0dd23 --- /dev/null +++ b/src/renderer/src/views/classTask/newClassTaskAssign/questionUpload/ocrImg2ExamQues.js @@ -0,0 +1,776 @@ +import { ElMessageBox, ElMessage } from "element-plus"; +import qs from "qs"; +import { pyOCRAPI } from "@/api/education/entpcoursework"; + + +const EXAM_JUDGED_DICTIONARY = ["正确", "对", "√", "T", "错误", "错", "×", "F"]; +const baidubceConfig = { + // Header + 'Content-Type': "application/x-www-form-urlencoded", + // 格式 + 'Accept' : 'application/json', + // id(临时测试) + 'client_id': "U0DrGBE6X92IXgV6cJMNON8F", + // 密钥(临时测试) + 'client_secret': 'oWb0M0YWMmZPMQIhIUkJX99ddr7h61qf', +}; + + + +/** + * @desc: [人工录入]中识别[单项]内容 + * @return: {*} + * @param {boolean} isLocalTest 本地测试 + * @param {string} imgBase64 识别图片的base64 + * @param {string} examType 需识别的试题类型 + * @param {string} curItem 需识别的单项类型 + * [curItem] 参数说明: title-题目 workdesc-单选题选项 workdesc:single:multi:blanks:judge:QAA(questions and answers)-复合题选项 workanswer-答案 + * method-答案分析 analyse-答案解答 discuss-答案点评 + */ +export const ocrImg2ItemByManualUpl = async (isLocalTest = false, imgBase64 = '', examType = '', curItem = '') => { + let examItem = null; + let ocrJson = null; + let regex = null; + // 识别内容拼接 + let ocrTxt = '' + + if(isLocalTest) { + // 临时本地测试(json格式跟百度ocr一致) + const response = await fetch('/cropImgTest/single.json'); + const resOcr = await response.json(); + ocrJson = resOcr.results; + // 识别内容拼接 + ocrJson.forEach(ele => { + ocrTxt += `${ele.words.word}
`; + }); + + //-------------------------------------------------------------- + // 备用ocr识别服务 (python的一个识别服务) + // const response = await ocrImgPyJson(imgBase64); + // if(!response?.data) { + // return examQues; + // } + // ocrJson = response.data; + // // 识别内容拼接 + // ocrJson.forEach(ele => { + // ocrTxt += `${ele}
`; + // }); + } + else { + const tmp = await ocrImg2Json(imgBase64); + if(!tmp?.data) { + return examItem; + } + ocrJson = tmp.data.results; + // 识别内容拼接 + ocrJson.forEach(ele => { + ocrTxt += `${ele.words.word}
`; + }); + } + + if(ocrJson == '') { + ElMessage.error('[人工录入-单项]识别的图片为空, 识别失败, 请检查重试!'); + return examItem; + } + + if(ocrTxt == '') { + ElMessage.error('[人工录入-单项]识别内容拼接失败, 请检查重试!'); + return examItem; + } + ocrTxt = ocrTxt.trim(); + + // 根据[单项类型]转换对应的识别内容 + if (curItem === 'title' || curItem === 'method' || curItem === 'analyse' || curItem === 'discuss') { + regex = /^(\d*[..。])?(\(.*?\)|(.*?\))/g + // 去掉开头的序号和题源(针对题目) + 去掉自定义的
标签 + ocrTxt = ocrTxt.replace(regex, '').replace(/
/g, ''); + examItem = ocrTxt; + } + else if (curItem === 'workdesc') { + // 该类型下无需[判断题]和[主观题]处理 + if (examType.includes('复合题')) { + // 因[题目+选项]分离正则匹配需要, 故需开头手动拼一个
+ let mutiParams = processExamMulti(`
${ocrTxt}`, ''); + examItem = { + worktype: '单选题', + params: [], + } + mutiParams.arrWorkDesc.forEach( item => { + const obj = { + title: item.title, + workanswer: '', + checkAnswer: [], + type: item.type, + options: item.options.map(element => {return {text: element.replace(/
/g, '')}}), + } + examItem.params.push(obj); + }); + return examItem; + + } + else if (examType.includes('单选题') || examType.includes('多选题')) { + /** 单选题/多选题 - 选项 */ + // 先判断是否存在选项标识, 且存在2个及以上(A.---1.---(1)---(1)) + regex = /\s*[A-H][..。]/g; + const matches = ocrTxt.match(regex); + if (matches==null || matches.length < 2){ + ElMessage.error('[人工录入-单项]识别[选项]失败, 请检查重试!'); + return examItem; + } + regex = /\s*[A-H][..。]/g; + examItem = ocrTxt.split(regex); + examItem.splice(0, 1); //将分隔出来的第一组空字符去掉(后续是否有空字符不管) + examItem = examItem.map(item => { + const obj = { + text: item.replace(/
/g, ''), + } + return obj; + }) + return examItem; + } + else if (examType.includes('填空题')) { + // 填空题 - 选项 + const obj = { + text: ocrTxt.replace(/
/g, ' ') + } + examItem = []; + examItem.push(obj); + return examItem; + } + + } + else if (curItem === 'workanswer') { + // 该类型下只做[主观题]和[复合题]的处理 + if (examType.includes('主观题')) { + ocrTxt = ocrTxt.replace(/
/g, ''); + examItem = ocrTxt; + } + } + + // 返回转换格式后的识别内容 + return examItem; +} + +/** + * @desc: [人工录入]中识别[整题]试题 + * @return: {*} + * @param {*} isLocalTest 本地测试 + * @param {*} imgBase64 识别图片的base64 + */ +export const ocrImg2ExamByManualUpl = async (isLocalTest = false, imgBase64 = '') => { + let examQues = {}; + let ocrJson = ''; + // 识别内容拼接 + let ocrTxt = ''; + + if(isLocalTest) { + // 临时本地测试(json格式跟百度ocr一致) + const response = await fetch('/cropImgTest/single.json'); + const resOcr = await response.json(); + ocrJson = resOcr.results; + // 识别内容拼接 + ocrJson.forEach(ele => { + ocrTxt += `${ele.words.word}
`; + }); + + //-------------------------------------------------------------- + // 备用ocr识别服务 (python的一个识别服务) + // const response = await ocrImgPyJson(imgBase64); + // if(!response?.data) { + // return examQues; + // } + // ocrJson = response.data; + // ocrJson.forEach(ele => { + // ocrTxt += `${ele}
`; + // }); + } else { + const tmp = await ocrImg2Json(imgBase64); + if(!tmp?.data) { + return examQues; + } + ocrJson = tmp.data.results; + ocrJson.forEach(ele => { + ocrTxt += `${ele.words.word}
`; + }); + } + + if(ocrJson == '') { + ElMessage.error('[人工录入-整题]图片识别内容为空, 识别失败, 请重试!'); + return examQues; + } + + + if(ocrTxt == '') { + ElMessage.error('[人工录入-整题]识别内容拼接失败, 请重试!'); + return examQues; + } + + // 识别内容转为试题结构 + examQues = assembleExam(ocrTxt); + if(examQues.err != '') { + ElMessage.error(`[人工录入-整题]${examQues.err}, 请重试!`); + examQues = {}; + } + return examQues; +} + +/** + * @desc: 百度云api识别图片转json + * @return: {*} + * @param {*} eachSub 图片的base64 + */ +const ocrImg2Json = async (urlBase64) => { + //判断是否存在截取图片 + if (!urlBase64 || 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("百度智能云图片识别错误"); + return null; + } + + return resOcr; +} +/** + * @desc: python_ocr备用方案:识别图片转json + * @return: {*} + * @param {*} eachSub 图片的base64 + */ +const ocrImgPyJson = async (urlBase64) => { + //判断是否存在截取图片 + if (!urlBase64 || urlBase64 == '') { + ElMessage.error("未检测到截图图片, 请截取图片后再识别"); + return null; + } + + const resOcr = await pyOCRAPI(urlBase64); + if (resOcr.status !== 200) { + ElMessage.error("图片识别错误"); + return null; + } + + return resOcr; +} + + +/** [百度智能云]获取token */ +const bdyAPI_getToken = async function () { + return axios({ + headers: { + 'Content-Type': `${baidubceConfig['Content-Type']}`, + }, + method: 'POST', + url: `/baidubce/oauth/2.0/token?grant_type=client_credentials&client_id=${baidubceConfig['client_id']}&client_secret=${baidubceConfig['client_secret']}`, + // data: { + // grant_type: 'client_credentials', + // client_id: `${baidubceConfig['client_id']}`, + // client_secret: `${baidubceConfig['client_secret']}`, + // }, + }) +} + +/** [百度智能云]ocr图片识别 */ +const bdyAPI_getOcrContent = async function (token, imgUrl, params) { + return axios({ + headers: { + 'Content-Type': `${baidubceConfig['Content-Type']}`, + 'Accept': `${baidubceConfig['Accept']}`, + }, + method: 'POST', + url: `/baidubce/rest/2.0/ocr/v1/doc_analysis?access_token=${token}`, + data: qs.stringify(params), + // data: { + // image: imgUrl, //图片地址(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 + // }, + }) +} + + +/** + * @desc: 根据识别内容组装试题结构 + * @return: {*} + * @param {*} eachSub 识别拼接完成后的整体内容 + */ +const assembleExam = (eachSub) => { + let subObj = { + id: 0, + + worktype: '单选题', // 题的类型 存的中文 单选题 多选题 + workgroup: '0', // 1:真题 0非真题 + examdate: '', // 题的生成时间(2024-04-16T00:00:00) + title: '', // 题目内容 + workdesc: '', // 题目选项 #&使用这个分割开 A 0 B 1 C 2 D 3 + workanswer: '', // 答案 + workanalysis: '', // 解析3合1 + worktag: '', // 题源信息( (2023•河北) 中文括号+4位年份+ • +地区 ) + difficulty: 0, // 试题难度(暂定为0-100) + timelength: 60, // 推荐用时(s) + status: '0', // 扫描上传时需将状态先置位为0(试题审核后改为1) + score: 4, // 试题基础分值 + + // 试题解析错误信息 + err: '', + + // 界面展示格式化 + titleFormat: '', + workdescFormat: '', + workanswerFormat: '', + method: [], //分析 + analyse: [], //解答 + discuss: [], //点评 + }; + + + let regex = null; + let titleAndWorkDesc = '', + answer = ''; + + + // 获取[题源] - 格式化 + regex = /^(\d*[..。])?(\(.*?\)|(.*?\))/g + let workTag = eachSub.match(regex); + if (workTag) { + subObj.worktag = workTag[0].replace(/^\d*[..。]/g, ''); + subObj.worktag = subObj.worktag.replace('(', '(').replace(')', ')'); + } + + // 去掉开头的序号和题源 + eachSub = eachSub.replace(regex, ''); + // 先判断是否存在答案 + regex = /[\[【]答案.*?[\]】]/g; + let hasAnswer = eachSub.match(regex); + if (!hasAnswer) { + // 不存在答案, 仅处理[题干+选项] + titleAndWorkDesc = eachSub; + }else { + // 存在答案, 需处理[题干+选项]和[答案+解析] + regex = /(
?\s*[【\[].*?[】\]])/g; + let tmpList = eachSub.split(regex); + if (tmpList.length < 2) { + subObj.err = '试题匹配答案失败, 请检查识别格式' + return subObj; + } + // 第一部分[题干-选项] 处理 + titleAndWorkDesc = tmpList[0]; + // 将4个以上连续的下划线统一替换为5个 + titleAndWorkDesc = titleAndWorkDesc.replace(/_{4,}/g, '_____'); + + // 第二部分[分析-答案] 处理 + let answerAndAnswer = {}; + // 将第二部分的内容做key-value绑定 - 键为【分析】、【讨论】、【方法】等. 值为随之分隔的内容 + for (let i=1; i|【|】|\[|\]/g, ''); + let value = tmpList[i+1]; + value = value.replace(/^
+|
+$/g, ''); + answerAndAnswer[key] = value; + } + + // [试题解析] 处理 + let method = '', analyse = '', discuss = ''; + if (answerAndAnswer['试题立意']) { + discuss += `${answerAndAnswer['试题立意']}

`; + } + if (answerAndAnswer['评分参考']) { + discuss += `${answerAndAnswer['评分参考']}

`; + } + discuss = discuss.replace(/
+$/, ""); + + if (answerAndAnswer['能力素养']) { + method += `${answerAndAnswer['能力素养']}

`; + } + if (answerAndAnswer['能力解读']) { + method += `${answerAndAnswer['能力解读']}

`; + } + method = method.replace(/
+$/, ""); + + if (answerAndAnswer['误项排除']) { + analyse += `${answerAndAnswer['误项排除']}

`; + } + if (answerAndAnswer['失分剖析']) { + analyse += `失分剖析: ${answerAndAnswer['失分剖析']}

`; + } + analyse = analyse.replace(/
+$/, ""); + // [试题解析] - 格式化 + const jjj = { analyse: [analyse], discuss: [discuss], method: [method] }; + subObj.workanalysis = JSON.stringify(jjj); + // [试题解析] - 界面展示格式化 + subObj.method.push(method); + subObj.analyse.push(analyse); + subObj.discuss.push(discuss); + + + // [答案] - 初步初始化 --- 根据答案判断试题大分类: 复合题(实际为大题) 或 其他基础题型(单选,多选,填空,判断) + answer = answerAndAnswer['答案'].trim(); + if(!answer) { + answer = answerAndAnswer['答案及评分参考'].trim(); + answer = answer.replace(/^\d+[\u4e00-\u9fa5][..。]\s*
/, ''); // 去掉 - 有些开头会有[xx分。] + } + // 将多余的空格替换为固定的4个空格 + answer = answer.replaceAll("\\s{3,}"," "); + if (answer == null | answer == '') { + subObj.err = '题目缺少[答案]'; + return subObj; + } + } + + let tmpExam = null; + if (answer === '') { + /** + * 基础题型 - [单选题] [多选题] [填空题] [判断题] [主观题] + */ + tmpExam = processExamSingle(titleAndWorkDesc, answer); + } + else { + // 匹配是否存在 1. (1) (1)的存在, 题目与答案都存在则说明题型为复合题(嵌套题) + regex = /^(\d+[..。]|\(\d+\)|(\d+))/; + let answerFind = regex.test(answer); + regex = /(\d+[..。]|\(\d+\)|(\d+))/; + let titleFind = regex.test(titleAndWorkDesc); + if(titleFind && answerFind){ + /** + * [复合题] - 处理逻辑 + */ + tmpExam = processExamMulti(titleAndWorkDesc, answer); + } + else { + /** + * 基础题型 - [单选题] [多选题] [填空题] [判断题] [主观题] + */ + tmpExam = processExamSingle(titleAndWorkDesc, answer); + } + } + + if (tmpExam) { + // 错误信息 + if(tmpExam.errMsg !== '') { + subObj.err = tmpExam.err; + return subObj; + } + subObj.worktype = tmpExam.workType; + subObj.title = tmpExam.title; + if (tmpExam.arrWorkDesc.length > 0) { + subObj.workdesc = JSON.stringify(tmpExam.arrWorkDesc); + } + if (tmpExam.arrWorkAnswer.length > 0) { + subObj.workanswer = JSON.stringify(tmpExam.arrWorkAnswer); + } + } + + return subObj; +} + + +/** + * @desc: 单题(基础题) 处理逻辑 + * @return: {*} + * @param {*} titleAndWorkDesc [题干]+[选项] + * @param {*} answer [答案] + */ +const processExamSingle = function (titleAndWorkDesc, answer) { + let examSingle = { + workType: '单选题', + title: '', + arrWorkDesc: [], + arrWorkAnswer: [], + errMsg: '', //以此判断当前是否处理成功 + } + let tmpSplit = []; + let regex = null; + let matcher = null; + + /** [判断题]的处理逻辑, resp: -1-未找到 0-*为对应匹配的index */ + let judgedStatus = answer!=='' ? containsExactMatch(answer) : -1; + + /** 其他基础题型(单选,多选,填空,判断)的处理逻辑 */ + // 先去掉开头的试题序号 + regex = /^\d+[..。]\s*/; + titleAndWorkDesc = titleAndWorkDesc.replace(regex, '').trim(); + + // 题型判断 + regex = /
\s*[A-H][..。]/ + if (regex.test(titleAndWorkDesc)) { + /** + * [单选题]或[多选题] + */ + answer = answer.replace("
", "").trim(); + + // [题型] - 格式化 - 根据答案字符个数区分[单选]或[多选] + examSingle.workType = answer==='' ? '单选题' : answer.length == 1 ? "单选题" : "多选题"; + + // 切分题干+选项 + regex = /
*\s*[A-H][..。]/g; + tmpSplit = titleAndWorkDesc.split(regex); + + // [题干]-格式化 --- 正常数据 + examSingle.title = tmpSplit[0].trim(); + + // [选项]-处理 --- ['ABC123','ABC123'] + for (let i = 1; i < tmpSplit.length; i++) { + let option = tmpSplit[i].replace("
", "").trim(); + //option = option.replace("_", ""); + // [选项] - 格式化 + examSingle.arrWorkDesc.push(option); + } + + // [题目答案] --- ['0'] | ['0','1'] + if (answer !== '') { + // 答案为空时, 置空后直接返回 + let ans2num = '' + for (let i = 0; i < answer.length; i++) { + ans2num += (answer.charCodeAt(i) - 65).toString(); + } + // [题目答案] - 格式化 + examSingle.arrWorkAnswer = ans2num.split('').sort((a, b) => a - b); + } + } + else if (titleAndWorkDesc.indexOf("_____") != -1) { + /** + * 填空题 + */ + // [题型] - 格式化 + examSingle.workType = "填空题"; + + // [题干]-格式化 + examSingle.title = titleAndWorkDesc; + + // [选项] - 格式化 --- 填空题无选项 + //examSingle.arrWorkDesc = []; + + // [题目答案] - 处理(已将3个连续以上的空格已转为4个空格, 故可直接替换) --- ['填空1','填空2'] + if (answer !== '') { + examSingle.arrWorkAnswer = answer.split(" "); + } + } + else if( judgedStatus != -1 ) { + /** + * 判断题 + */ + // [题型] - 格式化 + examSingle.workType = "判断题"; + + // [题干] - 格式化 + examSingle.title = titleAndWorkDesc; + + // [选项] - 格式化 --- 判断题无选项 + //examSingle.arrWorkDesc = []; + + // [题目答案] - 处理(字典前一半为正确, 后一半为错误, 如返回值小于长度的一半则为正常, 反之为错误) --- ['0'/'1'] + let resp = judgedStatus - JUDGED_DICTIONARY.length / 2 < 0 ? "1" : "0"; + // [题目答案] - 格式化 + examSingle.arrWorkAnswer.push(resp); + } + else { + /** + * 主观题 + */ + // [题型] - 格式化 + examSingle.workType = "主观题"; + + // [题干]-格式化 + examSingle.title = titleAndWorkDesc; + + // [选项] - 格式化 --- 主观题无选项 + //examSingle.arrWorkDesc = []; + + // [题目答案] - 处理 --- ['qweasd123'] + if (answer !== '') { + examSingle.arrWorkAnswer.push(answer); + } + } + + return examSingle; +} + + +/** + * @desc: 复合题 处理逻辑 + * @return: {*} + * @param {*} titleAndWorkDesc [题干]+[选项] + * @param {*} answer [答案] + */ +const processExamMulti = function (titleAndWorkDesc, answer) { + let examMulti = { + workType: '复合题', + title: '', + arrWorkDesc: [], + arrWorkAnswer: [], + errMsg: '', //以此判断当前是否处理成功 + } + let tmpSplit = []; + let regex = null; + let matcher = null; + + // [题型] - 格式化 + examMulti.workType = "复合题"; + + // 先确定当前是以什么形式的小题序号来切分 --- 需要全部独立判断, 避免出现复合题中, 每小题内还包含小题的情况--- 1.回答以下问题 (1)***** (2)****** + let cliceSucc = false; + let arrAnswer = [] + 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); + } + + cliceSucc = true; + } + } + } + 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); + } + + cliceSucc = true; + } + } + } + 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); + } + + cliceSucc = true; + } + } + } + if (!cliceSucc){ + examMulti.errMsg = '[复合题]小题与答案序号[不匹配]'; + return examMulti; + } + if (tmpSplit.length < 2){ + examMulti.errMsg = '[复合题]题干与小题[切分失败]'; + return examMulti; + } + if (answer !== '' && arrAnswer.length < 2){ + examMulti.errMsg = '[复合题]答案切分小题失败'; + return examMulti; + } + if (answer !== '' && tmpSplit.length != arrAnswer.length){ + examMulti.errMsg = '[复合题]小题个数与答案个数[不一致]'; + return examMulti; + } + + // [题干]-格式化 --- 正常数据 + examMulti.title = tmpSplit[0].trim(); + + // [选项]+[答案] - 逻辑处理 + for (let i=1; i