Article.vue 57 KB

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