index.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. <template>
  2. <div v-loading="loading" class="answer">
  3. <header class="header">
  4. <div class="back round" @click="goBack">
  5. <i class="el-icon-arrow-left"></i>
  6. <span>返回</span>
  7. </div>
  8. <div v-if="isAnnotations && is_objective" class="user-answer-info">
  9. <template v-if="user_answer.answer_status === 1">
  10. <span class="answer-status right"><SvgIcon icon-class="cross" />回答正确</span>
  11. </template>
  12. <template v-else-if="user_answer.answer_status === 2">
  13. <span class="answer-status error"><SvgIcon icon-class="check-mark" />回答错误</span>
  14. </template>
  15. </div>
  16. <div class="question-info">
  17. <el-popover
  18. v-if="!isStart && !isSubmit"
  19. v-model="isShowQuestionList"
  20. :width="200"
  21. :disabled="isStart"
  22. trigger="click"
  23. popper-class="question-wrapper"
  24. >
  25. <ul class="question-list">
  26. <li
  27. v-for="({ id, type: question_type, additional_type }, i) in questionList"
  28. :key="id"
  29. :class="[{ active: i === curQuestionIndex }]"
  30. @click="selectQuestion(i)"
  31. >
  32. <span>{{ i + 1 }}.</span>
  33. <span>{{ getExerciseName('list', question_type, additional_type) }}</span>
  34. </li>
  35. </ul>
  36. <div
  37. slot="reference"
  38. :style="{ backgroundColor: isShowQuestionList ? '#E9E8EA' : '' }"
  39. class="round question-index"
  40. >
  41. <SvgIcon icon-class="list" />
  42. <span>{{ curQuestionIndex + 1 }} / {{ questionList.length }}</span>
  43. <span>{{ getExerciseName('cur') }}</span>
  44. </div>
  45. </el-popover>
  46. <div v-if="!isTeacherAnnotations && !isSubmit" class="round primary">
  47. <SvgIcon icon-class="hourglass" />{{ secondFormatConversion(time) }}
  48. </div>
  49. </div>
  50. </header>
  51. <main class="main">
  52. <StartQuestion
  53. v-if="isStart"
  54. :question-length="questionList.length"
  55. :answer-time-limit-minute="answer_time_limit_minute"
  56. @startAnswer="startAnswer"
  57. />
  58. <AnswerReport v-else-if="isSubmit" :record-report="recordReport" @selectQuestion="selectQuestion" />
  59. <template v-for="({ id }, i) in questionList" v-else>
  60. <component
  61. :is="curQuestionPage"
  62. v-if="i === curQuestionIndex"
  63. :key="id"
  64. ref="exercise"
  65. :data="currentQuestion"
  66. />
  67. </template>
  68. </main>
  69. <footer class="footer" :style="{ justifyContent: isAnnotations ? 'space-between' : 'center' }">
  70. <el-popover v-model="isPopover" placement="top-start" trigger="click">
  71. <div v-if="isEnable(remark.is_remarked) && !isTeacher" class="remark-container">
  72. <div class="remark-info">
  73. <el-avatar :size="24" :src="remark.remark_person_image_url" />
  74. <span class="remark-name">{{ remark.remark_person_name }}</span>
  75. <span class="remark-time">{{ remark.remark_time }}</span>
  76. </div>
  77. <div class="remark">
  78. {{ remark.remark }}
  79. </div>
  80. <div class="file">
  81. <el-image v-for="{ file_url, file_id } in remark.file_list" :key="file_id" :src="file_url" fit="contain" />
  82. </div>
  83. </div>
  84. <div v-else class="annotations-container">
  85. <div class="title">增加批注</div>
  86. <div class="score">
  87. <span>分数</span>
  88. <el-input-number
  89. v-model="remark.score"
  90. :min="0"
  91. :disabled="is_objective"
  92. :max="question.score"
  93. :step="question.score_type === scoreTypeList[0].value ? 1 : 0.1"
  94. />
  95. </div>
  96. <el-input v-model="remark.remark" type="textarea" rows="6" resize="none" class="remark" />
  97. <div>图片/视频</div>
  98. <el-upload action="no" accept="video/*,image/*" :show-file-list="false" :http-request="upload">
  99. <div class="upload">
  100. <i class="el-icon-plus avatar-uploader-icon"></i>
  101. <span>Upload</span>
  102. </div>
  103. </el-upload>
  104. <ul class="file-list">
  105. <li v-for="({ file_name, file_id }, i) in remark.file_list" :key="file_id" @click="removeFile(i)">
  106. <span>{{ file_name }}</span>
  107. <SvgIcon icon-class="delete" />
  108. </li>
  109. </ul>
  110. <div class="popover-footer">
  111. <el-button @click="isPopover = false">取消</el-button>
  112. <el-button type="primary" @click="fillQuestionAnswerRemark">确定</el-button>
  113. </div>
  114. </div>
  115. <div
  116. v-show="isAnnotations"
  117. slot="reference"
  118. :class="['annotations', { has: isEnable(remark.is_remarked) && !isTeacher }]"
  119. >
  120. <template v-if="isEnable(remark.is_remarked) && !isTeacher">
  121. <span>有一条教师批注</span>
  122. </template>
  123. <template v-else><i class="el-icon-plus"></i><span>批注</span></template>
  124. </div>
  125. </el-popover>
  126. <div class="footer-opeartion">
  127. <template v-if="curQuestionIndex === -1 && !(user_answer_record_info.is_exist_answer_record === 'true')">
  128. <el-button type="primary" round @click="startAnswer">开始答题</el-button>
  129. </template>
  130. <template v-else-if="isSubmit">
  131. <el-button v-if="answer_mode === 1" round type="primary" @click="startAnswer">开始答题</el-button>
  132. </template>
  133. <template v-else>
  134. <el-button v-if="curQuestionIndex > 0" round @click="fillQuestionAnswer('pre')">上一题</el-button>
  135. <el-button
  136. v-if="
  137. curQuestionIndex === questionList.length - 1 &&
  138. !isTeacherAnnotations &&
  139. !isShow &&
  140. user_answer_record_info.is_exist_answer_record !== 'true'
  141. "
  142. type="primary"
  143. round
  144. @click="confirmSubmitAnswer"
  145. >
  146. 提交
  147. </el-button>
  148. <el-button
  149. v-else-if="curQuestionIndex < questionList.length - 1"
  150. type="primary"
  151. round
  152. @click="fillQuestionAnswer('next')"
  153. >下一题</el-button
  154. >
  155. </template>
  156. </div>
  157. <div v-if="isAnnotations" class="score_type">
  158. 本题分数:{{
  159. question.score_type === scoreTypeList[0].value
  160. ? `总分${question.score}分`
  161. : `总分${question.score}分 每小题${question.score_item}分`
  162. }}
  163. </div>
  164. </footer>
  165. </div>
  166. </template>
  167. <script>
  168. import { secondFormatConversion } from '@/utils/transform';
  169. import {
  170. GetExerciseQuestionIndexList,
  171. GetShareRecordInfo,
  172. StartAnswer,
  173. FillQuestionAnswer,
  174. SubmitAnswer,
  175. GetQuestionInfo_AnswerRecord,
  176. FillQuestionAnswerRemark,
  177. GetAnswerRecordReport,
  178. GetQuestionInfo,
  179. EndAnswer,
  180. } from '@/api/exercise';
  181. import { fileUpload } from '@/api/app';
  182. import { exerciseNames } from '@/views/exercise_questions/data/questionType';
  183. import { scoreTypeList } from '@/views/exercise_questions/data/common';
  184. import StartQuestion from './components/StartQuestion.vue';
  185. import AnswerReport from './components/AnswerReport.vue';
  186. import PreviewQuestionTypeMixin from '../data/PreviewQuestionTypeMixin';
  187. export default {
  188. name: 'AnswerPage',
  189. components: {
  190. StartQuestion,
  191. AnswerReport,
  192. },
  193. mixins: [PreviewQuestionTypeMixin],
  194. data() {
  195. const {
  196. id,
  197. share_record_id,
  198. answer_record_id,
  199. exercise_id,
  200. question_index,
  201. back_url,
  202. type = 'answer',
  203. } = this.$route.query;
  204. let questionIndex = Number(question_index);
  205. return {
  206. type, // 类型:answer【答题】show【展示】
  207. isShow: type === 'show', // 是否是展示模式
  208. exercise_id: id || exercise_id, // 练习题id
  209. share_record_id, // 分享记录id
  210. answer_record_id: answer_record_id ?? '', // 答题记录id
  211. back_url:
  212. back_url?.length > 0
  213. ? decodeURIComponent(back_url)
  214. : `${window.location.origin}/GCLS-Learn/#/main?tab=ExerciseList`, // 返回链接
  215. secondFormatConversion,
  216. isTeacher: this.$store.getters.isTeacher, // 是否是教师
  217. user_answer_record_info: {}, // 当前用户的答题记录信息
  218. correct_answer_show_mode: 1,
  219. scoreTypeList, // 分数类型列表
  220. // 问题列表
  221. questionList: [],
  222. // 当前问题
  223. currentQuestion: {},
  224. // 当前问题索引
  225. curQuestionIndex: -1,
  226. question_index: questionIndex >= 0 ? questionIndex : -1, // 跳转的问题索引
  227. loading: false,
  228. // 倒计时
  229. countDownTimer: null,
  230. answer_mode: 1, // 答题模式
  231. answer_time_limit_minute: 30, // 答题时间限制
  232. time: 1800,
  233. isSubmit: false,
  234. isView: false, // 是否从答题报告跳转到题目
  235. curQuestionPage: '', // 当前问题页面
  236. remark: {
  237. is_remarked: 'false',
  238. score: 0,
  239. remark: '',
  240. remark_person_image_url: '',
  241. remark_person_name: '',
  242. remark_time: '',
  243. file_list: [],
  244. }, // 批注
  245. isPopover: false,
  246. recordReport: {
  247. answer_record: {
  248. answer_duration: 0,
  249. right_count: 0,
  250. error_count: 0,
  251. is_remarked: 'false',
  252. },
  253. question_list: [],
  254. }, // 答题报告
  255. exerciseNames,
  256. isShowQuestionList: false, // 是否显示题目列表
  257. is_objective: false, // 是否客观题
  258. question: {
  259. score: 1,
  260. score_item: 1,
  261. score_type: 'aggregate',
  262. }, // 题目信息
  263. // 用户答案
  264. user_answer: {
  265. answer_status: 0,
  266. },
  267. };
  268. },
  269. computed: {
  270. isStart() {
  271. return (
  272. this.curQuestionIndex === -1 &&
  273. !(this.user_answer_record_info.is_exist_answer_record === 'true') &&
  274. !this.isShow &&
  275. !this.isSubmit
  276. );
  277. },
  278. // 是否教师批改
  279. isTeacherAnnotations() {
  280. return this.question_index >= 0 && this.isTeacher && this.type !== 'show';
  281. },
  282. // 是否显示批注
  283. isAnnotations() {
  284. return (
  285. (this.remark.is_remarked === 'true' || this.isTeacherAnnotations) &&
  286. this.curQuestionIndex >= 0 &&
  287. !this.isSubmit
  288. );
  289. },
  290. // 是否考试模式
  291. isExamMode() {
  292. return this.answer_mode === 2;
  293. },
  294. },
  295. watch: {
  296. curQuestionIndex(val) {
  297. if (val === -1) {
  298. this.curQuestionPage = '';
  299. this.currentQuestion = {};
  300. return;
  301. }
  302. if (this.isShow) {
  303. this.getQuestionInfo();
  304. return;
  305. }
  306. this.getQuestionInfo_AnswerRecord();
  307. },
  308. isSubmit(val) {
  309. if (val) {
  310. this.getAnswerRecordReport();
  311. }
  312. },
  313. },
  314. created() {
  315. this.init();
  316. },
  317. beforeDestroy() {
  318. if (this.countDownTimer) clearInterval(this.countDownTimer);
  319. },
  320. methods: {
  321. // 初始化
  322. init() {
  323. if (this.exercise_id) {
  324. this.getExerciseQuestionIndexList();
  325. }
  326. if (this.share_record_id && !this.exercise_id) {
  327. this.loading = true;
  328. GetShareRecordInfo({ share_record_id: this.share_record_id }).then(
  329. ({
  330. user_answer_record_info,
  331. share_record: { exercise_id, answer_mode, answer_time_limit_minute, correct_answer_show_mode },
  332. }) => {
  333. this.user_answer_record_info = user_answer_record_info;
  334. this.exercise_id = exercise_id;
  335. this.getExerciseQuestionIndexList();
  336. this.answer_time_limit_minute = answer_time_limit_minute;
  337. this.time = answer_time_limit_minute * 60;
  338. this.correct_answer_show_mode = correct_answer_show_mode;
  339. this.loading = false;
  340. this.answer_mode = answer_mode;
  341. // 如果已经存在答题记录,则直接显示答题报告
  342. if (this.user_answer_record_info.is_exist_answer_record === 'true') {
  343. this.answer_record_id = this.user_answer_record_info.answer_record_id;
  344. this.isSubmit = true;
  345. }
  346. if (!this.isTeacher) {
  347. this.getAnswerRecordReport();
  348. }
  349. },
  350. );
  351. }
  352. },
  353. // 获取答题报告
  354. getAnswerRecordReport() {
  355. if (!this.answer_record_id) return;
  356. GetAnswerRecordReport({ answer_record_id: this.answer_record_id })
  357. .then(({ answer_record, question_list }) => {
  358. if (answer_record.is_remarked === 'true') {
  359. this.isSubmit = true;
  360. }
  361. this.recordReport = {
  362. answer_record,
  363. question_list,
  364. };
  365. })
  366. .catch(() => {});
  367. },
  368. // 得到练习的题目索引列表
  369. getExerciseQuestionIndexList() {
  370. GetExerciseQuestionIndexList({ exercise_id: this.exercise_id }).then(({ index_list }) => {
  371. this.questionList = index_list.map((item) => ({
  372. ...item,
  373. isFill: this.isTeacherAnnotations || this.user_answer_record_info.is_exist_answer_record === 'true',
  374. }));
  375. if (this.isShow) {
  376. this.curQuestionIndex = this.question_index || 0;
  377. return;
  378. }
  379. if (this.question_index >= 0) {
  380. this.curQuestionIndex = this.question_index;
  381. return;
  382. }
  383. });
  384. },
  385. goBack() {
  386. if (this.isView) {
  387. this.isSubmit = true;
  388. this.isView = false;
  389. return;
  390. }
  391. if (this.back_url === 'not-return') return;
  392. window.location.href = this.back_url;
  393. },
  394. // 倒计时
  395. countDown() {
  396. this.countDownTimer = setInterval(() => {
  397. this.time -= 1;
  398. if (this.time === 0) {
  399. clearInterval(this.countDownTimer);
  400. this.endAnswer();
  401. }
  402. }, 1000);
  403. },
  404. endAnswer() {
  405. EndAnswer({ answer_record_id: this.answer_record_id }).then(() => {
  406. this.isSubmit = true;
  407. });
  408. },
  409. startAnswer() {
  410. if (!this.share_record_id) {
  411. this.curQuestionIndex = 0;
  412. return;
  413. }
  414. StartAnswer({ exercise_id: this.exercise_id, share_record_id: this.share_record_id })
  415. .then(({ answer_mode, answer_record_id, answer_time_limit_minute }) => {
  416. this.questionList = this.questionList.map((item) => ({
  417. ...item,
  418. isFill: false,
  419. }));
  420. this.answer_record_id = answer_record_id;
  421. this.answer_time_limit_minute = answer_time_limit_minute;
  422. this.time = answer_time_limit_minute * 60;
  423. this.countDown();
  424. this.answer_mode = answer_mode;
  425. this.curQuestionIndex = 0;
  426. this.isSubmit = false;
  427. this.user_answer_record_info.is_exist_answer_record = 'false';
  428. })
  429. .catch(() => {});
  430. },
  431. preQuestion() {
  432. if (this.curQuestionIndex === 0) return;
  433. this.curQuestionIndex -= 1;
  434. },
  435. nextQuestion() {
  436. if (this.curQuestionIndex === this.questionList.length - 1) return;
  437. this.curQuestionIndex += 1;
  438. },
  439. /**
  440. * 填写答案
  441. * @param {'pre' | 'next'} type 上一题/下一题
  442. */
  443. fillQuestionAnswer(type) {
  444. if (type === 'pre' && this.curQuestionIndex <= 0) return;
  445. if (type === 'next' && this.curQuestionIndex > this.questionList.length - 1) return;
  446. if (!this.answer_record_id) {
  447. this.curQuestionIndex =
  448. type === 'pre'
  449. ? Math.max(0, this.curQuestionIndex - 1)
  450. : Math.min(this.questionList.length - 1, this.curQuestionIndex + 1);
  451. return;
  452. }
  453. // 如果已填写或展示预览模式,直接跳转
  454. if (this.questionList[this.curQuestionIndex].isFill || this.isShow) {
  455. if (type === 'pre') return this.preQuestion();
  456. if (type === 'next') return this.nextQuestion();
  457. }
  458. return FillQuestionAnswer({
  459. answer_record_id: this.answer_record_id,
  460. question_id: this.questionList[this.curQuestionIndex].id,
  461. answer: JSON.stringify(this.$refs.exercise[0].answer),
  462. }).then(({ user_answer }) => {
  463. this.questionList[this.curQuestionIndex].isFill = true;
  464. if (!this.isEnable(user_answer.is_objective) || this.isExamMode) {
  465. if (type === 'pre') return this.preQuestion();
  466. if (type === 'next') return this.nextQuestion();
  467. }
  468. this.$refs.exercise[0].showAnswer(this.answer_mode === 1, this.correct_answer_show_mode === 1, null, true);
  469. });
  470. },
  471. getQuestionInfo() {
  472. if (this.questionList.length === 0) return;
  473. GetQuestionInfo({ question_id: this.questionList[this.curQuestionIndex].id }).then(({ question, file_list }) => {
  474. if (!question.content) return;
  475. this.curQuestionPage =
  476. this.curQuestionIndex < 0 ? '' : this.previewComponents[this.questionList[this.curQuestionIndex].type];
  477. // 将题目文件id列表添加到题目内容中
  478. let file_id_list = file_list.map(({ file_id }) => file_id);
  479. let content = JSON.parse(question.content);
  480. content.file_id_list = file_id_list;
  481. this.currentQuestion = content;
  482. });
  483. },
  484. // 得到答题记录题目信息
  485. getQuestionInfo_AnswerRecord() {
  486. if (this.questionList.length === 0) return;
  487. GetQuestionInfo_AnswerRecord({
  488. answer_record_id: this.answer_record_id,
  489. question_id: this.questionList[this.curQuestionIndex].id,
  490. }).then(({ question, user_answer: { is_fill_answer, content, is_objective, answer_status }, remark }) => {
  491. // 批注
  492. this.remark = remark;
  493. this.question = question;
  494. // 题目内容
  495. if (question.content) {
  496. this.currentQuestion = JSON.parse(question.content);
  497. this.curQuestionPage =
  498. this.questionList.length === 0 || this.curQuestionIndex < 0
  499. ? ''
  500. : this.previewComponents[this.questionList[this.curQuestionIndex].type];
  501. }
  502. this.is_objective = this.isEnable(is_objective);
  503. this.user_answer.answer_status = answer_status;
  504. // 如果已经填写过答案,直接显示答案
  505. if (is_fill_answer === 'true') {
  506. this.$nextTick().then(() => {
  507. /**
  508. * 是否判断对错
  509. * 1. 答题模式为练习模式
  510. * 2. 教师批改
  511. * 3. 答题模式为考试模式,且已经批改过
  512. * 4. 从答题报告跳转到题目
  513. */
  514. let isJudgingRightWrong =
  515. this.answer_mode === 1 ||
  516. this.isTeacherAnnotations ||
  517. (this.isExamMode && this.recordReport.answer_record.is_remarked === 'true') ||
  518. this.isView;
  519. /**
  520. * 是否显示正确答案
  521. * 1. 答题模式为练习模式,且正确答案显示模式为答题后显示
  522. * 2. 教师批改
  523. * 3. 从答题报告跳转到题目
  524. */
  525. let isShowRightAnswer =
  526. (this.answer_mode === 1 && this.correct_answer_show_mode === 1) ||
  527. this.isTeacherAnnotations ||
  528. this.isView;
  529. /**
  530. * 是否禁用答题
  531. * 1. 教师批改
  532. * 2. 答题模式为练习模式,且正确答案显示模式为答题后显示
  533. * 3. 教师已经批改过
  534. * 4. 从测试报告跳转到题目
  535. */
  536. let disabled =
  537. this.isTeacherAnnotations ||
  538. (this.answer_mode === 1 && this.correct_answer_show_mode === 1) ||
  539. this.recordReport.answer_record.is_remarked === 'true' ||
  540. this.isView;
  541. this.$refs.exercise?.[0].showAnswer(
  542. isJudgingRightWrong,
  543. isShowRightAnswer,
  544. content.length > 0 ? JSON.parse(content) : null,
  545. disabled,
  546. );
  547. });
  548. }
  549. // 在有批注且为主观题时,弹出批注框
  550. if (this.isAnnotations && !this.is_objective) {
  551. this.isPopover = true;
  552. }
  553. });
  554. },
  555. // 提交答题
  556. confirmSubmitAnswer() {
  557. if (!this.answer_record_id) return;
  558. this.$confirm('是否确认提交答题?', '提示', {
  559. confirmButtonText: '确定',
  560. cancelButtonText: '取消',
  561. type: 'warning',
  562. })
  563. .then(() => {
  564. this.handleSubmitAnswer();
  565. })
  566. .catch(() => {});
  567. },
  568. handleSubmitAnswer() {
  569. clearInterval(this.countDownTimer);
  570. // 如果已经填写过答案,直接提交
  571. if (this.questionList[this.curQuestionIndex].isFill) {
  572. this.submitAnswer();
  573. return;
  574. }
  575. this.fillQuestionAnswer('next').then(() => {
  576. this.submitAnswer();
  577. });
  578. },
  579. submitAnswer() {
  580. SubmitAnswer({ answer_record_id: this.answer_record_id })
  581. .then(() => {
  582. this.isSubmit = true;
  583. this.curQuestionIndex = -1;
  584. this.user_answer_record_info.is_exist_answer_record = 'true';
  585. })
  586. .catch(() => {});
  587. },
  588. selectQuestion(i) {
  589. if (this.isSubmit) {
  590. this.isSubmit = false;
  591. this.isView = true;
  592. }
  593. this.curQuestionIndex = i;
  594. },
  595. upload(file) {
  596. fileUpload('Mid', file).then(({ file_info_list }) => {
  597. if (file_info_list.length > 0) {
  598. const { file_id, file_url, file_name } = file_info_list[0];
  599. this.remark.file_list.push({ file_id, file_url, file_name });
  600. }
  601. });
  602. },
  603. removeFile(i) {
  604. this.remark.file_list.splice(i, 1);
  605. },
  606. // 填写批注
  607. fillQuestionAnswerRemark() {
  608. FillQuestionAnswerRemark({
  609. answer_record_id: this.answer_record_id,
  610. question_id: this.questionList[this.curQuestionIndex].id,
  611. file_id_list: this.remark.file_list.map(({ file_id }) => file_id),
  612. score: this.remark.score,
  613. remark: this.remark.remark,
  614. }).then(() => {
  615. this.$message.success('批注成功');
  616. this.isPopover = false;
  617. });
  618. },
  619. getExerciseName(type, question_type, additional_type) {
  620. if (type === 'cur') {
  621. if (this.curQuestionIndex < 0) return '';
  622. let { type: _type, additional_type: _additional_type } = this.questionList[this.curQuestionIndex];
  623. if (_type === 'select') {
  624. return _additional_type === 'single' ? '单选题' : '多选题';
  625. }
  626. return this.exerciseNames[_type];
  627. }
  628. if (type === 'list') {
  629. if (question_type === 'select') {
  630. return additional_type === 'single' ? '单选题' : '多选题';
  631. }
  632. return this.exerciseNames[question_type];
  633. }
  634. },
  635. },
  636. };
  637. </script>
  638. <style lang="scss" scoped>
  639. .answer {
  640. display: flex;
  641. flex-direction: column;
  642. row-gap: 16px;
  643. max-width: 1200px;
  644. min-height: 100%;
  645. padding: 16px;
  646. margin: 0 auto;
  647. background-color: #fff;
  648. border-radius: 24px;
  649. box-shadow: 0 6px 30px 5px #0000000d;
  650. .header {
  651. display: flex;
  652. align-items: center;
  653. justify-content: space-between;
  654. height: 38px;
  655. font-size: 14px;
  656. .back {
  657. cursor: pointer;
  658. }
  659. .user-answer-info {
  660. .answer-status {
  661. display: flex;
  662. column-gap: 8px;
  663. align-items: center;
  664. padding: 8px 16px;
  665. color: #fff;
  666. border-radius: 40px;
  667. &.right {
  668. background-color: #3acb85;
  669. }
  670. &.error {
  671. background-color: #e65656;
  672. }
  673. }
  674. }
  675. .question-info {
  676. display: flex;
  677. column-gap: 12px;
  678. .question-index {
  679. cursor: pointer;
  680. }
  681. }
  682. }
  683. .main {
  684. flex: 1;
  685. }
  686. .footer {
  687. position: relative;
  688. display: flex;
  689. align-items: center;
  690. .annotations {
  691. display: flex;
  692. column-gap: 8px;
  693. align-items: center;
  694. padding: 7px 16px;
  695. font-size: 14px;
  696. cursor: pointer;
  697. background-color: $fill-color;
  698. border-radius: 20px;
  699. &.has {
  700. color: $danger-color;
  701. background-color: #ffece8;
  702. }
  703. }
  704. &-opeartion {
  705. .el-button {
  706. font-size: 16px;
  707. }
  708. }
  709. .el-button {
  710. padding: 9px 40px;
  711. }
  712. .score_type {
  713. font-size: 14px;
  714. font-weight: bold;
  715. color: $main-color;
  716. }
  717. }
  718. }
  719. </style>
  720. <style lang="scss">
  721. .el-popover {
  722. display: flex;
  723. flex-direction: column;
  724. row-gap: 8px;
  725. padding: 8px 8px 14px;
  726. font-size: 14px;
  727. .remark-container {
  728. display: flex;
  729. flex-direction: column;
  730. row-gap: 8px;
  731. .remark-info {
  732. display: flex;
  733. column-gap: 8px;
  734. align-items: center;
  735. .remark-name {
  736. flex: 1;
  737. }
  738. .remark-time {
  739. font-size: 12px;
  740. color: #999;
  741. }
  742. }
  743. .file {
  744. display: flex;
  745. flex-wrap: wrap;
  746. gap: 8px;
  747. .el-image {
  748. width: 80px;
  749. height: 80px;
  750. background-color: #d9d9d9;
  751. }
  752. }
  753. }
  754. .annotations-container {
  755. display: flex;
  756. flex-direction: column;
  757. row-gap: 8px;
  758. .title {
  759. color: #000;
  760. }
  761. .score {
  762. display: flex;
  763. column-gap: 8px;
  764. align-items: center;
  765. :first-child {
  766. color: #999;
  767. }
  768. }
  769. .remark {
  770. width: 350px;
  771. }
  772. .upload {
  773. display: flex;
  774. flex-direction: column;
  775. align-items: center;
  776. justify-content: space-around;
  777. width: 80px;
  778. height: 80px;
  779. padding: 8px;
  780. background-color: $fill-color;
  781. border: 1px solid $border-color;
  782. }
  783. .file-list {
  784. display: flex;
  785. flex-direction: column;
  786. row-gap: 4px;
  787. > li {
  788. display: flex;
  789. column-gap: 4px;
  790. :first-child {
  791. flex: 1;
  792. }
  793. :last-child {
  794. cursor: pointer;
  795. }
  796. }
  797. }
  798. .popover-footer {
  799. display: flex;
  800. justify-content: flex-end;
  801. }
  802. }
  803. }
  804. .question-wrapper {
  805. max-height: 60vh;
  806. padding: 8px;
  807. overflow: auto;
  808. border-radius: 8px;
  809. box-shadow: 0 2px 8px 0 #00000040;
  810. .question-list {
  811. li {
  812. display: flex;
  813. column-gap: 8px;
  814. align-items: center;
  815. padding: 8px 16px;
  816. cursor: pointer;
  817. &.active {
  818. color: $main-color;
  819. background-color: #f4f8ff;
  820. border-radius: 2px;
  821. }
  822. }
  823. }
  824. }
  825. </style>