| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- <!-- 试卷详情 -->
- <template>
- <div class="page-container examDetails-addPage">
- <!-- 左侧面板 -->
- <div class="left-panel">
- <div class="exam-title">{{ examInfo.examTypeName }}</div>
- <div class="exam-subtitle">总{{ examInfo.questionCount }}题 / 共{{ examInfo.totalScore }}分</div>
- <div class="exam-name-box">{{ examInfo.examName }}</div>
- <div class="exam-info-box">
- <div class="info-item"><span class="label">试卷总分:</span>{{ examInfo.totalScore }}分</div>
- <div class="info-item"><span class="label">及格分数:</span>{{ examInfo.passScore }}分</div>
- <div class="info-item"><span class="label">我的得分:</span>{{ examInfo.earnedScore }}分</div>
- <div class="info-item"><span class="label">考试用时:</span>{{ examInfo.durationText }}</div>
- <div class="info-item">
- <span class="label">考试结果:</span>
- <span :class="examInfo.passStatus == 1 ? 'result-pass' : 'result-fail'">
- {{ examInfo.passStatus == 1?'通过':'无效' }}
- </span>
- </div>
- </div>
- <!-- 题目导航 -->
- <div v-for="group in questionTypeGroups" :key="group.type" class="question-group">
- <div class="group-header">
- <span class="group-title">{{ group.questionTypeName }}</span>
- <span class="group-count">{{ group.questionCount }}题 / 共{{ group.totalScore }}分</span>
- </div>
- <div class="question-nums">
- <div
- v-for="(item,index) in group.questions"
- :key="item.questionNo"
- :class="['num-item', getNumClass(item), currentIndex === item.questionNo ? 'num-active' : '']"
- @click="selectQuestion(item)">
- {{ item.questionNo }}
- </div>
- </div>
- </div>
- </div>
- <!-- 右侧内容区:只展示当前选中题目 -->
- <div class="right-panel" v-if="currentQuestion">
- <div class="question-card">
- <!-- 题目头部 -->
- <div class="question-header">
- <div class="question-index-box">{{ currentQuestion.questionNo }}</div>
- <div class="question-type-score">{{ currentQuestion.questionTypeName }}({{ currentQuestion.questionScore }}分)</div>
- <div class="question-stars">
- <i
- v-for="s in 4"
- :key="s"
- :class="['star-icon', s <= currentQuestion.difficulty ? 'star-active' : 'star-empty']"
- >★</i>
- </div>
- </div>
- <!-- 题目内容(富文本) -->
- <div class="question-content" v-html="currentQuestion.questionContent"></div>
- <!-- 单选题选项/多选题选项 -->
- <div class="options-list">
- <div v-for="(minItem,minIndex) in currentQuestion.options"
- :key="minIndex" class="option-item"
- :class="isValueInList(minItem.optionLabel,currentQuestion.correctAnswerTags)?'option-selected':''">
- <span class="option-radio">
- <i v-if="isValueInList(minItem.optionLabel,currentQuestion.correctAnswerTags)" class="el-icon-check radio-checked"></i>
- <i v-else class="radio-empty"></i>
- </span>
- <span class="option-key" v-if="currentQuestion.questionType == '1' || currentQuestion.questionType == '2'">{{ minItem.optionLabel }}</span>
- <span class="option-text" v-html="minItem.optionContent"></span>
- </div>
- </div>
- <!-- 答题结果 -->
- <div class="answer-result-box">
- <div class="answer-status">
- <span v-if="currentQuestion.isCorrect == 1" class="status-correct">✅ 回答正确</span>
- <span v-else-if="currentQuestion.isCorrect == 0" class="status-wrong">❌ 回答错误</span>
- <span v-else class="status-wrong">❌ 未作答</span>
- </div>
- <div class="answer-detail">
- <div class="answer-row"><span class="answer-label">回答选项:</span>{{ arrayToString(currentQuestion.answerTags) }}</div>
- <div class="analysis-title">试题解析</div>
- <div class="analysis-content" v-html="currentQuestion.analysis"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import {
- examElExamAttemptReviewSidebar, examElExamAttemptReviewQuestion
- } from '@/api/safetyEducationExaminationNew/index'
- export default {
- name: 'examDetails',
- props: {
- examDetailsPropsData: {}
- },
- data() {
- return {
- newData:{},
- currentIndex: null,
- examInfo: {
- examTypeName: '',
- examName: '',
- questionCount: 0,
- totalScore: 0,
- passScore: 0,
- earnedScore: 0,
- durationText: '',
- passStatus: null,
- },
- questionTypeGroups: [],
- questionCache: {},
- currentQuestion: null,
- // 右侧加载中状态
- questionLoading: false,
- mockQuestions: {}
- }
- },
- computed: {},
- created() {},
- mounted() {
- this.initialize()
- },
- methods: {
- initialize() {
- this.$set(this,'newData',this.examDetailsPropsData);
- //左侧数据
- examElExamAttemptReviewSidebar({attemptId:this.newData.attemptId}).then(response => {
- this.$set(this,'examInfo',{
- examTypeName: response.data.examTypeName?response.data.examTypeName:'',
- examName: response.data.examName?response.data.examName:'',
- questionCount: response.data.questionCount?response.data.questionCount:0,
- totalScore: response.data.totalScore?response.data.totalScore:0,
- passScore: response.data.passScore?response.data.passScore:0,
- earnedScore: response.data.earnedScore?response.data.earnedScore:0,
- durationText: response.data.durationText?response.data.durationText:'',
- passStatus: response.data.passStatus?response.data.passStatus:null,
- });
- this.$set(this,'questionTypeGroups',response.data.questionTypeGroups);
- // 默认选中第一题(假数据直接从 data 里读取,无需接口)
- const firstGroup = this.questionTypeGroups[0]
- if (firstGroup && firstGroup.questions.length) {
- this.$set(this, 'currentIndex', firstGroup.questions[0].questionNo);
- this.examElExamAttemptReviewQuestion(firstGroup.questions[0].snapshotQuestionId);
- }
- });
- },
- //右侧数据
- examElExamAttemptReviewQuestion(id){
- examElExamAttemptReviewQuestion({attemptId:this.newData.attemptId,snapshotQuestionId:id}).then(response => {
- this.$set(this,'currentQuestion',response.data);
- });
- },
- backPage() {
- this.$parent.tableButton(6)
- },
- // 点击左侧题号:切换当前题目,按需从接口加载详情
- selectQuestion(item) {
- this.$set(this, 'currentIndex', item.questionNo);
- this.examElExamAttemptReviewQuestion(item.snapshotQuestionId);
- },
- // 判断选项是否被选中
- isSelectedOption(q, key) {
- if (!q.userAnswer) return false
- return q.userAnswer.split(',').includes(key)
- },
- // 根据左侧题目列表的 status 字段获取题号样式
- // status: '1' → 绿色,'0' → 红色,'null' → 灰色
- getNumClass(item) {
- if (item.isCorrect === 1) return 'num-correct'
- if (item.isCorrect === 0) return 'num-wrong'
- return 'num-unanswered'
- },
- //匹配用户选择
- isValueInList(target, list) {
- if (!Array.isArray(list)) return false;
- return list.some(item => item === target);
- },
- //返回用户选择答案
- arrayToString(list) {
- // 防御性编程:如果传入的不是数组,返回空字符串防止报错
- if (!Array.isArray(list)) return '';
- return list.join(',');
- },
- }
- }
- </script>
- <style scoped lang="scss">
- .examDetails-addPage {
- flex: 1;
- display: flex;
- overflow: hidden;
- background: #f5f5f5;
- /* ========== 左侧面板 ========== */
- .left-panel {
- width: 400px;
- min-width: 400px;
- background: #fff;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- padding: 20px 16px;
- border-right: 1px solid #e8e8e8;
- &::-webkit-scrollbar { width: 4px; }
- &::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
- .exam-title {
- font-size: 16px;
- font-weight: 700;
- color: #1890ff;
- margin-bottom: 4px;
- }
- .exam-subtitle {
- font-size: 13px;
- color: #666;
- margin-bottom: 16px;
- }
- .exam-name-box {
- font-size: 13px;
- color: #333;
- margin-bottom: 12px;
- line-height: 1.6;
- }
- .exam-info-box {
- background: #f5f5f5;
- border-radius: 6px;
- padding: 12px;
- margin-bottom: 20px;
- .info-item {
- font-size: 13px;
- color: #333;
- margin-bottom: 6px;
- line-height: 1.5;
- &:last-child { margin-bottom: 0; }
- .label { font-weight: 500; }
- .result-pass { color: #52c41a; font-weight: 700; }
- .result-fail { color: #faad14; font-weight: 700; }
- }
- }
- .question-group {
- margin-bottom: 16px;
- .group-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- .group-title { font-size: 14px; font-weight: 600; color: #333; }
- .group-count { font-size: 12px; color: #999; }
- }
- .question-nums {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- .num-item {
- width: 36px;
- height: 36px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- position: relative;
- color: #fff;
- transition: opacity 0.2s;
- &:hover { opacity: 0.85; }
- &.num-correct { background: #52c41a; }
- &.num-wrong { background: #ff4d4f; }
- &.num-unanswered { background: #999; color: #fff; }
- &.num-active {
- outline: 3px solid #1890ff;
- outline-offset: 1px;
- }
- }
- }
- }
- }
- /* ========== 右侧内容区 ========== */
- .right-panel {
- flex: 1;
- overflow-y: auto;
- padding: 20px;
- &::-webkit-scrollbar { width: 6px; }
- &::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
- .question-card {
- background: #fff;
- border-radius: 8px;
- padding: 20px;
- .question-header {
- display: flex;
- align-items: center;
- margin-bottom: 12px;
- .question-index-box {
- width: 28px;
- height: 28px;
- background: #1890ff;
- color: #fff;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 13px;
- font-weight: 700;
- margin-right: 10px;
- flex-shrink: 0;
- }
- .question-type-score {
- font-size: 15px;
- font-weight: 600;
- color: #333;
- flex: 1;
- }
- .question-stars {
- display: flex;
- gap: 2px;
- .star-icon {
- font-size: 18px;
- font-style: normal;
- &.star-active { color: #fadb14; }
- &.star-empty { color: #d9d9d9; }
- }
- }
- }
- .question-content {
- font-size: 14px;
- font-weight: 600;
- color: #333;
- margin-bottom: 16px;
- line-height: 1.6;
- }
- .options-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-bottom: 16px;
- .option-item {
- display: flex;
- align-items: center;
- border: 1px solid #e8e8e8;
- border-radius: 6px;
- padding: 10px 14px;
- cursor: default;
- transition: border-color 0.2s;
- &.option-selected {
- border-color: #1890ff;
- background: #e6f7ff;
- .option-key { color: #1890ff; }
- .option-text { color: #1890ff; }
- }
- .option-radio {
- width: 16px;
- height: 16px;
- border-radius: 50%;
- border: 1px solid #d9d9d9;
- margin-right: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- .radio-checked { color: #1890ff; font-size: 12px; }
- .radio-empty { display: block; width: 8px; height: 8px; }
- }
- .option-checkbox {
- margin-right: 10px;
- flex-shrink: 0;
- }
- .option-key {
- font-size: 14px;
- font-weight: 600;
- color: #666;
- margin-right: 8px;
- width: 16px;
- flex-shrink: 0;
- }
- .option-text {
- font-size: 14px;
- color: #333;
- flex: 1;
- }
- }
- }
- .answer-result-box {
- background: #f5f5f5;
- border-radius: 6px;
- padding: 14px 16px;
- .answer-status {
- margin-bottom: 10px;
- font-size: 14px;
- font-weight: 600;
- .status-correct { color: #52c41a; }
- .status-wrong { color: #ff4d4f; }
- }
- .answer-detail {
- .answer-row {
- font-size: 14px;
- color: #333;
- margin-bottom: 10px;
- .answer-label { font-weight: 600; }
- }
- .analysis-title {
- font-size: 14px;
- font-weight: 600;
- color: #333;
- margin-bottom: 6px;
- }
- .analysis-content {
- font-size: 13px;
- /*color: #fa8c16;*/
- line-height: 1.7;
- }
- }
- }
- }
- }
- }
- </style>
- <style lang="scss">
- .viewGrades-addPage-drawer{
- .el-drawer__body{
- padding:0;
- }
- .el-drawer__header{
- margin:0;
- }
- }
- </style>
|