Browse Source

表格的答题方式

natasha 6 days ago
parent
commit
bbd5cd3000

+ 27 - 0
src/views/book/courseware/create/components/question/table/Table.vue

@@ -81,6 +81,14 @@
           </div>
         </div>
       </div>
+      <el-input
+        v-if="data.property.fill_type === fillTypeList[1].value"
+        v-model="data.vocabulary"
+        type="textarea"
+        :autosize="{ minRows: 2, maxRows: 4 }"
+        resize="none"
+        placeholder="请输入词汇,用于选词填空"
+      />
       <p class="tips">在需要作答的单元格内输入三个以上下划线“___”</p>
       <el-button @click="identifyText">识别</el-button>
       <el-button @click="handleMultilingual">多语言</el-button>
@@ -127,6 +135,7 @@ import {
   tableTypeList,
   fontFamilyList,
   getAnswerOption,
+  fillTypeList,
 } from '@/views/book/courseware/data/table';
 
 export default {
@@ -144,6 +153,7 @@ export default {
       multilingualText: '',
       isViewExplanatoryNoteDialog: false,
       oldRichData: {},
+      fillTypeList,
     };
   },
   watch: {
@@ -221,6 +231,21 @@ export default {
       },
       deep: true,
     },
+    'data.vocabulary': {
+      handler(val) {
+        if (!val) return;
+        this.data.word_list = val
+          .split(/[\n\r]+/)
+          .map((item) => item.split(' ').filter((s) => s))
+          .flat()
+          .map((content) => {
+            return {
+              content,
+              mark: getRandomNumber(),
+            };
+          });
+      },
+    },
   },
   methods: {
     // 识别文本
@@ -250,6 +275,8 @@ export default {
                       type: 'input',
                       mark: getRandomNumber(),
                       inputIndex,
+                      write_base64: '',
+                      audio_answer_list: [],
                     });
                     inputIndex++;
                   }

+ 9 - 1
src/views/book/courseware/create/components/question/table/TableSetting.vue

@@ -20,6 +20,13 @@
           <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">{{ label }}</el-radio>
         </el-radio-group>
       </el-form-item> -->
+      <el-form-item label="填空方式">
+        <el-radio-group v-model="property.fill_type">
+          <el-radio v-for="{ value, label } in fillTypeList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
       <el-divider />
       <el-form-item label="首行颜色">
         <el-color-picker v-model="property.first_line_color" />
@@ -63,7 +70,7 @@
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
 
-import { getTableProperty, switchOption, alignTypeList } from '@/views/book/courseware/data/table';
+import { getTableProperty, switchOption, alignTypeList, fillTypeList } from '@/views/book/courseware/data/table';
 import { isEnable, pinyinPositionList } from '@/views/book/courseware/data/common';
 
 export default {
@@ -76,6 +83,7 @@ export default {
       alignTypeList,
       pinyinPositionList,
       isEnable,
+      fillTypeList,
     };
   },
   methods: {},

+ 11 - 0
src/views/book/courseware/data/table.js

@@ -22,6 +22,14 @@ export const fontFamilyList = [
   { value: 'League', label: '拼音' },
 ];
 
