BlankAnswerEditor.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <template>
  2. <div class="blank-answer-editor">
  3. <el-card>
  4. <div class="blank-header">
  5. <span style="width: 80px;">填空序号</span>
  6. <span style="flex: 1;">正确答案</span>
  7. <span style="width: 120px; text-align: center;">分值</span>
  8. </div>
  9. <div
  10. v-for="(blank, index) in blankAnswers"
  11. :key="index"
  12. class="blank-item"
  13. >
  14. <span style="width: 80px;">{{ blank.blank_index }}</span>
  15. <el-input
  16. v-model="blank.correct_answer"
  17. placeholder="请输入正确答案"
  18. :disabled="readonly"
  19. style="flex: 1; margin: 0 10px;"
  20. />
  21. <el-input-number
  22. v-model="blank.score"
  23. :min="0"
  24. :max="100"
  25. :step="1"
  26. :precision="0"
  27. :controls="false"
  28. :disabled="readonly"
  29. style="width: 120px;"
  30. placeholder="分值"
  31. />
  32. </div>
  33. <div class="blank-tip">
  34. 提示:在题目中使用"___"(三个下划线)表示填空位置,系统会自动识别填空数量
  35. </div>
  36. </el-card>
  37. </div>
  38. </template>
  39. <script setup lang="ts">
  40. import { ref, watch, computed } from 'vue';
  41. interface BlankAnswer {
  42. blank_index: number;
  43. correct_answer: string;
  44. score: number;
  45. }
  46. interface Props {
  47. modelValue: BlankAnswer[];
  48. question: string;
  49. readonly?: boolean;
  50. }
  51. interface Emits {
  52. (e: 'update:modelValue', value: BlankAnswer[]): void;
  53. }
  54. const props = withDefaults(defineProps<Props>(), {
  55. readonly: false
  56. });
  57. const emit = defineEmits<Emits>();
  58. const blankAnswers = ref<BlankAnswer[]>([...props.modelValue]);
  59. // 计算题目中的填空数量
  60. const blankCount = computed(() => {
  61. return (props.question || '').split('___').length - 1;
  62. });
  63. // 监听数据变化
  64. watch(() => blankAnswers.value, (newVal) => {
  65. if (props.readonly) return;
  66. if (JSON.stringify(newVal) !== JSON.stringify(props.modelValue)) {
  67. emit('update:modelValue', newVal);
  68. }
  69. }, { deep: true });
  70. // 监听 props 变化
  71. watch(() => props.modelValue, (newVal) => {
  72. if (JSON.stringify(blankAnswers.value) !== JSON.stringify(newVal)) {
  73. blankAnswers.value = [...newVal];
  74. }
  75. }, { deep: true });
  76. // 监听题目变化,自动调整填空数量
  77. watch(() => props.question, (newQuestion) => {
  78. if (props.readonly) return;
  79. const newBlankCount = (newQuestion || '').split('___').length - 1;
  80. if (newBlankCount > 0 && blankAnswers.value.length !== newBlankCount) {
  81. const newBlankAnswers = Array.from({ length: newBlankCount }, (_, i) => ({
  82. blank_index: i + 1,
  83. correct_answer: blankAnswers.value[i]?.correct_answer || '',
  84. score: blankAnswers.value[i]?.score || 5
  85. }));
  86. if (JSON.stringify(newBlankAnswers) !== JSON.stringify(blankAnswers.value)) {
  87. blankAnswers.value = newBlankAnswers;
  88. }
  89. }
  90. });
  91. // 初始化填空模板
  92. if (!props.readonly && blankAnswers.value.length === 0) {
  93. blankAnswers.value = [{ blank_index: 1, correct_answer: '', score: 5 }];
  94. }
  95. </script>
  96. <style scoped>
  97. .blank-answer-editor {
  98. width: 100%;
  99. }
  100. .blank-header {
  101. display: flex;
  102. margin-bottom: 10px;
  103. font-weight: bold;
  104. background-color: #f5f7fa;
  105. padding: 8px;
  106. border-radius: 4px;
  107. }
  108. .blank-item {
  109. display: flex;
  110. align-items: center;
  111. margin-bottom: 10px;
  112. border: 1px solid #e4e7ed;
  113. border-radius: 4px;
  114. padding: 8px;
  115. }
  116. .blank-tip {
  117. color: #666;
  118. font-size: 12px;
  119. margin-top: 10px;
  120. padding: 8px;
  121. background-color: #f0f9ff;
  122. border-radius: 4px;
  123. border-left: 4px solid #3b82f6;
  124. }
  125. </style>