Article.vue 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813
  1. <template>
  2. <ModuleBase ref="base" :type="data.type">
  3. <template #content>
  4. <!-- eslint-disable max-len -->
  5. <div v-loading="loading" class="article-wrapper">
  6. <el-input v-model="data.content" placeholder="输入" type="textarea" />
  7. <span class="tips">输入课文文本后,请先点击生成分词</span>
  8. <el-button type="primary" @click="handleChangeContent">生成分词</el-button>
  9. <template v-if="data.detail.length > 0">
  10. <div class="new-btn-box">
  11. <p>文本内容</p>
  12. <div>
  13. <a @click="checkArticle">分词和拼音</a>
  14. <a @click="editWordsFlag = !editWordsFlag">生词和气泡</a>
  15. <a @click="handleMultilingual">多语种翻译</a>
  16. <MultilingualFill
  17. :visible.sync="multilingualVisible"
  18. :text="multilingualText"
  19. :translations="data.multilingual"
  20. @SubmitTranslation="handleMultilingualTranslation"
  21. />
  22. </div>
  23. </div>
  24. <div class="new-btn-box">
  25. <p>音频内容</p>
  26. <div>
  27. <SelectUpload label="" type="audio" :isNormal="true" @uploadSuccess="uploadAudioSuccess" />
  28. <el-button :loading="autoLoading" @click="handleAutoAudio">生成音频</el-button>
  29. <template v-if="data.mp3_list.length === 0">
  30. <el-tooltip class="item" effect="dark" content="请先设置音频" placement="top">
  31. <el-button disabled style="margin-left: 0">校对字幕</el-button>
  32. </el-tooltip>
  33. </template>
  34. <template v-else>
  35. <template v-if="data.wordTime && data.wordTime.length > 0">
  36. <a type="text" @click="againWordTime">重新生成字幕</a>
  37. <a size="medium" @click="openCheckSubtitles('句子')">句子字幕</a>
  38. <a size="medium" @click="compareTime('文字')">文字字幕</a>
  39. </template>
  40. <template v-else>
  41. <a v-if="!isWordTime" size="medium" @click="createWordTimes">自动生成字幕节点</a>
  42. <p v-else>字幕节点生成中...请等待</p>
  43. </template>
  44. </template>
  45. </div>
  46. </div>
  47. <div v-if="data.mp3_list.length > 0" class="upload-file">
  48. <div class="file-name">
  49. <span>
  50. <SvgIcon icon-class="note" size="12" />
  51. <span>{{ data.mp3_list[0].name }}</span>
  52. </span>
  53. </div>
  54. <SvgIcon icon-class="delete-black" size="12" @click="removeFile" />
  55. <ResourcesOperate :data="data.mp3_list[0]" />
  56. </div>
  57. <div class="new-btn-box">
  58. <p>多媒体内容</p>
  59. <div>
  60. <a @click="picArticle">上传图片</a>
  61. <a @click="picArticle">上传视频</a>
  62. </div>
  63. </div>
  64. <div class="table-rich-toolbar" v-if="articleAttrib">
  65. <el-select
  66. v-model="articleAttrib.font"
  67. placeholder="请选择"
  68. style="width: 130px"
  69. @change="handleChangeStyles"
  70. >
  71. <el-option v-for="{ value, label } in fontFamilyList" :key="value" :label="label" :value="value" />
  72. </el-select>
  73. <el-select
  74. v-model="articleAttrib.font_size"
  75. placeholder="请选择"
  76. style="width: 130px"
  77. @change="data.unified_attrib.font_size = articleAttrib.font_size"
  78. >
  79. <el-option
  80. v-for="(value, index) in 16"
  81. :key="index"
  82. :label="6 + value * 2 + 'pt'"
  83. :value="6 + value * 2 + 'pt'"
  84. />
  85. </el-select>
  86. <el-form :model="articleAttrib" inline>
  87. <el-form-item label="文字颜色">
  88. <el-color-picker v-model="articleAttrib.text_color" @change="handleChangeStyles" />
  89. </el-form-item>
  90. </el-form>
  91. <span :class="[data.other_attrib.isUnderline ? 'active' : '']" @click="setActiveTextStyle('underline')">
  92. <SvgIcon icon-class="underline" size="20" />
  93. </span>
  94. <span :class="[data.other_attrib.isBold ? 'active' : '']" @click="setActiveTextStyle('bold')">
  95. <SvgIcon icon-class="font-bold" size="20" />
  96. </span>
  97. <!-- <span
  98. :class="[data.other_attrib.isItalic ? 'active' : '']"
  99. @click="data.other_attrib.isItalic = !data.other_attrib.isItalic"
  100. >
  101. <SvgIcon icon-class="font-italic" size="20" />
  102. </span> -->
  103. <span
  104. :class="[data.other_attrib.isStrikethrough ? 'active' : '']"
  105. @click="setActiveTextStyle('line-through')"
  106. >
  107. <SvgIcon icon-class="strikethrough" size="20" />
  108. </span>
  109. <span :class="[data.other_attrib.border === 'dotted' ? 'active' : '']">
  110. <SvgIcon icon-class="borderDotted" title="下划虚线" size="16" @click="setActiveTextStyle('border')" />
  111. </span>
  112. <span
  113. :class="[articleAttrib.align === 'LEFT' ? 'active' : '']"
  114. @click="setActiveTextStyle('align', 'LEFT')"
  115. >
  116. <SvgIcon icon-class="align-left" size="20" />
  117. </span>
  118. <span
  119. :class="[articleAttrib.align === 'MIDDLE' ? 'active' : '']"
  120. @click="setActiveTextStyle('align', 'MIDDLE')"
  121. >
  122. <SvgIcon icon-class="align-center" size="20" />
  123. </span>
  124. <span
  125. :class="[articleAttrib.align === 'RIGHT' ? 'active' : '']"
  126. @click="setActiveTextStyle('align', 'RIGHT')"
  127. >
  128. <SvgIcon icon-class="align-right" size="20" />
  129. </span>
  130. </div>
  131. </template>
  132. <!-- <div v-if="data.content" class="btn-box">
  133. <a @click="handleChangeContent">生成分词</a>
  134. <a @click="checkArticle">文章校对</a>
  135. <a @click="picArticle">添加图片</a>
  136. <a @click="editWordsFlag = !editWordsFlag">编辑生词短语气泡</a>
  137. <template v-if="data.wordTime && data.wordTime.length > 0">
  138. <a type="text" @click="againWordTime">重新生成字幕时间</a>
  139. <a size="medium" @click="openCheckSubtitles('句子')">校对句子字幕时间</a>
  140. <a size="medium" @click="compareTime('文字')">校对文字字幕时间</a>
  141. </template>
  142. <template v-else>
  143. <a v-if="!isWordTime" size="medium" @click="createWordTimes">自动生成字幕节点</a>
  144. <p v-else>字幕节点生成中...请等待</p>
  145. </template>
  146. <el-button @click="handleMultilingual">课文多语言</el-button>
  147. <MultilingualFill
  148. :visible.sync="multilingualVisible"
  149. :text="multilingualText"
  150. :translations="data.multilingual"
  151. @SubmitTranslation="handleMultilingualTranslation"
  152. />
  153. </div> -->
  154. </div>
  155. <template v-if="data.detail.length > 0">
  156. <el-divider content-position="left"
  157. >预览效果<el-button
  158. v-show="data.detail.length > 0"
  159. type="text"
  160. icon="el-icon-refresh"
  161. title="刷新"
  162. class="refresh-pinyin-btn"
  163. @click.native="handleChangeContent"
  164. /></el-divider>
  165. <div v-for="(item, index) in data.detail" :key="index" class="text">
  166. <div class="text-item">
  167. <span
  168. class="word"
  169. :style="{
  170. fontSize: articleAttrib.font_size,
  171. }"
  172. >
  173. &nbsp;&nbsp;&nbsp;&nbsp;
  174. </span>
  175. </div>
  176. <div class="text-item">
  177. <span
  178. class="word"
  179. :style="{
  180. fontSize: articleAttrib.font_size,
  181. }"
  182. >
  183. &nbsp;&nbsp;&nbsp;&nbsp;
  184. </span>
  185. </div>
  186. <template v-for="(witem, windex) in item.wordsList">
  187. <div
  188. v-for="(wItem, wIndex) in witem"
  189. :key="windex + '-' + wIndex"
  190. class="text-item"
  191. :style="{
  192. textAlign: wIndex === 0 ? 'left' : '',
  193. }"
  194. >
  195. <span
  196. class="pinyin"
  197. :style="{
  198. fontSize: articleAttrib.pinyin_size,
  199. }"
  200. v-if="isEnable(data.property.view_pinyin)"
  201. @click="selectItem(wItem, wIndex)"
  202. >{{
  203. wIndex === 0 && data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true'
  204. ? wItem.pinyin_up
  205. : wItem.pinyin
  206. }}</span
  207. >
  208. <span
  209. class="word"
  210. :style="{
  211. fontFamily: wItem.fontFamily,
  212. textDecoration: wItem.textDecoration,
  213. borderBottom: wItem.border === 'dotted' ? '1px dotted' : '',
  214. fontWeight: wItem.fontWeight,
  215. color:
  216. wItem.matchNotesObj.con && wItem.matchNotesObj.notesColor
  217. ? wItem.matchNotesObj.notesColor
  218. : wItem.color,
  219. fontSize: articleAttrib.font_size,
  220. }"
  221. @click="matchItemNotes(wItem)"
  222. >{{ wItem.chs }}</span
  223. >
  224. </div>
  225. </template>
  226. </div>
  227. </template>
  228. <el-dialog
  229. v-if="dialogFlag"
  230. :visible.sync="dialogFlag"
  231. :show-close="false"
  232. :close-on-click-modal="false"
  233. :modal-append-to-body="false"
  234. :modal="false"
  235. width="250px"
  236. class="article-checkpinyin-dialog"
  237. >
  238. <div class="check-box">
  239. <div class="content">
  240. <div class="words-box">
  241. <span class="pinyin">
  242. {{ itemActiveIndex === 0 ? itemActive.pinyin_up : itemActive.pinyin }}
  243. </span>
  244. <span class="words">
  245. {{ itemActive.chs }}
  246. </span>
  247. </div>
  248. </div>
  249. <!-- <el-input v-model="checkPinyinInput" type="text" class="checkPinyinInput" /> -->
  250. <el-select
  251. v-model="checkPinyinInput"
  252. placeholder="请选择"
  253. filterable
  254. allow-create
  255. default-first-option
  256. class="checkPinyinInput"
  257. >
  258. <el-option v-for="item in pinyinList" :key="item.pinyin" :value="item.pinyin.replace(/,/g, '')">
  259. <span style="float: left">{{ item.pinyin.replace(/,/g, '') }}</span>
  260. <span style="float: right; font-size: 13px; color: #8492a6">{{
  261. item.storage_type === 1 ? '机构库' : item.storage_type === 2 ? '全域库' : '个人库'
  262. }}</span>
  263. </el-option>
  264. </el-select>
  265. <p class="tips">一到四声分别用数字1-4表示。拼音间用空格隔开,儿化音输入r,如“活儿”输入“huor2”。</p>
  266. <div class="btn-box">
  267. <el-button type="info" size="small" @click="cancleDialog">取消</el-button>
  268. <el-button type="primary" size="small" @click="surePinyin">保存</el-button>
  269. </div>
  270. </div>
  271. </el-dialog>
  272. <el-dialog
  273. v-if="showArticleFlag"
  274. :visible.sync="showArticleFlag"
  275. :show-close="true"
  276. :close-on-click-modal="true"
  277. :modal-append-to-body="true"
  278. :append-to-body="true"
  279. :lock-scroll="true"
  280. width="80%"
  281. class="practiceBox"
  282. >
  283. <CheckArticle
  284. ref="CheckArticle"
  285. :data="data"
  286. @saveWord="saveWord"
  287. @savePinyin="savePinyin"
  288. @saveStyle="saveStyle"
  289. />
  290. </el-dialog>
  291. <el-dialog title="校对字幕时间" :visible.sync="compareShow" width="50%" :before-close="handleClose" top="0">
  292. <CompareTime
  293. :data="compareData"
  294. :type="compareType"
  295. :changewords-result-list="changewordsResultList"
  296. :open-check-subtitles="openCheckSubtitles"
  297. />
  298. <span slot="footer" class="dialog-footer">
  299. <el-button @click="handleClose">取 消</el-button>
  300. <el-button :loading="compareloading" type="primary" @click="saveCompare">确 定</el-button>
  301. </span>
  302. </el-dialog>
  303. <!-- <el-dialog title="" :visible.sync="editWordsFlag" width="80%" :close-on-click-modal="true" top="0"> -->
  304. <template v-if="editWordsFlag">
  305. <div class="tabs-box">
  306. <a :class="[editWordIndex === 0 ? 'active' : '']" @click="editWordIndex = 0">生词</a>
  307. <a :class="[editWordIndex === 2 ? 'active' : '']" @click="editWordIndex = 2">其他词汇</a>
  308. <a :class="[editWordIndex === 1 ? 'active' : '']" @click="editWordIndex = 1">气泡</a>
  309. </div>
  310. <NewWord
  311. v-if="editWordIndex === 0"
  312. key="new_word"
  313. :data-new-word="data.new_word_list"
  314. :unified-attrib="data.unified_attrib ? data.unified_attrib : null"
  315. @sureNewWords="sureNewWords"
  316. />
  317. <Notes
  318. v-if="editWordIndex === 1"
  319. key="notes"
  320. :data-notes="data.notes_list"
  321. :unified-attrib="data.unified_attrib ? data.unified_attrib : null"
  322. @sureNotes="sureNotes"
  323. @handleSyncCon="handleSyncCon"
  324. />
  325. <NewWord
  326. v-if="editWordIndex === 2"
  327. :data-new-word="data.other_word_list"
  328. :unified-attrib="data.unified_attrib ? data.unified_attrib : null"
  329. @sureNewWords="sureOtherNewWords"
  330. />
  331. </template>
  332. <!-- </el-dialog> -->
  333. <!-- 添加图片 -->
  334. <el-dialog
  335. v-if="showPicArticleFlag"
  336. :visible.sync="showPicArticleFlag"
  337. :show-close="true"
  338. :close-on-click-modal="true"
  339. :modal-append-to-body="true"
  340. :append-to-body="true"
  341. :lock-scroll="true"
  342. width="80%"
  343. class="practiceBox"
  344. >
  345. <CheckPic :data="data" />
  346. </el-dialog>
  347. <CheckSubtitles
  348. v-if="visible"
  349. :visible.sync="visible"
  350. :audio-id="data.mp3_list[0] && data.mp3_list[0].file_id ? data.mp3_list[0].file_id : ''"
  351. :option-list="subtitleList"
  352. @saveSubtitles="saveSubtitles"
  353. />
  354. <!-- 关联注释 -->
  355. <el-dialog
  356. v-if="matchNoteFlag"
  357. :visible.sync="matchNoteFlag"
  358. :show-close="true"
  359. :close-on-click-modal="false"
  360. :modal-append-to-body="false"
  361. :modal="false"
  362. width="600px"
  363. class="article-matchNotes-dialog"
  364. :title="matchNotesObj.id ? '编辑气泡' : '添加气泡'"
  365. >
  366. <div class="matchNotes-box">
  367. <RichText
  368. v-if="matchNotesObj.isRich"
  369. ref="richText"
  370. v-model="matchNotesObj.con"
  371. :font-size="'14pt'"
  372. :font-family="data.unified_attrib?.font"
  373. :font-color="data.unified_attrib?.text_color"
  374. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright image media link"
  375. />
  376. <el-input v-else v-model="matchNotesObj.con" type="textarea" rows="5"></el-input>
  377. <div class="matchNotes-color">
  378. <span>气泡颜色<el-color-picker v-model="matchNotesObj.notesColor"></el-color-picker></span>
  379. <span @click="matchNotesObj.isRich = !matchNotesObj.isRich"
  380. ><SvgIcon icon-class="change" size="16" />切换文本模式</span
  381. >
  382. </div>
  383. <div class="btn-box">
  384. <el-button type="info" size="small" @click="cancleMatchNotesDialog">删除</el-button>
  385. <el-button type="primary" size="small" @click="sureMatchNotes">保存</el-button>
  386. </div>
  387. </div>
  388. </el-dialog>
  389. </template>
  390. </ModuleBase>
  391. </template>
  392. <script>
  393. import ModuleMixin from '../../common/ModuleMixin';
  394. import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
  395. import CheckArticle from './CheckArticle.vue';
  396. import CompareTime from './CompareTime.vue';
  397. import NewWord from './NewWord.vue';
  398. import Notes from './Notes.vue';
  399. import CheckPic from './CheckPic.vue';
  400. import CheckSubtitles from '@/views/book/courseware/create/components/question/voice_matrix/CheckSubtitles.vue';
  401. import ResourcesOperate from '../../common/ResourcesOperate.vue';
  402. import { getArticleData } from '@/views/book/courseware/data/article';
  403. import { fontFamilyList } from '@/views/book/courseware/data/table';
  404. import { TextToAudioFile } from '@/api/app';
  405. import { isEnable } from '@/views/book/courseware/data/common';
  406. import {
  407. segSentences,
  408. BatchSegContent,
  409. getWordTime,
  410. prepareTranscribe,
  411. fileToBase64Text,
  412. getWordTimes,
  413. } from '@/api/article';
  414. const Base64 = require('js-base64').Base64;
  415. import cnchar from 'cnchar';
  416. import { toolGetWordPinyinCorrectionList } from '@/api/pinyinCorrection';
  417. import { PinyinBuild_OldFormat } from '@/api/book';
  418. import { getRandomNumber } from '@/utils';
  419. export default {
  420. name: 'ArticlePage',
  421. components: {
  422. SelectUpload,
  423. CheckArticle,
  424. CompareTime,
  425. NewWord,
  426. Notes,
  427. CheckPic,
  428. CheckSubtitles,
  429. ResourcesOperate,
  430. },
  431. mixins: [ModuleMixin],
  432. data() {
  433. return {
  434. fontFamilyList,
  435. isEnable,
  436. data: getArticleData(),
  437. showArticleFlag: false, // 校对文章
  438. showPicArticleFlag: false, // 添加图片
  439. toneList: [' ', 'ˉ', 'ˊ', 'ˇ', 'ˋ'],
  440. loading: false,
  441. isWordTime: false,
  442. compareType: '', // 校对类型
  443. compareIndex: null, // 校对句子索引
  444. compareShow: false,
  445. compareData: null,
  446. compareloading: false,
  447. editWordsFlag: false,
  448. editWordIndex: 0,
  449. multilingualText: '',
  450. autoLoading: false,
  451. visible: false,
  452. subtitleList: [],
  453. itemActive: null,
  454. itemActiveIndex: null,
  455. dialogFlag: false,
  456. checkPinyinInput: '',
  457. oldInput: '',
  458. tableData: [
  459. ['ā', 'á', 'ǎ', 'à', 'a'],
  460. ['ō', 'ó', 'ǒ', 'ò', 'o'],
  461. ['ē', 'é', 'ě', 'è', 'e'],
  462. ['ī', 'í', 'ǐ', 'ì', 'i'],
  463. ['ū', 'ú', 'ǔ', 'ù', 'u'],
  464. ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
  465. ['Ā', 'Á', 'Â', 'À', 'A'],
  466. ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
  467. ['Ē', 'É', 'Ê', 'È', 'E'],
  468. ['Ī', 'Í', 'Î', 'Ì', 'I'],
  469. ['Ū', 'Ú', 'Û', 'Ù', 'U'],
  470. ['n', 'ń', 'ň', 'ǹ', 'n'],
  471. ['m̄', 'ḿ', 'm', 'm̀', 'm'],
  472. ],
  473. pinyinList: [], // 拼音校正列表
  474. articleAttrib: null,
  475. matchNoteItem: null, // 选中关联注释的词
  476. matchNoteFlag: false,
  477. };
  478. },
  479. watch: {
  480. 'data.content': 'handleMindMap',
  481. 'data.unified_attrib': {
  482. handler(val) {
  483. if (val) {
  484. this.articleAttrib = JSON.parse(JSON.stringify(val));
  485. this.handleChangeStyles();
  486. }
  487. },
  488. deep: true,
  489. immediate: true,
  490. },
  491. // 'data.unified_attrib.font': {
  492. // handler(val) {
  493. // if (val) {
  494. // this.handleChangeStyles();
  495. // }
  496. // },
  497. // deep: true,
  498. // immediate: true,
  499. // },
  500. // 'data.unified_attrib.text_color': {
  501. // handler(val) {
  502. // if (val) {
  503. // this.handleChangeStyles();
  504. // }
  505. // },
  506. // deep: true,
  507. // immediate: true,
  508. // },
  509. // 'data.unified_attrib.align': {
  510. // handler(val) {
  511. // if (val) {
  512. // this.handleChangeStyles();
  513. // }
  514. // },
  515. // deep: true,
  516. // immediate: true,
  517. // },
  518. },
  519. created() {},
  520. methods: {
  521. // 全局样式变化
  522. handleChangeStyles() {
  523. if (this.articleAttrib.font !== this.data.other_attrib.oldFont) {
  524. this.data.other_attrib.oldFont = this.articleAttrib.font;
  525. this.data.detail.forEach((item) => {
  526. item.wordsList.forEach((items) => {
  527. items.forEach((itemss) => {
  528. itemss.fontFamily = this.articleAttrib.font;
  529. });
  530. });
  531. });
  532. } else if (this.articleAttrib.text_color !== this.data.other_attrib.oldColor) {
  533. this.data.other_attrib.oldColor = this.articleAttrib.text_color;
  534. this.data.detail.forEach((item) => {
  535. item.wordsList.forEach((items) => {
  536. items.forEach((itemss) => {
  537. itemss.color = this.articleAttrib.text_color;
  538. });
  539. });
  540. });
  541. } else if (this.articleAttrib.align !== this.data.other_attrib.oldAlign) {
  542. this.data.other_attrib.oldAlign = this.articleAttrib.align;
  543. this.data.detail.forEach((item) => {
  544. item.paraAlign =
  545. this.articleAttrib.align === 'RIGHT' ? 'right' : this.articleAttrib.align === 'MIDDLE' ? 'center' : 'left';
  546. });
  547. }
  548. },
  549. // 解析输入内容
  550. handleChangeContent() {
  551. if (this.data.content.trim()) {
  552. if (this.data.detail.length === 0) {
  553. this.handleResetSet();
  554. } else {
  555. this.$confirm('重新生成分词会重置课文内拼音、气泡、字幕和图片视频附件,确定执行此操作吗?', '提示', {
  556. confirmButtonText: '确定',
  557. cancelButtonText: '取消',
  558. type: 'warning',
  559. })
  560. .then(() => {
  561. this.handleResetSet();
  562. this.data.notes_list.option = [];
  563. this.isWordTime = false;
  564. this.data.wordTime = [];
  565. })
  566. .catch(() => {});
  567. }
  568. // 分句
  569. // let sentenceList = []; // 句子按段数组
  570. // let data = {
  571. // textList,
  572. // };
  573. // segSentences(data)
  574. // .then((res) => {
  575. // let result = res.data.result;
  576. // sentenceList = JSON.parse(JSON.stringify(res.data.result));
  577. // result.forEach((item, index) => {
  578. // let sentenceListBase64 = [];
  579. // this.data.detail[index].sentences = item;
  580. // item.forEach((items) => {
  581. // sentenceListBase64.push(Base64.encode(items));
  582. // });
  583. // // 分词
  584. // BatchSegContent({ textList: sentenceListBase64 })
  585. // .then((res) => {
  586. // let list = res.data.result.list;
  587. // this.data.detail[index].segList = list;
  588. // this.setWordsList(list, index);
  589. // if (index === result.length - 1) {
  590. // this.handleSenWord();
  591. // this.loading = false;
  592. // }
  593. // })
  594. // .catch(() => {
  595. // this.loading = false;
  596. // });
  597. // });
  598. // })
  599. // .catch(() => {
  600. // this.loading = false;
  601. // });
  602. }
  603. },
  604. // 重置分词结构
  605. handleResetSet() {
  606. this.loading = true;
  607. this.data.detail = [];
  608. let contentArr = this.data.content.split('\n');
  609. let textList = [];
  610. let contentStr = '';
  611. let detailItem = {
  612. paraIndex: 0,
  613. para: '',
  614. sentences: [],
  615. segList: [],
  616. seg_words: [],
  617. wordsList: [],
  618. timeList: [],
  619. isTitle: false,
  620. sentencesEn: [],
  621. paraAlign: this.articleAttrib
  622. ? this.articleAttrib.align === 'RIGHT'
  623. ? 'right'
  624. : this.articleAttrib.align === 'MIDDLE'
  625. ? 'center'
  626. : 'left'
  627. : 'left',
  628. remark: {
  629. chs: '',
  630. en: '',
  631. heightNumber: null,
  632. img_list: [],
  633. widthNumber: null,
  634. chsBg: '#988ed6',
  635. enBg: '#fff',
  636. },
  637. sourceList: [],
  638. sourcePosition: 'after',
  639. heightNumber: null,
  640. widthNumber: null,
  641. };
  642. // 分段
  643. contentArr.forEach((item, index) => {
  644. if (item.trim()) {
  645. detailItem.para = item;
  646. detailItem.paraIndex = index;
  647. this.data.detail.push(JSON.parse(JSON.stringify(detailItem)));
  648. let str = Base64.encode(item);
  649. textList.push(str);
  650. contentStr += item.trim() + '\n';
  651. }
  652. });
  653. BatchSegContent({
  654. text: contentStr,
  655. is_build_pinyin: 'true',
  656. is_custom_fc: 'false',
  657. is_vocabulary_pinyin_lt: 'true',
  658. is_fill_punctuation_mark: 'true',
  659. is_fill_space: 'true',
  660. })
  661. .then((res) => {
  662. this.loading = false;
  663. if (res.status === 1) {
  664. res.paragraph_list.forEach((item, index) => {
  665. let sentenceList = []; // 句子按段数组
  666. let segList = []; // 句子分词结果
  667. item.forEach((items) => {
  668. let sentence = items.reduce((acc, itemss) => `${acc + itemss.text}`, '');
  669. let seg = items.map((itemss) => itemss.text);
  670. sentenceList.push(sentence);
  671. segList.push(seg);
  672. });
  673. this.data.detail[index].sentences = sentenceList;
  674. this.data.detail[index].segList = segList;
  675. this.setWordsList(item, index);
  676. });
  677. this.handleSenWord();
  678. }
  679. })
  680. .catch(() => {
  681. this.loading = false;
  682. });
  683. },
  684. // 处理句子和词的关系
  685. handleSenWord() {
  686. this.data.sentence_list_mp = [];
  687. this.data.detail.forEach((item) => {
  688. item.sentences.forEach((items, indexs) => {
  689. let word_list = [];
  690. item.segList[indexs].forEach((itemw) => {
  691. word_list.push({
  692. word: itemw,
  693. });
  694. });
  695. let obj = {
  696. sentence: items,
  697. word_list,
  698. };
  699. this.data.sentence_list_mp.push(obj);
  700. });
  701. });
  702. },
  703. setWordsList(list, paraIndex) {
  704. let wordsList = [];
  705. list.forEach((item, index) => {
  706. let sentArr = [];
  707. item.map((sItem) => {
  708. // let toneStr = [];
  709. // for (let i = 0; i < sItem.length; i++) {
  710. // const pattern = /[\u4e00-\u9fa5]/;
  711. // if (cnchar.isCnChar(sItem[i]) && !pattern.test(cnchar.spell(sItem[i], 'low', 'tone'))) {
  712. // toneStr.push(this.toneList[cnchar.spellInfo(cnchar.spell(sItem[i], 'low', 'tone')).tone]);
  713. // } else {
  714. // toneStr.push(' ');
  715. // }
  716. // }
  717. let obj = {
  718. chs: sItem.text,
  719. pinyin: sItem.pinyin,
  720. pinyin_up: sItem.pinyin_up,
  721. pinyin_tone: sItem.pinyin_tone,
  722. fontFamily: this.articleAttrib ? this.articleAttrib.font : '楷体',
  723. textDecoration: this.data.other_attrib
  724. ? this.data.other_attrib.isUnderline
  725. ? this.data.other_attrib.isUnderline
  726. : this.data.other_attrib.isStrikethrough
  727. ? this.data.other_attrib.isStrikethrough
  728. : ''
  729. : '',
  730. fontWeight: this.data.other_attrib ? this.data.other_attrib.isBold : '',
  731. border: this.data.other_attrib ? this.data.other_attrib.border : '',
  732. color: this.articleAttrib ? this.articleAttrib.text_color : '',
  733. matchWords: '',
  734. matchNotes: '',
  735. notesColor: '',
  736. img: [],
  737. imgPosition: 'after',
  738. matchNotesObj: {
  739. number: '',
  740. con: '',
  741. pinyin: '',
  742. interpret: '',
  743. note: '',
  744. notesColor: '#8206BF',
  745. id: '',
  746. isRich: true,
  747. matchNotes: sItem.text,
  748. },
  749. };
  750. sentArr.push(obj);
  751. });
  752. wordsList.push(sentArr);
  753. });
  754. this.data.detail[paraIndex].wordsList = wordsList;
  755. },
  756. uploadAudioSuccess(fileList) {
  757. if (fileList.length > 0) {
  758. const { file_name: name, file_url: temporary_url, file_id, media_duration } = fileList[0];
  759. this.data.mp3_list = [
  760. {
  761. name,
  762. media_duration,
  763. temporary_url,
  764. url: file_id,
  765. file_id,
  766. },
  767. ];
  768. this.data.file_id_list = [file_id];
  769. }
  770. },
  771. removeFile() {
  772. this.$confirm('是否删除当前文件?', '提示', {
  773. confirmButtonText: '确定',
  774. cancelButtonText: '取消',
  775. type: 'warning',
  776. })
  777. .then(() => {
  778. this.data.mp3_list = [];
  779. this.data.file_id_list = [];
  780. this.isWordTime = false;
  781. this.data.wordTime = [];
  782. })
  783. .catch(() => {});
  784. },
  785. // 校对文章
  786. checkArticle() {
  787. let verseList = [];
  788. this.data.detail.forEach((item) => {
  789. verseList = verseList.concat(item.sentences);
  790. });
  791. if (verseList.length > 0) {
  792. this.showArticleFlag = true;
  793. } else {
  794. this.$message.warning('请先生成分词');
  795. }
  796. },
  797. saveWord(saveArr) {
  798. BatchSegContent({
  799. text: this.data.content,
  800. is_build_pinyin: 'true',
  801. is_custom_fc: 'true',
  802. is_vocabulary_pinyin_lt: 'true',
  803. is_fill_punctuation_mark: 'true',
  804. is_fill_space: 'true',
  805. fc_paragraph_list: saveArr,
  806. }).then((res) => {
  807. if (res.status === 1) {
  808. res.paragraph_list.forEach((item, index) => {
  809. let sentenceList = []; // 句子按段数组
  810. let segList = []; // 句子分词结果
  811. let sentenceStr = [];
  812. let para = '';
  813. let wordsList = [];
  814. item.forEach((items) => {
  815. let sentence = items.reduce((acc, itemss) => `${acc + itemss.text}`, '');
  816. let sentenceS = items.reduce((acc, itemss) => `${acc + itemss.text}&nbsp;&nbsp;`, '');
  817. let seg = items.map((itemss) => itemss.text);
  818. para += items.map((itemss) => itemss.text);
  819. sentenceList.push(sentence);
  820. sentenceStr.push(sentenceS);
  821. segList.push(seg);
  822. let sentArr = [];
  823. items.forEach((sItem) => {
  824. let obj = {
  825. chs: sItem.text,
  826. pinyin: sItem.pinyin,
  827. pinyin_up: sItem.pinyin_up,
  828. pinyin_tone: sItem.pinyin_tone,
  829. fontFamily: this.articleAttrib ? this.articleAttrib.font : '楷体',
  830. textDecoration: this.data.other_attrib
  831. ? this.data.other_attrib.isUnderline
  832. ? this.data.other_attrib.isUnderline
  833. : this.data.other_attrib.isStrikethrough
  834. ? this.data.other_attrib.isStrikethrough
  835. : ''
  836. : '',
  837. fontWeight: this.data.other_attrib ? this.data.other_attrib.isBold : '',
  838. border: this.data.other_attrib ? this.data.other_attrib.border : '',
  839. color: this.articleAttrib ? this.articleAttrib.text_color : '',
  840. matchWords: '',
  841. matchNotes: '',
  842. notesColor: '',
  843. img: [],
  844. imgPosition: 'after',
  845. matchNotesObj: {
  846. number: '',
  847. con: '',
  848. pinyin: '',
  849. interpret: '',
  850. note: '',
  851. notesColor: '#8206BF',
  852. id: '',
  853. isRich: true,
  854. matchNotes: sItem.text,
  855. },
  856. };
  857. sentArr.push(obj);
  858. });
  859. wordsList.push(sentArr);
  860. });
  861. if (this.data.detail[index]) {
  862. this.data.detail[index].segList = segList;
  863. this.data.detail[index].para = para;
  864. this.data.detail[index].sentenceStr = sentenceStr;
  865. this.data.detail[index].sentences = sentenceList;
  866. this.data.detail[index].wordsList = wordsList;
  867. } else {
  868. let obj = {
  869. paraIndex: index,
  870. para,
  871. sentences: sentenceList,
  872. segList: item,
  873. seg_words: [],
  874. wordsList,
  875. timeList: [],
  876. isTitle: false,
  877. sentencesEn: [],
  878. sentenceStr,
  879. paraAlign: this.articleAttrib
  880. ? this.articleAttrib.align === 'RIGHT'
  881. ? 'right'
  882. : this.articleAttrib.align === 'MIDDLE'
  883. ? 'center'
  884. : 'left'
  885. : 'left',
  886. remark: {
  887. chs: '',
  888. en: '',
  889. heightNumber: null,
  890. img_list: [],
  891. widthNumber: null,
  892. chsBg: '#988ed6',
  893. enBg: '#fff',
  894. },
  895. sourceList: [],
  896. sourcePosition: 'after',
  897. heightNumber: null,
  898. widthNumber: null,
  899. };
  900. this.data.detail.push(obj);
  901. }
  902. });
  903. this.$refs.CheckArticle.refreshData();
  904. }
  905. });
  906. this.$message.success('保存成功,请校对拼音');
  907. this.handleSenWord();
  908. },
  909. // 保存拼音
  910. savePinyin(paraIndex, sentenceIndex, wordIndex, pinyin) {
  911. if (this.data.pinyin_type === 'tone') {
  912. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin_tone = pinyin;
  913. } else if (wordIndex === 0) {
  914. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin_up = pinyin;
  915. } else {
  916. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin = pinyin;
  917. }
  918. },
  919. saveStyle(
  920. paraIndex,
  921. sentenceIndex,
  922. wordIndex,
  923. fontFamily,
  924. textDecoration,
  925. fontWeight,
  926. border,
  927. color,
  928. matchWords,
  929. matchNotes,
  930. notesColor,
  931. img,
  932. imgPosition,
  933. ) {
  934. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].fontFamily = fontFamily;
  935. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].textDecoration = textDecoration;
  936. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].fontWeight = fontWeight;
  937. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].border = border;
  938. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].color = color;
  939. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].matchWords = matchWords;
  940. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].matchNotes = matchNotes;
  941. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].notesColor = notesColor;
  942. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].img = img;
  943. this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].imgPosition = imgPosition;
  944. },
  945. // 保存校对
  946. saveCompare() {
  947. this.compareloading = false;
  948. this.data.wordTime = this.compareData;
  949. this.handleClose();
  950. // this.compareloading = true;
  951. // compareSenTenceTime({ matchList: JSON.stringify(this.compareData) })
  952. // .then((res) => {
  953. // this.compareloading = false;
  954. // this.data.wordTime = res.data.result;
  955. // })
  956. // .catch(() => {
  957. // this.compareloading = false;
  958. // });
  959. },
  960. // 校对时间
  961. compareTime(type) {
  962. this.compareType = type;
  963. this.compareData = JSON.parse(JSON.stringify(this.data.wordTime));
  964. this.compareShow = true;
  965. },
  966. handleClose() {
  967. this.compareShow = false;
  968. this.compareData = null;
  969. this.compareType = '';
  970. },
  971. // 校对每个字的时间
  972. changewordsResultList(index, item) {
  973. this.data.wordTime[index].wordsResultList = JSON.parse(JSON.stringify(item));
  974. },
  975. againWordTime() {
  976. this.isWordTime = false;
  977. this.data.wordTime = [];
  978. },
  979. createWordTimes() {
  980. let _this = this;
  981. let verseList = [];
  982. if (_this.data.mp3_list && _this.data.mp3_list.length > 0 && _this.data.mp3_list[0].file_id) {
  983. if (_this.data.content) {
  984. _this.data.detail.forEach((item) => {
  985. verseList = verseList.concat(item.sentences);
  986. });
  987. if (verseList.length > 0) {
  988. _this.isWordTime = true;
  989. let data = {
  990. audio_file_id: _this.data.mp3_list[0].file_id,
  991. text: _this.data.content,
  992. text_type: 'line_text_list',
  993. line_text_list: verseList,
  994. };
  995. getWordTimes(data).then((res) => {
  996. _this.data.wordTime = res.data.result;
  997. _this.isWordTime = false;
  998. });
  999. } else {
  1000. this.$message.warning('请先生成分词');
  1001. }
  1002. }
  1003. } else {
  1004. _this.$message.warning('请先上传音频');
  1005. _this.loading = false;
  1006. }
  1007. },
  1008. createWordTime() {
  1009. let _this = this;
  1010. this.getfillLiu().then(() => {
  1011. if (_this.data.taskId) {
  1012. let verseList = [];
  1013. _this.data.detail.forEach((item) => {
  1014. verseList = verseList.concat(item.sentences);
  1015. });
  1016. if (verseList.length > 0) {
  1017. _this.isWordTime = true;
  1018. let data = {
  1019. taskId: _this.data.taskId,
  1020. verseList: JSON.stringify(verseList),
  1021. matchType: 'chinese',
  1022. language: 'ch',
  1023. };
  1024. getWordTime(data).then((res) => {
  1025. _this.data.wordTime = res.data.result;
  1026. _this.isWordTime = false;
  1027. });
  1028. }
  1029. } else {
  1030. _this.$message.warning('请先上传音频');
  1031. _this.loading = false;
  1032. }
  1033. });
  1034. },
  1035. // 得到文件流
  1036. getfillLiu() {
  1037. this.loading = true;
  1038. let _this = this;
  1039. return new Promise(function (resolve, reject) {
  1040. if (_this.data.mp3_list && _this.data.mp3_list.length > 0 && _this.data.mp3_list[0].file_id) {
  1041. let id = _this.data.mp3_list[0].file_id;
  1042. let data = {
  1043. file_id: id,
  1044. };
  1045. fileToBase64Text(data).then((res) => {
  1046. let taskIddata = {
  1047. fileName: _this.data.mp3_list[0].name,
  1048. speechBase64: res.base64_text,
  1049. language: 'ch',
  1050. };
  1051. prepareTranscribe(taskIddata)
  1052. .then((ress) => {
  1053. _this.$set(_this.data, 'taskId', ress.data.taskId);
  1054. _this.loading = false;
  1055. resolve();
  1056. })
  1057. .catch(() => {
  1058. _this.loading = false;
  1059. });
  1060. });
  1061. } else {
  1062. _this.$message.warning('请先上传音频');
  1063. _this.loading = false;
  1064. }
  1065. });
  1066. },
  1067. sureNewWords(data) {
  1068. this.data.new_word_list = data;
  1069. },
  1070. sureOtherNewWords(data) {
  1071. this.data.other_word_list = data;
  1072. },
  1073. sureNotes(data) {
  1074. this.data.notes_list = data;
  1075. },
  1076. handleMindMap() {
  1077. // 思维导图数据
  1078. let node_list = [];
  1079. node_list.push({
  1080. name: this.data.content,
  1081. id: Math.random().toString(36).substring(2, 12),
  1082. });
  1083. this.data.mind_map.node_list = node_list;
  1084. },
  1085. handleMultilingual() {
  1086. this.multilingualText = '';
  1087. let flag = false;
  1088. this.data.detail.forEach((item) => {
  1089. if (item.para) {
  1090. if (item.sentences && item.sentences.length > 0) {
  1091. flag = true;
  1092. }
  1093. item.sentences.forEach((items) => {
  1094. this.multilingualText += `<p>${items}<p>`;
  1095. });
  1096. } else {
  1097. this.multilingualText += '<p>&nbsp;</p>';
  1098. }
  1099. // this.multilingualText += item.para ? '<p>' + item.para + '<p>' : '<p>&nbsp;</p>';
  1100. });
  1101. if (flag) {
  1102. this.multilingualVisible = true;
  1103. } else {
  1104. this.$message.warning('请先生成分词');
  1105. }
  1106. },
  1107. // 点击插入图片按钮
  1108. picArticle() {
  1109. let verseList = [];
  1110. this.data.detail.forEach((item) => {
  1111. verseList = verseList.concat(item.sentences);
  1112. });
  1113. if (verseList.length > 0) {
  1114. this.showPicArticleFlag = true;
  1115. } else {
  1116. this.$message.warning('请先生成分词');
  1117. }
  1118. },
  1119. // 自动生成音频
  1120. handleAutoAudio() {
  1121. this.autoLoading = true;
  1122. TextToAudioFile({
  1123. text: this.data.content,
  1124. voice_type: this.data.property.voice_type,
  1125. emotion: this.data.property.emotion,
  1126. speed_ratio: this.data.property.speed_ratio,
  1127. sentence_interval: 1000,
  1128. })
  1129. .then(({ status, file_id, file_url }) => {
  1130. this.autoLoading = false;
  1131. if (status === 1) {
  1132. this.data.mp3_list = [
  1133. {
  1134. name: '自动生成课文音频.mp3',
  1135. media_duration: 0,
  1136. temporary_url: file_url,
  1137. url: file_id,
  1138. file_id,
  1139. },
  1140. ];
  1141. this.data.file_id_list = [file_id];
  1142. }
  1143. })
  1144. .catch(() => {
  1145. this.autoLoading = false;
  1146. });
  1147. },
  1148. /**
  1149. * 打开校对字幕
  1150. */
  1151. openCheckSubtitles(type, index) {
  1152. this.compareType = type;
  1153. // this.compareData = JSON.parse(JSON.stringify(this.data.wordTime));
  1154. // this.compareShow = true;
  1155. if (this.data.mp3_list.length === 0) {
  1156. return this.$message.warning('请先上传音频文件');
  1157. }
  1158. this.subtitleList = [];
  1159. if (this.compareType === '句子') {
  1160. this.data.wordTime.forEach((item, index) => {
  1161. let obj = {
  1162. content: item.onebest,
  1163. lrc_data: {
  1164. begin_time: item.bg,
  1165. end_time: item.ed,
  1166. },
  1167. };
  1168. this.subtitleList.push(obj);
  1169. });
  1170. } else {
  1171. this.compareIndex = index;
  1172. this.data.wordTime[index].wordsResultList.forEach((item, index) => {
  1173. let obj = {
  1174. content: item.wordsName,
  1175. lrc_data: {
  1176. begin_time: item.wordBg,
  1177. end_time: item.wordEd,
  1178. },
  1179. };
  1180. this.subtitleList.push(obj);
  1181. });
  1182. }
  1183. this.visible = true;
  1184. },
  1185. /**
  1186. * 保存字幕
  1187. * @param {Array} dataList
  1188. */
  1189. saveSubtitles(dataList) {
  1190. if (this.compareType === '句子') {
  1191. this.data.wordTime.forEach((item, index) => {
  1192. item.bg = dataList[index] ? dataList[index].lrc_data.begin_time : 0;
  1193. item.ed = dataList[index] ? dataList[index].lrc_data.end_time : 0;
  1194. });
  1195. } else {
  1196. this.data.wordTime[this.compareIndex].wordsResultList.forEach((item, index) => {
  1197. item.wordBg = dataList[index] ? dataList[index].lrc_data.begin_time : 0;
  1198. item.wordEd = dataList[index] ? dataList[index].lrc_data.end_time : 0;
  1199. });
  1200. }
  1201. },
  1202. selectItem(item, index) {
  1203. this.itemActive = item;
  1204. this.itemActiveIndex = index;
  1205. this.pinyinList = [];
  1206. toolGetWordPinyinCorrectionList({
  1207. word: item.chs,
  1208. })
  1209. .then((res) => {
  1210. if (res.status === 1) {
  1211. this.pinyinList = res.pinyin_correction_list;
  1212. }
  1213. this.dialogFlag = true;
  1214. })
  1215. .catch(() => {
  1216. this.dialogFlag = true;
  1217. });
  1218. this.checkPinyinInput = '';
  1219. },
  1220. cancleDialog() {
  1221. this.itemActive = null;
  1222. this.checkPinyinInput = '';
  1223. this.oldInput = '';
  1224. this.dialogFlag = false;
  1225. },
  1226. surePinyin() {
  1227. this.oldInput = JSON.parse(JSON.stringify(this.checkPinyinInput.trim()));
  1228. this.handleReplaceTone(this.checkPinyinInput.trim());
  1229. },
  1230. handleReplaceTone(e) {
  1231. let _this = this;
  1232. _this.$nextTick(() => {
  1233. let value = e;
  1234. _this.resArr = [];
  1235. if (value) {
  1236. let valueArr = [];
  1237. value.replace(/\s+/g, ' ');
  1238. valueArr = value.split(' ');
  1239. // let reg = /\s+/g;
  1240. // valueArr = value.split(reg);
  1241. valueArr.forEach((item, index) => {
  1242. if (this.data.pinyin_type === 'tone') {
  1243. this.resArr.push([
  1244. {
  1245. con: this.toneList[Number(item)],
  1246. },
  1247. ]);
  1248. } else {
  1249. this.handleValue(item);
  1250. }
  1251. });
  1252. let str = '';
  1253. setTimeout(() => {
  1254. _this.resArr.forEach((item) => {
  1255. str += ' ';
  1256. item.forEach((sItem) => {
  1257. if (sItem.number && sItem.con) {
  1258. let number = Number(sItem.number);
  1259. let con = sItem.con;
  1260. let word = _this.addTone(number, con);
  1261. str += word;
  1262. } else if (sItem.number) {
  1263. str += sItem.number;
  1264. } else if (sItem.con) {
  1265. str += ` ${sItem.con} `;
  1266. }
  1267. });
  1268. });
  1269. this.checkPinyinInput = str.trim();
  1270. this.itemActive.pinyin = this.checkPinyinInput.replace(/\s+/g, '');
  1271. this.itemActive.pinyin_up = this.checkPinyinInput.replace(/\s+/g, '');
  1272. this.$message.success('保存成功');
  1273. // this.itemActive = null;
  1274. this.checkPinyinInput = '';
  1275. this.oldInput = '';
  1276. this.dialogFlag = false;
  1277. }, 10);
  1278. }
  1279. });
  1280. },
  1281. handleValue(valItem) {
  1282. let reg = /\d/;
  1283. let reg2 = /[A-Za-z]+\d/g;
  1284. let numList = [];
  1285. let valArr = valItem.split('');
  1286. if (reg2.test(valItem)) {
  1287. for (let i = 0; i < valArr.length; i++) {
  1288. let item = valItem[i];
  1289. if (reg.test(item)) {
  1290. let numIndex = numList.length === 0 ? 0 : numList[numList.length - 1].index;
  1291. let con = valItem.substring(numIndex, i);
  1292. con = con.replace(/\d/g, '');
  1293. let obj = {
  1294. index: i,
  1295. number: item,
  1296. con,
  1297. isTran: true,
  1298. };
  1299. numList.push(obj);
  1300. }
  1301. }
  1302. } else {
  1303. numList = [];
  1304. }
  1305. if (numList.length === 0) {
  1306. this.resArr.push([{ con: valItem }]);
  1307. } else {
  1308. this.resArr.push(numList);
  1309. }
  1310. },
  1311. addTone(number, con) {
  1312. let _this = this;
  1313. let zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'A', 'O', 'E', 'I', 'U', 'n', 'm'];
  1314. if (number) {
  1315. for (let i = 0; i < zmList.length; i++) {
  1316. let zm = zmList[i];
  1317. if (con.indexOf(zm) > -1) {
  1318. let zm2 = _this.tableData[i][number - 1];
  1319. if (con.indexOf('iu') > -1) {
  1320. zm2 = _this.tableData[4][number - 1];
  1321. con = con.replace('u', zm2);
  1322. } else if (con.indexOf('ui') > -1) {
  1323. zm2 = _this.tableData[3][number - 1];
  1324. con = con.replace('i', zm2);
  1325. } else if (
  1326. con.indexOf('yv') > -1 ||
  1327. con.indexOf('jv') > -1 ||
  1328. con.indexOf('qv') > -1 ||
  1329. con.indexOf('xv') > -1
  1330. ) {
  1331. zm2 = _this.tableData[4][number - 1];
  1332. con = con.replace('v', zm2);
  1333. } else {
  1334. con = con.replace(zm, zm2);
  1335. }
  1336. break;
  1337. }
  1338. }
  1339. }
  1340. return con;
  1341. },
  1342. // 设置样式
  1343. setActiveTextStyle(type, val) {
  1344. let style = this.data.other_attrib;
  1345. if (type === 'underline') {
  1346. style.isUnderline = style.isUnderline === 'underline' ? '' : 'underline';
  1347. if (style.isUnderline === 'underline') {
  1348. style.isStrikethrough = '';
  1349. }
  1350. this.data.detail.forEach((item) => {
  1351. item.wordsList.forEach((items) => {
  1352. items.forEach((itemss) => {
  1353. itemss.textDecoration = style.isUnderline;
  1354. });
  1355. });
  1356. });
  1357. } else if (type === 'bold') {
  1358. style.isBold = style.isBold === 'bold' ? '' : 'bold';
  1359. this.data.detail.forEach((item) => {
  1360. item.wordsList.forEach((items) => {
  1361. items.forEach((itemss) => {
  1362. itemss.fontWeight = style.isBold;
  1363. });
  1364. });
  1365. });
  1366. } else if (type === 'line-through') {
  1367. style.isStrikethrough = style.isStrikethrough === 'line-through' ? '' : 'line-through';
  1368. if (style.isStrikethrough === 'line-through') {
  1369. style.isUnderline = '';
  1370. }
  1371. this.data.detail.forEach((item) => {
  1372. item.wordsList.forEach((items) => {
  1373. items.forEach((itemss) => {
  1374. itemss.textDecoration = style.isStrikethrough;
  1375. });
  1376. });
  1377. });
  1378. } else if (type === 'border') {
  1379. style.border = style.border === 'dotted' ? '' : 'dotted';
  1380. this.data.detail.forEach((item) => {
  1381. item.wordsList.forEach((items) => {
  1382. items.forEach((itemss) => {
  1383. itemss.border = style.border;
  1384. });
  1385. });
  1386. });
  1387. } else if (type === 'align') {
  1388. style.align = val;
  1389. this.articleAttrib.align = val;
  1390. this.data.detail.forEach((item) => {
  1391. item.paraAlign =
  1392. this.articleAttrib.align === 'RIGHT' ? 'right' : this.articleAttrib.align === 'MIDDLE' ? 'center' : 'left';
  1393. });
  1394. }
  1395. },
  1396. // 点击汉字打开关联注释弹窗
  1397. matchItemNotes(item) {
  1398. if (!item.matchNotesObj.id) {
  1399. item.matchNotesObj.id = getRandomNumber();
  1400. }
  1401. this.matchNotesObj = item.matchNotesObj;
  1402. this.matchNoteFlag = true;
  1403. },
  1404. cancleMatchNotesDialog() {
  1405. this.$confirm('确定要删除此条气泡吗?', '提示', {
  1406. confirmButtonText: '确定',
  1407. cancelButtonText: '取消',
  1408. type: 'warning',
  1409. })
  1410. .then(() => {
  1411. this.handleSyncCon(this.matchNotesObj, 'delete');
  1412. let index = this.data.notes_list.option.findIndex((items) => items.id === this.matchNotesObj.id);
  1413. this.data.notes_list.option.splice(index, 1);
  1414. this.matchNoteFlag = false;
  1415. })
  1416. .catch(() => {});
  1417. },
  1418. // 确定关联注释
  1419. sureMatchNotes() {
  1420. let index = this.data.notes_list.option.findIndex((items) => items.id === this.matchNotesObj.id);
  1421. if (index > -1) {
  1422. this.data.notes_list.option[index] = this.matchNotesObj;
  1423. } else {
  1424. this.data.notes_list.option.push(this.matchNotesObj);
  1425. }
  1426. this.matchNoteFlag = false;
  1427. this.editWordsFlag = true;
  1428. this.editWordIndex = 1;
  1429. },
  1430. async handleBlurCon() {
  1431. let text = this.matchNotesObj.con.replace(/<[^>]+>/g, '');
  1432. if (text) {
  1433. this.matchNotesObj.pinyin = await this.handlePinyins(
  1434. text,
  1435. this.data.notes_list.property.is_first_sentence_first_hz_pinyin_first_char_upper_case,
  1436. );
  1437. }
  1438. },
  1439. // 自动生成拼音
  1440. handlePinyins(text, flag) {
  1441. let data = {
  1442. text: text,
  1443. is_rich_text: 'false',
  1444. is_first_sentence_first_hz_pinyin_first_char_upper_case: flag ? flag : 'false',
  1445. is_fill_space: 'false',
  1446. is_custom_fc: 'false',
  1447. };
  1448. return new Promise((resolve, reject) => {
  1449. PinyinBuild_OldFormat(data).then((res) => {
  1450. if (res.parsed_text) {
  1451. let mergedData = '';
  1452. res.parsed_text.paragraph_list.map((outerArr, i) =>
  1453. outerArr.map((innerArr, j) =>
  1454. innerArr.map((newItem, k) => {
  1455. mergedData += newItem.pinyin + ' ';
  1456. }),
  1457. ),
  1458. );
  1459. resolve(mergedData);
  1460. }
  1461. });
  1462. });
  1463. },
  1464. // 在气泡列表里修改了内容,同步detail的内容
  1465. handleSyncCon(obj, type) {
  1466. this.data.detail.forEach((item) => {
  1467. item.wordsList.forEach((items) => {
  1468. items.forEach((itemss) => {
  1469. if (itemss.matchNotesObj.id === obj.id) {
  1470. if (type === 'delete') {
  1471. itemss.matchNotesObj = {
  1472. number: '',
  1473. con: '',
  1474. pinyin: '',
  1475. interpret: '',
  1476. note: '',
  1477. notesColor: '#8206BF',
  1478. id: '',
  1479. isRich: true,
  1480. matchNotes: itemss.chs,
  1481. };
  1482. } else {
  1483. itemss.matchNotesObj = obj;
  1484. }
  1485. }
  1486. });
  1487. });
  1488. });
  1489. },
  1490. },
  1491. };
  1492. </script>
  1493. <style lang="scss" scoped>
  1494. .article-wrapper {
  1495. display: flex;
  1496. flex-direction: column;
  1497. row-gap: 16px;
  1498. align-items: flex-start;
  1499. }
  1500. .upload-file {
  1501. display: flex;
  1502. column-gap: 12px;
  1503. align-items: center;
  1504. .file-name {
  1505. display: flex;
  1506. column-gap: 14px;
  1507. align-items: center;
  1508. justify-content: space-between;
  1509. max-width: 360px;
  1510. padding: 8px 12px;
  1511. font-size: 14px;
  1512. color: #1d2129;
  1513. background-color: #f7f8fa;
  1514. span {
  1515. display: flex;
  1516. column-gap: 14px;
  1517. align-items: center;
  1518. }
  1519. }
  1520. .svg-icon {
  1521. cursor: pointer;
  1522. }
  1523. }
  1524. .btn-box {
  1525. display: flex;
  1526. flex-flow: wrap;
  1527. gap: 10px;
  1528. a {
  1529. padding: 5px 16px;
  1530. font-size: 14px;
  1531. line-height: 22px;
  1532. color: #4e5969;
  1533. cursor: pointer;
  1534. background: #f2f3f5;
  1535. border-radius: 2px;
  1536. }
  1537. p {
  1538. margin: 0;
  1539. }
  1540. }
  1541. .new-btn-box {
  1542. p {
  1543. margin: 0 0 10px;
  1544. font-size: 14px;
  1545. font-weight: bold;
  1546. color: #333;
  1547. }
  1548. > div {
  1549. display: flex;
  1550. flex-wrap: wrap;
  1551. gap: 8px;
  1552. }
  1553. a {
  1554. display: block;
  1555. padding: 6px 14px;
  1556. font-size: 13px;
  1557. color: #000;
  1558. cursor: pointer;
  1559. background: #fff;
  1560. border: 1px solid #d9d9d9;
  1561. border-radius: 4px;
  1562. &:hover {
  1563. color: #1890ff;
  1564. border-color: #1890ff;
  1565. }
  1566. }
  1567. :deep .el-button {
  1568. padding: 6px 14px;
  1569. font-size: 13px;
  1570. line-height: 1.5;
  1571. color: #000;
  1572. cursor: pointer;
  1573. background: #fff;
  1574. border: 1px solid #d9d9d9;
  1575. border-radius: 4px;
  1576. &:hover {
  1577. color: #1890ff;
  1578. border-color: #1890ff;
  1579. }
  1580. &.is-disabled {
  1581. color: #ccc;
  1582. border-color: #e8e8e8;
  1583. }
  1584. }
  1585. }
  1586. .tips {
  1587. margin: 0;
  1588. font-size: 12px;
  1589. color: #e82d2d;
  1590. }
  1591. .tabs-box {
  1592. display: flex;
  1593. column-gap: 12px;
  1594. margin: 12px 0;
  1595. a {
  1596. padding: 5px 16px;
  1597. font-size: 14px;
  1598. font-weight: 400;
  1599. line-height: 22px; /* 157.143% */
  1600. color: #4e5969;
  1601. cursor: pointer;
  1602. border-radius: 100px;
  1603. &.active {
  1604. color: #165dff;
  1605. background: #f2f3f5;
  1606. }
  1607. }
  1608. }
  1609. .text {
  1610. display: flex;
  1611. flex-flow: wrap;
  1612. .text-item {
  1613. padding: 0 1px;
  1614. text-align: center;
  1615. cursor: pointer;
  1616. }
  1617. .pinyin {
  1618. display: block;
  1619. font-family: 'PINYIN-B';
  1620. }
  1621. .word {
  1622. display: block;
  1623. }
  1624. }
  1625. .table-rich-toolbar {
  1626. display: flex;
  1627. gap: 5px;
  1628. /* align-items: center; */
  1629. :deep .el-form-item--small.el-form-item {
  1630. margin: 0 5px 0 0;
  1631. }
  1632. :deep .el-form-item__label {
  1633. padding-right: 3px;
  1634. }
  1635. > span {
  1636. height: 30px;
  1637. padding: 5px 6px;
  1638. color: #222f3e;
  1639. cursor: pointer;
  1640. border-radius: 5px;
  1641. &:hover {
  1642. background: #cce2fa;
  1643. }
  1644. &.active {
  1645. background: #a6ccf7;
  1646. }
  1647. }
  1648. }
  1649. </style>
  1650. <style lang="scss">
  1651. .article-matchNotes-dialog {
  1652. .el-dialog__header,
  1653. .el-dialog__body {
  1654. padding: 5px 10px;
  1655. }
  1656. .btn-box {
  1657. display: block;
  1658. padding: 5px 0;
  1659. text-align: right;
  1660. .el-button {
  1661. padding: 2px 12px;
  1662. font-size: 12px;
  1663. line-height: 20px;
  1664. }
  1665. }
  1666. .matchNotes-color {
  1667. display: flex;
  1668. align-items: center;
  1669. justify-content: space-between;
  1670. margin-top: 5px;
  1671. span {
  1672. display: flex;
  1673. gap: 3px;
  1674. align-items: center;
  1675. cursor: pointer;
  1676. }
  1677. .el-color-picker--small {
  1678. height: 16px;
  1679. }
  1680. .el-color-picker__trigger {
  1681. width: 16px;
  1682. height: 16px;
  1683. padding: 0;
  1684. border: none;
  1685. }
  1686. }
  1687. }
  1688. .article-checkpinyin-dialog {
  1689. .el-dialog__header {
  1690. padding: 0;
  1691. }
  1692. .el-dialog__body {
  1693. padding: 0;
  1694. }
  1695. .el-input__inner {
  1696. text-align: center;
  1697. background: #f3f3f3;
  1698. }
  1699. .check-box {
  1700. padding: 24px;
  1701. background: #fff;
  1702. border-radius: 4px;
  1703. box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
  1704. .content {
  1705. display: flex;
  1706. align-items: center;
  1707. justify-content: center;
  1708. .words-box {
  1709. color: #000;
  1710. text-align: center;
  1711. .words {
  1712. display: block;
  1713. font-size: 28px;
  1714. font-weight: 400;
  1715. line-height: 40px;
  1716. }
  1717. .pinyin {
  1718. display: block;
  1719. height: 20px;
  1720. font-family: 'League';
  1721. font-size: 20px;
  1722. font-weight: 400;
  1723. line-height: 1;
  1724. }
  1725. }
  1726. }
  1727. .checkPinyinInput {
  1728. margin: 16px 0 8px;
  1729. }
  1730. .tips {
  1731. margin: 0 0 24px;
  1732. font-size: 12px;
  1733. font-weight: 400;
  1734. line-height: 18px;
  1735. color: #a0a0a0;
  1736. }
  1737. .btn-box {
  1738. display: block;
  1739. text-align: right;
  1740. .el-button {
  1741. padding: 2px 12px;
  1742. font-size: 12px;
  1743. line-height: 20px;
  1744. }
  1745. }
  1746. }
  1747. }
  1748. </style>