123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- <template>
- <div v-loading="loading" class="answer">
- <header class="header">
- <div class="back round" @click="goBack">
- <i class="el-icon-arrow-left"></i>
- <span>返回</span>
- </div>
- <div class="question-info">
- <div class="round">
- <SvgIcon icon-class="list" />
- <span>{{ curQuestionIndex + 1 }} / {{ questionList.length }}</span>
- </div>
- <div v-if="!isTeacherAnnotations" class="round primary">
- <SvgIcon icon-class="hourglass" />{{ secondFormatConversion(time) }}
- </div>
- </div>
- </header>
- <main class="main">
- <StartQuestion
- v-if="
- curQuestionIndex === -1 && !(user_answer_record_info.is_exist_answer_record === 'true' && answer_mode === 2)
- "
- :question-length="questionList.length"
- :answer-time-limit-minute="answer_time_limit_minute"
- @startAnswer="startAnswer"
- />
- <AnswerReport v-else-if="isSubmit" :record-report="recordReport" @selectQuestion="selectQuestion" />
- <template v-for="({ id }, i) in questionList" v-else>
- <component
- :is="curQuestionPage"
- v-if="i === curQuestionIndex"
- :key="id"
- ref="exercise"
- :data="currentQuestion"
- />
- </template>
- </main>
- <footer class="footer">
- <el-popover v-model="isPopover" placement="top-start" trigger="click">
- <div v-if="isEnable(remark.is_remarked) && !isTeacher" class="remark-container">
- <div class="remark-info">
- <el-avatar :size="24" :src="remark.remark_person_image_url" />
- <span class="remark-name">{{ remark.remark_person_name }}</span>
- <span class="remark-time">{{ remark.remark_time }}</span>
- </div>
- <div class="remark">
- {{ remark.remark }}
- </div>
- <div class="file">
- <el-image v-for="{ file_url, file_id } in remark.file_list" :key="file_id" :src="file_url" fit="contain" />
- </div>
- </div>
- <div v-else class="annotations-container">
- <div class="title">增加批注</div>
- <div class="score">
- <span>分数</span>
- <el-input-number v-model="remark.score" :min="0" :step="1" />
- </div>
- <el-input v-model="remark.remark" type="textarea" rows="6" resize="none" class="remark" />
- <div>图片/视频</div>
- <el-upload action="no" accept="video/*,image/*" :show-file-list="false" :http-request="upload">
- <div class="upload">
- <i class="el-icon-plus avatar-uploader-icon"></i>
- <span>Upload</span>
- </div>
- </el-upload>
- <ul class="file-list">
- <li v-for="({ file_name, file_id }, i) in remark.file_list" :key="file_id" @click="removeFile(i)">
- <span>{{ file_name }}</span>
- <SvgIcon icon-class="delete" />
- </li>
- </ul>
- <div class="popover-footer">
- <el-button @click="isPopover = false">取消</el-button>
- <el-button type="primary" @click="fillQuestionAnswerRemark">确定</el-button>
- </div>
- </div>
- <div
- v-show="isAnnotations"
- slot="reference"
- :class="['annotations', { has: isEnable(remark.is_remarked) && !isTeacher }]"
- >
- <template v-if="isEnable(remark.is_remarked) && !isTeacher">
- <span>有一条教师批注</span>
- </template>
- <template v-else><i class="el-icon-plus"></i><span>批注</span></template>
- </div>
- </el-popover>
- <div>
- <template
- v-if="
- curQuestionIndex === -1 && !(user_answer_record_info.is_exist_answer_record === 'true' && answer_mode === 2)
- "
- >
- <el-button type="primary" round @click="startAnswer">开始答题</el-button>
- </template>
- <template v-else-if="isSubmit">
- <el-button v-if="answer_mode === 1" round type="primary" @click="startAnswer">开始答题</el-button>
- </template>
- <template v-else>
- <el-button round @click="fillQuestionAnswer('pre')">上一题</el-button>
- <el-button
- v-if="curQuestionIndex === questionList.length - 1 && !isTeacherAnnotations"
- type="primary"
- round
- @click="submitAnswer"
- >
- 提交
- </el-button>
- <el-button
- v-else-if="curQuestionIndex < questionList.length - 1 || !isTeacherAnnotations"
- type="primary"
- round
- @click="fillQuestionAnswer('next')"
- >下一题</el-button
- >
- </template>
- </div>
- </footer>
- </div>
- </template>
- <script>
- import { secondFormatConversion } from '@/utils/transform';
- import {
- GetExerciseQuestionIndexList,
- GetShareRecordInfo,
- StartAnswer,
- FillQuestionAnswer,
- SubmitAnswer,
- GetQuestionInfo_AnswerRecord,
- FillQuestionAnswerRemark,
- GetAnswerRecordReport,
- } from '@/api/exercise';
- import { subjectiveQuestionList } from './answer';
- import { fileUpload } from '@/api/app';
- import StartQuestion from './components/StartQuestion.vue';
- import AnswerReport from './components/AnswerReport.vue';
- import PreviewQuestionTypeMixin from '../data/PreviewQuestionTypeMixin';
- export default {
- name: 'AnswerPage',
- components: {
- StartQuestion,
- AnswerReport,
- },
- mixins: [PreviewQuestionTypeMixin],
- data() {
- const { id, share_record_id, answer_record_id, exercise_id, question_index } = this.$route.query;
- let questionIndex = Number(question_index);
- return {
- exercise_id: id || exercise_id, // 练习题id
- share_record_id, // 分享记录id
- answer_record_id: answer_record_id ?? '', // 答题记录id
- secondFormatConversion,
- isTeacher: this.$store.getters.isTeacher, // 是否是教师
- user_answer_record_info: {
- correct_answer_show_mode: 1, // 正确答案显示模式
- }, // 当前用户的答题记录信息
- // 问题列表
- questionList: [],
- // 当前问题
- currentQuestion: {},
- // 当前问题索引
- curQuestionIndex: -1,
- question_index: questionIndex >= 0 ? questionIndex : -1, // 跳转的问题索引
- loading: false,
- // 倒计时
- countDownTimer: null,
- answer_mode: 1, // 答题模式
- answer_time_limit_minute: 30, // 答题时间限制
- time: 1800,
- isSubmit: false,
- isView: false, // 练习模式下是否查看
- curQuestionPage: '', // 当前问题页面
- remark: {
- is_remarked: 'false',
- score: 0,
- remark: '',
- remark_person_image_url: '',
- remark_person_name: '',
- remark_time: '',
- file_list: [],
- }, // 批注
- isPopover: false,
- recordReport: {
- answer_record: {
- answer_duration: 0,
- right_count: 0,
- error_count: 0,
- is_remarked: 'false',
- },
- question_list: [],
- }, // 答题报告
- };
- },
- computed: {
- // 是否教师批改
- isTeacherAnnotations() {
- return this.question_index >= 0 && this.isTeacher;
- },
- // 是否显示批注
- isAnnotations() {
- return this.remark.is_remarked === 'true' || this.isTeacherAnnotations;
- },
- // 是否考试模式
- isExamMode() {
- return this.answer_mode === 2;
- },
- },
- watch: {
- curQuestionIndex() {
- this.getQuestionInfo_AnswerRecord();
- },
- isSubmit(val) {
- if (val) {
- this.getAnswerRecordReport();
- }
- },
- },
- created() {
- this.init();
- },
- beforeDestroy() {
- if (this.countDownTimer) clearInterval(this.countDownTimer);
- },
- methods: {
- // 初始化
- init() {
- if (this.exercise_id) {
- this.getExerciseQuestionIndexList();
- }
- if (this.share_record_id && !this.exercise_id) {
- this.loading = true;
- GetShareRecordInfo({ share_record_id: this.share_record_id }).then(
- ({ user_answer_record_info, share_record: { exercise_id, answer_mode, answer_time_limit_minute } }) => {
- this.user_answer_record_info = user_answer_record_info;
- this.exercise_id = exercise_id;
- this.getExerciseQuestionIndexList();
- this.answer_time_limit_minute = answer_time_limit_minute;
- this.time = answer_time_limit_minute * 60;
- this.loading = false;
- // 如果是考试模式,且已经存在答题记录,则直接显示答题报告
- if (this.user_answer_record_info.is_exist_answer_record === 'true' && !this.isTeacher) {
- this.answer_record_id = this.user_answer_record_info.answer_record_id;
- this.answer_mode = answer_mode;
- // 如果是考试模式,且已经存在答题记录,则直接显示答题报告
- if (this.isExamMode) this.isSubmit = true;
- }
- if (!this.isTeacher) {
- this.getAnswerRecordReport();
- }
- },
- );
- }
- },
- // 获取答题报告
- getAnswerRecordReport() {
- if (!this.answer_record_id) return;
- GetAnswerRecordReport({ answer_record_id: this.answer_record_id })
- .then(({ answer_record, question_list }) => {
- if (answer_record.is_remarked === 'true') {
- this.isSubmit = true;
- this.curQuestionIndex = 0;
- }
- this.recordReport = {
- answer_record,
- question_list,
- };
- })
- .catch(() => {});
- },
- getExerciseQuestionIndexList() {
- GetExerciseQuestionIndexList({ exercise_id: this.exercise_id }).then(({ index_list }) => {
- this.questionList = index_list.map((item) => ({
- ...item,
- isFill: this.isTeacherAnnotations,
- }));
- if (this.question_index >= 0) {
- this.curQuestionIndex = this.question_index;
- }
- });
- },
- goBack() {
- if (this.isView) {
- this.isSubmit = true;
- this.isView = false;
- return;
- }
- this.$router.push('/personal_question');
- },
- // 倒计时
- countDown() {
- this.countDownTimer = setInterval(() => {
- this.time -= 1;
- if (this.time === 0) {
- clearInterval(this.countDownTimer);
- }
- }, 1000);
- },
- startAnswer() {
- if (!this.share_record_id) {
- this.curQuestionIndex = 0;
- return;
- }
- StartAnswer({ exercise_id: this.exercise_id, share_record_id: this.share_record_id }).then(
- ({ answer_mode, answer_record_id }) => {
- this.answer_record_id = answer_record_id;
- this.countDown();
- this.answer_mode = answer_mode;
- this.curQuestionIndex = 0;
- this.isSubmit = false;
- },
- );
- },
- preQuestion() {
- if (this.curQuestionIndex === 0) return;
- this.curQuestionIndex -= 1;
- },
- nextQuestion() {
- if (this.curQuestionIndex === this.questionList.length - 1) return;
- this.curQuestionIndex += 1;
- },
- /**
- * 填写答案
- * @param {'pre' | 'next'} type 上一题/下一题
- */
- fillQuestionAnswer(type) {
- if (type === 'pre' && this.curQuestionIndex <= 0) return;
- if (type === 'next' && this.curQuestionIndex > this.questionList.length - 1) return;
- if (!this.answer_record_id) {
- this.curQuestionIndex = type === 'pre' ? this.curQuestionIndex - 1 : this.curQuestionIndex + 1;
- return;
- }
- // 如果已填写,直接跳转
- if (this.questionList[this.curQuestionIndex].isFill) {
- if (type === 'pre') return this.preQuestion();
- if (type === 'next') return this.nextQuestion();
- }
- return FillQuestionAnswer({
- answer_record_id: this.answer_record_id,
- question_id: this.questionList[this.curQuestionIndex].id,
- answer: JSON.stringify(this.$refs.exercise[0].answer),
- }).then(() => {
- this.questionList[this.curQuestionIndex].isFill = true;
- if (subjectiveQuestionList.includes(this.currentQuestion.type) || this.isExamMode) {
- if (type === 'pre') return this.preQuestion();
- if (type === 'next') return this.nextQuestion();
- }
- // 显示答案
- this.$refs.exercise[0].showAnswer(
- this.answer_mode === 1,
- this.user_answer_record_info.correct_answer_show_mode === 1,
- );
- });
- },
- // 得到答题记录题目信息
- getQuestionInfo_AnswerRecord() {
- GetQuestionInfo_AnswerRecord({
- answer_record_id: this.answer_record_id,
- question_id: this.questionList[this.curQuestionIndex].id,
- }).then(({ question, user_answer: { is_fill_answer, content }, remark }) => {
- // 批注
- this.remark = remark;
- // 题目内容
- if (question.content) {
- this.currentQuestion = JSON.parse(question.content);
- this.curQuestionPage =
- this.questionList.length === 0 || this.curQuestionIndex < 0
- ? ''
- : this.previewComponents[this.questionList[this.curQuestionIndex].type];
- }
- // 如果已经填写过答案,直接显示答案
- if (is_fill_answer === 'true') {
- this.$nextTick().then(() => {
- this.$refs.exercise?.[0].showAnswer(
- this.answer_mode === 1 && !this.isTeacherAnnotations,
- this.user_answer_record_info.correct_answer_show_mode === 1 && !this.isTeacherAnnotations,
- content.length > 0 ? JSON.parse(content) : null,
- );
- });
- }
- });
- },
- // 提交答题
- submitAnswer() {
- if (!this.answer_record_id) return;
- this.$confirm('是否确认提交答题?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- })
- .then(() => {
- clearInterval(this.countDownTimer);
- // 如果已经填写过答案,直接提交
- if (this.questionList[this.curQuestionIndex].isFill) {
- SubmitAnswer({ answer_record_id: this.answer_record_id })
- .then(() => {
- this.isSubmit = true;
- })
- .catch(() => {});
- return;
- }
- this.fillQuestionAnswer('next').then(() => {
- SubmitAnswer({ answer_record_id: this.answer_record_id })
- .then(() => {
- this.isSubmit = true;
- })
- .catch(() => {});
- });
- })
- .catch(() => {});
- },
- selectQuestion(i) {
- this.isSubmit = false;
- this.isView = true;
- this.curQuestionIndex = i;
- },
- upload(file) {
- fileUpload('Mid', file).then(({ file_info_list }) => {
- if (file_info_list.length > 0) {
- const { file_id, file_url, file_name } = file_info_list[0];
- this.remark.file_list.push({ file_id, file_url, file_name });
- }
- });
- },
- removeFile(i) {
- this.remark.file_list.splice(i, 1);
- },
- // 填写批注
- fillQuestionAnswerRemark() {
- FillQuestionAnswerRemark({
- answer_record_id: this.answer_record_id,
- question_id: this.questionList[this.curQuestionIndex].id,
- file_id_list: this.remark.file_list.map(({ file_id }) => file_id),
- score: this.remark.score,
- remark: this.remark.remark,
- }).then(() => {
- this.$message.success('批注成功');
- this.isPopover = false;
- });
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .answer {
- display: flex;
- flex-direction: column;
- row-gap: 16px;
- height: 100%;
- padding: 16px;
- background-color: #fff;
- border-radius: 24px;
- box-shadow: 0 6px 30px 5px #0000000d;
- .header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 38px;
- font-size: 14px;
- .back {
- cursor: pointer;
- }
- .question-info {
- display: flex;
- column-gap: 12px;
- }
- }
- .main {
- flex: 1;
- }
- .footer {
- position: relative;
- display: flex;
- justify-content: center;
- .annotations {
- position: absolute;
- left: 0;
- display: flex;
- column-gap: 8px;
- align-items: center;
- padding: 7px 16px;
- font-size: 14px;
- cursor: pointer;
- background-color: $fill-color;
- border-radius: 20px;
- &.has {
- color: $danger-color;
- background-color: #ffece8;
- }
- }
- .el-button {
- padding: 9px 40px;
- }
- }
- }
- </style>
- <style lang="scss">
- .el-popover {
- display: flex;
- flex-direction: column;
- row-gap: 8px;
- padding: 8px 8px 14px;
- font-size: 14px;
- .remark-container {
- display: flex;
- flex-direction: column;
- row-gap: 8px;
- .remark-info {
- display: flex;
- column-gap: 8px;
- align-items: center;
- .remark-name {
- flex: 1;
- }
- .remark-time {
- font-size: 12px;
- color: #999;
- }
- }
- .file {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- .el-image {
- width: 80px;
- height: 80px;
- background-color: #d9d9d9;
- }
- }
- }
- .annotations-container {
- display: flex;
- flex-direction: column;
- row-gap: 8px;
- .title {
- color: #000;
- }
- .score {
- display: flex;
- column-gap: 8px;
- align-items: center;
- :first-child {
- color: #999;
- }
- }
- .remark {
- width: 350px;
- }
- .upload {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-around;
- width: 80px;
- height: 80px;
- padding: 8px;
- background-color: $fill-color;
- border: 1px solid $border-color;
- }
- .file-list {
- display: flex;
- flex-direction: column;
- row-gap: 4px;
- > li {
- display: flex;
- column-gap: 4px;
- :first-child {
- flex: 1;
- }
- :last-child {
- cursor: pointer;
- }
- }
- }
- .popover-footer {
- display: flex;
- justify-content: flex-end;
- }
- }
- }
- </style>
|