Table.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. <template>
  2. <ModuleBase ref="base" :type="data.type">
  3. <template #content>
  4. <!-- <div class="fun-type">
  5. <a
  6. v-for="{ value, label } in tableTypeList"
  7. :key="value"
  8. :class="[data.mode === value ? 'active' : '']"
  9. @click="data.mode = value"
  10. >{{ label }}</a
  11. >
  12. </div> -->
  13. <div v-loading="loading">
  14. <div class="table-rich-toolbar">
  15. <el-select
  16. v-model="data.styles.fontFamily"
  17. placeholder="请选择"
  18. style="width: 130px"
  19. @change="BatchSetFormat('fontFamily', data.styles.fontFamily)"
  20. >
  21. <el-option v-for="{ value, label } in fontFamilyList" :key="value" :label="label" :value="value" />
  22. </el-select>
  23. <el-select
  24. v-model="data.styles.fontSize"
  25. placeholder="请选择"
  26. style="width: 130px"
  27. @change="BatchSetFormat('fontSize', data.styles.fontSize)"
  28. >
  29. <el-option
  30. v-for="(value, index) in 16"
  31. :key="index"
  32. :label="6 + value * 2 + 'pt'"
  33. :value="6 + value * 2 + 'pt'"
  34. />
  35. </el-select>
  36. <el-form :model="property" inline>
  37. <el-form-item label="文字颜色">
  38. <el-color-picker
  39. v-model="data.styles.fontColor"
  40. @change="BatchSetFormat('color', data.styles.fontColor)"
  41. />
  42. </el-form-item>
  43. <el-form-item label="背景色">
  44. <el-color-picker v-model="data.styles.bgColor" />
  45. </el-form-item>
  46. </el-form>
  47. <span
  48. :class="[data.styles.isUnderline ? 'active' : '']"
  49. @click="
  50. data.styles.isUnderline = !data.styles.isUnderline;
  51. BatchSetFormat('underline');
  52. "
  53. >
  54. <SvgIcon icon-class="underline" size="20" />
  55. </span>
  56. <span
  57. :class="[data.styles.isBold ? 'active' : '']"
  58. @click="
  59. data.styles.isBold = !data.styles.isBold;
  60. BatchSetFormat('bold');
  61. "
  62. >
  63. <SvgIcon icon-class="font-bold" size="20" />
  64. </span>
  65. <span
  66. :class="[data.styles.isItalic ? 'active' : '']"
  67. @click="
  68. data.styles.isItalic = !data.styles.isItalic;
  69. BatchSetFormat('italic');
  70. "
  71. >
  72. <SvgIcon icon-class="font-italic" size="20" />
  73. </span>
  74. <span
  75. :class="[data.styles.isStrikethrough ? 'active' : '']"
  76. @click="
  77. data.styles.isStrikethrough = !data.styles.isStrikethrough;
  78. BatchSetFormat('strikethrough');
  79. "
  80. >
  81. <SvgIcon icon-class="strikethrough" size="20" />
  82. </span>
  83. <span
  84. :class="[data.styles.textAlign === 'LEFT' ? 'active' : '']"
  85. @click="
  86. data.styles.textAlign = data.styles.textAlign === 'LEFT' ? '' : 'LEFT';
  87. BatchSetFormat('align', 'LEFT');
  88. "
  89. >
  90. <SvgIcon icon-class="align-left" size="20" />
  91. </span>
  92. <span
  93. :class="[data.styles.textAlign === 'MIDDLE' ? 'active' : '']"
  94. @click="
  95. data.styles.textAlign = data.styles.textAlign === 'MIDDLE' ? '' : 'MIDDLE';
  96. BatchSetFormat('align', data.styles.textAlign === 'MIDDLE' ? 'MIDDLE' : 'LEFT');
  97. "
  98. >
  99. <SvgIcon icon-class="align-center" size="20" />
  100. </span>
  101. <span
  102. :class="[data.styles.textAlign === 'RIGHT' ? 'active' : '']"
  103. @click="
  104. data.styles.textAlign = data.styles.textAlign === 'RIGHT' ? '' : 'RIGHT';
  105. BatchSetFormat('align', data.styles.textAlign === 'RIGHT' ? 'RIGHT' : 'LEFT');
  106. "
  107. >
  108. <SvgIcon icon-class="align-right" size="20" />
  109. </span>
  110. </div>
  111. <div class="option-list">
  112. <div v-for="(item, i) in data.option_list" :key="i" class="table-node">
  113. <el-color-picker v-model="data.rows_bg_list[i]" />
  114. <div v-for="(li, j) in item" :key="li.mark" class="table-item">
  115. <!-- eslint-disable max-len -->
  116. <RichText
  117. v-if="property.isGetContent"
  118. ref="richText"
  119. v-model="li.content"
  120. :font-size="data?.unified_attrib?.font_size"
  121. :font-family="data?.unified_attrib?.font"
  122. :font-color="data?.unified_attrib?.text_color"
  123. :inline="true"
  124. :item-index="i + '#' + j"
  125. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright image media link"
  126. @createParsedTextInfoPinyin="handleParsedTextPinyin($event, i, j)"
  127. />
  128. <el-input v-else v-model="li.content" @blur="handleBlurCon" />
  129. <el-button size="mini" @click="editCell(li.cell)">设置</el-button>
  130. </div>
  131. </div>
  132. <div class="table-node">
  133. <div style="flex-shrink: 0; width: 32px"></div>
  134. <div v-for="(value, index) in data.col_width" :key="index" class="table-item table-items">
  135. <el-color-picker v-model="data.cols_bg_list[index]" />
  136. <el-input v-model="value.value">
  137. <template slot="prepend">列宽</template><template slot="append">%</template></el-input
  138. >
  139. </div>
  140. </div>
  141. </div>
  142. <el-input
  143. v-if="data.property.fill_type === fillTypeList[1].value"
  144. v-model="data.vocabulary"
  145. type="textarea"
  146. :autosize="{ minRows: 2, maxRows: 4 }"
  147. resize="none"
  148. placeholder="请输入词汇,用于选词填空"
  149. />
  150. <p class="tips">在需要作答的单元格内输入三个以上下划线“___”</p>
  151. <el-button @click="handleViewPinyin()">识别</el-button>
  152. <el-button @click="handleMultilingual">多语言</el-button>
  153. <template v-if="isEnable(data.has_identify)">
  154. <p class="tips">
  155. 在需要作答的单元格内录入标准答案,多个填空答案用换行录入,同一个填空有多个答案用斜线“/”隔开
  156. </p>
  157. <div class="option-list">
  158. <div v-for="(item, i) in data.answer_lists" :key="i" class="table-node">
  159. <div v-for="(li, j) in item" :key="i + 'col' + j" class="table-item">
  160. <el-input v-model="li.answer" type="textarea" :readonly="!data.option_list[i][j].hasInput" />
  161. <span
  162. v-if="data.option_list[i][j].cell.isCross"
  163. style="display: flex; align-items: center; font-size: 14px"
  164. >勾叉答案<i
  165. :class="[
  166. { 'el-icon-check': li.crossAnswer === statusList[1] },
  167. { 'el-icon-close': li.crossAnswer === statusList[2] },
  168. ]"
  169. style="display: block; width: 14px; height: 14px; border: 1px solid #000"
  170. @click="toggle(li)"
  171. ></i
  172. ></span>
  173. </div>
  174. </div>
  175. </div>
  176. </template>
  177. <AnswerAnalysisList
  178. v-if="data.answer_list?.length > 0 || data.analysis_list?.length > 0"
  179. :answer-list="data.answer_list"
  180. :analysis-list="data.analysis_list"
  181. :unified-attrib="data.unified_attrib"
  182. @updateAnswerAnalysisFileList="updateAnswerAnalysisFileList"
  183. @deleteAnswerAnalysis="deleteAnswerAnalysis"
  184. />
  185. <MultilingualFill
  186. :visible.sync="multilingualVisible"
  187. :text="multilingualText"
  188. :translations="data.multilingual"
  189. @SubmitTranslation="handleMultilingualTranslation"
  190. />
  191. <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">
  192. 拼音效果
  193. <el-button
  194. v-show="isEnable(data.property.view_pinyin)"
  195. type="text"
  196. icon="el-icon-refresh"
  197. title="刷新"
  198. class="refresh-pinyin-btn"
  199. @click.native="handleViewPinyin(true)"
  200. /></el-divider>
  201. <template v-if="isEnable(data.property.view_pinyin)">
  202. <!-- <template v-for="(item, index) in data.option_list">
  203. <PinyinText
  204. v-for="(items, indexs) in item"
  205. :id="'table_pinyin_text_' + index + '_' + indexs"
  206. :key="index + '_' + indexs"
  207. ref="PinyinText"
  208. :rich-text-list="items.rich_text_list"
  209. :pinyin-position="data.property.pinyin_position"
  210. :pinyin-size="data?.unified_attrib?.pinyin_size"
  211. :font-size="data?.unified_attrib?.font_size"
  212. :font-family="data?.unified_attrib?.font"
  213. @fillCorrectPinyin="fillCorrectPinyin"
  214. />
  215. </template> -->
  216. <table class="proofread-table">
  217. <tbody>
  218. <template v-for="(row, i) in data.option_list">
  219. <tr v-for="(item, j) in row" :key="item.mark">
  220. <td class="pinyin-cell">
  221. <PinyinText
  222. :id="'table_pinyin_text_' + i + '_' + j"
  223. :key="i + '_' + j"
  224. ref="PinyinText"
  225. :paragraph-list="item.paragraph_list"
  226. :rich-text-list="item.rich_text_list"
  227. :pinyin-position="data.property.pinyin_position"
  228. :pinyin-size="data?.unified_attrib?.pinyin_size"
  229. :font-size="data?.unified_attrib?.font_size"
  230. :font-family="data?.unified_attrib?.font"
  231. @fillCorrectPinyin="fillCorrectPinyin($event, i, j)"
  232. />
  233. </td>
  234. <td class="proofread-cell">
  235. <WordProofread
  236. button-text="分词校对"
  237. :word-data="getOptionWordData(item)"
  238. @save="saveOptionWord($event, i, j)"
  239. />
  240. </td>
  241. </tr>
  242. </template>
  243. </tbody>
  244. </table>
  245. </template>
  246. </div>
  247. <el-dialog
  248. v-if="editCellFlag"
  249. title="设置单元格"
  250. :visible="editCellFlag"
  251. width="460px"
  252. :close-on-click-modal="false"
  253. @close="editCellFlag = false"
  254. >
  255. <el-form :model="activeCell" :inline="true">
  256. <el-form-item label="背景色">
  257. <el-color-picker v-model="activeCell.bgColor" />
  258. </el-form-item>
  259. <el-form-item label="勾叉">
  260. <el-switch v-model="activeCell.isCross" />
  261. </el-form-item>
  262. <el-form-item label="合并行">
  263. <el-input v-model="activeCell.rowspan" class="rowspan" type="number" />
  264. </el-form-item>
  265. <el-form-item label="合并列">
  266. <el-input v-model="activeCell.colspan" class="colspan" type="number" />
  267. </el-form-item>
  268. </el-form>
  269. <div slot="footer">
  270. <el-button @click="editCellFlag = false">取消</el-button>
  271. <el-button type="primary" @click="editCellFlag = false">确定</el-button>
  272. </div>
  273. </el-dialog>
  274. </template>
  275. </ModuleBase>
  276. </template>
  277. <script>
  278. import { isEnable } from '@/views/book/courseware/data/common';
  279. import ModuleMixin from '../../common/ModuleMixin';
  280. import { PinyinBuild_OldFormat } from '@/api/book';
  281. import { getRandomNumber } from '@/utils';
  282. import PinyinText from '@/components/PinyinText.vue';
  283. import WordProofread from '@/views/book/courseware/create/components/common/WordProofread.vue';
  284. import { buildWordProofreadData } from '@/views/book/courseware/create/components/common/wordProofread';
  285. import {
  286. getTableData,
  287. getOption,
  288. tableTypeList,
  289. fontFamilyList,
  290. getAnswerOption,
  291. fillTypeList,
  292. } from '@/views/book/courseware/data/table';
  293. export default {
  294. name: 'TablePage',
  295. components: {
  296. PinyinText,
  297. WordProofread,
  298. },
  299. mixins: [ModuleMixin],
  300. data() {
  301. return {
  302. isEnable,
  303. data: getTableData(),
  304. tableTypeList,
  305. fontFamilyList,
  306. multilingualText: '',
  307. isViewExplanatoryNoteDialog: false,
  308. oldRichData: {},
  309. fillTypeList,
  310. activeCell: null,
  311. editCellFlag: false,
  312. statusList: ['normal', 'tick', 'cross'],
  313. editContentIndex: '', // 编辑内容的单元格索引
  314. loading: false,
  315. optionWordDataMap: {},
  316. optionWordDataSourceMap: {},
  317. inited: false,
  318. };
  319. },
  320. watch: {
  321. 'data.property.row_count': {
  322. handler(val) {
  323. if (val < this.data.option_list.length) {
  324. this.data.option_list = this.data.option_list.slice(0, val);
  325. this.data.answer_lists = this.data.answer_lists.slice(0, val);
  326. this.data.rows_bg_list = this.data.rows_bg_list.slice(0, val);
  327. } else {
  328. const diff = val - this.data.option_list.length;
  329. for (let i = 0; i < diff; i++) {
  330. this.data.option_list.push(Array.from({ length: this.data.property.column_count }, getOption));
  331. this.data.answer_lists.push(Array.from({ length: this.data.property.column_count }, getAnswerOption));
  332. this.data.rows_bg_list.push('');
  333. }
  334. }
  335. },
  336. },
  337. 'data.property.column_count': {
  338. handler(val) {
  339. const diff = val - this.data.option_list[0].length;
  340. for (let i = 0; i < diff; i++) {
  341. this.data.col_width.push({
  342. value: '',
  343. });
  344. this.data.cols_bg_list.push('');
  345. }
  346. this.data.cols_bg_list = this.data.cols_bg_list.slice(0, val);
  347. this.data.option_list = this.data.option_list.map((row) => {
  348. if (val < row.length) {
  349. this.data.col_width = this.data.col_width.slice(0, val);
  350. return row.slice(0, val);
  351. }
  352. const diff = val - row.length;
  353. return row.concat(Array.from({ length: diff }, getOption));
  354. });
  355. this.data.answer_lists = this.data.answer_lists?.map((row) => {
  356. if (val < row.length) {
  357. return row.slice(0, val);
  358. }
  359. const diff = val - row.length;
  360. return row.concat(Array.from({ length: diff }, getAnswerOption));
  361. });
  362. },
  363. },
  364. 'data.property.view_pinyin': {
  365. handler(val) {
  366. // 下面都是原来的处理方式
  367. // let text = '';
  368. // this.data.option_list.forEach((item) => {
  369. // item.forEach((items) => {
  370. // text += `${items.content.replace(/<[^>]+>/g, '')}\n`;
  371. // });
  372. // });
  373. // if (!isEnable(val)) {
  374. // this.data.paragraph_list = [];
  375. // return;
  376. // }
  377. // if (isEnable(val) && text && this.data.paragraph_list.length <= 0) {
  378. // this.data.paragraph_list_parameter.text = text;
  379. // this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case =
  380. // this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
  381. // this.data.option_list.forEach((item, index) => {
  382. // item.forEach((items, indexs) => {
  383. // this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`);
  384. // });
  385. // });
  386. // }
  387. if (this.property.isSilentPropertyWatch) return;
  388. if (!this.isEnable(val)) {
  389. this.data.option_list.forEach((row) => {
  390. row.forEach((item) => {
  391. item.paragraph_list = [];
  392. item.paragraph_list_parameter = {
  393. text: '',
  394. pinyin_proofread_word_list: [],
  395. };
  396. });
  397. });
  398. this.optionWordDataMap = {};
  399. this.optionWordDataSourceMap = {};
  400. return;
  401. }
  402. this.handleViewPinyin().finally(() => {
  403. this.inited = true;
  404. });
  405. },
  406. deep: true,
  407. },
  408. // 'data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case': {
  409. // handler(val, oldVal) {
  410. // if (val === oldVal) return;
  411. // let text = '';
  412. // this.data.option_list.forEach((item) => {
  413. // item.forEach((items) => {
  414. // text += `${items.content.replace(/<[^>]+>/g, '')}\n`;
  415. // });
  416. // });
  417. // if (!isEnable(this.data.property.view_pinyin)) {
  418. // this.data.paragraph_list = [];
  419. // return;
  420. // }
  421. // if (text && isEnable(this.data.property.view_pinyin)) {
  422. // this.data.paragraph_list_parameter.text = text;
  423. // this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case = val;
  424. // this.data.option_list.forEach((item, index) => {
  425. // item.forEach((items, indexs) => {
  426. // this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`);
  427. // });
  428. // });
  429. // }
  430. // },
  431. // deep: true,
  432. // },
  433. 'data.vocabulary': {
  434. handler(val) {
  435. if (!val) return;
  436. this.data.word_list = val
  437. .split(/[\n\r]+/)
  438. .map((item) => item.split(' ').filter((s) => s))
  439. .flat()
  440. .map((content) => {
  441. return {
  442. content,
  443. mark: getRandomNumber(),
  444. };
  445. });
  446. },
  447. },
  448. 'data.unified_attrib': {
  449. handler(val) {
  450. if (val) {
  451. this.data.styles.fontFamily = this.data?.unified_attrib?.font || '楷体,微软雅黑';
  452. this.data.styles.fontSize = this.data?.unified_attrib?.font_size || '12pt';
  453. this.data.styles.fontColor = this.data?.unified_attrib?.text_color || '#1d2129';
  454. this.data.styles.textAlign = this.data?.unified_attrib?.align || '';
  455. }
  456. },
  457. deep: true,
  458. immediate: true,
  459. },
  460. 'data.option_list': {
  461. handler(newVal) {
  462. if (this.loading) return;
  463. if (this.inited || !Array.isArray(newVal) || newVal.length <= 0) return;
  464. if (!this.isEnable(this.data.property.view_pinyin)) return;
  465. this.ensureOptionWordProofreadData();
  466. this.inited = true;
  467. },
  468. deep: true,
  469. },
  470. },
  471. methods: {
  472. toggle(status) {
  473. const index = this.statusList.findIndex((item) => status.crossAnswer === item);
  474. status.crossAnswer = index === this.statusList.length - 1 ? this.statusList[0] : this.statusList[index + 1];
  475. },
  476. editCell(cell) {
  477. this.activeCell = cell;
  478. this.editCellFlag = true;
  479. },
  480. // 设置样式
  481. BatchSetFormat(text, val = '') {
  482. this.$refs.richText.forEach((richText) => {
  483. richText.setRichFormat(text, val, true);
  484. });
  485. },
  486. ensureOptionWordProofreadData() {
  487. this.data.option_list.forEach((row) => {
  488. row.forEach((option) => {
  489. if (!option?.mark) return;
  490. const wordData = { detail: [{ sentences: [], segList: [] }] };
  491. option.rich_text_list?.forEach((item) => {
  492. const sentence = (item.word_list || []).reduce((acc, itemss) => `${acc + itemss.text}`, '');
  493. const seg = (item.word_list || []).map((itemss) => itemss.text);
  494. wordData.detail[0].sentences.push(sentence);
  495. wordData.detail[0].segList.push(seg);
  496. });
  497. this.$set(this.optionWordDataMap, option.mark, wordData);
  498. this.$set(this.optionWordDataSourceMap, option.mark, option.content);
  499. });
  500. });
  501. },
  502. async handleViewPinyin(isPrompt = false) {
  503. this.data.has_identify = 'false';
  504. if (isPrompt) {
  505. try {
  506. await this.$confirm('刷新将会重置分词和拼音,是否刷新?', '提示', {
  507. confirmButtonText: '确定',
  508. cancelButtonText: '取消',
  509. type: 'warning',
  510. });
  511. } catch (e) {
  512. return;
  513. }
  514. }
  515. this.loading = true;
  516. try {
  517. if (!this.isEnable(this.data.property.view_pinyin)) {
  518. this.data.option_list.forEach((row) => {
  519. row.forEach((item) => {
  520. item.paragraph_list = [];
  521. item.paragraph_list_parameter = {
  522. text: '',
  523. pinyin_proofread_word_list: [],
  524. };
  525. item.model_essay = [];
  526. // this.data.answer_list[index][indexs].answer_list = [];
  527. if (item.content) {
  528. let inputIndex = 0;
  529. item.content
  530. .split(/<(p|div)[^>]*>(.*?)<\/(p|div)>/g)
  531. .filter((s) => s && !s.match(/^(p|div)$/))
  532. .forEach((itemss) => {
  533. if (itemss.charCodeAt() === 10) return;
  534. let strArr = itemss.split(/_{3,}/g);
  535. strArr.forEach((itemS, index) => {
  536. item.model_essay.push({
  537. value: itemS,
  538. type: 'text',
  539. });
  540. if (index !== strArr.length - 1) {
  541. item.model_essay.push({
  542. value: '',
  543. type: 'input',
  544. mark: getRandomNumber(),
  545. inputIndex,
  546. write_base64: '',
  547. audio_answer_list: [],
  548. });
  549. inputIndex += 1;
  550. this.$set(item, 'hasInput', true);
  551. this.data.has_identify = 'true';
  552. }
  553. });
  554. // // 去除所有的 font-size 样式
  555. // .replace(/font-size:\s*\d+(\.\d+)?px;/gi, '')
  556. // // 匹配 class 名为 rich-fill 的 span 标签和三个以上的_,并将它们组成数组
  557. // .replace(/<span class="rich-fill".*?>(.*?)<\/span>|([_]{3,})/gi, '###$1$2###');
  558. });
  559. }
  560. item.crossAnswer = 'normal';
  561. });
  562. });
  563. this.optionWordDataMap = {};
  564. this.optionWordDataSourceMap = {};
  565. return;
  566. }
  567. const tasks = [];
  568. this.data.option_list.forEach((row, i) => {
  569. row.forEach((col, j) => {
  570. const text = col.content;
  571. if (!text) return;
  572. let obj = {
  573. text: text,
  574. pinyin_proofread_word_list: [],
  575. };
  576. this.$set(col, 'paragraph_list_parameter', obj);
  577. tasks.push(
  578. this.createParsedTextInfoPinyin(text, i, j).then(({ parsed_text, rich_text }) => {
  579. this.$set(this.optionWordDataMap, col.mark, buildWordProofreadData(parsed_text?.paragraph_list || []));
  580. this.$set(this.optionWordDataSourceMap, col.mark, text);
  581. let list = rich_text.text_list;
  582. list.forEach((item) => {
  583. let inputIndex = 0;
  584. if (item.word_list) {
  585. item.word_list.forEach((items, index) => {
  586. let isUnderline = /^_{3,}$/.test(items.text);
  587. if (isUnderline) {
  588. let obj = {
  589. value: '',
  590. type: 'input',
  591. mark: getRandomNumber(),
  592. inputIndex,
  593. write_base64: '',
  594. audio_answer_list: [],
  595. };
  596. inputIndex++;
  597. item.word_list[index] = { ...items, ...obj };
  598. this.$set(col, 'hasInput', true);
  599. this.data.has_identify = 'true';
  600. } else {
  601. items.type = 'text';
  602. }
  603. });
  604. } else {
  605. item.type = 'text';
  606. }
  607. });
  608. this.$set(col, 'rich_text_list', list);
  609. }),
  610. );
  611. });
  612. });
  613. await Promise.all(tasks);
  614. } finally {
  615. this.loading = false;
  616. }
  617. // this.identifyText();
  618. },
  619. // 识别文本
  620. identifyText(editIndex) {
  621. let text = '';
  622. this.data.has_identify = 'true';
  623. this.data.option_list.forEach((item, index) => {
  624. item.forEach((items, indexs) => {
  625. items.model_essay = [];
  626. // this.data.answer_list[index][indexs].answer_list = [];
  627. if (items.content) {
  628. let inputIndex = 0;
  629. items.content
  630. .split(/<(p|div)[^>]*>(.*?)<\/(p|div)>/g)
  631. .filter((s) => s && !s.match(/^(p|div)$/))
  632. .forEach((itemss) => {
  633. if (itemss.charCodeAt() === 10) return;
  634. let strArr = itemss.split(/_{3,}/g);
  635. strArr.forEach((itemS, index) => {
  636. items.model_essay.push({
  637. value: itemS,
  638. type: 'text',
  639. });
  640. if (index !== strArr.length - 1) {
  641. items.model_essay.push({
  642. value: '',
  643. type: 'input',
  644. mark: getRandomNumber(),
  645. inputIndex,
  646. write_base64: '',
  647. audio_answer_list: [],
  648. });
  649. inputIndex += 1;
  650. this.$set(items, 'hasInput', true);
  651. }
  652. });
  653. // // 去除所有的 font-size 样式
  654. // .replace(/font-size:\s*\d+(\.\d+)?px;/gi, '')
  655. // // 匹配 class 名为 rich-fill 的 span 标签和三个以上的_,并将它们组成数组
  656. // .replace(/<span class="rich-fill".*?>(.*?)<\/span>|([_]{3,})/gi, '###$1$2###');
  657. });
  658. }
  659. items.crossAnswer = 'normal';
  660. // text += `${items.content.replace(/<[^>]+>/g, '')}\n`;
  661. text += `${items.content}\n`;
  662. });
  663. });
  664. if (isEnable(this.data.property.view_pinyin)) {
  665. if (editIndex) {
  666. let arr = editIndex.split('#');
  667. text = this.data.option_list[arr[0]][arr[1]].content;
  668. this.createParsedTextInfoPinyin(text, editIndex);
  669. } else {
  670. this.data.option_list.forEach((item, index) => {
  671. item.forEach((items, indexs) => {
  672. this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`, true);
  673. });
  674. });
  675. }
  676. }
  677. },
  678. // 获取拼音解析文本
  679. // createParsedTextInfoPinyin(text, editIndex, flag) {
  680. // if (text === '') {
  681. // this.data.paragraph_list_parameter.pinyin_proofread_word_list = [];
  682. // return;
  683. // }
  684. // // .replace(/<[^>]+>/g, '')
  685. // this.data.paragraph_list_parameter.text = text;
  686. // this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case =
  687. // this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
  688. // this.data.paragraph_list_parameter.is_rich_text = 'true';
  689. // if (flag) this.loading = true;
  690. // PinyinBuild_OldFormat(this.data.paragraph_list_parameter).then((res) => {
  691. // if (flag) {
  692. // this.loading = false;
  693. // }
  694. // if (res.parsed_text) {
  695. // const mergedData = res.parsed_text.paragraph_list.map((outerArr, i) =>
  696. // outerArr.map((innerArr, j) =>
  697. // innerArr.map((newItem, k) => {
  698. // // 从 originalData 中找到对应的项
  699. // const originalItem = this.data.paragraph_list[i]?.[j]?.[k];
  700. // // 如果 originalItem 有 activeTextStyle,就合并到 newItem
  701. // if (originalItem?.activeTextStyle) {
  702. // return {
  703. // ...newItem,
  704. // activeTextStyle: originalItem.activeTextStyle,
  705. // };
  706. // }
  707. // // 否则直接返回 newItem
  708. // return newItem;
  709. // }),
  710. // ),
  711. // );
  712. // this.data.paragraph_list = mergedData;
  713. // if (editIndex) {
  714. // let arr = editIndex.split('#');
  715. // let list = res.rich_text.text_list;
  716. // list.forEach((item) => {
  717. // let inputIndex = 0;
  718. // if (item.word_list) {
  719. // item.word_list.forEach((items, index) => {
  720. // let isUnderline = /^_{3,}$/.test(items.text);
  721. // if (isUnderline) {
  722. // let obj = {
  723. // value: '',
  724. // type: 'input',
  725. // mark: getRandomNumber(),
  726. // inputIndex,
  727. // write_base64: '',
  728. // audio_answer_list: [],
  729. // };
  730. // inputIndex++;
  731. // item.word_list[index] = { ...items, ...obj };
  732. // } else {
  733. // items.type = 'text';
  734. // }
  735. // });
  736. // } else {
  737. // item.type = 'text';
  738. // }
  739. // });
  740. // this.$set(this.data.option_list[arr[0]][arr[1]], 'rich_text_list', list);
  741. // } else {
  742. // this.$set(this.data, 'rich_text_list', res.rich_text.text_list);
  743. // }
  744. // let pinyin_index = 0;
  745. // this.data.option_list.forEach((item, index) => {
  746. // item.forEach((items, indexs) => {
  747. // items.model_pinyin = [];
  748. // let inputIndex = 0;
  749. // if (items.content && mergedData[pinyin_index] && mergedData[pinyin_index][0]) {
  750. // mergedData[pinyin_index][0].forEach((itemP) => {
  751. // let isUnderline = /^_{3,}$/.test(itemP.text);
  752. // if (isUnderline) {
  753. // items.model_pinyin.push({
  754. // value: '',
  755. // type: 'input',
  756. // mark: getRandomNumber(),
  757. // inputIndex,
  758. // });
  759. // inputIndex++;
  760. // } else {
  761. // items.model_pinyin.push(itemP);
  762. // }
  763. // });
  764. // pinyin_index++;
  765. // }
  766. // });
  767. // });
  768. // if (this.editContentIndex) {
  769. // this.editContentIndex = '';
  770. // }
  771. // }
  772. // });
  773. // },
  774. // // 填充校对后的拼音
  775. // fillCorrectPinyin({ selectContent: { text, pinyin, activeTextStyle }, i, j, k }) {
  776. // this.data.paragraph_list_parameter.pinyin_proofread_word_list.push({
  777. // paragraph_index: i,
  778. // sentence_index: j,
  779. // word_index: k,
  780. // word: text,
  781. // pinyin,
  782. // });
  783. // if (pinyin) this.data.paragraph_list[i][j][k].pinyin = pinyin;
  784. // if (activeTextStyle) this.data.paragraph_list[i][j][k].activeTextStyle = activeTextStyle;
  785. // },
  786. // 思维导图数据
  787. handleMindMap() {
  788. let node_list = [];
  789. this.data.option_list.forEach((item) => {
  790. item.forEach((items) => {
  791. node_list.push({
  792. name: items.content.replace(/<[^>]*>?/gm, ''),
  793. id: Math.random().toString(36).substring(2, 12),
  794. });
  795. });
  796. });
  797. this.data.mind_map.node_list = node_list;
  798. },
  799. handleBlurCon(index) {
  800. this.editContentIndex = index;
  801. this.identifyText(index);
  802. this.handleMindMap();
  803. },
  804. handleMultilingual() {
  805. this.multilingualText = '';
  806. this.data.option_list.forEach((item) => {
  807. item.forEach((items) => {
  808. this.multilingualText += items.content ? `<p>${items.content}</p>` : '<p>&nbsp;</p>';
  809. });
  810. });
  811. this.multilingualVisible = true;
  812. },
  813. getOptionWordData(option) {
  814. if (!option || !option.mark) return { detail: [] };
  815. return this.optionWordDataMap[option.mark] || { detail: [] };
  816. },
  817. async saveOptionWord(saveArr, rowIndex, colIndex) {
  818. const option = this.data.option_list[rowIndex]?.[colIndex];
  819. if (!option) return;
  820. const { parsed_text } = await this.createParsedTextInfoPinyin(
  821. option.content,
  822. rowIndex,
  823. colIndex,
  824. 'option_list',
  825. saveArr,
  826. ).then(({ rich_text }) => {
  827. let list = rich_text.text_list;
  828. list.forEach((item) => {
  829. let inputIndex = 0;
  830. if (item.word_list) {
  831. item.word_list.forEach((items, index) => {
  832. let isUnderline = /^_{3,}$/.test(items.text);
  833. if (isUnderline) {
  834. let obj = {
  835. value: '',
  836. type: 'input',
  837. mark: getRandomNumber(),
  838. inputIndex,
  839. write_base64: '',
  840. audio_answer_list: [],
  841. };
  842. inputIndex++;
  843. item.word_list[index] = { ...items, ...obj };
  844. } else {
  845. items.type = 'text';
  846. }
  847. });
  848. } else {
  849. item.type = 'text';
  850. }
  851. });
  852. this.$set(this.data.option_list[rowIndex][colIndex], 'rich_text_list', list);
  853. });
  854. this.$set(this.optionWordDataMap, option.mark, buildWordProofreadData(parsed_text?.paragraph_list || []));
  855. this.$set(this.optionWordDataSourceMap, option.mark, option.content || '');
  856. },
  857. handleParsedTextPinyin(text, i, j) {
  858. if (!this.isEnable(this.data.property.view_pinyin)) return;
  859. if (this.data.option_list?.length > 0 && this.data.option_list[0].paragraph_list?.length > 0) return;
  860. if (!text) return;
  861. this.data.option_list[i][j].paragraph_list_parameter.text = text.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ');
  862. this.createParsedTextInfoPinyin(text, i, j);
  863. },
  864. },
  865. };
  866. </script>
  867. <style lang="scss" scoped>
  868. .option-list {
  869. margin-bottom: 12px;
  870. .table-node {
  871. display: flex;
  872. row-gap: 16px;
  873. .table-item {
  874. flex: 1;
  875. min-width: 140px;
  876. padding: 8px;
  877. border: 1px solid $border-color;
  878. &:not(:last-child) {
  879. border-right: 0;
  880. }
  881. :deep p {
  882. margin: 0;
  883. }
  884. :deep .el-input-group__append,
  885. :deep .el-input-group__prepend {
  886. padding: 0 5px;
  887. }
  888. &.table-items {
  889. display: flex;
  890. align-items: center;
  891. }
  892. }
  893. }
  894. }
  895. .fun-type {
  896. display: flex;
  897. gap: 5px;
  898. width: 100%;
  899. padding-bottom: 10px;
  900. border-bottom: 1px solid #e5e6eb;
  901. a {
  902. padding: 5px 10px;
  903. font-weight: normal;
  904. color: #1d2129;
  905. cursor: pointer;
  906. background: #f2f3f5;
  907. border: 1px solid #f2f3f5;
  908. border-radius: 2px;
  909. &.active {
  910. color: #165dff;
  911. background: #e7eeff;
  912. border-color: #165dff;
  913. }
  914. }
  915. }
  916. .table-rich-toolbar {
  917. display: flex;
  918. gap: 5px;
  919. padding: 5px 0;
  920. /* align-items: center; */
  921. :deep .el-form-item--small.el-form-item {
  922. margin: 0 5px 0 0;
  923. }
  924. :deep .el-form-item__label {
  925. padding-right: 3px;
  926. }
  927. > span {
  928. height: 30px;
  929. padding: 5px 6px;
  930. color: #222f3e;
  931. cursor: pointer;
  932. border-radius: 5px;
  933. &:hover {
  934. background: #cce2fa;
  935. }
  936. &.active {
  937. background: #a6ccf7;
  938. }
  939. }
  940. }
  941. .tips {
  942. font-size: 12px;
  943. color: #999;
  944. }
  945. :deep .el-input-group--append .el-input__inner {
  946. padding: 0;
  947. text-align: center;
  948. }
  949. .proofread-table {
  950. width: 100%;
  951. border-collapse: collapse;
  952. td {
  953. padding: 8px;
  954. vertical-align: top;
  955. border: 1px solid #ebeef5;
  956. }
  957. .pinyin-cell {
  958. width: calc(100% - 140px);
  959. }
  960. .proofread-cell {
  961. width: 140px;
  962. text-align: center;
  963. white-space: nowrap;
  964. }
  965. }
  966. </style>