| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997 |
- <template>
- <!-- 教辅作业作答页。路由固定为 /teachingHomework/answer,不使用地址栏参数 -->
- <div class="homework_answer" v-if="homework">
- <!-- 顶部:返回、科目、任务信息、倒计时、作答进度 -->
- <div class="answer_header">
- <el-button class="back_btn" @click="goBack">
- <i class="iconfont icon_return"></i>返回
- </el-button>
- <div class="header_main">
- <div class="title_row">
- <span class="subject_badge" :style="{ background: subjectColor }">{{ homework.subjectName }}</span>
- <span class="task_title">{{ taskTitle }}</span>
- </div>
- <div class="meta_row">
- <span>布置教师:{{ homework.teacherName }}</span>
- <span>试题数量:{{ questions.length }}</span>
- <span>结束时间:{{ homework.endTime || '-' }}</span>
- <span class="countdown">倒计时:{{ countdownText }}</span>
- <span class="progress">作答进度:{{ answeredCount }}/{{ questions.length }}</span>
- </div>
- </div>
- </div>
- <!-- 主体:左侧试题序号,右侧题干 + 底部固定作答区 -->
- <div class="answer_body" v-loading="loading">
- <!-- 试题列表:单选题 / 解答题分组,序号可切换当前题 -->
- <div class="question_nav">
- <div class="nav_title">试题列表</div>
- <div class="nav_group" v-for="group in questionGroups" :key="group.type">
- <div class="group_name">{{ group.name }}</div>
- <div class="num_grid">
- <!-- 序号仅展示作答进度,不支持点击跳题,只能按顺序逐题作答 -->
- <span v-for="item in group.list" :key="item.id" class="num_btn" :class="numClass(item)">
- {{ item.index + 1 }}
- </span>
- </div>
- </div>
- </div>
- <!-- 右侧题目:上方滚动题干,下方作答区不随题干滚动 -->
- <div class="question_panel" v-if="currentQuestion">
- <div class="question_scroll">
- <div class="q_tags">
- <span class="q_no">{{ currentQuestion.index + 1 }}</span>
- <span class="q_type">{{ currentQuestion.typeName }}</span>
- <span class="q_level">{{ currentQuestion.difficulty }}</span>
- </div>
- <div class="q_stem" v-html="currentQuestion.stem"></div>
- <div v-if="currentQuestion.type === 'single' && hasOptionText" class="q_options">
- <div class="q_option" v-for="opt in currentQuestion.options" :key="opt.key">
- <span class="opt_key">{{ opt.key }}.</span>
- <span>{{ opt.text }}</span>
- </div>
- </div>
- </div>
- <!-- 作答区固定在题目卡片底部。选择题选选项,解答题只拍照,没有手写输入框 -->
- <div class="answer_dock">
- <!-- 选择题:我的作答 + 选项;解答题:已上传图片,可删除 -->
- <div class="dock_answer">
- <span class="dock_label">我的作答:</span>
- <span v-if="isSingle" class="dock_sub_no">(1)</span>
- <el-checkbox-group v-if="isSingle" class="choice_radio" v-model="answers[currentQuestion.id]"
- @change="persistState">
- <el-checkbox v-for="opt in currentQuestion.options" :key="opt.key" :label="opt.key">
- {{ opt.key }}
- </el-checkbox>
- </el-checkbox-group>
- <div v-else class="photo_list">
- <div
- class="photo_item"
- v-for="(img, imgIndex) in currentPhotos"
- :key="imgIndex"
- :class="{ active: imgIndex === 0 }">
- <img :src="img" alt="" @click="previewPhoto(imgIndex)">
- <i class="el-icon-close photo_remove" @click.stop="removePhoto(imgIndex)"></i>
- </div>
- </div>
- </div>
- <!-- 选择题禁用拍照;解答题可上传。最后一题把「下一题」换成「提交」 -->
- <div class="dock_actions">
- <el-button class="photo_btn" :type="isSingle ? 'default' : 'primary'"
- :disabled="isSingle || photoLimitReached" :loading="uploading"
- @click="handlePhoto">
- <img class="camera_icon" :src="isSingle ? cameraGray : cameraWhite" alt="">
- 拍照上传
- </el-button>
- <el-button class="nav_btn" :disabled="currentQuestion.index === 0" @click="prevQuestion">
- 上一题
- </el-button>
- <el-button v-if="!isLastQuestion" class="nav_btn" :loading="saving" @click="nextQuestion">
- 下一题
- </el-button>
- <el-button v-else class="nav_btn" :loading="saving" @click="submitAnswer">
- 提交
- </el-button>
- </div>
- <input ref="photoInput" class="photo_input" type="file" accept="image/*" multiple @change="onPhotoChange">
- </div>
- </div>
- </div>
- <!-- 图片放大预览:支持缩放、旋转,复用 Element UI ImageViewer -->
- <el-image-viewer
- v-if="imgVisible"
- :z-index="9999"
- :initial-index="previewIndex"
- :url-list="currentPhotos"
- :on-close="closePhotoPreview" />
- </div>
- </template>
- <script>
- import ElImageViewer from 'element-ui/packages/image/src/image-viewer';
- // 作答状态只存 sessionStorage,刷新仍停留本页,且地址栏不带参数
- const STORAGE_KEY = 'teachingHomeworkAnswer';
- const OPTION_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- // 每道主观题最多上传的作答图片数量
- const MAX_PHOTOS = 4;
- function isChoiceQuestion(item) {
- return Number(item.isObjective) === 1 || Number(item.isJudge) === 1;
- }
- function buildOptions(item) {
- const count = Number(item.optionsNum) || (Number(item.isJudge) === 1 ? 2 : 4);
- const options = [];
- for (let i = 0; i < count && i < OPTION_KEYS.length; i += 1) {
- options.push({ key: OPTION_KEYS.charAt(i), text: '' });
- }
- return options;
- }
- function mapQuestion(item, index) {
- const choice = isChoiceQuestion(item);
- const id = item.homeWorkStuQuesId || item.questionId || index;
- return {
- id,
- questionId: item.questionId,
- homeWorkStuQuesId: item.homeWorkStuQuesId,
- type: choice ? 'single' : 'photo',
- typeName: item.questionTypeName || (choice ? '客观题' : '主观题'),
- groupName: item.questionTypeName || (choice ? '客观题' : '主观题'),
- difficulty: item.difficultyName || '',
- stem: item.questionContent || '',
- options: choice ? buildOptions(item) : [],
- writeAnswer: item.writeAnswer || '',
- writeUrls: Array.isArray(item.writeUrls) ? item.writeUrls : [],
- submitEndTime: item.submitEndTime || '',
- };
- }
- export default {
- name: 'TeachingHomeworkAnswer',
- components: {
- ElImageViewer,
- },
- data() {
- return {
- homework: null, // 当前作业任务,来自 sessionStorage
- questions: [],
- loading: false,
- currentIndex: 0, // 当前题下标
- answers: {}, // 选择题为所选选项 key 数组,拍照题为图片 url 数组
- imgVisible: false, // 是否打开图片放大预览
- previewIndex: 0, // 预览起始图片下标
- cameraGray: require('@/assets/homeworkTask/camera_999.png'),
- cameraWhite: require('@/assets/homeworkTask/camera_fff.png'),
- nowTick: Date.now(),
- timer: null,
- uploading: false,
- saving: false,
- };
- },
- computed: {
- // 标题后拼接班级,名称里已有班级则不重复加
- taskTitle() {
- if (!this.homework) return '';
- const name = this.homework.homeworkName || '';
- const className = this.homework.className || '';
- if (!className || name.indexOf(className) !== -1) return name;
- return `${name} ${className}`;
- },
- // 科目色标,与靶向提升科目颜色一致
- subjectColor() {
- const name = (this.homework && this.homework.subjectName) || '';
- return this.$global.getSubjectsName(name, null, 'color');
- },
- // 按题型分组,供左侧序号导航使用
- questionGroups() {
- const groups = [];
- this.questions.forEach((item, index) => {
- let group = groups.find(g => g.type === item.type);
- if (!group) {
- group = { type: item.type, name: item.groupName, list: [] };
- groups.push(group);
- }
- group.list.push({ ...item, index });
- });
- return groups;
- },
- // 当前正在作答的题目
- currentQuestion() {
- const item = this.questions[this.currentIndex];
- if (!item) return null;
- return { ...item, index: this.currentIndex };
- },
- // 当前题是否为选择题
- isSingle() {
- return !!(this.currentQuestion && this.currentQuestion.type === 'single');
- },
- hasOptionText() {
- const options = (this.currentQuestion && this.currentQuestion.options) || [];
- return options.some(opt => opt.text);
- },
- // 是否最后一题,最后一题显示「提交」
- isLastQuestion() {
- return this.currentIndex >= this.questions.length - 1;
- },
- // 当前拍照题已上传的图片
- currentPhotos() {
- if (!this.currentQuestion || this.isSingle) return [];
- const val = this.answers[this.currentQuestion.id];
- return Array.isArray(val) ? val : [];
- },
- // 当前题是否已达到图片上传上限
- photoLimitReached() {
- return this.currentPhotos.length >= MAX_PHOTOS;
- },
- // 已作答题数,用于顶部进度
- answeredCount() {
- return this.questions.filter(item => this.isAnswered(item.id)).length;
- },
- // 倒计时:截止日期减去当前时间,按天、时、分、秒显示,并每秒刷新
- countdownText() {
- const endTime = this.homework && this.homework.endTime;
- if (!endTime) return '-';
- const text = String(endTime).trim();
- const end = new Date(text.replace(/-/g, '/'));
- if (!Number.isFinite(end.getTime())) return '-';
- if (/^\d{4}-\d{2}-\d{2}$/.test(text)) {
- end.setHours(23, 59, 59, 0);
- }
- const diff = Math.max(0, end.getTime() - this.nowTick);
- const day = Math.floor(diff / 86400000);
- const hour = Math.floor((diff % 86400000) / 3600000);
- const minute = Math.floor((diff % 3600000) / 60000);
- const second = Math.floor((diff % 60000) / 1000);
- const pad = value => String(value).padStart(2, '0');
- return `${day}天 ${pad(hour)}:${pad(minute)}:${pad(second)}`;
- },
- },
- created() {
- this.restoreState();
- this.startTimer();
- },
- beforeDestroy() {
- this.clearTimer();
- this.persistState();
- },
- methods: {
- // 进入页面或刷新时恢复作答;没有作业记录则回到列表
- restoreState() {
- let cache = null;
- try {
- cache = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || 'null');
- } catch (e) {
- cache = null;
- }
- if (!cache || !cache.homework) {
- this.$router.replace('/teachingHomework/list');
- return;
- }
- this.homework = cache.homework;
- this.currentIndex = Number(cache.currentIndex) || 0;
- this.savedAnswers = cache.answers && typeof cache.answers === 'object' ? cache.answers : {};
- this.fetchDetail();
- },
- fetchDetail() {
- const homework = this.homework || {};
- if (!homework.homeWorkStuId && !homework.homeWorkId) {
- this.questions = [];
- return;
- }
- this.loading = true;
- const params = {
- homeWorkStuId: homework.homeWorkStuId,
- homeWorkId: homework.homeWorkId,
- };
- this.$api.teachingHomework.viewHomeWorkWriteDetail(params).then(res => {
- this.applyDetail(res);
- }).catch(err => {
- const body = this.pickBody(err);
- if (body) {
- this.applyDetail(body);
- return;
- }
- this.questions = [];
- }).finally(() => {
- this.loading = false;
- });
- },
- pickBody(payload) {
- if (!payload) return null;
- if (payload.code === 0 || payload.code === 200) return payload;
- if (payload.data && (payload.data.code === 0 || payload.data.code === 200)) return payload.data;
- if (payload.response) return this.pickBody(payload.response);
- return null;
- },
- applyDetail(res) {
- const list = Array.isArray(res && res.data) ? res.data : [];
- this.questions = list.map(mapQuestion);
- const endTime = (this.questions[0] && this.questions[0].submitEndTime) || this.homework.endTime;
- if (endTime && this.homework) this.homework.endTime = endTime;
- this.fillAnswers();
- if (this.currentIndex < 0 || this.currentIndex >= this.questions.length) {
- this.currentIndex = 0;
- }
- this.persistState();
- },
- fillAnswers() {
- const savedAnswers = this.savedAnswers || {};
- const answers = {};
- this.questions.forEach(item => {
- const saved = savedAnswers[item.id] != null ? savedAnswers[item.id] : savedAnswers[String(item.id)];
- const hasSaved = savedAnswers[item.id] != null || savedAnswers[String(item.id)] != null;
- if (item.type === 'photo') {
- answers[item.id] = hasSaved && Array.isArray(saved) ? saved : item.writeUrls.slice();
- } else {
- // 选择题多选,答案统一为选项 key 数组;兼容历史字符串缓存与接口回显
- answers[item.id] = this.normalizeChoiceAnswer(hasSaved ? saved : item.writeAnswer, item.options);
- }
- });
- this.answers = answers;
- },
- // 把接口/缓存里的答案归一化为合法选项 key 数组,兼容 "A,C"、"AC"、"A|C"、["A"] 等格式
- normalizeChoiceAnswer(value, options) {
- const validKeys = (options || []).map(opt => opt.key);
- let keys = [];
- if (Array.isArray(value)) {
- keys = value;
- } else if (value !== null && value !== undefined && String(value).trim() !== '') {
- keys = String(value).toUpperCase().match(/[A-Z]/g) || [];
- }
- const result = [];
- keys.forEach(key => {
- const k = String(key);
- if (validKeys.indexOf(k) !== -1 && result.indexOf(k) === -1) result.push(k);
- });
- return result;
- },
- // 保存作业、当前题和答案,保证刷新不丢失
- persistState() {
- if (!this.homework) return;
- sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
- homework: this.homework,
- currentIndex: this.currentIndex,
- answers: this.answers,
- }));
- },
- startTimer() {
- this.clearTimer();
- this.timer = setInterval(() => {
- this.nowTick = Date.now();
- }, 1000);
- },
- clearTimer() {
- if (this.timer) {
- clearInterval(this.timer);
- this.timer = null;
- }
- },
- // 选择题有选项、拍照题有图片,即视为已作答
- isAnswered(id) {
- const val = this.answers[id];
- if (Array.isArray(val)) return val.length > 0;
- return val !== undefined && val !== null && String(val).trim() !== '';
- },
- // 序号样式:当前题蓝底,已作答绿框,未作答蓝框
- numClass(item) {
- const answered = this.isAnswered(item.id);
- return {
- current: item.index === this.currentIndex,
- done: answered && item.index !== this.currentIndex,
- };
- },
- // 上一题
- prevQuestion() {
- if (this.currentIndex <= 0) return;
- this.currentIndex -= 1;
- this.persistState();
- },
- // 下一题先校验并保存,最后一题不走这里
- nextQuestion() {
- if (this.currentIndex >= this.questions.length - 1) return;
- this.saveCurrentAnswer(0).then(ok => {
- if (!ok) return;
- this.currentIndex += 1;
- this.persistState();
- });
- },
- // 选择题不可拍照;解答题达到上限或上传中也不可再选
- handlePhoto() {
- if (this.isSingle || this.uploading) return;
- if (this.currentPhotos.length >= MAX_PHOTOS) {
- this.$message.warning(`最多上传${MAX_PHOTOS}张图片`);
- return;
- }
- const input = this.$refs.photoInput;
- if (input) input.click();
- },
- // 选择图片后校验剩余空位:超出上限整体拒绝不上传;合法数量内逐张顺序上传,成功一张追加一张
- async onPhotoChange(e) {
- const files = Array.from(e.target.files || []);
- e.target.value = '';
- if (!files.length || !this.currentQuestion || this.isSingle) return;
- // 本次选择数量超过剩余空位时整体拒绝:不上传、不渲染,请用户重新选择
- const remaining = MAX_PHOTOS - this.currentPhotos.length;
- if (remaining <= 0) {
- this.$message.warning(`最多上传${MAX_PHOTOS}张图片`);
- return;
- }
- if (files.length > remaining) {
- this.$message.warning(`最多上传${MAX_PHOTOS}张图片,当前还可上传${remaining}张,请重新选择`);
- return;
- }
- this.uploading = true;
- let failed = 0;
- for (const file of files) {
- const url = await this.uploadOnePhoto(file);
- if (url) {
- this.appendPhotoUrl(url);
- } else {
- failed += 1;
- }
- }
- this.uploading = false;
- if (failed) {
- this.$message.warning(failed === files.length ? '图片上传失败,请重试' : `有${failed}张图片上传失败,请重试`);
- }
- },
- // 上传单个图片文件,成功返回地址,失败返回空字符串
- uploadOnePhoto(file) {
- const formData = new FormData();
- formData.append('file', file);
- return this.$api.teachingHomework.uploadFilesSelect(formData).then(res => {
- return this.pickFileUrl(res);
- }).catch(err => {
- const body = this.pickBody(err);
- return body ? this.pickFileUrl(body) : '';
- });
- },
- pickFileUrl(body) {
- const data = body && body.data;
- if (!data) return '';
- if (typeof data === 'string') return data;
- if (Array.isArray(data)) {
- const first = data[0];
- if (typeof first === 'string') return first;
- return (first && (first.url || first.fileUrl || first.path || first.filePath)) || '';
- }
- return data.url || data.fileUrl || data.path || data.filePath || '';
- },
- appendPhotoUrl(url) {
- if (!url || !this.currentQuestion) {
- this.$message.warning('图片上传失败');
- return;
- }
- const id = this.currentQuestion.id;
- const list = Array.isArray(this.answers[id]) ? this.answers[id].slice() : [];
- list.push(url);
- this.$set(this.answers, id, list);
- this.persistState();
- },
- // 删除当前拍照题中的一张图片
- removePhoto(index) {
- if (!this.currentQuestion) return;
- const id = this.currentQuestion.id;
- const list = Array.isArray(this.answers[id]) ? this.answers[id].slice() : [];
- list.splice(index, 1);
- this.$set(this.answers, id, list);
- this.persistState();
- },
- // 点击缩略图放大预览(可缩放、旋转)
- previewPhoto(index) {
- this.previewIndex = index;
- this.imgVisible = true;
- },
- closePhotoPreview() {
- this.imgVisible = false;
- },
- // 当前题未作答时提示题号;已作答则保存。最后一题 commitFlag 为 1
- saveCurrentAnswer(commitFlag) {
- const question = this.currentQuestion;
- if (!question || this.saving || this.uploading) {
- if (this.uploading) this.$message.warning('图片正在上传,请稍候');
- return Promise.resolve(false);
- }
- if (!this.isAnswered(question.id)) {
- this.$message.warning(`第${question.index + 1}题没有上传答案,请作答`);
- return Promise.resolve(false);
- }
- const isChoice = question.type === 'single';
- const payload = {
- homeWorkId: this.homework && this.homework.homeWorkId,
- homeWorkStuId: this.homework && this.homework.homeWorkStuId,
- homeWorkStuQuesId: question.homeWorkStuQuesId,
- questionId: question.questionId,
- commitFlag,
- // 选择题多选,以英文逗号拼接所选选项;拍照题提交图片地址
- writeAnswer: isChoice ? (this.answers[question.id] || []).join(',') : '',
- writeUrls: isChoice ? [] : (this.answers[question.id] || []),
- };
- this.saving = true;
- return this.$api.teachingHomework.commitHomeWorkWriteAnswer(payload).then(res => {
- if (res && res.code != null && res.code !== 0 && res.code !== 200) {
- this.$message.warning(res.msg || '保存失败');
- return false;
- }
- return true;
- }).catch(err => {
- const body = this.pickBody(err);
- if (body) return true;
- this.$message.warning((err && err.data && err.data.msg) || '保存失败');
- return false;
- }).finally(() => {
- this.saving = false;
- });
- },
- // 最后一题提交
- submitAnswer() {
- this.saveCurrentAnswer(1).then(ok => {
- if (!ok) return;
- this.persistState();
- this.$message.success('提交成功');
- this.$router.push('/teachingHomework/list');
- });
- },
- // 返回列表,作答进度仍保留在 sessionStorage
- goBack() {
- this.persistState();
- this.$router.push('/teachingHomework/list');
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- /* 整页纵向:顶栏固定,下方主体占满剩余高度 */
- .homework_answer {
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- min-width: 0;
- min-height: 0;
- box-sizing: border-box;
- overflow: hidden;
- .answer_header {
- flex-shrink: 0;
- display: flex;
- align-items: flex-start;
- gap: 16px;
- padding: 10px;
- background: #fff;
- border-radius: 10px;
- box-sizing: border-box;
- .back_btn {
- flex-shrink: 0;
- height: 36px;
- padding: 0 12px;
- background: #fff;
- border: 1px solid #DCDFE6;
- color: #666;
- :deep(span) {
- display: inline-flex;
- align-items: center;
- gap: 2px;
- }
- }
- .header_main {
- flex: 1;
- min-width: 0;
- .title_row {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 10px;
- margin-top: 6px;
- .subject_badge {
- flex-shrink: 0;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 40px;
- height: 25px;
- padding: 0 4px;
- border-radius: 4px;
- color: #fff;
- font-size: 12px;
- line-height: 1;
- }
- .task_title {
- font-size: 16px;
- font-weight: 550;
- color: #333;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- .meta_row {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- font-size: 14px;
- color: #999;
- .countdown {
- color: #F56C6C;
- }
- .progress {
- color: #2E64FA;
- }
- }
- }
- }
- /* 左导航 + 右题目 */
- .answer_body {
- flex: 1;
- min-height: 0;
- display: flex;
- gap: 10px;
- margin-top: 10px;
- overflow: hidden;
- .question_nav {
- flex-shrink: 0;
- width: 240px;
- height: 100%;
- background: #fff;
- border-radius: 10px;
- box-sizing: border-box;
- overflow-y: auto;
- .nav_title {
- font-size: 14px;
- font-weight: 550;
- color: #333;
- padding: 10px;
- border-bottom: 1px solid #EBEEF5;
- }
- .nav_group {
- padding: 10px;
- &+.nav_group {
- margin-top: 24px;
- }
- .group_name {
- font-size: 14px;
- color: #333;
- font-weight: 550;
- padding-bottom: 12px;
- }
- .num_grid {
- display: grid;
- grid-template-columns: repeat(5, 32px);
- gap: 15px;
- /* 未作答蓝框,已作答绿框,当前题蓝底白字;仅展示进度,不可点击跳题 */
- .num_btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- padding: 0;
- border-radius: 4px;
- border: 1px solid #2E64FA;
- box-sizing: border-box;
- background: #fff;
- color: #2E64FA;
- font-size: 14px;
- cursor: default;
- &.done {
- border-color: #3BA272;
- color: #3BA272;
- }
- &.current {
- background: #2E64FA;
- border-color: #2E64FA;
- color: #fff;
- }
- }
- }
- }
- }
- .question_panel {
- flex: 1;
- min-width: 0;
- height: 100%;
- display: flex;
- flex-direction: column;
- background: #fff;
- border-radius: 10px;
- overflow: hidden;
- .question_scroll {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- padding: 16px;
- box-sizing: border-box;
- .q_tags {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 16px;
- .q_no {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 24px;
- height: 24px;
- padding: 0 6px;
- background: #F3F3F3;
- border-radius: 2px;
- font-weight: 550;
- font-size: 14px;
- color: #333333;
- box-sizing: border-box;
- }
- .q_type {
- font-weight: 550;
- font-size: 14px;
- color: #333333;
- }
- .q_level {
- font-weight: 400;
- font-size: 14px;
- color: #90CB75;
- margin-left: 8px;
- }
- }
- .q_stem {
- font-size: 15px;
- line-height: 1.7;
- color: #333;
- margin-bottom: 16px;
- :deep(img) {
- max-width: 100%;
- }
- }
- .q_options {
- .q_option {
- display: flex;
- gap: 6px;
- margin-bottom: 10px;
- font-size: 14px;
- color: #333;
- line-height: 1.6;
- .opt_key {
- flex-shrink: 0;
- }
- }
- }
- }
- /* 作答区贴在题目卡片底部,不随题干滚动 */
- .answer_dock {
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- gap: 16px;
- padding: 0 16px 16px 16px;
- background: #fff;
- box-sizing: border-box;
- .dock_answer {
- display: flex;
- align-items: center;
- gap: 16px;
- min-height: 56px;
- padding: 10px 16px;
- background: #F5F7FA;
- border-radius: 10px;
- box-sizing: border-box;
- border: 1px solid #DCDFE6;
- .dock_label {
- flex-shrink: 0;
- display: inline-flex;
- align-items: center;
- height: 20px;
- line-height: 20px;
- font-size: 14px;
- color: #333;
- }
- .dock_sub_no {
- flex-shrink: 0;
- display: inline-flex;
- align-items: center;
- height: 20px;
- line-height: 20px;
- font-size: 14px;
- color: #333;
- }
- .choice_radio {
- display: inline-flex;
- align-items: center;
- min-height: 20px;
- gap: 20px;
- :deep(.el-checkbox) {
- margin-right: 0 !important;
- height: 20px;
- display: inline-flex !important;
- align-items: center;
- }
- :deep(.el-checkbox__input) {
- display: inline-flex;
- align-items: center;
- height: 16px;
- line-height: 16px;
- }
- :deep(.el-checkbox__inner) {
- width: 16px;
- height: 16px;
- border: 1px solid #DCDFE6;
- background: #fff;
- border-radius: 2px;
- &::after {
- top: 1px;
- left: 5px;
- width: 3px;
- height: 8px;
- }
- }
- :deep(.el-checkbox__input.is-checked .el-checkbox__inner),
- :deep(.el-checkbox__input.is-focus .el-checkbox__inner) {
- background-color: #2E64FA;
- box-shadow: none;
- border-color: #2E64FA;
- }
- :deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
- color: #333;
- }
- :deep(.el-checkbox__label) {
- display: inline-flex;
- align-items: center;
- height: 20px;
- padding-left: 8px;
- font-size: 14px;
- color: #333;
- line-height: 20px;
- }
- }
- .photo_list {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- min-width: 0;
- .photo_item {
- position: relative;
- width: 64px;
- height: 64px;
- border-radius: 4px;
- border: 1px solid transparent;
- box-sizing: border-box;
- background: #fff;
- &.active {
- border-color: #2E64FA;
- }
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 4px;
- display: block;
- cursor: zoom-in;
- }
- .photo_remove {
- position: absolute;
- top: -6px;
- right: -6px;
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: #C0C4CC;
- color: #fff;
- font-size: 12px;
- line-height: 16px;
- text-align: center;
- cursor: pointer;
- }
- }
- }
- }
- .photo_input {
- display: none;
- }
- .dock_actions {
- display: flex;
- align-items: center;
- gap: 16px;
- .el-button {
- height: 36px;
- padding: 0 14px;
- border-radius: 4px;
- line-height: 34px;
- margin:0;
- &.is-disabled,
- &.is-disabled:hover,
- &.is-disabled:focus {
- background: #F3F3F3;
- color: #C0C4CC;
- border: 1px solid #C0C4CC;
- }
- }
- .photo_btn {
- :deep(span) {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- }
- .camera_icon {
- width: 16px;
- height: 16px;
- display: block;
- }
- }
- .nav_btn {
- color: #2E64FA;
- border-color: #2E64FA;
- background: #fff;
- &.is-disabled,
- &.is-disabled:hover,
- &.is-disabled:focus {
- background: #F3F3F3;
- color: #C0C4CC;
- border: 1px solid #C0C4CC;
- }
- }
- }
- }
- }
- }
- }
- </style>
|