Forráskód Böngészése

Merge branch 'master' into lhd

# Conflicts:
#	src/views/exercise_questions/create/index.vue
natasha 1 éve
szülő
commit
b8b6d9bea8

+ 54 - 0
src/api/exercise.js

@@ -140,3 +140,57 @@ export function GetMyCourseList_Teacher(data) {
 export function GetCourseStudentList(data) {
 export function GetCourseStudentList(data) {
   return http.post(`${process.env.VUE_APP_LearnWebSI}?MethodName=teaching-course_manager-GetCourseStudentList`, data);
   return http.post(`${process.env.VUE_APP_LearnWebSI}?MethodName=teaching-course_manager-GetCourseStudentList`, data);
 }
 }
+
+/**
+ * 得到分享记录信息
+ * @param {object} data
+ * @param {string} data.share_record_id 分享记录id
+ */
+export function GetShareRecordInfo(data) {
+  return http.post(`/TeachingServer/ExerciseManager/GetShareRecordInfo`, data);
+}
+
+/**
+ * 开始答题
+ * @param {object} data
+ * @param {string} data.exercise_id 练习题id
+ * @param {string} data.share_record_id 分享记录id
+ */
+export function StartAnswer(data) {
+  return http.post(`/TeachingServer/ExerciseManager/StartAnswer`, data);
+}
+
+/**
+ * 填写题目答案
+ */
+export function FillQuestionAnswer(data) {
+  return http.post(`/TeachingServer/ExerciseManager/FillQuestionAnswer`, data);
+}
+
+/**
+ * 得到答题记录题目信息
+ */
+export function GetQuestionInfo_AnswerRecord(data) {
+  return http.post(`/TeachingServer/ExerciseManager/GetQuestionInfo_AnswerRecord`, data);
+}
+
+/**
+ * 提交答题
+ */
+export function SubmitAnswer(data) {
+  return http.post(`/TeachingServer/ExerciseManager/SubmitAnswer`, data);
+}
+
+/**
+ * 得到答题记录信息
+ */
+export function GetAnswerRecordInfo(data) {
+  return http.post(`/TeachingServer/AnswerManger/GetAnswerRecordInfo`, data);
+}
+
+/**
+ * 得到答题记录报告
+ */
+export function GetAnswerRecordReport(data) {
+  return http.post(`/TeachingServer/ExerciseManager/GetAnswerRecordReport`, data);
+}

+ 3 - 0
src/icons/svg/hourglass.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2.6665 1.3335H13.3332V4.30679L9.02424 8.00016L13.3332 11.6936V14.6668H2.6665V11.6936L6.97544 8.00016L2.6665 4.30679V1.3335ZM10.8643 4.66683L11.9998 3.69354V2.66683H3.99984V3.69354L5.13534 4.66683H10.8643ZM7.99984 8.87823L3.99984 12.3068V13.3335H4.6665L7.99984 11.3335L11.3332 13.3335H11.9998V12.3068L7.99984 8.87823Z" fill="#175DFF"/>
+</svg>

+ 3 - 0
src/icons/svg/list.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.33322 3.99984V5.99984H3.33322V3.99984H5.33322ZM1.99989 2.6665V7.33317H6.66657V2.6665H1.99989ZM8.66657 2.6665H13.9999V3.99984H8.66657V2.6665ZM8.66657 7.33317H13.9999V8.6665H8.66657V7.33317ZM8.66657 11.9998H13.9999V13.3332H8.66657V11.9998ZM7.13797 10.8046L6.19515 9.86177L3.99989 12.057L2.80462 10.8618L1.86182 11.8046L3.99989 13.9426L7.13797 10.8046Z" fill="#34343A"/>
+</svg>

+ 48 - 0
src/layouts/answer/header/index.vue

@@ -52,10 +52,58 @@ export default {
   display: flex;
   display: flex;
   column-gap: 24px;
   column-gap: 24px;
   align-items: center;
   align-items: center;
+  justify-content: space-between;
   height: $header-h;
   height: $header-h;
   padding: 0 24px;
   padding: 0 24px;
   overflow: hidden;
   overflow: hidden;
   background-color: #fff;
   background-color: #fff;
   border-bottom: 1px solid #ebebeb;
   border-bottom: 1px solid #ebebeb;
+
+  .logo {
+    height: 48px;
+    margin-right: 36px;
+  }
+
+  .user {
+    cursor: pointer;
+
+    .el-dropdown-link {
+      display: flex;
+      align-items: center;
+
+      .avatar {
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+      }
+
+      .real_name {
+        display: inline-block;
+        padding-left: 10px;
+        font-size: 16px;
+        color: #000;
+        vertical-align: super;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.user-menu {
+  min-width: 156px;
+
+  .el-dropdown-menu__item {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    color: #000;
+
+    img {
+      width: 24px;
+      height: 24px;
+      margin-right: 10px;
+    }
+  }
 }
 }
 </style>
 </style>

+ 3 - 0
src/layouts/answer/index.vue

@@ -28,7 +28,10 @@ export default {
   flex-direction: column;
   flex-direction: column;
 
 
   &-container {
   &-container {
+    height: calc(100vh - 64px);
+    padding: 16px;
     overflow: auto;
     overflow: auto;
+    background-color: #f8f7f8;
   }
   }
 }
 }
 </style>
 </style>

+ 13 - 0
src/styles/common.scss

@@ -62,3 +62,16 @@
   line-height: 22px;
   line-height: 22px;
   color: #4e5969;
   color: #4e5969;
 }
 }
+
+.round {
+  display: flex;
+  column-gap: 12px;
+  align-items: center;
+  padding: 8px 16px;
+  background-color: $content-color;
+  border-radius: 40px;
+
+  &.primary {
+    background-color: $main-active-color;
+  }
+}

+ 1 - 0
src/styles/variables.scss

@@ -3,6 +3,7 @@ $main-color: #165dff;
 $light-main-color: #306eff;
 $light-main-color: #306eff;
 $main-background-color: #f7f8fa;
 $main-background-color: #f7f8fa;
 $main-hover-color: #3371ff;
 $main-hover-color: #3371ff;
+$main-active-color: #e7eeff;
 $danger-color: #f53f3f;
 $danger-color: #f53f3f;
 $danger-hover-color: #f56060;
 $danger-hover-color: #f56060;
 $font-color: #1d2129;
 $font-color: #1d2129;

+ 8 - 1
src/utils/transform.js

@@ -21,9 +21,10 @@ export function zeroFill(val) {
 /**
 /**
  * 将秒转为时:分:秒格式
  * 将秒转为时:分:秒格式
  * @param {Number|String} val 秒
  * @param {Number|String} val 秒
+ * @param {'normal'|'chinese'} type 格式类型
  * @returns {String} hh:MM:ss 小于1小时返回 MM:ss
  * @returns {String} hh:MM:ss 小于1小时返回 MM:ss
  */
  */
-export function secondFormatConversion(val) {
+export function secondFormatConversion(val, type = 'normal') {
   const seconds = parseInt(val); // 输入的秒数
   const seconds = parseInt(val); // 输入的秒数
   const hours = Math.floor(seconds / 3600); // 小时部分
   const hours = Math.floor(seconds / 3600); // 小时部分
   const minutes = Math.floor((seconds % 3600) / 60); // 分钟部分
   const minutes = Math.floor((seconds % 3600) / 60); // 分钟部分
@@ -36,8 +37,14 @@ export function secondFormatConversion(val) {
 
 
   // 根据时间范围返回不同的格式
   // 根据时间范围返回不同的格式
   if (hours > 0) {
   if (hours > 0) {
+    if (type === 'chinese') {
+      return `${hours}时${minutes}分${remainingSeconds}秒`;
+    }
     return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
     return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
   }
   }
+  if (type === 'chinese') {
+    return `${minutes}分${remainingSeconds}秒`;
+  }
   return `${formattedMinutes}:${formattedSeconds}`;
   return `${formattedMinutes}:${formattedSeconds}`;
 }
 }
 
 

+ 147 - 0
src/views/exercise_questions/answer/components/AnswerReport.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="answer-report">
+    <div class="title">测试报告</div>
+    <div class="answer-info">
+      <div>
+        <span>耗时</span>
+        <span>{{ secondFormatConversion(answer_record.answer_duration, 'chinese') }}</span>
+      </div>
+      <div>
+        <span>正确</span>
+        <span>{{ answer_record.right_count }}</span>
+      </div>
+      <div>
+        <span>错误</span>
+        <span>{{ answer_record.error_count }}</span>
+      </div>
+    </div>
+    <div
+      v-if="
+        answer_record.answer_mode === 1 || (answer_record.answer_mode === 2 && answer_record.is_remarked === 'true')
+      "
+      class="answer-list"
+    >
+      <span
+        v-for="({ question_id, is_objective, answer_status }, i) in question_list"
+        :key="question_id"
+        :class="['answer-list-item', { subjectivity: is_objective === 'false', error: answer_status === 2 }]"
+        @click="selectQuestion(i)"
+      >
+        {{ i + 1 }}
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import { GetAnswerRecordReport } from '@/api/exercise';
+
+import { secondFormatConversion } from '@/utils/transform';
+
+export default {
+  name: 'AnswerReport',
+  props: {
+    answerRecordId: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      secondFormatConversion,
+      answer_record: {
+        answer_duration: 0,
+        right_count: 0,
+        error_count: 0,
+      },
+      question_list: [],
+    };
+  },
+  created() {
+    this.getAnswerRecordReport();
+  },
+  methods: {
+    selectQuestion(i) {
+      this.$emit('selectQuestion', i);
+    },
+
+    // 获取答题报告
+    getAnswerRecordReport() {
+      GetAnswerRecordReport({ answer_record_id: this.answerRecordId })
+        .then(({ answer_record, question_list }) => {
+          this.answer_record = answer_record;
+          this.question_list = question_list;
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.answer-report {
+  display: flex;
+  flex-direction: column;
+  row-gap: 24px;
+  align-items: center;
+  height: 100%;
+  padding: 24px 40px;
+
+  .title {
+    font-size: 32px;
+    font-weight: bold;
+    color: $light-main-color;
+  }
+
+  .answer-info {
+    display: flex;
+    column-gap: 120px;
+    justify-content: center;
+    width: 770px;
+    padding: 24px 40px;
+    background-color: #f7f7f7;
+    border-radius: 8px;
+
+    > div {
+      display: flex;
+      flex-direction: column;
+      row-gap: 8px;
+
+      :first-child {
+        color: #949494;
+      }
+
+      :last-child {
+        font-size: 24px;
+        font-weight: bold;
+        color: #000;
+      }
+    }
+  }
+
+  .answer-list {
+    display: flex;
+    gap: 24px;
+    width: 770px;
+
+    &-item {
+      width: 56px;
+      height: 40px;
+      padding: 8px;
+      text-align: center;
+      cursor: pointer;
+      background-color: #f0f0f0;
+      border-radius: 20px;
+
+      &.error {
+        color: #fff;
+        background-color: #f2555a;
+      }
+
+      &.subjectivity {
+        background-color: #fef2a4;
+      }
+    }
+  }
+}
+</style>

+ 54 - 0
src/views/exercise_questions/answer/components/StartQuestion.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="start-question">
+    <div class="notice">答题须知</div>
+    <div class="prompt">
+      <div>本练习题共{{ questionLength }}题,限时{{ answerTimeLimitMinute }}分钟</div>
+      <div>点击“开始答题”按钮进行作答,作答完毕后请点击“提交”按钮。</div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'StartQuestion',
+  props: {
+    questionLength: {
+      type: Number,
+      default: 0,
+    },
+    answerTimeLimitMinute: {
+      type: Number,
+      default: 30,
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+.start-question {
+  display: flex;
+  flex-direction: column;
+  row-gap: 24px;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  padding: 24px 40px;
+  background: #f7f7f7;
+  border-radius: 4px;
+
+  .notice {
+    font-size: 40px;
+    font-weight: bold;
+    color: $light-main-color;
+  }
+
+  .prompt {
+    font-size: 24px;
+    text-align: center;
+  }
+}
+</style>

+ 313 - 4
src/views/exercise_questions/answer/index.vue

@@ -1,15 +1,324 @@
 <template>
 <template>
-  <div></div>
+  <div class="answer">
+    <header class="header">
+      <div class="back round" @click="$router.push('/personal_question')">
+        <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 class="round primary"><SvgIcon icon-class="hourglass" />{{ secondFormatConversion(time) }}</div>
+      </div>
+    </header>
+
+    <main class="main">
+      <StartQuestion
+        v-if="curQuestionIndex === -1"
+        :question-length="questionList.length"
+        :answer-time-limit-minute="answer_time_limit_minute"
+        @startAnswer="startAnswer"
+      />
+
+      <AnswerReport v-else-if="isSubmit" :answer-record-id="answer_record_id" @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">
+      <div v-show="isAnnotations" class="annotations"><i class="el-icon-plus"></i>批注</div>
+
+      <div>
+        <template v-if="curQuestionIndex === -1">
+          <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" type="primary" round @click="submitAnswer">
+            提交
+          </el-button>
+          <el-button v-else type="primary" round @click="fillQuestionAnswer('next')">下一题</el-button>
+        </template>
+      </div>
+    </footer>
+  </div>
 </template>
 </template>
 
 
 <script>
 <script>
+import { secondFormatConversion } from '@/utils/transform';
+import {
+  GetExerciseQuestionIndexList,
+  GetQuestionInfo,
+  GetShareRecordInfo,
+  StartAnswer,
+  FillQuestionAnswer,
+  SubmitAnswer,
+  GetQuestionInfo_AnswerRecord,
+} from '@/api/exercise';
+
+import StartQuestion from './components/StartQuestion.vue';
+import AnswerReport from './components/AnswerReport.vue';
+import PreviewQuestionTypeMixin from '../data/PreviewQuestionTypeMixin';
+
 export default {
 export default {
   name: 'AnswerPage',
   name: 'AnswerPage',
+  components: {
+    StartQuestion,
+    AnswerReport,
+  },
+  mixins: [PreviewQuestionTypeMixin],
   data() {
   data() {
-    return {};
+    const { id, share_record_id } = this.$route.query;
+
+    return {
+      exercise_id: id, // 练习题id
+      share_record_id, // 分享记录id
+      answer_record_id: '', // 答题记录id
+      secondFormatConversion,
+      user_answer_record_info: {
+        correct_answer_show_mode: 1, // 正确答案显示模式
+      }, // 当前用户的答题记录信息
+      // 问题列表
+      questionList: [],
+      // 当前问题
+      currentQuestion: {},
+      // 当前问题索引
+      curQuestionIndex: -1,
+      // 倒计时
+      countDownTimer: null,
+      answer_mode: 1, // 答题模式
+      answer_time_limit_minute: 30, // 答题时间限制
+      time: 1800,
+      isSubmit: false,
+      curQuestionPage: '', // 当前问题页面
+      isAnnotations: false, // 是否显示批注
+    };
+  },
+  watch: {
+    curQuestionIndex() {
+      this.getQuestionInfo();
+      this.getQuestionInfo_AnswerRecord();
+    },
+  },
+  created() {
+    this.init();
+  },
+  beforeDestroy() {
+    if (this.countDownTimer) clearInterval(this.countDownTimer);
+  },
+  methods: {
+    // 初始化
+    init() {
+      if (this.exercise_id) {
+        this.getExerciseQuestionIndexList();
+      }
+
+      if (this.share_record_id) {
+        GetShareRecordInfo({ share_record_id: this.share_record_id }).then(
+          ({ user_answer_record_info, share_record: { exercise_id } }) => {
+            this.user_answer_record_info = user_answer_record_info;
+            this.exercise_id = exercise_id;
+            this.getExerciseQuestionIndexList();
+          },
+        );
+      }
+    },
+    getExerciseQuestionIndexList() {
+      GetExerciseQuestionIndexList({ exercise_id: this.exercise_id }).then(({ index_list }) => {
+        this.questionList = index_list.map((item) => ({
+          ...item,
+          isFill: false,
+        }));
+      });
+    },
+    // 倒计时
+    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_time_limit_minute, answer_mode, answer_record_id }) => {
+          this.answer_record_id = answer_record_id;
+          this.answer_time_limit_minute = answer_time_limit_minute;
+          this.time = answer_time_limit_minute * 60;
+          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;
+    },
+    // 获取题目信息
+    getQuestionInfo() {
+      GetQuestionInfo({ question_id: this.questionList[this.curQuestionIndex].id })
+        .then(({ question }) => {
+          if (!question.content) return;
+
+          this.currentQuestion = JSON.parse(question.content);
+          this.curQuestionPage =
+            this.questionList.length === 0 || this.curQuestionIndex < 0
+              ? ''
+              : this.previewComponents[this.questionList[this.curQuestionIndex].type];
+        })
+        .catch(() => {});
+    },
+    /**
+     * 填写答案
+     * @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();
+      }
+
+      let answer = this.$refs.exercise[0].answer;
+      return FillQuestionAnswer({
+        answer_record_id: this.answer_record_id,
+        question_id: this.questionList[this.curQuestionIndex].id,
+        answer: JSON.stringify(answer),
+      }).then(() => {
+        this.questionList[this.curQuestionIndex].isFill = true;
+        // 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() {
+      if (!this.questionList[this.curQuestionIndex].isFill) return;
+
+      GetQuestionInfo_AnswerRecord({
+        answer_record_id: this.answer_record_id,
+        question_id: this.questionList[this.curQuestionIndex].id,
+      }).then(({ user_answer: { is_fill_answer, content } }) => {
+        if (is_fill_answer === 'false') return;
+        this.$refs.exercise[0].showAnswer(
+          this.answer_mode === 1,
+          this.user_answer_record_info.correct_answer_show_mode === 1,
+          JSON.parse(content),
+        );
+      });
+    },
+    // 提交答题
+    submitAnswer() {
+      if (!this.answer_record_id) return;
+
+      // 如果已经填写过答案,直接提交
+      if (this.questionList[this.curQuestionIndex].isFill) {
+        SubmitAnswer({ answer_record_id: this.answer_record_id }).then(() => {
+          this.isSubmit = true;
+        });
+        return;
+      }
+
+      this.fillQuestionAnswer('next').then(() => {
+        SubmitAnswer({ answer_record_id: this.answer_record_id }).then(() => {
+          this.isSubmit = true;
+        });
+      });
+    },
+    selectQuestion(i) {
+      console.log(i);
+      this.isSubmit = false;
+      this.curQuestionIndex = i;
+    },
   },
   },
-  methods: {},
 };
 };
 </script>
 </script>
 
 
-<style lang="scss" scoped></style>
+<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;
+      cursor: pointer;
+      background-color: $fill-color;
+      border-radius: 20px;
+    }
+
+    .el-button {
+      padding: 9px 40px;
+    }
+  }
+}
+</style>

+ 2 - 2
src/views/exercise_questions/create/components/exercises/TalkPictureQuestion.vue

@@ -53,7 +53,7 @@
             </div>
             </div>
           </template>
           </template>
         </div>
         </div>
-        <UploadDrag @fileUploadSuccess="fileUploadSuccess" :limit="999" ref="uploadDrag"></UploadDrag>
+        <UploadDrag ref="uploadDrag" :limit="999" @fileUploadSuccess="fileUploadSuccess" />
       </div>
       </div>
     </template>
     </template>
 
 
@@ -143,8 +143,8 @@ import UploadDrag from '../common/UploadDrag.vue';
 
 
 export default {
 export default {
   name: 'TalkPicture',
   name: 'TalkPicture',
-  mixins: [QuestionMixin],
   components: { UploadDrag },
   components: { UploadDrag },
+  mixins: [QuestionMixin],
   data() {
   data() {
     return {
     return {
       data: JSON.parse(JSON.stringify(talkPictrueData)),
       data: JSON.parse(JSON.stringify(talkPictrueData)),

+ 22 - 68
src/views/exercise_questions/create/index.vue

@@ -43,7 +43,9 @@
             <span class="preview-button plain" @click="refreshPreviewData">
             <span class="preview-button plain" @click="refreshPreviewData">
               <SvgIcon icon-class="loop" size="14" /><span>刷新</span>
               <SvgIcon icon-class="loop" size="14" /><span>刷新</span>
             </span>
             </span>
-            <span class="preview-button"><SvgIcon icon-class="eye" /><span>完整预览</span></span>
+            <span class="preview-button" @click="fullPreview">
+              <SvgIcon icon-class="eye" /> <span>完整预览</span>
+            </span>
             <span class="preview-button plain" @click="setPreview"><SvgIcon icon-class="close" />关闭预览</span>
             <span class="preview-button plain" @click="setPreview"><SvgIcon icon-class="close" />关闭预览</span>
           </template>
           </template>
           <template v-else>
           <template v-else>
@@ -70,57 +72,19 @@ import {
 } from '@/api/exercise';
 } from '@/api/exercise';
 
 
 import CreateMain from './components/create.vue';
 import CreateMain from './components/create.vue';
-import SelectPreview from '@/views/exercise_questions/preview/SelectPreview.vue';
-import JudgePreview from '@/views/exercise_questions/preview/JudgePreview.vue';
-import MatchingPreview from '@/views/exercise_questions/preview/MatchingPreview.vue';
-import ChinesePreview from '@/views/exercise_questions/preview/ChinesePreview.vue';
-import WritePreview from '../preview/WritePreview.vue';
-import FillPreview from '../preview/FillPreview.vue';
-import ReadAloudPreview from '../preview/ReadAloudPreview.vue';
-import DialoguePreview from '../preview/DialoguePreview.vue';
-import TalkPictruePreview from '../preview/TalkPictruePreview.vue';
-import ChooseTonePreview from '../preview/ChooseTonePreview.vue';
-import RepeatPreview from '../preview/RepeatPreview.vue';
-import ReadPreview from '../preview/ReadPreview.vue';
-import SortPreview from '../preview/SortPreview.vue';
-import ListenSelectPreview from '../preview/ListenSelectPreview.vue';
-import ListenFillPreview from '../preview/ListenFillPreview.vue';
-import ListenJudgePreview from '../preview/ListenJudgePreview.vue';
-import WordCardPreview from '../preview/WordCardPreview.vue';
-import AnswerQuestionPreview from '../preview/AnswerQuestionPreview.vue';
-import WritePictruePreview from '../preview/WritePictruePreview.vue';
-import ReplaceAnswerPreview from '../preview/ReplaceAnswerPreview.vue';
+import PreviewQuestionTypeMixin from '../data/PreviewQuestionTypeMixin';
 
 
 export default {
 export default {
   name: 'CreateExercise',
   name: 'CreateExercise',
   components: {
   components: {
     CreateMain,
     CreateMain,
-    SelectPreview,
-    JudgePreview,
-    MatchingPreview,
-    ChinesePreview,
-    WritePreview,
-    FillPreview,
-    ReadAloudPreview,
-    DialoguePreview,
-    TalkPictruePreview,
-    ChooseTonePreview,
-    RepeatPreview,
-    ReadPreview,
-    SortPreview,
-    ListenSelectPreview,
-    ListenFillPreview,
-    ListenJudgePreview,
-    WordCardPreview,
-    AnswerQuestionPreview,
-    WritePictruePreview,
-    ReplaceAnswerPreview,
   },
   },
+  mixins: [PreviewQuestionTypeMixin],
   provide() {
   provide() {
     return {
     return {
       isSetUp: () => this.isSetUp,
       isSetUp: () => this.isSetUp,
       updateCurQuestionType: this.updateCurQuestionType,
       updateCurQuestionType: this.updateCurQuestionType,
-      exercise_id: this.id,
+      exercise_id: this.exercise_id,
       refreshPreviewData: this.refreshPreviewData,
       refreshPreviewData: this.refreshPreviewData,
     };
     };
   },
   },
@@ -128,7 +92,7 @@ export default {
     const { id } = this.$route.query;
     const { id } = this.$route.query;
 
 
     return {
     return {
-      id, // 练习id
+      exercise_id: id, // 练习id
       exercise: { name: '' }, // 练习信息
       exercise: { name: '' }, // 练习信息
       isEditExercise: false, // 是否编辑练习
       isEditExercise: false, // 是否编辑练习
       curIndex: 0, // 当前练习索引
       curIndex: 0, // 当前练习索引
@@ -137,28 +101,6 @@ export default {
       isSetUp: false, // 设置
       isSetUp: false, // 设置
       preview: false, // 预览显示
       preview: false, // 预览显示
       previewData: {}, // 预览数据
       previewData: {}, // 预览数据
-      previewComponents: {
-        select: SelectPreview,
-        judge: JudgePreview,
-        matching: MatchingPreview,
-        chinese: ChinesePreview,
-        write: WritePreview,
-        fill: FillPreview,
-        read_aloud: ReadAloudPreview,
-        dialogue: DialoguePreview,
-        talk_picture: TalkPictruePreview,
-        choose_tone: ChooseTonePreview,
-        repeat: RepeatPreview,
-        read: ReadPreview,
-        sort: SortPreview,
-        listen_select: ListenSelectPreview,
-        listen_fill: ListenFillPreview,
-        listen_judge: ListenJudgePreview,
-        word_card: WordCardPreview,
-        answer_question: AnswerQuestionPreview,
-        write_picture: WritePictruePreview,
-        replace_answer: ReplaceAnswerPreview,
-      },
     };
     };
   },
   },
   computed: {
   computed: {
@@ -190,7 +132,7 @@ export default {
      */
      */
     addQuestionToExercise(type, additional_type, content = '') {
     addQuestionToExercise(type, additional_type, content = '') {
       AddQuestionToExercise({
       AddQuestionToExercise({
-        exercise_id: this.id,
+        exercise_id: this.exercise_id,
         type,
         type,
         additional_type,
         additional_type,
         content,
         content,
@@ -215,7 +157,7 @@ export default {
      * 获取练习题目索引列表
      * 获取练习题目索引列表
      */
      */
     getExerciseQuestionIndexList(init) {
     getExerciseQuestionIndexList(init) {
-      GetExerciseQuestionIndexList({ exercise_id: this.id })
+      GetExerciseQuestionIndexList({ exercise_id: this.exercise_id })
         .then(({ index_list }) => {
         .then(({ index_list }) => {
           this.index_list = index_list;
           this.index_list = index_list;
 
 
@@ -227,7 +169,7 @@ export default {
         });
         });
     },
     },
     getExerciseInfo() {
     getExerciseInfo() {
-      GetExerciseInfo({ exercise_id: this.id }).then(({ exercise }) => {
+      GetExerciseInfo({ exercise_id: this.exercise_id }).then(({ exercise }) => {
         this.exercise = exercise;
         this.exercise = exercise;
       });
       });
     },
     },
@@ -301,6 +243,18 @@ export default {
       this.previewData = this.$refs.createMain.$refs.exercise?.[0].data || {};
       this.previewData = this.$refs.createMain.$refs.exercise?.[0].data || {};
       this.childPreviewData = this.$refs.createMain.$refs.exercise?.[0].childPreviewData || [];
       this.childPreviewData = this.$refs.createMain.$refs.exercise?.[0].childPreviewData || [];
     },
     },
+    // 完整预览
+    fullPreview() {
+      window.open(
+        this.$router.resolve({
+          path: '/answer',
+          query: {
+            id: this.exercise_id,
+          },
+        }).href,
+        '_blank',
+      );
+    },
     // 预览
     // 预览
     setPreview() {
     setPreview() {
       this.preview = !this.preview;
       this.preview = !this.preview;

+ 73 - 0
src/views/exercise_questions/data/PreviewQuestionTypeMixin.js

@@ -0,0 +1,73 @@
+import SelectPreview from '@/views/exercise_questions/preview/SelectPreview.vue';
+import JudgePreview from '@/views/exercise_questions/preview/JudgePreview.vue';
+import MatchingPreview from '@/views/exercise_questions/preview/MatchingPreview.vue';
+import ChinesePreview from '@/views/exercise_questions/preview/ChinesePreview.vue';
+import WritePreview from '../preview/WritePreview.vue';
+import FillPreview from '../preview/FillPreview.vue';
+import ReadAloudPreview from '../preview/ReadAloudPreview.vue';
+import DialoguePreview from '../preview/DialoguePreview.vue';
+import TalkPictruePreview from '../preview/TalkPictruePreview.vue';
+import ChooseTonePreview from '../preview/ChooseTonePreview.vue';
+import RepeatPreview from '../preview/RepeatPreview.vue';
+import ReadPreview from '../preview/ReadPreview.vue';
+import SortPreview from '../preview/SortPreview.vue';
+import ListenSelectPreview from '../preview/ListenSelectPreview.vue';
+import ListenFillPreview from '../preview/ListenFillPreview.vue';
+import ListenJudgePreview from '../preview/ListenJudgePreview.vue';
+import WordCardPreview from '../preview/WordCardPreview.vue';
+import AnswerQuestionPreview from '../preview/AnswerQuestionPreview.vue';
+import WritePictruePreview from '../preview/WritePictruePreview.vue';
+import ReplaceAnswerPreview from '../preview/ReplaceAnswerPreview.vue';
+
+const PreviewQuestionTypeMixin = {
+  components: {
+    SelectPreview,
+    JudgePreview,
+    MatchingPreview,
+    ChinesePreview,
+    WritePreview,
+    FillPreview,
+    ReadAloudPreview,
+    DialoguePreview,
+    TalkPictruePreview,
+    ChooseTonePreview,
+    RepeatPreview,
+    ReadPreview,
+    SortPreview,
+    ListenSelectPreview,
+    ListenFillPreview,
+    ListenJudgePreview,
+    WordCardPreview,
+    AnswerQuestionPreview,
+    WritePictruePreview,
+    ReplaceAnswerPreview,
+  },
+  data() {
+    return {
+      previewComponents: {
+        select: SelectPreview,
+        judge: JudgePreview,
+        matching: MatchingPreview,
+        chinese: ChinesePreview,
+        write: WritePreview,
+        fill: FillPreview,
+        read_aloud: ReadAloudPreview,
+        dialogue: DialoguePreview,
+        talk_picture: TalkPictruePreview,
+        choose_tone: ChooseTonePreview,
+        repeat: RepeatPreview,
+        read: ReadPreview,
+        sort: SortPreview,
+        listen_select: ListenSelectPreview,
+        listen_fill: ListenFillPreview,
+        listen_judge: ListenJudgePreview,
+        word_card: WordCardPreview,
+        answer_question: AnswerQuestionPreview,
+        write_picture: WritePictruePreview,
+        replace_answer: ReplaceAnswerPreview,
+      },
+    };
+  },
+};
+
+export default PreviewQuestionTypeMixin;

+ 4 - 3
src/views/exercise_questions/data/common.js

@@ -22,7 +22,7 @@ export const questionTypeOption = [
       { label: '听说训练', value: 'repeat' },
       { label: '听说训练', value: 'repeat' },
       { label: '看图说话', value: 'talk_picture' },
       { label: '看图说话', value: 'talk_picture' },
       { label: '对话题', value: 'dialogue' },
       { label: '对话题', value: 'dialogue' },
-      { label: '回答问题', value: 'answer_question' },
+      // { label: '回答问题', value: 'answer_question' },
       { label: '替换练习', value: 'replace_answer' },
       { label: '替换练习', value: 'replace_answer' },
     ],
     ],
   },
   },
@@ -220,11 +220,12 @@ export function addTone(number, con) {
   }
   }
   return cons;
   return cons;
 }
 }
-//
+
 /**
 /**
  * 输入框输入小于0的返回0 且为整数
  * 输入框输入小于0的返回0 且为整数
+ * @param {number|string} number
  */
  */
 export function handleInputNumber(number) {
 export function handleInputNumber(number) {
-  let number_int = number * 1;
+  const number_int = Number(number);
   return number_int > 0 ? Math.floor(number_int) : 1;
   return number_int > 0 ? Math.floor(number_int) : 1;
 }
 }

+ 40 - 3
src/views/exercise_questions/preview/SelectPreview.vue

@@ -14,12 +14,12 @@
       <li
       <li
         v-for="({ content, mark }, i) in data.option_list"
         v-for="({ content, mark }, i) in data.option_list"
         :key="mark"
         :key="mark"
-        :class="['option-item', { active: isAnswer(mark) }]"
+        :class="['option-item', { active: isAnswer(mark) }, ...computedAnswerClass(mark)]"
         @click="selectAnswer(mark)"
         @click="selectAnswer(mark)"
       >
       >
         <span class="selectionbox"></span>
         <span class="selectionbox"></span>
         <span>{{ computeOptionMethods[data.option_number_show_mode](i) }} </span>
         <span>{{ computeOptionMethods[data.option_number_show_mode](i) }} </span>
-        <span v-html="sanitizeHTML(content)"></span>
+        <span class="content" v-html="sanitizeHTML(content)"></span>
       </li>
       </li>
     </ul>
     </ul>
   </div>
   </div>
@@ -40,7 +40,7 @@ export default {
   },
   },
   methods: {
   methods: {
     isAnswer(mark) {
     isAnswer(mark) {
-      return this.answer.answer_list.indexOf(mark) !== -1;
+      return this.answer.answer_list.includes(mark);
     },
     },
     selectAnswer(mark) {
     selectAnswer(mark) {
       const index = this.answer.answer_list.indexOf(mark);
       const index = this.answer.answer_list.indexOf(mark);
@@ -55,6 +55,17 @@ export default {
         }
         }
       }
       }
     },
     },
+    computedAnswerClass(mark) {
+      if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
+        return [];
+      }
+      let isHas = this.answer.answer_list.includes(mark);
+      if (!isHas) {
+        return [];
+      }
+      let isRight = this.data.answer.answer_list.includes(mark) && this.isJudgingRightWrong;
+      return isRight ? ['right'] : ['wrong'];
+    },
   },
   },
 };
 };
 </script>
 </script>
@@ -88,6 +99,10 @@ export default {
         border-radius: 50%;
         border-radius: 50%;
       }
       }
 
 
+      .content {
+        flex: 1;
+      }
+
       &.active {
       &.active {
         color: #34343a;
         color: #34343a;
         background-color: #e7eeff;
         background-color: #e7eeff;
@@ -96,6 +111,28 @@ export default {
           border-color: $light-main-color;
           border-color: $light-main-color;
           border-width: 4px;
           border-width: 4px;
         }
         }
+
+        &.right {
+          background-color: $content-color;
+          border: 1px solid #30a47d;
+
+          &::after {
+            font-size: 14px;
+            color: #30a47d;
+            content: '已选';
+          }
+        }
+
+        &.wrong {
+          background-color: $content-color;
+          border: 1px solid #f2555a;
+
+          &::after {
+            font-size: 14px;
+            color: #f2555a;
+            content: '已选';
+          }
+        }
       }
       }
     }
     }
   }
   }

+ 1 - 2
src/views/exercise_questions/preview/WritePictruePreview.vue

@@ -72,13 +72,12 @@
 <script>
 <script>
 import PreviewMixin from './components/PreviewMixin';
 import PreviewMixin from './components/PreviewMixin';
 import { GetFileStoreInfo } from '@/api/app';
 import { GetFileStoreInfo } from '@/api/app';
-import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
+
 import UploadFiles from './components/common/UploadFiles.vue';
 import UploadFiles from './components/common/UploadFiles.vue';
 
 
 export default {
 export default {
   name: 'WritePicturePreview',
   name: 'WritePicturePreview',
   components: {
   components: {
-    SoundRecordPreview,
     UploadFiles,
     UploadFiles,
   },
   },
   mixins: [PreviewMixin],
   mixins: [PreviewMixin],

+ 13 - 0
src/views/exercise_questions/preview/components/PreviewMixin.js

@@ -18,6 +18,8 @@ const PreviewMixin = {
     return {
     return {
       isEnable,
       isEnable,
       answer: { answer_list: [] }, // 答案
       answer: { answer_list: [] }, // 答案
+      isJudgingRightWrong: false, // 是否判断对错
+      isShowRightAnswer: false, // 是否显示正确答案
     };
     };
   },
   },
   watch: {
   watch: {
@@ -38,6 +40,17 @@ const PreviewMixin = {
       return this.answer;
       return this.answer;
     },
     },
     /**
     /**
+     * 显示答案
+     * @param {Boolean} isJudgingRightWrong 是否判断对错
+     * @param {Boolean} isShowRightAnswer 是否显示正确答案
+     * @param {Object} userAnswer 用户答案
+     */
+    showAnswer(isJudgingRightWrong, isShowRightAnswer, userAnswer) {
+      this.isJudgingRightWrong = isJudgingRightWrong;
+      this.isShowRightAnswer = isShowRightAnswer;
+      if (userAnswer) this.answer = userAnswer;
+    },
+    /**
      * 过滤 html,防止 xss 攻击
      * 过滤 html,防止 xss 攻击
      * @param {String} html 需要过滤的html
      * @param {String} html 需要过滤的html
      * @returns {String} 过滤后的html
      * @returns {String} 过滤后的html

+ 23 - 6
src/views/share/ShareExercise.vue

@@ -3,11 +3,17 @@
 </template>
 </template>
 
 
 <script>
 <script>
+import { GetShareRecordInfo } from '@/api/exercise';
+
 export default {
 export default {
   name: 'ShareExercise',
   name: 'ShareExercise',
 
 
   data() {
   data() {
-    return {};
+    let { share_record_id } = this.$route.query;
+
+    return {
+      share_record_id,
+    };
   },
   },
   created() {
   created() {
     let info = navigator.userAgent;
     let info = navigator.userAgent;
@@ -15,13 +21,24 @@ export default {
     let isPhone = /mobile|iPad/i.test(info);
     let isPhone = /mobile|iPad/i.test(info);
     // 如果包含“Mobile”(是手机设备)则返回true
     // 如果包含“Mobile”(是手机设备)则返回true
     if (isPhone) {
     if (isPhone) {
-      window.location.href = `${window.location.origin}/GCLS-Mobile/#/open/share/exercise?share_record_id=${this.$route.query.share_record_id}`;
+      window.location.href = `${window.location.origin}/GCLS-Mobile/#/open/share/exercise?share_record_id=${this.share_record_id}`;
     } else {
     } else {
-      console.log('pc');
+      this.getShareRecordInfo();
     }
     }
   },
   },
-  methods: {},
+  methods: {
+    getShareRecordInfo() {
+      GetShareRecordInfo({
+        share_record_id: this.share_record_id,
+      }).then(({ share_record }) => {
+        if (share_record.access_popedom === 1) {
+          this.$router.push({ path: '/exercise', query: { id: share_record.exercise_id } });
+        }
+        if (share_record.access_popedom === 2) {
+          this.$router.push({ path: '/answer', query: { share_record_id: this.share_record_id } });
+        }
+      });
+    },
+  },
 };
 };
 </script>
 </script>
-
-<style lang="scss" scoped></style>