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