|
|
@@ -6,6 +6,9 @@
|
|
|
<div class="info-list">
|
|
|
<div class="info-item"><span>姓名:</span><span>{{ studentName }}</span></div>
|
|
|
<div class="info-item"><span>报名岗位:</span><span>{{ positionName }}</span></div>
|
|
|
+ <div class="info-item"><span>开始时间:</span><span>{{ startTime || '-' }}</span></div>
|
|
|
+ <div class="info-item"><span>结束时间:</span><span>{{ commitTime || '-' }}</span></div>
|
|
|
+ <div class="info-item"><span>答题时长:</span><span>{{ ansTime || '-' }}</span></div>
|
|
|
</div>
|
|
|
|
|
|
<div class="card-title second">答题信息</div>
|
|
|
@@ -18,7 +21,9 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="card-title second">维度分析</div>
|
|
|
- <div class="chart-placeholder">暂不支持</div>
|
|
|
+ <div v-if="scores.length >= 3" ref="radarChartRef" class="radar-chart"></div>
|
|
|
+ <div v-else class="chart-placeholder">维度数据不足,至少需要3项能力</div>
|
|
|
+
|
|
|
<div class="footer-btn">
|
|
|
<el-button @click="handleBack">返回</el-button>
|
|
|
</div>
|
|
|
@@ -28,7 +33,7 @@
|
|
|
<template v-if="questionList.length > 0">
|
|
|
<div v-for="(item, index) in questionList" :key="item.id" class="question-block">
|
|
|
<div class="question-head">
|
|
|
- <span>第{{ index + 1 }}题</span>
|
|
|
+ <span>第{{ index + 1 }}题 <el-tag size="small" type="info">{{ item.typeName }}</el-tag></span>
|
|
|
<div class="question-result">
|
|
|
<el-tag :type="item.correct ? 'success' : 'danger'">{{ item.correct ? '正确' : '错误' }}</el-tag>
|
|
|
<span>得分</span>
|
|
|
@@ -36,12 +41,25 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="question-title" v-html="item.questionTitle"></div>
|
|
|
- <div class="question-options">
|
|
|
- <div v-for="option in item.options" :key="option.label" class="question-option">
|
|
|
- <el-radio :model-value="item.answer" :label="option.label">{{ option.label }}.<span v-html="option.text"></span></el-radio>
|
|
|
- <span v-if="option.correct" class="correct-text">正确答案</span>
|
|
|
+ <!-- 选择题:显示选项,标记用户选择和正确答案 -->
|
|
|
+ <div v-if="item.options && item.options.length > 0" class="question-options">
|
|
|
+ <div v-for="option in item.options" :key="option.label" class="question-option" :class="{ 'is-user-answer': item.answer && item.answer.includes(option.label) }">
|
|
|
+ <!-- 单选题 -->
|
|
|
+ <el-radio v-if="item.typeName === '单选题'" :model-value="item.answer" :label="option.label" disabled>{{ option.label }}.<span v-html="option.text"></span></el-radio>
|
|
|
+ <!-- 多选题 -->
|
|
|
+ <el-checkbox v-else :model-value="item.answer ? item.answer.includes(option.label) : false" :label="option.label" disabled>{{ option.label }}.<span v-html="option.text"></span></el-checkbox>
|
|
|
+ <el-tag v-if="option.correct" size="small" type="success">正确答案</el-tag>
|
|
|
+ <el-tag v-if="item.answer && item.answer.includes(option.label) && !option.correct" size="small" type="danger">考生选择</el-tag>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <!-- 问答题:显示考生作答 -->
|
|
|
+ <div v-if="item.answer && (!item.options || item.options.length === 0)" class="question-user-answer">
|
|
|
+ <span>考生作答:</span><span v-html="item.answer"></span>
|
|
|
+ </div>
|
|
|
+ <!-- 正确答案(问答题) -->
|
|
|
+ <div v-if="item.typeName === '问答题' && item.testAnsRight" class="question-right-answer">
|
|
|
+ <span>参考答案:</span><span v-html="item.testAnsRight"></span>
|
|
|
+ </div>
|
|
|
<div v-if="item.analysis" class="question-analysis">
|
|
|
<div>答案解析:</div>
|
|
|
<div v-html="item.analysis"></div>
|
|
|
@@ -54,9 +72,10 @@
|
|
|
</PageShell>
|
|
|
</template>
|
|
|
|
|
|
-<script setup name="PostManageEvaluationView" lang="ts">
|
|
|
-import { getCurrentInstance, type ComponentInternalInstance, ref, onMounted } from 'vue';
|
|
|
+<script setup name="EvaluationView" lang="ts">
|
|
|
+import { getCurrentInstance, type ComponentInternalInstance, ref, onMounted, nextTick, watch } from 'vue';
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
+import * as echarts from 'echarts';
|
|
|
import PageShell from '@/components/PageShell/index.vue';
|
|
|
import { getEvaluation, getExamScoreList, getExamPaperQuestions, getExamAnswerList } from '@/api/main/evaluation/index';
|
|
|
|
|
|
@@ -69,129 +88,304 @@ const evaluationId = route.query.evaluationId as string;
|
|
|
const studentId = route.query.studentId as string;
|
|
|
const studentName = ref((route.query.name as string) || '');
|
|
|
const positionName = ref('');
|
|
|
+const startTime = ref('');
|
|
|
+const commitTime = ref('');
|
|
|
+const ansTime = ref('');
|
|
|
|
|
|
const scores = ref<any[]>([]);
|
|
|
const questionList = ref<any[]>([]);
|
|
|
-
|
|
|
-const parseJson = (str: any) => {
|
|
|
- if (typeof str !== 'string') return str || {};
|
|
|
- try { return JSON.parse(str); } catch (e) { return {}; }
|
|
|
+const radarChartRef = ref<HTMLElement | null>(null);
|
|
|
+let radarChart: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+/** 解析接口返回的 msg 字段(JSON 字符串),提取 bizContent */
|
|
|
+const parseBizContent = (res: any) => {
|
|
|
+ const msgStr = res?.msg;
|
|
|
+ if (typeof msgStr !== 'string') return {};
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(msgStr);
|
|
|
+ return parsed?.bizContent || parsed || {};
|
|
|
+ } catch (e) {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-const findStudentInList = (data: any, sid: string, sname: string) => {
|
|
|
- let list = Array.isArray(data) ? data : (data?.data?.list || data?.list || data?.data || data?.rows || data?.records || []);
|
|
|
- if (!Array.isArray(list)) {
|
|
|
- if(data && typeof data === 'object' && Object.keys(data).length > 0) {
|
|
|
- if (sid && (String(data.userId) === String(sid) || String(data.user_id) === String(sid))) {
|
|
|
- return data;
|
|
|
- }
|
|
|
- }
|
|
|
- return null;
|
|
|
+/** 从 score-list 的 bizContent.rows 中查找指定学生的最新记录 */
|
|
|
+const findStudentInScoreList = (bizContent: any, sid: string) => {
|
|
|
+ const rows: any[] = bizContent?.rows || [];
|
|
|
+ if (!Array.isArray(rows) || !sid) return null;
|
|
|
+ // 同一考生可能有多条记录,取最后一条(最新的)
|
|
|
+ let result: any = null;
|
|
|
+ for (const item of rows) {
|
|
|
+ if (String(item.userId) === String(sid)) {
|
|
|
+ result = item;
|
|
|
+ }
|
|
|
}
|
|
|
- return list.find((item: any) =>
|
|
|
- (sid && (String(item.userId) === String(sid) || String(item.user_id) === String(sid) || String(item.studentId) === String(sid))) ||
|
|
|
- (sname && (String(item.userName) === String(sname) || String(item.user_name) === String(sname) || String(item.name) === String(sname)))
|
|
|
- );
|
|
|
+ return result;
|
|
|
};
|
|
|
|
|
|
-const extractQuestions = (data: any) => {
|
|
|
- let qlist = Array.isArray(data) ? data : (data?.data?.questions || data?.data?.questionList || data?.questions || data?.questionList || data?.data || []);
|
|
|
- return Array.isArray(qlist) ? qlist : [];
|
|
|
+/** 将 com_ans 中的 key1~key4 转为 A/B/C/D 标签 */
|
|
|
+const convertComAnsToLabel = (comAns: string): string => {
|
|
|
+ if (!comAns) return '';
|
|
|
+ const keyMap: Record<string, string> = { key1: 'A', key2: 'B', key3: 'C', key4: 'D' };
|
|
|
+ // com_ans 格式可能是 "key2," 或 "key1,key3," 等
|
|
|
+ const keys = comAns.split(',').map((k: string) => k.trim()).filter(Boolean);
|
|
|
+ return keys.map((k: string) => keyMap[k] || k).join('');
|
|
|
};
|
|
|
|
|
|
-const extractAnswers = (ansData: any) => {
|
|
|
- let answers = ansData?.details || ansData?.answerList || ansData?.answers || ansData?.questionList || ansData?.data || [];
|
|
|
- return Array.isArray(answers) ? answers : [];
|
|
|
+const TYPE_NAME_MAP: Record<number, string> = {
|
|
|
+ 1: '单选题',
|
|
|
+ 2: '多选题',
|
|
|
+ 3: '判断题',
|
|
|
+ 5: '问答题'
|
|
|
};
|
|
|
|
|
|
-const combineQA = (questions: any[], answers: any[]) => {
|
|
|
+/** 从 paper-questions 的 bizContent.rows 中提取题目列表
|
|
|
+ * 注意:paper-questions 的 testId 是 "--",真实的 test_id 在 answer-list 中
|
|
|
+ * 所以这里需要用 answer-list 中的信息来确定题目 ID
|
|
|
+ */
|
|
|
+const extractQuestions = (bizContent: any, ansMap: Map<string, any>) => {
|
|
|
+ const rows: any[] = bizContent?.rows || [];
|
|
|
+ if (!Array.isArray(rows)) return [];
|
|
|
+
|
|
|
+ // 建立 question 文本 -> answer-map 中 test_id 的映射
|
|
|
+ const questionToTestId = new Map<string, string>();
|
|
|
+ ansMap.forEach((detail: any, testId: string) => {
|
|
|
+ const q = detail.question || '';
|
|
|
+ if (q) {
|
|
|
+ questionToTestId.set(q, testId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const questions: any[] = [];
|
|
|
+ let seq = 0;
|
|
|
+ rows.forEach((row: any) => {
|
|
|
+ const tc = row.testContent || {};
|
|
|
+ const qType = Number(tc.type);
|
|
|
+ const questionTitle = tc.question || '';
|
|
|
+ const analysis = tc.analysis || '';
|
|
|
+
|
|
|
+ // 优先用 answer-list 中的 test_id 匹配
|
|
|
+ let questionId = questionToTestId.get(questionTitle);
|
|
|
+ if (!questionId) {
|
|
|
+ questionId = tc.test_id || row.testId || `q_${seq}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建选项:问答题无选项,单选/多选题从 answer1~answer4 + key1~key4 构建
|
|
|
+ const options: { label: string; text: string; correct: boolean }[] = [];
|
|
|
+ if (qType === 1 || qType === 2) {
|
|
|
+ const answerFields = ['answer1', 'answer2', 'answer3', 'answer4'];
|
|
|
+ const keyFields = ['key1', 'key2', 'key3', 'key4'];
|
|
|
+ const labels = ['A', 'B', 'C', 'D'];
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
+ const text = tc[answerFields[i]];
|
|
|
+ if (text) {
|
|
|
+ options.push({
|
|
|
+ label: labels[i],
|
|
|
+ text,
|
|
|
+ correct: tc[keyFields[i]] === '1'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ questions.push({
|
|
|
+ id: questionId,
|
|
|
+ questionTitle,
|
|
|
+ analysis,
|
|
|
+ options,
|
|
|
+ type: qType,
|
|
|
+ typeName: TYPE_NAME_MAP[qType] || '其他',
|
|
|
+ testName: row.testName || ''
|
|
|
+ });
|
|
|
+ seq++;
|
|
|
+ });
|
|
|
+ return questions;
|
|
|
+};
|
|
|
+
|
|
|
+/** 从 answer-list 的 bizContent.rows 中查找指定学生,并提取其作答信息 */
|
|
|
+const extractAnswers = (bizContent: any, sid: string) => {
|
|
|
+ const rows: any[] = bizContent?.rows || [];
|
|
|
+ if (!Array.isArray(rows) || !sid) return new Map<string, any>();
|
|
|
+ const studentRow = rows.find((item: any) => String(item.userId) === String(sid));
|
|
|
+ if (!studentRow?.ansAndScore) return new Map<string, any>();
|
|
|
+
|
|
|
+ const ansMap = new Map<string, any>();
|
|
|
+ const ansAndScore: any[] = studentRow.ansAndScore;
|
|
|
+
|
|
|
+ for (const group of ansAndScore) {
|
|
|
+ if (!Array.isArray(group)) continue;
|
|
|
+ for (let i = 1; i < group.length; i++) {
|
|
|
+ const entry = group[i];
|
|
|
+ if (Array.isArray(entry) && entry.length >= 2) {
|
|
|
+ const testId = String(entry[0]);
|
|
|
+ const detail = entry[1] || {};
|
|
|
+ ansMap.set(testId, detail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ansMap;
|
|
|
+};
|
|
|
+
|
|
|
+/** 将题目和作答合并为展示数据 */
|
|
|
+const combineQA = (questions: any[], ansMap: Map<string, any>) => {
|
|
|
return questions.map((q, index) => {
|
|
|
- let ans = answers.find(a => String(a.questionId) === String(q.id) || String(a.question_id) === String(q.id) || String(a.id) === String(q.id));
|
|
|
-
|
|
|
- let optionsObj = q.options || q.qOption || q.questionOptions || [];
|
|
|
- if (typeof optionsObj === 'string') {
|
|
|
- try { optionsObj = JSON.parse(optionsObj); } catch(e) {}
|
|
|
+ const ans = ansMap.get(String(q.id));
|
|
|
+
|
|
|
+ let correct = false;
|
|
|
+ if (ans) {
|
|
|
+ if (ans.is_ok === 'right') correct = true;
|
|
|
}
|
|
|
-
|
|
|
- let formattedOptions = Array.isArray(optionsObj) ? optionsObj.map((o: any, idx: number) => {
|
|
|
- return {
|
|
|
- label: o.label || o.key || o.option || String.fromCharCode(65 + idx),
|
|
|
- text: o.text || o.content || o.value || o.optionDesc || '',
|
|
|
- correct: !!o.correct || !!o.isRight || !!o.isCorrect
|
|
|
- };
|
|
|
- }) : [];
|
|
|
-
|
|
|
- let c = false;
|
|
|
+
|
|
|
+ // 用户答案
|
|
|
+ let answer = '';
|
|
|
if (ans) {
|
|
|
- if(ans.isCorrect !== undefined) c = !!ans.isCorrect;
|
|
|
- else if (ans.correct !== undefined) c = !!ans.correct;
|
|
|
- else if (Number(ans.score) > 0) c = true;
|
|
|
+ const comAns = ans.com_ans || '';
|
|
|
+ if (comAns && (q.type === 1 || q.type === 2)) {
|
|
|
+ // 选择题:将 key1~key4 转为 A~D
|
|
|
+ answer = convertComAnsToLabel(comAns);
|
|
|
+ } else if (comAns) {
|
|
|
+ // 问答题:直接使用 HTML 内容
|
|
|
+ answer = comAns;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 正确答案文本(问答题用)
|
|
|
+ const testAnsRight = ans?.test_ans_right || '';
|
|
|
+
|
|
|
+ const options = q.options.map((o: any) => ({ ...o }));
|
|
|
+
|
|
|
return {
|
|
|
- id: q.id || `q_${index}_${Math.random().toString(36).substring(2, 6)}`,
|
|
|
- questionTitle: q.title || q.content || q.questionContent || '未命名题目',
|
|
|
- correct: c,
|
|
|
- score: ans?.score || 0,
|
|
|
- answer: ans?.userAnswer || ans?.answer || ans?.myAnswer || '',
|
|
|
- options: formattedOptions.length > 0 ? formattedOptions : [ { label: 'A', text: '选项A' }, { label: 'B', text: '选项B' } ],
|
|
|
- analysis: q.analysis || q.answerAnalysis || q.qAnalysis || q.questionAnalysis || ''
|
|
|
+ id: q.id || `q_${index}`,
|
|
|
+ questionTitle: q.questionTitle || '未命名题目',
|
|
|
+ correct,
|
|
|
+ score: ans?.score ?? 0,
|
|
|
+ answer,
|
|
|
+ options: options.length > 0 ? options : q.type === 5 ? [] : [{ label: 'A', text: '选项A' }, { label: 'B', text: '选项B' }],
|
|
|
+ analysis: q.analysis || '',
|
|
|
+ typeName: q.typeName,
|
|
|
+ testName: q.testName,
|
|
|
+ testAnsRight
|
|
|
};
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const loadData = async () => {
|
|
|
- if(!evaluationId) return;
|
|
|
- loading.value = true;
|
|
|
- try {
|
|
|
- const res = await getEvaluation(evaluationId);
|
|
|
- const evalData = res.data;
|
|
|
- positionName.value = evalData.position || '未知岗位';
|
|
|
-
|
|
|
- const abilityConfigs = evalData.abilityConfigs || [];
|
|
|
- let allQuestions: any[] = [];
|
|
|
- let scoreItems: any[] = [];
|
|
|
-
|
|
|
- for (const ability of abilityConfigs) {
|
|
|
- const examId = ability.thirdExamInfoId;
|
|
|
- if(!examId) continue;
|
|
|
-
|
|
|
- const scoreRes = await getExamScoreList({ examInfoId: examId, page: 1 }).catch(()=>({data: {}}));
|
|
|
- const scoreData = parseJson(scoreRes.data);
|
|
|
- const myScoreObj = findStudentInList(scoreData, studentId, studentName.value);
|
|
|
-
|
|
|
- const score = myScoreObj?.totalScore || myScoreObj?.score || myScoreObj?.examScore || 0;
|
|
|
- const passMark = ability.thirdExamPassMark || 0;
|
|
|
- const pass = Number(score) >= Number(passMark);
|
|
|
-
|
|
|
- scoreItems.push({
|
|
|
- abilityName: ability.abilityName || ability.thirdExamName || '考核',
|
|
|
- score: score,
|
|
|
- totalScore: ability.thirdExamTotalScore || 100,
|
|
|
- pass
|
|
|
- });
|
|
|
-
|
|
|
- const paperRes = await getExamPaperQuestions({ examInfoId: examId }).catch(()=>({data: {}}));
|
|
|
- const paperData = parseJson(paperRes.data);
|
|
|
- const questions = extractQuestions(paperData);
|
|
|
-
|
|
|
- const answerRes = await getExamAnswerList({ examInfoId: examId }).catch(()=>({data: {}}));
|
|
|
- const answerData = parseJson(answerRes.data);
|
|
|
- const myAnswerObj = findStudentInList(answerData, studentId, studentName.value);
|
|
|
- const answers = myAnswerObj ? extractAnswers(myAnswerObj) : [];
|
|
|
-
|
|
|
- allQuestions.push(...combineQA(questions, answers));
|
|
|
+/** 渲染雷达图 */
|
|
|
+const renderRadarChart = () => {
|
|
|
+ if (!radarChartRef.value || scores.value.length < 3) return;
|
|
|
+
|
|
|
+ if (!radarChart) {
|
|
|
+ radarChart = echarts.init(radarChartRef.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ const indicator = scores.value.map((s: any) => ({
|
|
|
+ name: s.abilityName,
|
|
|
+ max: Number(s.totalScore) || 100
|
|
|
+ }));
|
|
|
+
|
|
|
+ const dataValues = scores.value.map((s: any) => Number(s.score) || 0);
|
|
|
+
|
|
|
+ radarChart.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
+ },
|
|
|
+ radar: {
|
|
|
+ indicator,
|
|
|
+ radius: '65%',
|
|
|
+ name: {
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 11
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitArea: {
|
|
|
+ areaStyle: {
|
|
|
+ color: ['rgba(64,158,255,0.05)', 'rgba(64,158,255,0.1)', 'rgba(64,158,255,0.15)', 'rgba(64,158,255,0.2)']
|
|
|
}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ type: 'radar',
|
|
|
+ data: [{
|
|
|
+ value: dataValues,
|
|
|
+ name: '得分',
|
|
|
+ areaStyle: {
|
|
|
+ color: 'rgba(64,158,255,0.2)'
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ color: '#409EFF'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409EFF'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ }]
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const loadData = async () => {
|
|
|
+ if (!evaluationId) return;
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await getEvaluation(evaluationId);
|
|
|
+ const evalData = res.data;
|
|
|
+ positionName.value = evalData.position || '未知岗位';
|
|
|
+
|
|
|
+ const abilityConfigs = evalData.abilityConfigs || [];
|
|
|
+ let allQuestions: any[] = [];
|
|
|
+ let scoreItems: any[] = [];
|
|
|
+
|
|
|
+ for (const ability of abilityConfigs) {
|
|
|
+ const examId = ability.thirdExamInfoId;
|
|
|
+ if (!examId) continue;
|
|
|
+
|
|
|
+ // 获取成绩列表
|
|
|
+ const scoreRes = await getExamScoreList({ examInfoId: examId, page: 1 }).catch(() => ({ msg: '{}' }));
|
|
|
+ const scoreBiz = parseBizContent(scoreRes);
|
|
|
+ const myScoreObj = findStudentInScoreList(scoreBiz, studentId);
|
|
|
+
|
|
|
+ const score = myScoreObj?.score || 0;
|
|
|
+ const passMark = ability.thirdExamPassMark || ability.passingScore || 0;
|
|
|
+ const pass = myScoreObj ? String(myScoreObj.isPass) === '1' : Number(score) >= Number(passMark);
|
|
|
+
|
|
|
+ // 取考生时间信息(只需取第一次有数据的记录)
|
|
|
+ if (myScoreObj && !startTime.value) {
|
|
|
+ startTime.value = myScoreObj.startTime || '';
|
|
|
+ commitTime.value = myScoreObj.commitTime || '';
|
|
|
+ ansTime.value = myScoreObj.ansTime || '';
|
|
|
+ }
|
|
|
|
|
|
- scores.value = scoreItems;
|
|
|
- questionList.value = allQuestions;
|
|
|
+ scoreItems.push({
|
|
|
+ abilityName: ability.abilityName || ability.thirdExamName || '考核',
|
|
|
+ score: score,
|
|
|
+ totalScore: ability.thirdExamTotalScore || ability.score || 100,
|
|
|
+ pass
|
|
|
+ });
|
|
|
|
|
|
- } catch (e) {
|
|
|
- console.error(e);
|
|
|
- proxy?.$modal?.msgError('获取测评数据失败');
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
+ // 获取答题详情(先获取,因为需要用 test_id 来匹配 paper-questions)
|
|
|
+ const answerRes = await getExamAnswerList({ examInfoId: examId }).catch(() => ({ msg: '{}' }));
|
|
|
+ const answerBiz = parseBizContent(answerRes);
|
|
|
+ const ansMap = extractAnswers(answerBiz, studentId);
|
|
|
+
|
|
|
+ // 获取试卷题目
|
|
|
+ const paperRes = await getExamPaperQuestions({ examInfoId: examId }).catch(() => ({ msg: '{}' }));
|
|
|
+ const paperBiz = parseBizContent(paperRes);
|
|
|
+ const questions = extractQuestions(paperBiz, ansMap);
|
|
|
+
|
|
|
+ allQuestions.push(...combineQA(questions, ansMap));
|
|
|
}
|
|
|
+
|
|
|
+ scores.value = scoreItems;
|
|
|
+ questionList.value = allQuestions;
|
|
|
+
|
|
|
+ // 渲染雷达图
|
|
|
+ await nextTick();
|
|
|
+ renderRadarChart();
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ proxy?.$modal?.msgError('获取测评数据失败');
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const handleBack = () => {
|
|
|
@@ -200,22 +394,31 @@ const handleBack = () => {
|
|
|
};
|
|
|
|
|
|
onMounted(() => {
|
|
|
- loadData();
|
|
|
+ loadData();
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.evaluation-view-page {
|
|
|
display: grid;
|
|
|
- grid-template-columns: 180px 1fr;
|
|
|
+ grid-template-columns: 280px 1fr;
|
|
|
gap: 16px;
|
|
|
}
|
|
|
|
|
|
-.left-card,
|
|
|
+.left-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 16px;
|
|
|
+ height: calc(100vh - 180px);
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
.right-card {
|
|
|
background: #fff;
|
|
|
border-radius: 6px;
|
|
|
padding: 16px;
|
|
|
+ height: calc(100vh - 180px);
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.card-title {
|
|
|
@@ -252,6 +455,12 @@ onMounted(() => {
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.radar-chart {
|
|
|
+ height: 220px;
|
|
|
+ margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
.footer-btn {
|
|
|
@@ -298,6 +507,12 @@ onMounted(() => {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-option.is-user-answer {
|
|
|
+ background: #ecf5ff;
|
|
|
}
|
|
|
|
|
|
.correct-text {
|
|
|
@@ -310,4 +525,22 @@ onMounted(() => {
|
|
|
color: #606266;
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
+
|
|
|
+.question-user-answer {
|
|
|
+ margin-top: 8px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-right-answer {
|
|
|
+ margin-top: 8px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ background: #f0f9eb;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #67c23a;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
</style>
|