FillQuestion.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <template>
  2. <QuestionBase>
  3. <template #content>
  4. <div class="stem">
  5. <RichText v-model="data.stem" :font-size="18" placeholder="输入题干" />
  6. <RichText
  7. v-if="isEnable(data.property.is_enable_description)"
  8. v-model="data.description"
  9. placeholder="输入提示"
  10. />
  11. </div>
  12. <el-input
  13. v-if="isEnable(data.property.is_enable_word_select_fill)"
  14. v-model="data.word_select_fill"
  15. rows="3"
  16. resize="none"
  17. type="textarea"
  18. placeholder="输入词汇"
  19. />
  20. <div class="content">
  21. <RichText
  22. ref="modelEssay"
  23. v-model="data.article"
  24. :is-fill="true"
  25. :toolbar="false"
  26. :wordlimit-num="false"
  27. placeholder="输入文段"
  28. />
  29. <el-button @click="identifyText">识别</el-button>
  30. <template v-if="data.answer.answer_list.length > 0 && isEnable(data.property.is_enable_word_select_fill)">
  31. <div class="subtitle">正确答案</div>
  32. <div class="correct-answer">
  33. <el-input
  34. v-for="(item, i) in data.answer.answer_list.filter(({ type }) => type === 'any_one')"
  35. :key="item.mark"
  36. v-model="item.value"
  37. @blur="handleTone(item.value, i)"
  38. >
  39. <span slot="prefix">{{ i + 1 }}.</span>
  40. </el-input>
  41. </div>
  42. </template>
  43. <template v-if="!isEnable(data.property.is_enable_word_select_fill)">
  44. <div class="reference-title">参考答案:</div>
  45. <el-input
  46. v-model="data.reference_answer"
  47. type="textarea"
  48. class="reference-answer"
  49. resize="none"
  50. :autosize="{ minRows: 3 }"
  51. placeholder="输入参考答案"
  52. />
  53. </template>
  54. <div v-if="isEnable(data.property.is_enable_analysis)" class="analysis">
  55. <div class="analysis-title">解析:</div>
  56. <RichText v-model="data.analysis" :is-border="true" :font-size="14" placeholder="输入解析" />
  57. </div>
  58. </div>
  59. </template>
  60. <template #property>
  61. <el-form :model="data.property" label-width="72px" label-position="left">
  62. <el-form-item label="题号">
  63. <el-input v-model="data.property.question_number" />
  64. </el-form-item>
  65. <el-form-item>
  66. <el-radio
  67. v-for="{ value, label } in questionNumberTypeList"
  68. :key="value"
  69. v-model="data.other.question_number_type"
  70. :label="value"
  71. >
  72. {{ label }}
  73. </el-radio>
  74. </el-form-item>
  75. <el-form-item label="题干题号">
  76. <el-select v-model="data.property.stem_question_number_font_size">
  77. <el-option v-for="item in fontSizeList" :key="item" :label="item" :value="item" />
  78. </el-select>
  79. </el-form-item>
  80. <el-form-item label="提示">
  81. <el-radio
  82. v-for="{ value, label } in switchOption"
  83. :key="value"
  84. v-model="data.property.is_enable_description"
  85. :label="value"
  86. >
  87. {{ label }}
  88. </el-radio>
  89. </el-form-item>
  90. <el-form-item label="解析">
  91. <el-radio
  92. v-for="{ value, label } in switchOption"
  93. :key="value"
  94. v-model="data.property.is_enable_analysis"
  95. :label="value"
  96. >
  97. {{ label }}
  98. </el-radio>
  99. </el-form-item>
  100. <el-form-item label="选词填空">
  101. <el-radio
  102. v-for="{ value, label } in switchOption"
  103. :key="value"
  104. v-model="data.property.is_enable_word_select_fill"
  105. :label="value"
  106. >
  107. {{ label }}
  108. </el-radio>
  109. </el-form-item>
  110. <el-form-item label="分值">
  111. <el-radio
  112. v-for="{ value, label } in scoreTypeList"
  113. :key="value"
  114. v-model="data.property.score_type"
  115. :label="value"
  116. >
  117. {{ label }}
  118. </el-radio>
  119. </el-form-item>
  120. <el-form-item>
  121. <el-input-number
  122. v-model="data.property.score"
  123. :min="0"
  124. :step="data.property.score_type === scoreTypeList[0].value ? 1 : 0.1"
  125. />
  126. </el-form-item>
  127. </el-form>
  128. </template>
  129. </QuestionBase>
  130. </template>
  131. <script>
  132. import QuestionMixin from '../common/QuestionMixin.js';
  133. import { getRandomNumber } from '@/utils';
  134. import { addTone, handleToneValue } from '@/views/exercise_questions/data/common';
  135. import { fillData, analysisRecognitionFillData } from '@/views/exercise_questions/data/fill';
  136. export default {
  137. name: 'FillQuestion',
  138. mixins: [QuestionMixin],
  139. data() {
  140. return {
  141. data: JSON.parse(JSON.stringify(fillData)),
  142. };
  143. },
  144. methods: {
  145. /**
  146. * 智能识别
  147. * @param {String} text 识别数据
  148. */
  149. recognition(text) {
  150. let arr = this.recognitionCommon(text);
  151. let obj = analysisRecognitionFillData(arr);
  152. this.recognitionCommonSetObj(obj);
  153. this.identifyText();
  154. },
  155. // 识别文本
  156. identifyText() {
  157. this.data.model_essay = [];
  158. this.data.answer.answer_list = [];
  159. this.data.article
  160. .split(/<p.*?>(.*?)<\/p>/gi)
  161. .filter((item) => item)
  162. .forEach((item) => {
  163. if (item.charCodeAt() === 10) return;
  164. let str = item
  165. // 去除所有的 font-size 样式
  166. .replace(/font-size:\s*\d+(\.\d+)?px;/gi, '')
  167. // 匹配 class 名为 rich-fill 的 span 标签和三个以上的_,并将它们组成数组
  168. .replace(/<span class="rich-fill".*?>(.*?)<\/span>|([_]{3,})/gi, '###$1$2###');
  169. this.data.model_essay.push(this.splitRichText(str));
  170. });
  171. },
  172. // 分割富文本
  173. splitRichText(str) {
  174. let _str = str;
  175. let start = 0;
  176. let index = 0;
  177. let arr = [];
  178. let matchNum = 0;
  179. while (index !== -1) {
  180. index = _str.indexOf('###', start);
  181. if (index === -1) break;
  182. matchNum += 1;
  183. arr.push({ content: _str.slice(start, index), type: 'text' });
  184. if (matchNum % 2 === 0 && arr.length > 0) {
  185. arr[arr.length - 1].type = 'input';
  186. let mark = getRandomNumber();
  187. arr[arr.length - 1].mark = mark;
  188. let content = arr[arr.length - 1].content;
  189. // 设置答案数组
  190. let isUnderline = /^_{3,}$/.test(content);
  191. if (this.isEnable(this.data.property.is_enable_word_select_fill)) {
  192. this.data.answer.answer_list.push({
  193. value: isUnderline ? '' : content,
  194. mark,
  195. type: isUnderline ? 'any_one' : 'only_one',
  196. });
  197. }
  198. // 将 content 设置为空,为预览准备
  199. arr[arr.length - 1].content = '';
  200. }
  201. start = index + 3;
  202. }
  203. let last = _str.slice(start);
  204. if (last) {
  205. arr.push({ content: last, type: 'text' });
  206. }
  207. return arr;
  208. },
  209. handleTone(value, i) {
  210. if (!/^[a-zA-Z0-9\s]+$/.test(value)) return;
  211. this.data.answer.answer_list[i].value = value
  212. .trim()
  213. .split(/\s+/)
  214. .map((item) => {
  215. return handleToneValue(item);
  216. })
  217. .map((item) =>
  218. item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
  219. )
  220. .filter((item) => item.length > 0)
  221. .join(' ');
  222. },
  223. },
  224. };
  225. </script>
  226. <style lang="scss" scoped>
  227. .content {
  228. position: relative;
  229. .el-button {
  230. margin-top: 8px;
  231. }
  232. .subtitle {
  233. margin: 8px 0;
  234. font-size: 14px;
  235. color: #4e5969;
  236. }
  237. .correct-answer {
  238. display: flex;
  239. flex-wrap: wrap;
  240. gap: 8px;
  241. .el-input {
  242. width: 180px;
  243. :deep &__prefix {
  244. display: flex;
  245. align-items: center;
  246. color: $text-color;
  247. }
  248. }
  249. }
  250. .reference-title {
  251. margin-top: 8px;
  252. font-size: 14px;
  253. color: #000;
  254. }
  255. .reference-answer {
  256. margin-top: 8px;
  257. }
  258. }
  259. </style>