+// 填空方式
+export const fillTypeList = [
+  { value: 'input', label: '输入' },
+  { value: 'select', label: '选词' },
+  { value: 'handwriting', label: '手写' },
+  { value: 'voice', label: '语音' },
+];
+
 
 export function getTableProperty() {
   return {
@@ -35,6 +43,7 @@ export function getTableProperty() {
     row_count: 2,
     column_count: 3,
     auto_wrap: switchOption[0].value, // 自动换行
+    fill_type: fillTypeList[0].value,
     first_line_color: '', // 首行颜色
     first_column_color: '', // 首列颜色
     border_color: '#e6e6e6', // 边框颜色
@@ -69,6 +78,8 @@ export function getTableData() {
     record_list: [],
     answer_list:Array.from({ length: 2 }, () => Array.from({ length: 3 }, getAnswerOption)),
     mode: tableTypeList[0].value,
+    vocabulary: '', // 用于选词的词汇
+    word_list: [], // 选词列表
     styles: {
       fontFamily: 'Arial',
       fontSize: '12pt',

+ 2 - 2
src/views/book/courseware/preview/components/newWord_template/NewWordTemplatePreview.vue

@@ -255,10 +255,10 @@ export default {
   }
 
   .pinyin {
-    height: 30px;
+    height: 24px;
     min-height: 16px;
     font-family: 'League';
-    font-size: 20px;
+    font-size: 16px;
     font-weight: 400;
     color: rgba(0, 0, 0, 50%);
     text-align: center;

+ 202 - 21
src/views/book/courseware/preview/components/table/TablePreview.vue

@@ -57,16 +57,61 @@
                       :class="[index === 0 ? 'pinyin-text-left' : '']"
                     >
                       <template v-if="item.type === 'input'">
-                        <el-input
-                          :key="index"
-                          v-model="item.value"
-                          :disabled="disabled"
-                          :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
-                        />
+                        <template v-if="data.property.fill_type === fillTypeList[0].value">
+                          <el-input
+                            :key="index"
+                            v-model="item.value"
+                            :disabled="disabled"
+                            :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
+                          />
+                        </template>
+                        <template v-else-if="data.property.fill_type === fillTypeList[1].value">
+                          <el-popover :key="index" placement="top" trigger="click">
+                            <div class="word-list">
+                              <span
+                                v-for="{ content, mark } in data.word_list"
+                                :key="mark"
+                                class="word-item"
+                                @click="handleSelectWord(content, mark, item)"
+                              >
+                                {{ content }}
+                              </span>
+                            </div>
+
+                            <el-input
+                              slot="reference"
+                              v-model="item.value"
+                              :readonly="true"
+                              :class="[data.property.fill_font, ...computedAnswerClass(item.mark)]"
+                              :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
+                            />
+                          </el-popover>
+                        </template>
+
+                        <template v-else-if="data.property.fill_type === fillTypeList[2].value">
+                          <span :key="j" class="write-click" @click="handleWriteClick(item)">
+                            <img
+                              v-show="item.write_base64"
+                              style="background-color: #f4f4f4"
+                              :src="item.write_base64"
+                              alt="write-show"
+                            />
+                          </span>
+                        </template>
+
+                        <template v-else-if="data.property.fill_type === fillTypeList[3].value">
+                          <SoundRecordBox
+                            ref="record"
+                            :key="j"
+                            type="mini"
+                            :many-times="false"
+                            class="record-box"
+                            :answer-record-list="data.audio_answer_list"
+                            :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
+                            @handleWav="handleMiniWav($event, item)"
+                          />
+                        </template>
                         <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">&nbsp;</span>
-                        <!-- <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${indexs}`" class="right-answer">
-                {{ computedAnswerText(li.mark) }}
-              </span> -->
                       </template>
                       <template v-else>
                         <span v-if="data.property.pinyin_position === 'top'" class="pinyin">
@@ -85,12 +130,60 @@
                     <p v-for="(item, index) in col.model_essay" :key="index">
                       <span v-if="item.type === 'text'" :key="index" v-html="sanitizeHTML(item.value)"></span>
                       <template v-if="item.type === 'input'">
-                        <el-input
-                          :key="index"
-                          v-model="item.value"
-                          :disabled="disabled"
-                          :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
-                        />
+                        <template v-if="data.property.fill_type === fillTypeList[0].value">
+                          <el-input
+                            :key="index"
+                            v-model="item.value"
+                            :disabled="disabled"
+                            :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
+                          />
+                        </template>
+                        <template v-else-if="data.property.fill_type === fillTypeList[1].value">
+                          <el-popover :key="index" placement="top" trigger="click">
+                            <div class="word-list">
+                              <span
+                                v-for="{ content, mark } in data.word_list"
+                                :key="mark"
+                                class="word-item"
+                                @click="handleSelectWord(content, mark, item)"
+                              >
+                                {{ content }}
+                              </span>
+                            </div>
+
+                            <el-input
+                              slot="reference"
+                              v-model="item.value"
+                              :readonly="true"
+                              :class="[data.property.fill_font, ...computedAnswerClass(item.mark)]"
+                              :style="[{ width: Math.max(80, item.value.length * 21.3) + 'px' }]"
+                            />
+                          </el-popover>
+                        </template>
+
+                        <template v-else-if="data.property.fill_type === fillTypeList[2].value">
+                          <span :key="j" class="write-click" @click="handleWriteClick(item)">
+                            <img
+                              v-show="item.write_base64"
+                              style="background-color: #f4f4f4"
+                              :src="item.write_base64"
+                              alt="write-show"
+                            />
+                          </span>
+                        </template>
+
+                        <template v-else-if="data.property.fill_type === fillTypeList[3].value">
+                          <SoundRecordBox
+                            ref="record"
+                            :key="j"
+                            type="mini"
+                            :many-times="false"
+                            class="record-box"
+                            :answer-record-list="data.audio_answer_list"
+                            :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
+                            @handleWav="handleMiniWav($event, item)"
+                          />
+                        </template>
                         <!-- <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${indexs}`" class="right-answer">
                 {{ computedAnswerText(li.mark) }}
               </span> -->
@@ -113,24 +206,30 @@
         </table>
       </div>
     </div>
+    <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
   </div>
 </template>
 
 <script>
-import { getTableData } from '@/views/book/courseware/data/table';
+import { getTableData, fillTypeList } from '@/views/book/courseware/data/table';
 
 import PreviewMixin from '../common/PreviewMixin';
-import { isEnable } from '../../../data/common';
+import WriteDialog from '../fill/components/WriteDialog.vue';
+import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
 
 export default {
   name: 'TablePreview',
-  components: {},
+  components: { SoundRecordBox, WriteDialog },
   mixins: [PreviewMixin],
   data() {
     return {
       data: getTableData(),
       table_width: 0,
       multilingualTextList: {}, // 多语言对应的切割后的翻译
+      fillTypeList,
+      selectedWordList: [], // 用于存储选中的词汇
+      writeVisible: false,
+      writeMark: '',
     };
   },
   computed: {
@@ -186,7 +285,6 @@ export default {
           this.$set(this.multilingualTextList, item.type, chunkedArr);
         });
       }
-      console.log(this.data);
     },
     computedAnswerText(mark) {
       if (!this.isShowRightAnswer) return '';
@@ -199,6 +297,65 @@ export default {
       if (isRight) return '';
       return `(${answerValue})`;
     },
+    /**
+     * 计算答题对错选项字体颜色
+     * @param {string} mark 选项标识
+     */
+    computedAnswerClass(mark) {
+      if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
+        return '';
+      }
+      let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
+      let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
+      if (!selectOption) return '';
+      let selectValue = selectOption.value;
+      let answerValue = answerOption.value;
+      let answerType = answerOption.type;
+      let classList = [];
+      let isRight =
+        answerType === 'only_one' ? selectValue === answerValue : answerValue.split('/').includes(selectValue);
+      if (this.isJudgingRightWrong) {
+        isRight ? classList.push('right') : classList.push('wrong');
+      }
+
+      if (this.isShowRightAnswer && !isRight) {
+        classList.push('show-right-answer');
+      }
+      return classList;
+    },
+    /**
+     * 处理小音频录音
+     * @param {Object} data 音频数据
+     * @param {String} mark 选项标识
+     */
+    handleMiniWav(data, mark) {
+      if (!data || !mark) return;
+      this.$set(mark, 'audio_answer_list', data);
+    },
+    /**
+     * 处理选中词汇
+     * @param {String} content 选中的词汇内容
+     * @param {String} mark 选项标识
+     * @param {Object} li 当前输入框对象
+     */
+    handleSelectWord(content, mark, li) {
+      if (!content || !mark || !li) return;
+
+      li.value = content;
+      this.selectedWordList.push(mark);
+    },
+    /**
+     * 处理书写区确认
+     * @param {String} data 书写区数据
+     */
+    handleWriteConfirm(data) {
+      if (!data) return;
+      this.$set(this.writeMark, 'write_base64', data);
+    },
+    handleWriteClick(mark) {
+      this.writeVisible = true;
+      this.writeMark = mark;
+    },
   },
 };
 </script>
@@ -234,6 +391,28 @@ $border-color: #e6e6e6;
         margin: 0;
       }
 
+      .record-box {
+        display: inline-flex;
+        align-items: center;
+        background-color: #fff;
+        border-bottom: 1px solid $font-color;
+      }
+
+      .write-click {
+        display: inline-block;
+        width: 104px;
+        height: 32px;
+        padding: 0 4px;
+        vertical-align: bottom;
+        cursor: pointer;
+        border-bottom: 1px solid $font-color;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
       .el-input {
         display: inline-flex;
         align-items: center;
@@ -276,7 +455,8 @@ $border-color: #e6e6e6;
 
         :deep input.el-input__inner {
           padding: 0;
-          font-size: 16pt;
+
+          // font-size: 16pt;
           color: $font-color;
           text-align: center;
           background-color: #fff;
@@ -302,13 +482,14 @@ $border-color: #e6e6e6;
       }
 
       .pinyin {
+        height: 18px;
         font-family: 'League';
         font-size: 12px;
       }
 
       &-left {
         span {
-          text-align: left;
+          // text-align: left;
         }
       }
     }