Notes.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. <template>
  2. <ModuleBase ref="base" :type="data.type">
  3. <template #content>
  4. <!-- <label>标题:</label>
  5. <RichText
  6. ref="richText"
  7. v-model="data.title_con"
  8. :inline="true"
  9. :placeholder="'输入标题'"
  10. :font-size="data?.unified_attrib?.font_size"
  11. :font-family="data?.unified_attrib?.font"
  12. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  13. class="title-box"
  14. /> -->
  15. <el-table :key="refreshKey" :data="data.option" border style="width: 100%">
  16. <el-table-column prop="number" label="序号" width="70">
  17. <template slot-scope="scope">
  18. <el-input v-model="scope.row.number" />
  19. </template>
  20. </el-table-column>
  21. <el-table-column prop="con" label="内容" width="200">
  22. <template slot-scope="scope">
  23. <RichText
  24. v-if="property.isGetContent"
  25. ref="richText"
  26. :key="'richtext-1-' + scope.$index"
  27. v-model="scope.row.con"
  28. :inline="true"
  29. :item-index="scope.$index"
  30. :font-size="data?.unified_attrib?.font_size"
  31. :font-family="data?.unified_attrib?.font"
  32. :font-color="data?.unified_attrib?.text_color"
  33. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  34. @handleRichTextBlur="handleBlurCon"
  35. />
  36. </template>
  37. </el-table-column>
  38. <el-table-column v-if="isEnable(data.property.view_pinyin)" prop="con" label="拼音" width="150">
  39. <template slot-scope="scope">
  40. <RichText
  41. v-if="property.isGetContent"
  42. ref="richText"
  43. :key="'richtext-2-' + scope.$index"
  44. v-model="scope.row.pinyin"
  45. :inline="true"
  46. :font-size="data?.unified_attrib?.pinyin_size"
  47. :font-family="'League'"
  48. :font-color="data?.unified_attrib?.text_color"
  49. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  50. />
  51. </template>
  52. </el-table-column>
  53. <el-table-column prop="interpret" label="翻译" width="200">
  54. <template slot-scope="scope">
  55. <RichText
  56. v-if="property.isGetContent"
  57. ref="richText"
  58. :key="'richtext-3-' + scope.$index"
  59. v-model="scope.row.interpret"
  60. :inline="true"
  61. :font-size="data?.unified_attrib?.font_size"
  62. :font-family="data?.unified_attrib?.font"
  63. :font-color="data?.unified_attrib?.text_color"
  64. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  65. />
  66. </template>
  67. </el-table-column>
  68. <el-table-column prop="note" label="注释" width="200">
  69. <template slot-scope="scope">
  70. <RichText
  71. v-if="property.isGetContent"
  72. ref="richText"
  73. :key="'richtext-4-' + scope.$index"
  74. v-model="scope.row.note"
  75. :inline="true"
  76. :font-size="data?.unified_attrib?.font_size"
  77. :font-family="data?.unified_attrib?.font"
  78. :font-color="data?.unified_attrib?.text_color"
  79. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  80. />
  81. </template>
  82. </el-table-column>
  83. <el-table-column prop="img_list" label="图片" width="300">
  84. <template slot-scope="scope">
  85. <UploadPicture
  86. :file-info="scope.row.file_list[0]"
  87. :item-index="scope.$index"
  88. :show-upload="!scope.row.file_list[0]"
  89. @upload="uploadPic"
  90. @deleteFile="deletePic"
  91. />
  92. </template>
  93. </el-table-column>
  94. <el-table-column prop="match_jump" label="关联跳转" width="300">
  95. <template slot-scope="scope">
  96. <template v-if="scope.row.match_jump.type === 'outer' && scope.row.match_jump.con">
  97. 已关联:{{ scope.row.match_jump.con }}
  98. </template>
  99. <template v-else-if="scope.row.match_jump.type === 'inner' && scope.row.match_jump.source_courseware_name">
  100. 已关联:{{ scope.row.match_jump.source_courseware_name }}
  101. </template>
  102. <div class="match-btn" @click="handleMatch(scope.row)"><SvgIcon icon-class="match" /><span>关联</span></div>
  103. </template>
  104. </el-table-column>
  105. <el-table-column label="操作" width="150">
  106. <template slot-scope="scope">
  107. <el-button size="mini" type="text" @click="handleDelete(scope.$index)">删除</el-button>
  108. <el-button size="mini" type="text" @click="moveElement(scope.row, scope.$index, 'up')">上移</el-button>
  109. <el-button size="mini" type="text" @click="moveElement(scope.row, scope.$index, 'down')">下移</el-button>
  110. </template>
  111. </el-table-column>
  112. </el-table>
  113. <el-button icon="el-icon-plus" style="margin: 24px 0" @click="addElement">增加一个</el-button>
  114. <el-button @click="handleMultilingual">多语言</el-button>
  115. <MultilingualFill
  116. :visible.sync="multilingualVisible"
  117. :text="multilingualText"
  118. :translations="data.multilingual"
  119. @SubmitTranslation="handleMultilingualTranslation"
  120. />
  121. <el-dialog
  122. v-if="dialogFlag"
  123. :visible.sync="dialogFlag"
  124. :close-on-click-modal="false"
  125. :modal-append-to-body="false"
  126. :modal="false"
  127. width="600px"
  128. class="Notes-edit-dialog"
  129. title="关联跳转"
  130. >
  131. <div class="check-box">
  132. <el-form ref="form" :model="matchObj" label-width="80px">
  133. <el-form-item label="类型">
  134. <el-radio-group v-model="matchObj.type">
  135. <el-radio label="outer">外部跳转</el-radio>
  136. <el-radio label="inner">内部跳转</el-radio>
  137. </el-radio-group>
  138. </el-form-item>
  139. <el-form-item label="关联链接" v-if="matchObj.type === 'outer'">
  140. <el-input v-model="matchObj.con"></el-input>
  141. </el-form-item>
  142. <el-form-item label="关联节点" v-else>
  143. <el-cascader
  144. v-model="matchObj.source_courseware_id_list"
  145. :options="courseware_node_list"
  146. :show-all-levels="false"
  147. @change="handleChangeCourseware"
  148. clearable
  149. :ref="refId"
  150. />
  151. </el-form-item>
  152. </el-form>
  153. <div class="btn-box">
  154. <el-button type="info" size="small" @click="cancleDialog">取消</el-button>
  155. <el-button type="primary" size="small" @click="sureMatch">确定</el-button>
  156. </div>
  157. </div>
  158. </el-dialog>
  159. </template>
  160. </ModuleBase>
  161. </template>
  162. <script>
  163. import { isEnable } from '@/views/book/courseware/data/common';
  164. import ModuleMixin from '../../common/ModuleMixin';
  165. import { ChapterGetBookChapterStruct, PinyinBuild_OldFormat } from '@/api/book';
  166. import UploadPicture from '../new_word/components/UploadPicture.vue';
  167. import { getNotesData, getOption } from '@/views/book/courseware/data/notes';
  168. import cnchar from 'cnchar';
  169. export default {
  170. name: 'NotesPage',
  171. components: { UploadPicture },
  172. mixins: [ModuleMixin],
  173. data() {
  174. return {
  175. isEnable,
  176. data: getNotesData(),
  177. multilingualText: '',
  178. refreshKey: '',
  179. dialogFlag: false,
  180. matchObj: null,
  181. activeMatchRow: null,
  182. courseware_node_list: [],
  183. refId: 'Notes-' + Math.random().toString(36).substring(2, 12),
  184. };
  185. },
  186. watch: {
  187. 'data.option': 'handleMindMap',
  188. 'data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case': {
  189. handler(val, oldVal) {
  190. if (val === oldVal) return;
  191. if (isEnable(this.data.property.view_pinyin)) {
  192. this.handlePinyin();
  193. }
  194. },
  195. deep: true,
  196. },
  197. },
  198. created() {
  199. this.getBookChapterStructExpandList();
  200. },
  201. methods: {
  202. /**
  203. * 得到教材章节结构展开列表
  204. */
  205. getBookChapterStructExpandList() {
  206. ChapterGetBookChapterStruct({
  207. book_id: this.$route.query.project_id,
  208. node_deep_mode: 0,
  209. is_contain_producer: 'false',
  210. is_contain_auditor: 'false',
  211. }).then(({ nodes }) => {
  212. this.courseware_node_list = nodes;
  213. nodes.forEach((item) => {
  214. item.label = item.name;
  215. item.pid = '';
  216. item.nodexIndex = 0;
  217. item.isLeaf = item.is_leaf === 'true';
  218. item.value = item.id;
  219. if (item.nodes) {
  220. item.children = item.nodes;
  221. item.lists = item.nodes;
  222. this.handleData(item, 1);
  223. }
  224. });
  225. });
  226. },
  227. // 递归
  228. handleData(data, nodeIndex) {
  229. if (data.nodes) {
  230. data.nodes.forEach((item) => {
  231. item.label = item.name;
  232. item.pid = data.id;
  233. item.nodexIndex = nodeIndex;
  234. item.isLeaf = item.is_leaf === 'true';
  235. item.value = item.id;
  236. if (item.nodes) {
  237. item.children = item.nodes;
  238. item.lists = item.nodes;
  239. this.handleData(item, nodeIndex + 1);
  240. }
  241. });
  242. }
  243. },
  244. handleChangeCourseware(val) {
  245. this.matchObj.source_courseware_id = val[val.length - 1];
  246. this.matchObj.source_courseware_name = this.$refs[this.refId].getCheckedNodes()[0]
  247. ? this.$refs[this.refId].getCheckedNodes()[0].pathLabels.join('/')
  248. : '';
  249. },
  250. // 删除行
  251. handleDelete(index) {
  252. this.data.option.splice(index, 1);
  253. },
  254. // 上移下移
  255. moveElement(dItem, index, type) {
  256. let obj = JSON.parse(JSON.stringify(dItem));
  257. if (type === 'up' && index > 0) {
  258. this.data.option.splice(index - 1, 0, obj);
  259. this.data.option.splice(index + 1, 1);
  260. }
  261. if (type === 'down' && index < this.data.option.length - 1) {
  262. this.data.option[index] = this.data.option.splice(index + 1, 1, this.data.option[index])[0];
  263. }
  264. },
  265. // 增加
  266. addElement() {
  267. this.data.option.push(getOption());
  268. },
  269. handleMindMap() {
  270. // 思维导图数据
  271. let node_list = [];
  272. this.data.option.forEach((item) => {
  273. node_list.push({
  274. name: item.con.replace(/<[^>]*>?/gm, ''),
  275. id: Math.random().toString(36).substring(2, 12),
  276. });
  277. if (!item.hasOwnProperty('match_jump')) {
  278. this.$set(item, 'match_jump', { type: 'outer', con: '', source_courseware_id_list: [] });
  279. }
  280. });
  281. this.data.mind_map.node_list = node_list;
  282. },
  283. async handleBlurCon(i) {
  284. let text = this.data.option[i].con.replace(/<[^>]+>/g, '');
  285. if (text) {
  286. this.data.option[i].pinyin = await this.handlePinyins(
  287. text,
  288. this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case,
  289. );
  290. }
  291. // this.data.option[i].pinyin = cnchar
  292. // .spell(
  293. // text,
  294. // 'array',
  295. // this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true' ? 'high' : 'low',
  296. // 'tone',
  297. // )
  298. // .join(' ');
  299. this.handleMindMap();
  300. },
  301. async handlePinyin() {
  302. for (const item of this.data.option) {
  303. let text = item.con.replace(/<[^>]+>/g, '');
  304. if (text) {
  305. item.pinyin = await this.handlePinyins(
  306. text,
  307. this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case,
  308. );
  309. }
  310. }
  311. // this.data.option.forEach((item) => {
  312. // let text = item.con.replace(/<[^>]+>/g, '');
  313. // item.pinyin = cnchar
  314. // .spell(
  315. // text,
  316. // 'array',
  317. // this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true' ? 'high' : 'low',
  318. // 'tone',
  319. // )
  320. // .join(' ');
  321. // });
  322. },
  323. // 自动生成拼音
  324. handlePinyins(text, flag) {
  325. let data = {
  326. text,
  327. is_rich_text: 'false',
  328. is_first_sentence_first_hz_pinyin_first_char_upper_case: flag || 'false',
  329. is_fill_space: 'false',
  330. is_custom_fc: 'false',
  331. };
  332. return new Promise((resolve, reject) => {
  333. PinyinBuild_OldFormat(data).then((res) => {
  334. if (res.parsed_text) {
  335. let mergedData = '';
  336. res.parsed_text.paragraph_list.map((outerArr, i) =>
  337. outerArr.map((innerArr, j) =>
  338. innerArr.map((newItem, k) => {
  339. mergedData += `${newItem.pinyin} `;
  340. }),
  341. ),
  342. );
  343. resolve(mergedData);
  344. }
  345. });
  346. });
  347. },
  348. handleMultilingual() {
  349. this.multilingualText = this.data.title_con ? this.data.title_con : '<p>&nbsp;</p>';
  350. this.data.option.forEach((item) => {
  351. this.multilingualText += item.con ? item.con : '<p>&nbsp;</p>';
  352. this.multilingualText += item.note ? item.note : '<p>&nbsp;</p>';
  353. });
  354. this.multilingualVisible = true;
  355. },
  356. // 获取拼音解析文本
  357. createParsedTextInfoPinyin(text) {
  358. if (text === '') {
  359. this.data.paragraph_list_parameter.pinyin_proofread_word_list = [];
  360. return;
  361. }
  362. this.data.paragraph_list_parameter.text = text.replace(/<[^>]+>/g, '');
  363. this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case =
  364. this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
  365. PinyinBuild_OldFormat(this.data.paragraph_list_parameter).then((res) => {
  366. if (res.parsed_text) {
  367. const mergedData = res.parsed_text.paragraph_list.map((outerArr, i) =>
  368. outerArr.map((innerArr, j) =>
  369. innerArr.map((newItem, k) => {
  370. // 从 originalData 中找到对应的项
  371. const originalItem = this.data.paragraph_list[i]?.[j]?.[k];
  372. // 如果 originalItem 有 activeTextStyle,就合并到 newItem
  373. if (originalItem?.activeTextStyle) {
  374. return {
  375. ...newItem,
  376. activeTextStyle: originalItem.activeTextStyle,
  377. };
  378. }
  379. // 否则直接返回 newItem
  380. return newItem;
  381. }),
  382. ),
  383. );
  384. this.data.paragraph_list = mergedData;
  385. let pinyin_index = 0;
  386. this.data.option.forEach((items, index) => {
  387. items.model_pinyin = [];
  388. if (items.content && mergedData[pinyin_index] && mergedData[pinyin_index][0]) {
  389. mergedData[pinyin_index][0].forEach((itemP) => {
  390. items.model_pinyin.push(itemP);
  391. });
  392. pinyin_index++;
  393. }
  394. });
  395. }
  396. });
  397. },
  398. // 填充校对后的拼音
  399. fillCorrectPinyin({ selectContent: { text, pinyin, activeTextStyle }, i, j, k }) {
  400. this.data.paragraph_list_parameter.pinyin_proofread_word_list.push({
  401. paragraph_index: i,
  402. sentence_index: j,
  403. word_index: k,
  404. word: text,
  405. pinyin,
  406. });
  407. if (pinyin) this.data.paragraph_list[i][j][k].pinyin = pinyin;
  408. if (activeTextStyle) this.data.paragraph_list[i][j][k].activeTextStyle = activeTextStyle;
  409. },
  410. uploadPic(file_id, index, file) {
  411. this.data.option[index].file_list[0] = file;
  412. this.data.file_id_list.push(file_id);
  413. this.refreshKey = Math.random();
  414. },
  415. deletePic(file_id, index) {
  416. this.data.option[index].file_list[0] = '';
  417. this.data.file_id_list = this.data.file_id_list.filter((item) => item !== file_id);
  418. },
  419. // 点击关联按钮
  420. handleMatch(row) {
  421. this.matchObj = JSON.parse(JSON.stringify(row.match_jump));
  422. this.activeMatchRow = row;
  423. this.dialogFlag = true;
  424. },
  425. // 确定关联
  426. sureMatch() {
  427. this.dialogFlag = false;
  428. this.activeMatchRow.match_jump = JSON.parse(JSON.stringify(this.matchObj));
  429. this.matchObj = null;
  430. },
  431. cancleDialog() {
  432. this.dialogFlag = false;
  433. this.matchObj = null;
  434. this.activeMatchRow = null;
  435. },
  436. },
  437. };
  438. </script>
  439. <style lang="scss" scoped>
  440. .upload-file {
  441. display: flex;
  442. column-gap: 12px;
  443. align-items: center;
  444. margin: 8px 0;
  445. .file-name {
  446. display: flex;
  447. column-gap: 14px;
  448. align-items: center;
  449. justify-content: space-between;
  450. max-width: 360px;
  451. padding: 8px 12px;
  452. font-size: 14px;
  453. color: #1d2129;
  454. background-color: #f7f8fa;
  455. span {
  456. display: flex;
  457. column-gap: 14px;
  458. align-items: center;
  459. }
  460. }
  461. .svg-icon {
  462. cursor: pointer;
  463. }
  464. }
  465. .title-box {
  466. padding: 5px;
  467. margin: 5px 0;
  468. background-color: #f2f3f5;
  469. border: 1px solid #f2f3f5;
  470. border-radius: 4px;
  471. :deep p {
  472. margin: 0;
  473. }
  474. }
  475. .match-btn {
  476. display: flex;
  477. column-gap: 12px;
  478. align-items: center;
  479. width: 233px;
  480. padding: 4px 12px;
  481. font-size: 14px;
  482. color: #1d2129;
  483. cursor: pointer;
  484. background-color: #f2f3f5;
  485. }
  486. </style>
  487. <style lang="scss">
  488. .tox .tox-editor-header {
  489. z-index: 3 !important;
  490. }
  491. .Notes-edit-dialog {
  492. .el-dialog__body {
  493. padding: 0;
  494. }
  495. .check-box {
  496. padding: 24px;
  497. background: #fff;
  498. border-radius: 4px;
  499. box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
  500. .btn-box {
  501. display: block;
  502. text-align: right;
  503. .el-button {
  504. padding: 2px 12px;
  505. font-size: 12px;
  506. line-height: 20px;
  507. }
  508. }
  509. }
  510. }
  511. </style>