TablePreview.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="table-preview" :style="[getAreaStyle(), getComponentStyle()]">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main">
  6. <div
  7. class="table-box"
  8. :style="{
  9. width: isMobile ? '100%' : data.property.width + 'px',
  10. height: data.property.height + 'px',
  11. }"
  12. >
  13. <table
  14. :style="{
  15. width: isMobile ? '100%' : table_width + 'px',
  16. height: data.property.height + 'px',
  17. }"
  18. >
  19. <colgroup>
  20. <col v-for="(item, i) in data.col_width" :key="`col-${i}`" :style="{ width: `${item.value}px` }" />
  21. </colgroup>
  22. <tr v-for="(row, i) in data.option_list" :key="`tr-${i}`">
  23. <template v-for="(col, j) in row">
  24. <td
  25. :key="col.mark"
  26. :style="{
  27. borderTop: i === 0 ? '1px solid ' + data.property.border_color : '',
  28. borderBottom: '1px solid ' + data.property.border_color,
  29. borderLeft:
  30. i === 0 && data.property.first_line_color
  31. ? '1px solid ' + data.property.border_color
  32. : j === 0
  33. ? '2px solid ' +
  34. (data.property.decoration_color ? data.property.decoration_color : data.property.border_color)
  35. : '1px dotted ' + data.property.border_color,
  36. borderRight:
  37. i === 0 && data.property.first_line_color
  38. ? '1px solid ' + data.property.border_color
  39. : j === row.length - 1
  40. ? '2px solid ' +
  41. (data.property.decoration_color ? data.property.decoration_color : data.property.border_color)
  42. : '1px dotted ' + data.property.border_color,
  43. borderRadius: i === 0 && data.property.first_line_color ? '4px ' : '0',
  44. background:
  45. i === 0 && data.property.first_line_color
  46. ? data.property.first_line_color
  47. : j === 0
  48. ? data.property.first_column_color
  49. : data.mode === 'short' && data.styles.bgColor
  50. ? data.styles.bgColor
  51. : '',
  52. }"
  53. >
  54. <div :style="[tdStyle, computedRichStyle(col.content)]" class="cell-wrap">
  55. <template v-if="isEnable(data.property.view_pinyin)">
  56. <div
  57. v-for="(item, index) in col.model_pinyin"
  58. :key="index"
  59. class="pinyin-text"
  60. :class="[index === 0 ? 'pinyin-text-left' : '']"
  61. >
  62. <template v-if="item.type === 'input'">
  63. <template v-if="data.property.fill_type === fillTypeList[0].value">
  64. <el-input
  65. :key="index"
  66. v-model="item.value"
  67. :disabled="disabled"
  68. :class="[...computedAnswerClass(item, i, j)]"
  69. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  70. />
  71. </template>
  72. <template v-else-if="data.property.fill_type === fillTypeList[1].value">
  73. <el-popover :key="index" placement="top" trigger="click">
  74. <div class="word-list">
  75. <span
  76. v-for="{ content, mark } in data.word_list"
  77. :key="mark"
  78. class="word-item"
  79. @click="handleSelectWord(content, mark, item)"
  80. >
  81. {{ convertText(content) }}
  82. </span>
  83. </div>
  84. <el-input
  85. slot="reference"
  86. v-model="item.value"
  87. :readonly="true"
  88. :class="[...computedAnswerClass(item, i, j)]"
  89. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  90. />
  91. </el-popover>
  92. </template>
  93. <template v-else-if="data.property.fill_type === fillTypeList[2].value">
  94. <span :key="j" class="write-click" @click="handleWriteClick(item)">
  95. <img
  96. v-show="item.write_base64"
  97. style="background-color: #f4f4f4"
  98. :src="item.write_base64"
  99. alt="write-show"
  100. />
  101. </span>
  102. </template>
  103. <template v-else-if="data.property.fill_type === fillTypeList[3].value">
  104. <SoundRecordBox
  105. ref="record"
  106. :key="j"
  107. type="mini"
  108. :many-times="false"
  109. class="record-box"
  110. :answer-record-list="data.audio_answer_list"
  111. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  112. :attrib="data.unified_attrib"
  113. @handleWav="handleMiniWav($event, item)"
  114. />
  115. </template>
  116. <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">&nbsp;</span>
  117. </template>
  118. <template v-else>
  119. <span v-if="data.property.pinyin_position === 'top'" class="pinyin">
  120. {{ item.pinyin.replace(/\s+/g, '') }}
  121. </span>
  122. <span :style="{ ...item.activeTextStyle }">
  123. {{ convertText(item.text) }}
  124. </span>
  125. <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">{{
  126. item.pinyin.replace(/\s+/g, '')
  127. }}</span>
  128. </template>
  129. </div>
  130. </template>
  131. <template v-else>
  132. <div v-for="(item, index) in col.model_essay" :key="index" :style="[tdStyle]">
  133. <span
  134. v-if="item.type === 'text'"
  135. :key="index"
  136. :style="{
  137. fontSize:
  138. data.unified_attrib && data.unified_attrib.font_size ? data.unified_attrib.font_size : '',
  139. fontFamily: data.unified_attrib && data.unified_attrib.font ? data.unified_attrib.font : '',
  140. }"
  141. v-html="convertText(sanitizeHTML(item.value))"
  142. ></span>
  143. <template v-if="item.type === 'input'">
  144. <template v-if="data.property.fill_type === fillTypeList[0].value">
  145. <el-input
  146. :key="index"
  147. v-model="item.value"
  148. :disabled="disabled"
  149. :class="[...computedAnswerClass(item, i, j)]"
  150. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  151. />
  152. </template>
  153. <template v-else-if="data.property.fill_type === fillTypeList[1].value">
  154. <el-popover :key="index" placement="top" trigger="click">
  155. <div class="word-list">
  156. <span
  157. v-for="{ content, mark } in data.word_list"
  158. :key="mark"
  159. class="word-item"
  160. @click="handleSelectWord(content, mark, item)"
  161. >
  162. {{ convertText(content) }}
  163. </span>
  164. </div>
  165. <el-input
  166. slot="reference"
  167. v-model="item.value"
  168. :readonly="true"
  169. :class="[...computedAnswerClass(item, i, j)]"
  170. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  171. />
  172. </el-popover>
  173. </template>
  174. <template v-else-if="data.property.fill_type === fillTypeList[2].value">
  175. <span :key="j" class="write-click" @click="handleWriteClick(item)">
  176. <img
  177. v-show="item.write_base64"
  178. style="background-color: #f4f4f4"
  179. :src="item.write_base64"
  180. alt="write-show"
  181. />
  182. </span>
  183. </template>
  184. <template v-else-if="data.property.fill_type === fillTypeList[3].value">
  185. <SoundRecordBox
  186. ref="record"
  187. :key="j"
  188. type="mini"
  189. :many-times="false"
  190. class="record-box"
  191. :answer-record-list="data.audio_answer_list"
  192. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  193. :attrib="data.unified_attrib"
  194. @handleWav="handleMiniWav($event, item)"
  195. />
  196. </template>
  197. </template>
  198. </div>
  199. </template>
  200. </div>
  201. <span v-if="showLang" class="multilingual" :style="[tdStyle, computedRichStyle(col.content)]">
  202. {{
  203. multilingualTextList[getLang()] &&
  204. multilingualTextList[getLang()][i] &&
  205. multilingualTextList[getLang()][i][j]
  206. ? multilingualTextList[getLang()][i][j]
  207. : ''
  208. }}
  209. </span>
  210. </td>
  211. </template>
  212. </tr>
  213. </table>
  214. </div>
  215. <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @retry="retry" v-if="isHasInput" />
  216. <AnswerCorrect
  217. :answer-correct="data?.answer_correct"
  218. :visible.sync="visibleAnswerCorrect"
  219. @closeAnswerCorrect="closeAnswerCorrect"
  220. />
  221. <AnswerAnalysis
  222. :visible.sync="visibleAnswerAnalysis"
  223. :answer-list="data.answer_list"
  224. :analysis-list="data.analysis_list"
  225. @closeAnswerAnalysis="closeAnswerAnalysis"
  226. >
  227. <div
  228. slot="right-answer"
  229. class="table-box"
  230. :style="{
  231. width: isMobile ? '100%' : data.property.width + 'px',
  232. height: data.property.height + 'px',
  233. }"
  234. >
  235. <table
  236. :style="{
  237. width: isMobile ? '100%' : table_width + 'px',
  238. height: data.property.height + 'px',
  239. }"
  240. >
  241. <colgroup>
  242. <col v-for="(item, i) in data.col_width" :key="`col-${i}`" :style="{ width: `${item.value}px` }" />
  243. </colgroup>
  244. <tr v-for="(row, i) in data.option_list" :key="`tr-${i}`">
  245. <template v-for="(col, j) in row">
  246. <td
  247. :key="col.mark"
  248. :style="{
  249. borderTop: i === 0 ? '1px solid ' + data.property.border_color : '',
  250. borderBottom: '1px solid ' + data.property.border_color,
  251. borderLeft:
  252. i === 0 && data.property.first_line_color
  253. ? '1px solid ' + data.property.border_color
  254. : j === 0
  255. ? '2px solid ' +
  256. (data.property.decoration_color
  257. ? data.property.decoration_color
  258. : data.property.border_color)
  259. : '1px dotted ' + data.property.border_color,
  260. borderRight:
  261. i === 0 && data.property.first_line_color
  262. ? '1px solid ' + data.property.border_color
  263. : j === row.length - 1
  264. ? '2px solid ' +
  265. (data.property.decoration_color
  266. ? data.property.decoration_color
  267. : data.property.border_color)
  268. : '1px dotted ' + data.property.border_color,
  269. borderRadius: i === 0 && data.property.first_line_color ? '4px ' : '0',
  270. background:
  271. i === 0 && data.property.first_line_color
  272. ? data.property.first_line_color
  273. : j === 0
  274. ? data.property.first_column_color
  275. : data.mode === 'short' && data.styles.bgColor
  276. ? data.styles.bgColor
  277. : '',
  278. }"
  279. >
  280. <div :style="[tdStyle, computedRichStyle(col.content)]" class="cell-wrap">
  281. <template v-if="isEnable(data.property.view_pinyin)">
  282. <div
  283. v-for="(item, index) in col.model_pinyin"
  284. :key="index"
  285. class="pinyin-text"
  286. :class="[index === 0 ? 'pinyin-text-left' : '']"
  287. >
  288. <template v-if="item.type === 'input'">
  289. <template v-if="data.property.fill_type === fillTypeList[0].value">
  290. <el-input
  291. :key="index"
  292. v-model="item.value"
  293. :disabled="disabled"
  294. :class="[...computedAnswerClass(item, i, j)]"
  295. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  296. />
  297. </template>
  298. <template v-else-if="data.property.fill_type === fillTypeList[1].value">
  299. <el-popover :key="index" placement="top" trigger="click">
  300. <div class="word-list">
  301. <span
  302. v-for="{ content, mark } in data.word_list"
  303. :key="mark"
  304. class="word-item"
  305. @click="handleSelectWord(content, mark, item)"
  306. >
  307. {{ convertText(content) }}
  308. </span>
  309. </div>
  310. <el-input
  311. slot="reference"
  312. v-model="item.value"
  313. :readonly="true"
  314. :class="[...computedAnswerClass(item, i, j)]"
  315. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  316. />
  317. </el-popover>
  318. </template>
  319. <template v-else-if="data.property.fill_type === fillTypeList[2].value">
  320. <span :key="j" class="write-click" @click="handleWriteClick(item)">
  321. <img
  322. v-show="item.write_base64"
  323. style="background-color: #f4f4f4"
  324. :src="item.write_base64"
  325. alt="write-show"
  326. />
  327. </span>
  328. </template>
  329. <template v-else-if="data.property.fill_type === fillTypeList[3].value">
  330. <SoundRecordBox
  331. ref="record"
  332. :key="j"
  333. type="mini"
  334. :many-times="false"
  335. class="record-box"
  336. :answer-record-list="data.audio_answer_list"
  337. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  338. :attrib="data.unified_attrib"
  339. @handleWav="handleMiniWav($event, item)"
  340. />
  341. </template>
  342. <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">&nbsp;</span>
  343. </template>
  344. <template v-else>
  345. <span v-if="data.property.pinyin_position === 'top'" class="pinyin">
  346. {{ item.pinyin.replace(/\s+/g, '') }}
  347. </span>
  348. <span :style="{ ...item.activeTextStyle }">
  349. {{ convertText(item.text) }}
  350. </span>
  351. <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">{{
  352. item.pinyin.replace(/\s+/g, '')
  353. }}</span>
  354. </template>
  355. <span
  356. v-show="computedAnswerText(item, i, j).length > 0"
  357. :key="`answer-${j}`"
  358. class="right-answer"
  359. >
  360. {{ convertText(computedAnswerText(item, i, j)) }}
  361. </span>
  362. </div>
  363. </template>
  364. <template v-else>
  365. <div v-for="(item, index) in col.model_essay" :key="index" :style="[tdStyle]">
  366. <span
  367. v-if="item.type === 'text'"
  368. :key="index"
  369. :style="{
  370. fontSize:
  371. data.unified_attrib && data.unified_attrib.font_size ? data.unified_attrib.font_size : '',
  372. fontFamily: data.unified_attrib && data.unified_attrib.font ? data.unified_attrib.font : '',
  373. }"
  374. v-html="convertText(sanitizeHTML(item.value))"
  375. ></span>
  376. <template v-if="item.type === 'input'">
  377. <template v-if="data.property.fill_type === fillTypeList[0].value">
  378. <el-input
  379. :key="index"
  380. v-model="item.value"
  381. :disabled="disabled"
  382. :class="[...computedAnswerClass(item, i, j)]"
  383. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  384. />
  385. </template>
  386. <template v-else-if="data.property.fill_type === fillTypeList[1].value">
  387. <el-popover :key="index" placement="top" trigger="click">
  388. <div class="word-list">
  389. <span
  390. v-for="{ content, mark } in data.word_list"
  391. :key="mark"
  392. class="word-item"
  393. @click="handleSelectWord(content, mark, item)"
  394. >
  395. {{ convertText(content) }}
  396. </span>
  397. </div>
  398. <el-input
  399. slot="reference"
  400. v-model="item.value"
  401. :readonly="true"
  402. :class="[...computedAnswerClass(item, i, j)]"
  403. :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
  404. />
  405. </el-popover>
  406. </template>
  407. <template v-else-if="data.property.fill_type === fillTypeList[2].value">
  408. <span :key="j" class="write-click" @click="handleWriteClick(item)">
  409. <img
  410. v-show="item.write_base64"
  411. style="background-color: #f4f4f4"
  412. :src="item.write_base64"
  413. alt="write-show"
  414. />
  415. </span>
  416. </template>
  417. <template v-else-if="data.property.fill_type === fillTypeList[3].value">
  418. <SoundRecordBox
  419. ref="record"
  420. :key="j"
  421. type="mini"
  422. :many-times="false"
  423. class="record-box"
  424. :answer-record-list="data.audio_answer_list"
  425. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  426. :attrib="data.unified_attrib"
  427. @handleWav="handleMiniWav($event, item)"
  428. />
  429. </template>
  430. {{ computedAnswerText(item, i, j) }}
  431. <span
  432. v-show="computedAnswerText(item, i, j).length > 0"
  433. :key="`answer-${j}`"
  434. class="right-answer"
  435. >
  436. {{ convertText(computedAnswerText(item, i, j)) }}
  437. </span>
  438. </template>
  439. </div>
  440. </template>
  441. </div>
  442. <span v-if="showLang" class="multilingual" :style="[tdStyle, computedRichStyle(col.content)]">
  443. {{
  444. multilingualTextList[getLang()] &&
  445. multilingualTextList[getLang()][i] &&
  446. multilingualTextList[getLang()][i][j]
  447. ? multilingualTextList[getLang()][i][j]
  448. : ''
  449. }}
  450. </span>
  451. </td>
  452. </template>
  453. </tr>
  454. </table>
  455. </div>
  456. </AnswerAnalysis>
  457. </div>
  458. <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
  459. </div>
  460. </template>
  461. <script>
  462. import { getTableData, fillTypeList } from '@/views/book/courseware/data/table';
  463. import PreviewMixin from '../common/PreviewMixin';
  464. import WriteDialog from '../fill/components/WriteDialog.vue';
  465. import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
  466. export default {
  467. name: 'TablePreview',
  468. components: { SoundRecordBox, WriteDialog },
  469. mixins: [PreviewMixin],
  470. props: {
  471. isMobile: {
  472. type: Boolean,
  473. default: false,
  474. },
  475. },
  476. data() {
  477. return {
  478. data: getTableData(),
  479. table_width: 0,
  480. multilingualTextList: {}, // 多语言对应的切割后的翻译
  481. fillTypeList,
  482. selectedWordList: [], // 用于存储选中的词汇
  483. writeVisible: false,
  484. writeMark: '',
  485. isHasInput: false,
  486. };
  487. },
  488. computed: {
  489. tdStyle() {
  490. let obj = {};
  491. if (this.data.mode === 'short') {
  492. let styles = this.data.styles;
  493. obj = {
  494. fontFamily: styles.fontFamily,
  495. fontSize: styles.fontSize,
  496. color: styles.fontColor,
  497. textDecoration: styles.isUnderline ? 'underline' : styles.isStrikethrough ? 'line-through' : '',
  498. fontWeight: styles.isBold ? 'bold' : '',
  499. fontStyle: styles.isItalic ? 'italic' : '',
  500. justifyContent: styles.textAlign,
  501. textAlign: styles.textAlign,
  502. };
  503. } else {
  504. obj = {
  505. width: '100%',
  506. };
  507. }
  508. return obj;
  509. },
  510. },
  511. watch: {
  512. 'data.col_width': {
  513. handler(val) {
  514. this.handleData();
  515. },
  516. },
  517. isJudgingRightWrong(val) {
  518. if (!val) return;
  519. this.data.answer_lists = this.answer.answer_list;
  520. // this.answer.answer_list.forEach(({ mark, value }) => {
  521. // this.modelEssay.forEach((item) => {
  522. // item.forEach((li) => {
  523. // if (li.mark === mark) {
  524. // li.content = value;
  525. // }
  526. // });
  527. // });
  528. // });
  529. },
  530. },
  531. created() {
  532. this.handleData();
  533. },
  534. methods: {
  535. handleData() {
  536. this.table_width = 0;
  537. this.isHasInput = false;
  538. this.data.col_width.forEach((item) => {
  539. this.table_width += Number(item.value);
  540. });
  541. this.data.multilingual.forEach((item) => {
  542. let trans_arr = item.translation.split('\n');
  543. let chunkSize = this.data.property.column_count;
  544. let chunkedArr = trans_arr.reduce((acc, curr, index) => {
  545. // 当索引是chunkSize的倍数时,开始一个新的子数组
  546. if (index % chunkSize === 0) {
  547. acc.push([curr]); // 开始新的子数组并添加当前元素
  548. } else {
  549. acc[acc.length - 1].push(curr); // 将当前元素添加到最后一个子数组中
  550. }
  551. return acc;
  552. }, []);
  553. this.$set(this.multilingualTextList, item.type, chunkedArr);
  554. });
  555. if (!this.isJudgingRightWrong) {
  556. this.answer.answer_list = this.data.answer_lists;
  557. }
  558. this.data.option_list.forEach((item) => {
  559. item.forEach((items) => {
  560. items.model_essay.forEach((li) => {
  561. if (li.type === 'input') {
  562. this.isHasInput = true;
  563. }
  564. });
  565. });
  566. });
  567. },
  568. computedAnswerText(item, i, j) {
  569. if (!this.isShowRightAnswer) return '';
  570. let answerOption =
  571. this.data.answer_lists[i] && this.data.answer_lists[i][j] ? this.data.answer_lists[i][j].answer : '';
  572. let answerOptionList = answerOption.split('\n');
  573. if (!item) return '';
  574. let selectValue = item.value ? item.value.trim() : '';
  575. let answerValue = answerOptionList[item.inputIndex] ? answerOptionList[item.inputIndex].split('/') : '';
  576. let isRight = answerValue && answerValue.includes(selectValue);
  577. if (isRight || !answerValue) return '';
  578. return `(${answerValue.join('/')})`;
  579. },
  580. /**
  581. * 计算答题对错选项字体颜色
  582. * @param {string} mark 选项标识
  583. */
  584. computedAnswerClass(item, i, j) {
  585. if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
  586. return '';
  587. }
  588. let answerOption =
  589. this.data.answer_lists[i] && this.data.answer_lists[i][j] ? this.data.answer_lists[i][j].answer : '';
  590. let answerOptionList = answerOption.split('\n');
  591. if (!item) return '';
  592. let selectValue = item.value ? item.value.trim() : '';
  593. let answerValue = answerOptionList[item.inputIndex] ? answerOptionList[item.inputIndex].split('/') : '';
  594. let classList = [];
  595. let isRight = answerValue && answerValue.includes(selectValue);
  596. if (this.isJudgingRightWrong && answerValue) {
  597. isRight ? classList.push('right') : classList.push('wrong');
  598. }
  599. if (this.isShowRightAnswer && !isRight) {
  600. classList.push('show-right-answer');
  601. }
  602. return classList;
  603. },
  604. /**
  605. * 处理小音频录音
  606. * @param {Object} data 音频数据
  607. * @param {String} mark 选项标识
  608. */
  609. handleMiniWav(data, mark) {
  610. if (!data || !mark) return;
  611. this.$set(mark, 'audio_answer_list', data);
  612. },
  613. /**
  614. * 处理选中词汇
  615. * @param {String} content 选中的词汇内容
  616. * @param {String} mark 选项标识
  617. * @param {Object} li 当前输入框对象
  618. */
  619. handleSelectWord(content, mark, li) {
  620. if (!content || !mark || !li || this.disabled) return;
  621. li.value = content;
  622. this.selectedWordList.push(mark);
  623. },
  624. /**
  625. * 处理书写区确认
  626. * @param {String} data 书写区数据
  627. */
  628. handleWriteConfirm(data) {
  629. if (!data) return;
  630. this.$set(this.writeMark, 'write_base64', data);
  631. },
  632. handleWriteClick(mark) {
  633. this.writeVisible = true;
  634. this.writeMark = mark;
  635. },
  636. computedRichStyle(content) {
  637. if (this.data.mode === 'short') return {};
  638. if (!content) return {};
  639. // 提取首个标签的 style 样式属性
  640. let styleMatch = content.match(/<\w+\s+[^>]*style=["']([^"']*)["'][^>]*>/i);
  641. if (styleMatch && styleMatch[1]) {
  642. let styleString = styleMatch[1];
  643. let styleArray = styleString.split(';').filter((s) => s.trim() !== '');
  644. let styleObject = {};
  645. styleArray.forEach((style) => {
  646. let [key, value] = style.split(':');
  647. if (key && value) {
  648. styleObject[key.trim()] = value.trim();
  649. }
  650. });
  651. return styleObject;
  652. }
  653. return {};
  654. },
  655. // 重做
  656. retry() {
  657. this.data.option_list.forEach((item) => {
  658. item.forEach((items) => {
  659. items.model_essay.forEach((li) => {
  660. if (li.type === 'input') {
  661. li.value = '';
  662. li.write_base64 = '';
  663. }
  664. });
  665. });
  666. });
  667. if (this.$refs.record) {
  668. if (this.$refs.record.length > 0) {
  669. this.$refs.record.forEach((item) => {
  670. item.handleReset();
  671. });
  672. } else {
  673. this.$refs.record.handleReset();
  674. }
  675. }
  676. },
  677. },
  678. };
  679. </script>
  680. <style lang="scss" scoped>
  681. @use '@/styles/mixin.scss' as *;
  682. $select-color: #1890ff;
  683. $border-color: #e6e6e6;
  684. .table-preview {
  685. @include preview-base;
  686. .main {
  687. color: #262626;
  688. .table-box {
  689. margin: 0 auto;
  690. overflow: auto;
  691. }
  692. table {
  693. // border-spacing: 3px;
  694. border-collapse: separate;
  695. }
  696. .cell-wrap {
  697. display: flex;
  698. flex-flow: wrap;
  699. grid-area: fill;
  700. align-items: end;
  701. p {
  702. flex-grow: 1; // 为了常规模式时设置居中显示撑满整个单元格
  703. max-width: 100%;
  704. margin: 0;
  705. }
  706. .record-box {
  707. display: inline-flex;
  708. align-items: center;
  709. background-color: #fff;
  710. border-bottom: 1px solid $font-color;
  711. }
  712. .write-click {
  713. display: inline-block;
  714. width: 104px;
  715. height: 32px;
  716. padding: 0 4px;
  717. vertical-align: bottom;
  718. cursor: pointer;
  719. border-bottom: 1px solid $font-color;
  720. img {
  721. width: 100%;
  722. height: 100%;
  723. }
  724. }
  725. .el-input {
  726. display: inline-flex;
  727. align-items: center;
  728. width: 120px;
  729. margin: 0 2px;
  730. font-size: 16px;
  731. &.pinyin :deep input.el-input__inner {
  732. font-family: 'PINYIN-B', sans-serif;
  733. }
  734. &.chinese :deep input.el-input__inner {
  735. font-family: 'arial', sans-serif;
  736. }
  737. &.english :deep input.el-input__inner {
  738. font-family: 'arial', sans-serif;
  739. }
  740. &.right {
  741. :deep input.el-input__inner {
  742. color: $right-color;
  743. }
  744. }
  745. &.wrong {
  746. :deep input.el-input__inner {
  747. color: $error-color;
  748. }
  749. }
  750. & + .right-answer {
  751. position: relative;
  752. left: -4px;
  753. display: inline-block;
  754. height: 32px;
  755. line-height: 28px;
  756. vertical-align: bottom;
  757. border-bottom: 1px solid $font-color;
  758. }
  759. :deep input.el-input__inner {
  760. padding: 0;
  761. // font-size: 16pt;
  762. color: $font-color;
  763. text-align: center;
  764. background-color: #fff;
  765. border-width: 0;
  766. border-bottom: 1px solid $font-color;
  767. border-radius: 0;
  768. }
  769. }
  770. &-normal {
  771. p {
  772. width: 100%; // 为了解决换行问题
  773. }
  774. }
  775. }
  776. .multilingual {
  777. display: block;
  778. word-break: break-word;
  779. }
  780. .pinyin-text {
  781. display: flex;
  782. flex-direction: column;
  783. padding: 0 1px;
  784. font-size: 16px;
  785. span {
  786. text-align: center;
  787. }
  788. .pinyin {
  789. height: 18px;
  790. font-family: 'League';
  791. font-size: 12px;
  792. }
  793. &-left {
  794. span {
  795. // text-align: left;
  796. }
  797. }
  798. }
  799. }
  800. }
  801. .word-list {
  802. display: flex;
  803. flex-wrap: wrap;
  804. gap: 8px;
  805. align-items: center;
  806. .word-item {
  807. cursor: pointer;
  808. }
  809. }
  810. </style>