1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210 |
- 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<ImageItem>[] = [];
- 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<Track>(q.headerTrackList)
- : addTrackColorAttr<Track>(q.markerTrackList);
- })
- .flat();
- const tagLists = (rawTask.questionList || [])
- .map((q) =>
- q.headerTagList?.length
- ? addHeaderTrackColorAttr<SpecialTag>(q.headerTagList)
- : addTrackColorAttr<SpecialTag>(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<ImageItem[]> {
- 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<T extends { color?: string }>(
- 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<string, string> = {};
- 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<DrawTrackItem[]> {
- const dataList: Array<DrawTrackItem[]> = [];
- 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<number, string[]> = {};
- // 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<number, string[]> = {};
- 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<ObjectiveAnswerTagItem[]> = [];
- 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,
- };
- }
|