import { getStudentTrack } from '@/api/task'; import { Task, Track, SpecialTag, Question, MarkArea } from '@/api/types/task'; import { TrackConfigType } from '@/store/modules/app/types'; import { PictureTypeEnum, PICTURE_TYPE } from '@/constants/enumerate'; import { calcSumPrecision, deepCopy, maxNum, strGbLen, toPrecision, } from '@/utils/utils'; import { DrawTrackItem } from '../../../../electron/preload/types'; import { TrackTaskData } from '../../../../electron/db/models/trackTask'; import { TrackTaskDetailData } from '../../../../electron/db/models/trackTaskDetail'; type AnswerMap = Record< string, { answer: string; isRight: boolean; score: number; totalScore: number } >; interface TrackItemType { url: string; width: number; height: number; outpath: string; drawTrackList: DrawTrackItem[]; } type ElementType = | 'FILL_QUESTION' | 'FILL_LINE' | 'EXPLAIN' | 'COMPOSITION' | 'TOPIC_HEAD' | 'CARD_HEAD'; interface CardBaseElement { id: string; type: ElementType; topicNo: number; startNumber: number; questionsCount: number; } interface CardElement extends CardBaseElement { parent: CardBaseElement; } interface CardDataItem { exchange: { answer_area: Array<{ main_number: number; sub_number: number | string; area: [number, number, number, number]; }>; fill_area: Array<{ field: 'question' | 'examNumber'; index: number; single: boolean; horizontal: boolean; items: Array<{ main_number: number; sub_number: number | string; options: [number, number, number, number][]; }>; }>; }; columns: Array<{ elements: CardElement[]; }>; } interface CardContentType { pages: CardDataItem[]; } interface QuestionItem { mainNumber: number; subNumber: number | string; } interface QuestionArea { i: number; x: number; y: number; w: number; h: number; qStruct: string; } type UserMapType = Record< string, { userId: string; userName: string; color: string; scores: Array<{ subNumber: number; score: number }>; prename: string; score: number; } >; interface ImageItem { url: string; width: number; height: number; } interface PaperRecogData { page_index: number; question: Array<{ index: number; fill_result: Array<{ main_number: number; sub_number: number; single: number; fill_option: number[]; suspect_flag: number; fill_position: string[]; fill_size: number[]; }>; }>; } interface DrawConfig { domain: string; task: TrackTaskData; taskDetail: TrackTaskDetailData; trackConfig: TrackConfigType; winId: number; } export default function useDraw(drawConfig: DrawConfig) { const { domain, task, taskDetail, trackConfig, winId } = drawConfig; let answerMap = {} as AnswerMap; let cardData = [] as CardDataItem[]; let markAreas = [] as MarkArea[]; let recogDatas: string[] = []; let rawTask = {} as Task; let trackData = [] as TrackItemType[]; let originImgs = [] as ImageItem[]; let trackFiles = [] as ImageItem[]; let curStudentId = ''; const hasOrigin = trackConfig.pictureType.includes('origin'); const hasTrack = trackConfig.pictureType.includes('track'); const hasPdf = trackConfig.pictureType.includes('pdf'); const defaultColorConfig = { track: ['red', 'blue', 'gray'], head: 'green', }; let colorConfig = { track: ['red', 'blue', 'gray'], head: 'green' }; function updateColorConfig() { if (trackConfig.trackColorType === 'ALL_RED') { colorConfig.head = 'red'; colorConfig.track = ['red', 'red', 'red']; } else { colorConfig = deepCopy(defaultColorConfig); } } updateColorConfig(); function addLog(content: string, type?: 'info' | 'error') { window.api.logger(`win:${winId} ${content}`, type); } async function runTask() { initData(); curStudentId = taskDetail.studentId; addLog(`[${curStudentId}] 01-开始任务`); let result = true; try { await getTaskData(curStudentId); addLog(`[${curStudentId}] 02-获取任务数据成功`); originImgs = await downloadImages(rawTask.sheetUrls); addLog(`[${curStudentId}] 02-1-图片下载成功`); if (hasTrack || hasPdf) { await parseDrawList(); addLog(`[${curStudentId}] 03-解析绘制数据成功`); trackFiles = await drawTask(); addLog(`[${curStudentId}] 04-绘制成功`); if (hasPdf) { await window.api.imagesToPdf(trackFiles, getOutputPath('pdf')); addLog(`[${curStudentId}] 05-生成pdf成功`); } } clearResult(); } catch (error) { const e = error as Error; console.log(e); addLog( `[${curStudentId}-${rawTask.studentCode}] 08-任务失败,原因:${ e.message || '未知' }`, 'error' ); result = false; } const status = result ? 'FINISH' : 'INIT'; await window.db.updateTrackTaskDetailStatus({ id: taskDetail.id, status, }); addLog(`[${curStudentId}] 09-任务结束`); return true; } function initData() { cardData = [] as CardDataItem[]; recogDatas = [] as string[]; rawTask = {} as Task; trackData = [] as TrackItemType[]; answerMap = {} as AnswerMap; originImgs = [] as ImageItem[]; trackFiles = [] as ImageItem[]; curStudentId = ''; } function clearResult() { if (!hasOrigin) { window.api.clearFilesSync(originImgs.map((item) => item.url)); } if (!hasTrack && trackFiles.length) { window.api.clearFilesSync(trackFiles.map((item) => item.url)); } } async function getTaskData(studentId: string) { const res = await getStudentTrack(studentId); if (!res?.studentId) return; rawTask = { examId: res.examId, studentId: res.studentId, secretNumber: res.secretNumber, courseCode: res.courseCode, courseName: res.courseName, paperNumber: res.paperNumber, studentCode: res.studentCode, studentName: res.studentName, paperType: res.paperType, objectiveScore: res.objectiveScore || 0, markerScore: (res.objectiveScore || 0) + (res.subjectiveScore || 0), sheetUrls: res.sheetUrls ? res.sheetUrls.map((item) => item.url) : [], questionList: res.subjectiveQuestions, sliceConfig: [], jsonUrl: '', markerTime: 0, }; recogDatas = (res.sheetUrls || []).map((item) => item.recogData); markAreas = res.subjectiveQuestions.map((item) => { return { mainNumber: item.mainNumber, subNumber: item.subNumber, questionType: item.questionType, splitConfig: item.picList || [], }; }); // 获取客观题选项信息 res.objectiveQuestions.forEach((item) => { answerMap[`${item.mainNumber}_${item.subNumber}`] = { answer: item.answer, totalScore: item.totalScore, score: item.score, isRight: item.answer === item.standardAnswer, }; }); // 获取题卡数据 const cardCont: CardContentType = res.cardContent ? JSON.parse(res.cardContent) : { pages: [] }; cardData = cardCont.pages; } /** * 获取文件存储路径,规则:学期/考试/课程/试卷编号/教学班/下载文件类型/学生图片 */ function getOutputPath(type: PictureTypeEnum, index?: number) { const transfromStr = (str: string) => str.replace(/[*|:?<>]/g, ''); let filename = trackConfig.studentFileRule === 'CODE_NAME' ? `${rawTask.studentCode}-${rawTask.studentName}` : rawTask.studentCode; filename = transfromStr(filename); if (index !== undefined) { filename += `-${index}`; } filename += type === 'pdf' ? '.pdf' : '.jpg'; const paths = [ trackConfig.curOutputDir, transfromStr(task.semesterName), transfromStr(task.examName), transfromStr(`${rawTask.courseName}(${rawTask.courseCode})`), transfromStr(rawTask.paperNumber), transfromStr(taskDetail.className), ]; if (trackConfig.pictureType.includes(type)) { paths.push(PICTURE_TYPE[type]); } else { filename = `${type}-${filename}`; } paths.push(filename); return window.api.joinPath(paths); } async function downloadImages(urls: string[]) { const downloads: Promise[] = []; for (let i = 0; i < urls.length; i++) { let url = urls[i]; if (!url.startsWith('http://') && !url.startsWith('https://')) { url = `${domain}/${url}`; } // url += `&t=${Date.now()}`; downloads.push( window.api.downloadImage(url, getOutputPath('origin', i + 1)) ); } const images = await Promise.all(downloads).catch((error) => { console.log(error); }); if (!images) { return Promise.reject(new Error('下载图片失败')); } return images; } async function parseDrawList() { trackData = []; const trackLists = (rawTask.questionList || []) .map((q) => { return q.headerTrackList?.length ? addHeaderTrackColorAttr(q.headerTrackList) : addTrackColorAttr(q.markerTrackList); }) .flat(); const tagLists = (rawTask.questionList || []) .map((q) => q.headerTagList?.length ? addHeaderTrackColorAttr(q.headerTagList) : addTrackColorAttr(q.markerTagList || []) ) .flat(); const markDeailList = parseMarkDetailList(originImgs); const objectiveAnswerTagList = parseObjectiveAnswerTags(originImgs); for (let i = 0; i < originImgs.length; i++) { const img = originImgs[i]; const drawTrackList = [] as DrawTrackItem[]; trackLists .filter((item) => item.offsetIndex === i + 1) .forEach((item) => { drawTrackList.push(getDrawTrackItem(item)); }); tagLists .filter((item) => item.offsetIndex === i + 1) .forEach((item) => { drawTrackList.push(getDrawTagTrackItem(item)); }); const answerTags = paserRecogData(i); drawTrackList.push(...answerTags); drawTrackList.push(...(markDeailList[i] || [])); const oTags = (objectiveAnswerTagList[i] || []).map( (tag) => tag.trackItem ); drawTrackList.push(...oTags); drawTrackList.push(getTotalTrack(img)); trackData[i] = { url: img.url, width: img.width, height: img.height, outpath: getOutputPath('track', i + 1), drawTrackList, }; } } async function drawTask(): Promise { if (!trackData.length) return []; await window.api.drawTracks(trackData); return trackData.map((item) => { return { url: item.outpath, width: item.width, height: item.height, }; }); } // track ----- start-> const trackTextFontSize = 30; const trackInfoFontSize = 20; const trackInfoLineHeight = 20 * 1.2; function getDrawTrackItem(track: Track): DrawTrackItem { return { type: 'text', option: { x: track.offsetX, y: track.offsetY - trackTextFontSize / 2, text: String(track.score), color: track.color, fontSize: trackTextFontSize, }, }; } function getDrawTagTrackItem(track: SpecialTag): DrawTrackItem { if (track.tagType === 'LINE') { const tagProp = JSON.parse(track.tagName) as { len: number; }; return { type: 'line', option: { x0: track.offsetX, y0: track.offsetY, x1: track.offsetX + tagProp.len, y1: track.offsetY, }, }; } if (track.tagType === 'CIRCLE') { const tagProp = JSON.parse(track.tagName) as { width: number; height: number; }; return { type: 'circle', option: { x0: track.offsetX, y0: track.offsetY, x1: track.offsetX + tagProp.width, y1: track.offsetY + tagProp.height, }, }; } return { type: 'text', option: { x: track.offsetX, y: track.offsetY - trackTextFontSize / 2, text: track.tagName, color: track.color, fontSize: trackTextFontSize, }, }; } function addHeaderTrackColorAttr( headerTrackList: T[] ): T[] { return headerTrackList.map((item) => { item.color = colorConfig.head; return item; }); } function addTrackColorAttr< T extends { userId: string; color?: string; isByMultMark?: boolean; } >(tList: T[]): T[] { let markerIds: string[] = tList.map((v) => v.userId).filter((x) => !!x); markerIds = Array.from(new Set(markerIds)); // markerIds.sort(); const colorMap: Record = {}; for (let i = 0; i < markerIds.length; i++) { const mId = markerIds[i]; if (i === 0) { colorMap[mId] = colorConfig.track[0]; } else if (i === 1) { colorMap[mId] = colorConfig.track[1]; } else if (i > 1) { colorMap[mId] = colorConfig.track[2]; } } type ColorK = keyof typeof colorMap; tList = tList.map((item: T) => { const k = item.userId as ColorK; item.color = colorMap[k] || 'red'; item.isByMultMark = markerIds.length > 1; return item; }); return tList; } // track ----- end-> // mark detail ----- start-> // 解析各试题答题区域以及评分 function parseMarkDetailList(images: ImageItem[]): Array { const dataList: Array = []; const questions = rawTask.questionList || []; const fillQues = getFillLines(); let fillQuestions = [] as Question[]; let otherQuestions = questions; if (Object.keys(fillQues).length) { const fillQNos = Object.values(fillQues).flat(); fillQuestions = questions.filter((q) => fillQNos.includes(`${q.mainNumber}_${q.subNumber}`) ); otherQuestions = questions.filter( (q) => !fillQNos.includes(`${q.mainNumber}_${q.subNumber}`) ); } // 填空题:合并所有小题为一个区域 Object.values(fillQues).forEach((qnos) => { const groupQuestions = fillQuestions.filter((q) => qnos.includes(`${q.mainNumber}_${q.subNumber}`) ); const areas = parseQuestionAreas(groupQuestions); if (!areas.length) return; const area = { ...areas[0] }; const imgIndex = area.i - 1; if (!dataList[imgIndex]) { dataList[imgIndex] = []; } const img = images[imgIndex] as ImageItem; area.x *= img.width; area.y *= img.height; area.w *= img.width; const dataArr = dataList[imgIndex]; const userMap: UserMapType = {}; const isDoubleMark = !groupQuestions.some((question) => { let userIds = question.markerTrackList.map((track) => track.userId); if ( !userIds.length && question.markerList && question.markerList.length ) { userIds = question.markerList .filter((marker) => !marker.header) .map((marker) => marker.userId); } const uids = new Set(userIds); return uids.size === 1; }); groupQuestions.forEach((question) => { question.markerTrackList.forEach((track) => { if (!userMap[track.userId]) { userMap[track.userId] = { userId: track.userId, userName: track.userName, color: track.color || '', prename: '', scores: [], score: 0, }; } const existUserScore = userMap[track.userId].scores.find( (s) => s.subNumber === track.subNumber ); if (existUserScore) { existUserScore.score += track.score; } else { userMap[track.userId].scores.push({ score: track.score, subNumber: track.subNumber, }); } }); // 普通模式没有轨迹 if ( !question.markerTrackList.length && question.markerList && question.markerList.length ) { question.markerList .filter((marker) => !marker.header) .forEach((marker) => { if (!userMap[marker.userId]) { userMap[marker.userId] = { userId: marker.userId, userName: marker.userName, color: marker.header ? 'green' : 'red', prename: '', scores: [], score: 0, }; } userMap[marker.userId].scores.push({ score: marker.score, subNumber: question.subNumber, }); }); } }); const users = Object.values(userMap).map((user, index) => { const zhs = ['一', '二', '三']; const prename = isDoubleMark ? `${zhs[index] || ''}评` : '评卷员'; return { ...user, prename, score: calcSumPrecision(user.scores.map((s) => s.score)), }; }); // 新增仲裁和复核记录 const headerTrackQuestions = groupQuestions.filter( (question) => question.headerTrackList?.length ); if (headerTrackQuestions.length) { const updateTypeUser = (type: 'ARBITRATED' | 'MARKED') => { const typeQuestions = headerTrackQuestions.filter( (question) => question.headerTrackList[0].headerType === type ); typeQuestions.forEach((question) => { const scores = question.headerTrackList.map((track) => { return { score: track.score, subNumber: track.subNumber, }; }); const score = calcSumPrecision(scores.map((s) => s.score)); const user = { userId: question.headerTrackList[0].userId, userName: question.headerTrackList[0].userName, color: 'green', prename: type === 'ARBITRATED' ? '仲裁' : '复核', scores, score, }; users.push(user); }); }; // 仲裁记录 updateTypeUser('ARBITRATED'); // 复核记录 updateTypeUser('MARKED'); } // 填空题的打分需要自动换行,目前一行只展示最多10个评分 let offsetY = -1 * trackInfoLineHeight; const lineScoreCount = 10; const groupLineScore = (userScore: string[], color: string) => { const groupCount = Math.ceil(userScore.length / lineScoreCount); const groups: string[] = []; for (let i = 0; i < groupCount; i++) { groups.push( userScore .slice(i * lineScoreCount, (i + 1) * lineScoreCount) .join(',') ); } groups.forEach((group) => { offsetY += 20; dataArr.push({ type: 'text', option: { x: area.x, y: area.y + offsetY, text: group, fontSize: trackInfoFontSize, color, }, }); }); }; users.forEach((user) => { offsetY += trackInfoLineHeight; dataArr.push({ type: 'text', option: { x: area.x, y: area.y + offsetY, text: `${user.prename}:${user.userName},评分:`, fontSize: trackInfoFontSize, color: user.color, }, }); const userScore = user.scores.map( (item) => `${item.subNumber}:${item.score}分` ); groupLineScore(userScore, user.color); }); const score = calcSumPrecision( groupQuestions.map((item) => item.markerScore || 0) ); const maxScore = calcSumPrecision( groupQuestions.map((item) => item.maxScore) ); const tCont = `得分:${score},满分:${maxScore}`; const tContLen = strGbLen(tCont) / 2; dataArr.push({ type: 'text', option: { x: area.x + area.w - Math.ceil(tContLen * trackTextFontSize), y: area.y, text: tCont, fontSize: trackTextFontSize, }, }); }); // 其他试题 otherQuestions.forEach((question) => { const areas = parseQuestionAreas([question]); if (!areas.length) return; const area = { ...areas[0] }; const imgIndex = area.i - 1; if (!dataList[imgIndex]) { dataList[imgIndex] = []; } const img = images[imgIndex] as ImageItem; area.x *= img.width; area.y *= img.height; area.w *= img.width; const dataArr = dataList[imgIndex]; const userMap: UserMapType = {}; const hasHeaderTrack = question.headerTrackList?.length; // 是否仲裁 const isArbitration = hasHeaderTrack && question.headerTrackList[0].headerType === 'ARBITRATED'; // 是否复核 const isReview = hasHeaderTrack && question.headerTrackList[0].headerType === 'MARKED'; const tList = hasHeaderTrack ? question.headerTrackList || [] : question.markerTrackList || []; tList.forEach((track) => { if (!userMap[track.userId]) { userMap[track.userId] = { userId: track.userId, userName: track.userName, color: hasHeaderTrack ? 'green' : track.color || 'red', prename: '', scores: [], score: 0, }; } userMap[track.userId].scores.push({ score: track.score, subNumber: track.subNumber, }); }); // 是否双评,复核或者仲裁只会是一个人 const isDoubleMark = Object.keys(userMap).length > 1; const zhs = ['一', '二', '三']; let users = Object.values(userMap).map((user, index) => { let prename = ''; if (isArbitration) { prename = '仲裁'; } else if (isReview) { prename = '复核'; } else { prename = isDoubleMark ? `${zhs[index] || ''}评` : '评卷员'; } return { ...user, prename, score: calcSumPrecision(user.scores.map((s) => s.score)), }; }); // 普通模式没有轨迹 if (!tList.length && question.markerList && question.markerList.length) { let markers = question.markerList.filter((marker) => marker.header); if (!markers.length) { markers = question.markerList.filter((marker) => !marker.header); } users = markers.map((item, index) => { return { userId: item.userId, userName: item.userName, color: item.header ? 'green' : 'red', prename: markers.length > 1 ? `${zhs[index] || ''}评` : '评卷员', scores: [], score: item.score, }; }); } users.forEach((user, index) => { const content = `${user.prename}:${user.userName},评分:${user.score}`; dataArr.push({ type: 'text', option: { x: area.x, y: area.y + index * trackInfoLineHeight, text: content, fontSize: trackInfoFontSize, color: user.color, }, }); }); const tCont = `得分:${question.markerScore},满分:${question.maxScore}`; const tContLen = strGbLen(tCont) / 2; dataArr.push({ type: 'text', option: { x: area.x + area.w - Math.ceil(tContLen * trackTextFontSize), y: area.y, text: tCont, fontSize: trackTextFontSize, }, }); }); return dataList; } function getTotalTrack(image: ImageItem): DrawTrackItem { const totalScore = rawTask.markerScore || 0; const objectiveScore = rawTask.objectiveScore || 0; const subjectiveScore = toPrecision(totalScore - objectiveScore); return { type: 'text', option: { x: 0.15 * image.width, y: 0.01 * image.height, text: `总分:${totalScore},主观题得分:${subjectiveScore},客观题得分:${objectiveScore}`, fontSize: 40, }, }; } // 通过题卡获取属于填空题的试题号 // function getFillLinesFromCard() { // const questions: Record = {}; // cardData.forEach((page) => { // page.columns.forEach((column) => { // column.elements.forEach((element) => { // if (element.type !== 'FILL_LINE') return; // if (!questions[element.topicNo]) questions[element.topicNo] = []; // for (let i = 0; i < element.questionsCount; i++) { // questions[element.topicNo].push( // `${element.topicNo}_${element.startNumber + i}` // ); // } // }); // }); // }); // return questions; // } // 通过评卷区获取属于填空题的试题号 function getFillLines() { if (!markAreas?.length) return {}; const questions: Record = {}; markAreas.forEach((markArea) => { const { mainNumber, subNumber, questionType } = markArea; if (questionType !== 4) return; if (!questions[mainNumber]) questions[mainNumber] = []; questions[mainNumber].push(`${mainNumber}_${subNumber}`); }); return questions; } // 获取题型的评卷区 function parseQuestionAreas(questions: QuestionItem[]) { if (!questions.length || !markAreas?.length) return []; const pictureConfigs: QuestionArea[] = []; const structs = questions.map( (item) => `${item.mainNumber}_${item.subNumber}` ); markAreas.forEach((markArea) => { const qStruct = `${markArea.mainNumber}_${markArea.subNumber}`; if (!structs.includes(qStruct)) return; (markArea.splitConfig || []).forEach((area) => { pictureConfigs.push({ i: area.i, x: area.x, y: area.y, w: area.w, h: area.h, qStruct, }); }); }); // 合并相邻区域 const combinePictureConfigList = combinePictureConfig(pictureConfigs); // console.log(combinePictureConfigList); return combinePictureConfigList; } // 通过题卡获取试题评卷区 // function parseCardQuestionAreas(questions: QuestionItem[]) { // if (!questions.length || !cardData?.length) return []; // const pictureConfigs: QuestionArea[] = []; // const structs = questions.map( // (item) => `${item.mainNumber}_${item.subNumber}` // ); // cardData.forEach((page, pindex) => { // page.exchange.answer_area.forEach((area) => { // const [x, y, w, h] = area.area; // const qStruct = `${area.main_number}_${area.sub_number}`; // const pConfig: QuestionArea = { // i: pindex + 1, // x, // y, // w, // h, // qStruct, // }; // if (typeof area.sub_number === 'number') { // if (!structs.includes(qStruct)) return; // pictureConfigs.push(pConfig); // return; // } // // 复合区域处理,比如填空题,多个小题合并为一个区域 // if (typeof area.sub_number === 'string') { // const areaStructs = area.sub_number // .split(',') // .map((subNumber) => `${area.main_number}_${subNumber}`); // if ( // structs.some((struct) => areaStructs.includes(struct)) && // !pictureConfigs.find((item) => item.qStruct === qStruct) // ) { // pictureConfigs.push(pConfig); // } // } // }); // }); // // console.log(pictureConfigs); // // 合并相邻区域 // const combinePictureConfigList: QuestionArea[] = // combinePictureConfig(pictureConfigs); // // console.log(combinePictureConfigList); // return combinePictureConfigList; // } function combinePictureConfig(pictureConfigs: QuestionArea[]) { pictureConfigs.sort((a, b) => { return a.i - b.i || a.x - b.x || a.y - b.y; }); const combinePictureConfigList: QuestionArea[] = []; const elasticRate = 0.01; let prevConfig = {} as QuestionArea; pictureConfigs.forEach((item, index) => { if (!index) { prevConfig = { ...item }; combinePictureConfigList.push(prevConfig); return; } if ( prevConfig.i === item.i && prevConfig.y + prevConfig.h + elasticRate >= item.y && prevConfig.w === item.w && prevConfig.x === item.x ) { prevConfig.h = item.y + item.h - prevConfig.y; } else { prevConfig = { ...item }; combinePictureConfigList.push(prevConfig); } }); // console.log(combinePictureConfigList); return combinePictureConfigList; } // mark detail ----- end-> // answer tag ----- start-> // 解析客观题答案展示位置 function paserRecogData(imageIndex: number): DrawTrackItem[] { if (!recogDatas.length || !recogDatas[imageIndex]) return []; const recogData: PaperRecogData = JSON.parse( window.atob(recogDatas[imageIndex]) ); const answerTags: DrawTrackItem[] = []; recogData.question.forEach((question) => { question.fill_result.forEach((result) => { const fillPositions = result.fill_position.map((pos) => { return pos.split(',').map((n) => Number(n)); }); const offsetLt = result.fill_size.map((item) => item * 0.4); const tagLeft = maxNum(fillPositions.map((pos) => pos[0])) + result.fill_size[0] - offsetLt[0]; const tagTop = fillPositions[0][1] - offsetLt[1]; const { answer, isRight } = answerMap[`${result.main_number}_${result.sub_number}`] || {}; answerTags.push({ type: 'text', option: { x: tagLeft, y: tagTop, text: answer || '', color: isRight ? '#05b575' : '#f53f3f', }, }); }); }); return answerTags; } // answer tag ----- end-> // objective answer tag ----- start-> interface ObjectiveAnswerTagItem { id: string; mainNumber: number; subNumbers: string; score: number; totalScore: number; trackItem: DrawTrackItem; } function parseObjectiveAnswerTags(images: ImageItem[]) { const objectiveAnswerTags: Array = []; if (!cardData?.length) return objectiveAnswerTags; cardData.forEach((page, pindex) => { if (!objectiveAnswerTags[pindex]) objectiveAnswerTags[pindex] = []; const img = images[pindex] as ImageItem; page.columns.forEach((column) => { column.elements.forEach((element) => { if (element.type !== 'FILL_QUESTION') return; const ogroup = objectiveAnswerTags.find((tgroup) => tgroup.some((oitem) => oitem.id === element.parent.id) ); if (ogroup) return; const parent = element.parent; const oaTagItem: ObjectiveAnswerTagItem = { id: parent.id, mainNumber: parent.topicNo, subNumbers: `${parent.startNumber}~${ parent.startNumber + parent.questionsCount - 1 }`, score: 0, totalScore: 0, trackItem: {} as DrawTrackItem, }; // 位置 const area = { x: 0, y: 0, w: 0.44 }; page.exchange.fill_area.forEach((fa) => { fa.items.forEach((fitem) => { if ( fitem.main_number === oaTagItem.mainNumber && fitem.sub_number === parent.startNumber ) { const [x, y] = fitem.options[0]; area.x = x; area.y = y; } }); }); area.x = (area.x - 0.015) * img.width; area.y = (area.y - 0.04) * img.height; area.w *= img.width; // 分数统计 const questions: Array<{ score: number; totalScore: number }> = []; for (let i = 0; i < parent.questionsCount; i++) { const qans = answerMap[ `${parent.topicNo}_${i + parent.startNumber}` ] || { score: 0, totalScore: 0 }; questions[i] = { score: qans.score, totalScore: qans.totalScore, }; } oaTagItem.score = calcSumPrecision( questions.map((q) => q.score || 0) ); oaTagItem.totalScore = calcSumPrecision( questions.map((q) => q.totalScore || 0) ); const tCont = `得分:${oaTagItem.score},满分:${oaTagItem.totalScore}`; const tContLen = strGbLen(tCont) / 2; oaTagItem.trackItem = { type: 'text', option: { x: area.x + area.w - Math.ceil(tContLen * trackTextFontSize), y: area.y, text: tCont, fontSize: trackTextFontSize, }, }; objectiveAnswerTags[pindex].push(oaTagItem); }); }); }); return objectiveAnswerTags; } // objective answer tag ----- end-> /* 首页汇总信息 // 获取汇总记录中评卷员信息 function getSummaryMarkerName(q: Question): string { let markerName = ''; if (q.headerTrackList && q.headerTrackList.length) { markerName = q.headerTrackList[0].userName; } else if (q.markerTrackList && q.markerTrackList.length) { markerName = q.markerTrackList[0].userName; } else if (q.markerList && q.markerList.length) { let markers = q.markerList.filter((marker) => marker.header); if (!markers.length) { markers = q.markerList.filter((marker) => !marker.header); } if (markers.length) markerName = markers[0].userName; } return markerName; } // 解析首页汇总信息 function parseSummaryData(img: ImageItem): DrawTrackItem[] { const isDoubleMark = (rawTask.questionList || []).some((question) => { let userIds = question.markerTrackList.map((track) => track.userId); if ( !userIds.length && question.markerList && question.markerList.length ) { userIds = question.markerList .filter((marker) => !marker.header) .map((marker) => marker.userId); } const uids = new Set(userIds); return uids.size === 2; }); if (isDoubleMark) return []; const dataList: DrawTrackItem[] = []; const sources: string[][] = [['主观题号', '分数', '评卷员']]; (rawTask.questionList || []).forEach((q) => { sources.push([ `${q.mainNumber}-${q.subNumber}`, `${q.score}`, getSummaryMarkerName(q), ]); }); const rowX = img.width * 0.05; const rowY = img.height * 0.11; // const rowW = img.width * 0.45; const columnOffsetLeft = [0, 150, 80 + 150]; sources.forEach((source, sindex) => { source.forEach((cont, cindex) => { dataList.push({ type: 'text', option: { x: rowX + columnOffsetLeft[cindex], y: rowY + sindex * trackInfoLineHeight, text: cont, color: 'red', fontSize: trackInfoFontSize, }, }); }); }); return dataList; } */ return { runTask, }; }