PracticeModel.vue 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <!-- -->
  2. <template>
  3. <div class="NNPE-ArticleView" v-if="articleInfo">
  4. <template v-if="resArr&&resArr.wordsList&&resArr.wordsList[0]&&resArr.wordsList[0][0]&&resArr.wordsList[0][0].hasOwnProperty('pno')&&resArr.wordsList[0][0].pno===0">
  5. <h2 :class="['NNPE-words',]">
  6. <span v-for="(itemR,indexR) in resArr.wordsList[0]" :key="indexR" :style="{color:colorObj.titleColor,fontSize:(wordFontsize+30)+'px',lineHeight:(wordFontsize+38)+'px',marginRight:'10px',fontWeight:'700'}"
  7. :class="[
  8. itemR.tokens[9]===''?'marginRight':'',itemR.marginRight?'marginSingleRight':''
  9. ]">
  10. <template v-if="itemR.isShow">
  11. <span
  12. class="NNPE-chs"
  13. :class="[
  14. itemR.type,itemR.tokens[9]===''?'marginRight':'',itemR.highIndex?'fontWeight':'',itemR.marginRight?'marginSingleRight':''
  15. ]"
  16. >{{ itemR.tokens[2] }}</span
  17. >
  18. <span
  19. class="NNPE-chs NNPE-chs-both"
  20. v-if="resArr.wordsList[0][indexR + 1] &&
  21. resArr.wordsList[0][indexR + 1].tokens[2] &&
  22. enFhList.indexOf(resArr.wordsList[0][indexR + 1].tokens[2]) > -1"
  23. :class="[
  24. resArr.wordsList[0][indexR + 1].type,resArr.wordsList[0][indexR + 1].tokens[8]===''?'marginLeft':'',resArr.wordsList[0][indexR + 1].highIndex?'fontWeight':'',resArr.wordsList[0][indexR + 1].marginRight?'marginSingleRight':''
  25. ]"
  26. >{{ resArr.wordsList[0][indexR + 1].tokens[2] }}</span
  27. >
  28. </template>
  29. <!-- {{itemR.tokens[2]}} -->
  30. </span>
  31. </h2>
  32. </template>
  33. <h6 class="nnpe-article-author" :style="{color:colorObj.sourceColor,fontSize:(wordFontsize-4)+'px',lineHeight:(wordFontsize+4)+'px',fontWeight:'400'}">
  34. {{articleInfo.art_author+' · '+articleInfo.study_phase_name+'版 · 第 '+articleInfo.iss_no+' 期 · '+articleInfo.release_date+' · '+articleInfo.chn_item+(articleInfo.page_no_in_pub?' · P'+articleInfo.page_no_in_pub:'')}}
  35. </h6>
  36. <div class="audio-box">
  37. <div
  38. class="aduioLine-content aduioLine-box"
  39. v-if="
  40. articleInfo.art_sound_url
  41. " :style="{background:colorObj.audiobg,borderColor:colorObj.audioBorder}"
  42. >
  43. <AudioLine
  44. audioId="artNormalAudio"
  45. :mp3="articleInfo.art_sound_url"
  46. :getCurTime="getCurTime"
  47. ref="audioLine"
  48. :mp3Source="'mp3'"
  49. type="audioLine"
  50. :ed="ed"
  51. :showEd="showEd"
  52. @emptyEd="emptyEd"
  53. />
  54. <svg-icon icon-class="icon-wrapper" class="wrapper" @click="fullScreen"></svg-icon>
  55. </div>
  56. </div>
  57. <template v-if="resArr.wordsList&&resArr.wordsList.length > 0">
  58. <div class="table-box" :class="['table-box-'+colorObj.type]">
  59. <div
  60. :class="['NNPE-detail']"
  61. v-for="(item, index) in resArr.wordsList"
  62. :key="'detail' + index"
  63. >
  64. <a class="history-btn" v-if="resArr.timeList[index] && resArr.timeList[index] && curTime >= resArr.timeList[index].s && curTime <= resArr.timeList[index].e" @click="lookHistory(index)">历史记录</a>
  65. <b class="para-index" :style="{fontSize:wordFontsize + 'px',color:resArr.timeList[index] && resArr.timeList[index] && curTime >= resArr.timeList[index].s && curTime <= resArr.timeList[index].e ? colorObj.type==='white'?'#2F3742':colorObj.type==='darkGreen'?'#299772':colorObj.type==='armyGreen'?'#30A47D':'#5373E7':colorObj.type==='white'?'#D0D3D9':colorObj.type==='darkGreen'?'#7A8983':colorObj.type==='armyGreen'?'#6B7C74':'#737781'}">{{index+1}}</b>
  66. <div class="wordsList-box">
  67. <div class="nnpe-sentence-box">
  68. <div v-for="(pItem, pIndex) in item" :key="'wordsList' + pIndex">
  69. <template v-if="pItem.isShow">
  70. <div
  71. :class="[
  72. 'NNPE-words',
  73. isPlaying &&
  74. resArr.timeList[index] &&
  75. resArr.timeList[index] &&
  76. curTime >= resArr.timeList[index].s &&
  77. curTime <= resArr.timeList[index].e
  78. ? 'sentActive'
  79. : '',
  80. pItem.pno == paraIndex && pItem.sno == sentIndex
  81. ? 'overActive'
  82. : '',
  83. ]"
  84. @click="
  85. handleChangeTime(
  86. resArr.timeList[index] &&
  87. resArr.timeList[index] &&
  88. resArr.timeList[index].s
  89. )
  90. "
  91. @mouseover="handleMouseover(pItem)"
  92. @mouseleave="handleMouseleave"
  93. >
  94. <span
  95. class="NNPE-chs"
  96. :class="[
  97. isPlaying &&
  98. resArr.timeList[index] &&
  99. resArr.timeList[index] &&
  100. resArr.timeList[index].e &&
  101. resArr.timeList[index].tokens &&
  102. resArr.timeList[index].tokens[pItem.wIndex]&&
  103. curTime >=
  104. resArr.timeList[index].tokens[pItem.wIndex].s &&
  105. curTime <= resArr.timeList[index].e
  106. ? 'wordActive'
  107. : '',
  108. pItem.tokens[9]===''?'marginRight':'',pItem.marginRight?'marginSingleRight':''
  109. ]"
  110. :style="{fontSize:wordFontsize + 'px',color: isPlaying &&
  111. resArr.timeList[index] &&
  112. resArr.timeList[index] &&
  113. resArr.timeList[index].tokens[pItem.wIndex]&&
  114. curTime >=
  115. resArr.timeList[index].tokens[pItem.wIndex].s &&
  116. curTime <= resArr.timeList[index].tokens[pItem.wIndex].e?colorObj.statisticValue:
  117. resArr.timeList[index] &&
  118. resArr.timeList[index] &&
  119. curTime >= resArr.timeList[index].s &&
  120. curTime <= resArr.timeList[index].e?colorObj.contentColor:colorObj.type==='white'?'#D0D3D9':colorObj.type==='darkGreen'?'#7A8983':colorObj.type==='darkBlue'?'#737781':'#6B7C74'}"
  121. >{{ pItem.tokens[2] }}</span
  122. >
  123. <span
  124. class="NNPE-chs NNPE-chs-both"
  125. v-if="item[pIndex + 1] &&
  126. item[pIndex + 1].tokens[2] &&
  127. enFhList.indexOf(item[pIndex + 1].tokens[2]) > -1"
  128. :class="[
  129. isPlaying &&
  130. resArr.timeList[index] &&
  131. resArr.timeList[index] &&
  132. resArr.timeList[index].tokens[pItem.wIndex]&&
  133. curTime >=
  134. resArr.timeList[index].tokens[pItem.wIndex].s &&
  135. curTime <= resArr.timeList[index].e
  136. ? 'wordActive'
  137. : '',
  138. item[pIndex + 1].tokens[8]===''?'marginLeft':'',item[pIndex + 1].marginRight?'marginSingleRight':''
  139. ]"
  140. :style="{fontSize:wordFontsize + 'px',color: isPlaying &&
  141. resArr.timeList[index] &&
  142. resArr.timeList[index] &&
  143. resArr.timeList[index].tokens[pItem.wIndex]&&
  144. curTime >=
  145. resArr.timeList[index].tokens[pItem.wIndex].s &&
  146. curTime <= resArr.timeList[index].tokens[pItem.wIndex].e?colorObj.statisticValue:resArr.timeList[index] &&
  147. resArr.timeList[index] &&
  148. curTime >= resArr.timeList[index].s &&
  149. curTime <= resArr.timeList[index].e?colorObj.contentColor:colorObj.type==='white'?'#D0D3D9':colorObj.type==='darkGreen'?'#7A8983':colorObj.type==='darkBlue'?'#737781':'#6B7C74'}"
  150. >{{ item[pIndex + 1].tokens[2] }}</span
  151. >
  152. </div>
  153. </template>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. <div class="operate-box" :style="{background:colorObj.contentBg}">
  159. <div class="operate-box-inner" :style="{background:colorObj.contentInnerBg}">
  160. <div class="operate-box-inner-content">
  161. <div class="operate-item" @click="changePlaySent('-')">
  162. <svg-icon icon-class="Go-start" :style="{color:colorObj.type==='white'||colorObj.type==='darkGreen'?'#000':'#fff'}"></svg-icon>
  163. <span :style="{color:colorObj.type==='armyGreen'?'#7C8983':''}">上一句</span>
  164. </div>
  165. <div class="operate-item" @click="compare">
  166. <svg-icon icon-class="Type-drive" :style="{color:colorObj.type==='white'||colorObj.type==='darkGreen'?'#000':'#fff'}"></svg-icon>
  167. <span :style="{color:colorObj.type==='armyGreen'?'#7C8983':''}">听对比</span>
  168. </div>
  169. <div class="operate-item">
  170. <b class="luyin-btn" v-if="!microphoneStatus" @click="microphone">
  171. <svg-icon icon-class="Voice-luyin" :style="{color:'#fff'}"></svg-icon>
  172. </b>
  173. <img class="luyin-gif" @click="microphone" v-else src="../../../assets/voice-gif.png" />
  174. <span v-if="microphoneStatus" class="record-time" :style="{color:'#F2555A',fontWeight: '600'}">{{
  175. handleDateTime(recordtime)
  176. }}</span>
  177. </div>
  178. <div class="operate-item" @click="playmicrophone(selectIndex || selectIndex == 0 ? recordList[selectIndex].toltime : '')">
  179. <svg-icon icon-class="Headphone-sound" :style="{color:colorObj.type==='white'||colorObj.type==='darkGreen'?'#000':'#fff'}"></svg-icon>
  180. <span :style="{color:colorObj.type==='armyGreen'?'#7C8983':''}">我读的</span>
  181. </div>
  182. <div class="operate-item" @click="changePlaySent('+')">
  183. <svg-icon icon-class="Go-end" :style="{color:colorObj.type==='white'||colorObj.type==='darkGreen'?'#000':'#fff'}"></svg-icon>
  184. <span :style="{color:colorObj.type==='armyGreen'?'#7C8983':''}">下一句</span>
  185. </div>
  186. </div>
  187. </div>
  188. </div>
  189. </div>
  190. </template>
  191. <!-- <img src="../../../assets/article-img.png" style="max-width:100%;margin:24px 0;" /> -->
  192. <div class="clear-box">
  193. <h5>词汇标记设置</h5>
  194. <div class="item">
  195. <span>跟读</span>
  196. <el-switch
  197. v-model="repeatAfter"
  198. active-color="#175DFF"
  199. inactive-color="#D0D3D9"
  200. key="sent-repeatAfter">
  201. </el-switch>
  202. </div>
  203. <div class="item">
  204. <span>单句模式</span>
  205. <el-switch
  206. v-model="singleModel"
  207. active-color="#175DFF"
  208. inactive-color="#D0D3D9"
  209. key="sent-singleModel"
  210. @change="mutualExclusive('singleModel','autoNextSent')">
  211. </el-switch>
  212. </div>
  213. <div class="item">
  214. <span>自动下一句</span>
  215. <el-switch
  216. v-model="autoNextSent"
  217. active-color="#175DFF"
  218. inactive-color="#D0D3D9"
  219. key="sent-autoNextSent"
  220. @change="mutualExclusive('autoNextSent','singleModel')">
  221. </el-switch>
  222. </div>
  223. </div>
  224. <el-dialog
  225. :visible.sync="historyFlag"
  226. :show-close="false"
  227. :close-on-click-modal="false"
  228. width="680px"
  229. :modal="false"
  230. class="login-dialog"
  231. v-if="historyFlag">
  232. <history-record-list :list="historySentRecordList" :sentData="resArr.wordsList[playSentIndex]" :timeData="resArr.timeList[playSentIndex]" :mp3Url="articleInfo.art_sound_url" @closeHistory="closeHistory" :colorObj="colorObj" :wordFontsize="wordFontsize" @handleChangeTime="handleChangeTime" @returnCurrentTime="returnCurrentTime" :parentCurtimt="curTime" :parentPlay="Playing" v-loading="historyLoading"></history-record-list>
  233. </el-dialog>
  234. <div class="voice-full-screen" :id="'screen-' + mathNum">
  235. <Voicefullscreen
  236. v-if="isFull"
  237. :curQue="articleInfo"
  238. :sentIndex="playSentIndex===-1?0:playSentIndex"
  239. :mp3="articleInfo.art_sound_url"
  240. :likeSentencelist="likeSentencelist"
  241. @exitFullscreen="exitFullscreen"
  242. @changeIsFull="changeIsFull"
  243. />
  244. </div>
  245. </div>
  246. </template>
  247. <script>
  248. import AudioLine from "@/components/common/AudioLine.vue"
  249. import HistoryRecordList from "./HistoryRecordList.vue"
  250. import Recorder from "js-audio-recorder"; // 录音插件
  251. import { getLogin } from "@/api/ajax";
  252. import Voicefullscreen from './Voicefullscreen.vue';
  253. export default {
  254. name: "ArticleView",
  255. props: [ "titleFontsize", "wordFontsize", "colorObj","articleType","articleInfo","likeSentencelist"],
  256. components: {
  257. AudioLine,
  258. HistoryRecordList,
  259. Voicefullscreen
  260. },
  261. data() {
  262. return {
  263. resArr: [],
  264. curTime: 0, //单位s
  265. enFhList: [
  266. ",",
  267. ".",
  268. ";",
  269. "?",
  270. "!",
  271. ":",
  272. ">",
  273. "<",
  274. "'",
  275. "’",
  276. "n't",
  277. "n’t",
  278. "n’ts",
  279. "n‘t",
  280. "'t",
  281. "’t",
  282. "‘t",
  283. "'s",
  284. "’s",
  285. "‘s",
  286. "'m",
  287. "’m",
  288. "‘m",
  289. "'re",
  290. "’re",
  291. "‘re",
  292. "'d",
  293. "’d",
  294. "‘d",
  295. "'ve",
  296. "’ve",
  297. "‘ve",
  298. ")",
  299. "'ll",
  300. "’ll",
  301. "‘ll",
  302. "”",
  303. ],
  304. articleImg: {}, // 文章图片
  305. paraIndex: -1, //段落索引
  306. sentIndex: -1, // 句子索引
  307. repeatAfter: false,
  308. singleModel: true,
  309. autoNextSent: false,
  310. recorder: new Recorder({
  311. sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
  312. sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
  313. numChannels: 1, // 声道,支持 1 或 2, 默认是1
  314. }),
  315. microphoneStatus: false,
  316. hasMicro: "", // 录音后的样式class
  317. wavblob: null,
  318. audio: new window.Audio(),
  319. audioc: new window.Audio(), // 对比
  320. recordList: [], // 录音文件数组
  321. recordtime: 0, // 录音时长
  322. timer: null, // 计时器
  323. recordFile: 1, // 录音文件名
  324. selectIndex: null, // 选中的录音索引
  325. oldIndex: null, // 存储播放录音索引
  326. playtime: 0, // 播放时间
  327. isPlayings: false,
  328. playSentIndex: -1, // 播放的句子索引
  329. historyFlag: false, // 历史记录弹窗
  330. historySentRecordList: [], // 单句历史录音list
  331. ed: undefined,
  332. showEd: false, //是否看ed的值
  333. historyLoading: false,
  334. timeC: null,
  335. isCompare: false,
  336. isFull: false,
  337. mathNum: Math.random().toString(36).substr(2),
  338. };
  339. },
  340. computed: {
  341. isPlaying: function () {
  342. let playing = false;
  343. if (this.$refs.audioLine) {
  344. playing = this.$refs.audioLine.audio.isPlaying;
  345. }
  346. return playing;
  347. },
  348. Playing: function () {
  349. let playing = false;
  350. if (this.$refs.audioLine) {
  351. playing = this.$refs.audioLine.audio.playing;
  352. }
  353. return playing;
  354. },
  355. },
  356. watch: {},
  357. //方法集合
  358. methods: {
  359. changePlaySent(type){
  360. this.audio.pause();
  361. this.audioc.pause();
  362. if(type==='-'){
  363. if(this.playSentIndex>0){
  364. this.playSentIndex--
  365. }else{
  366. this.playSentIndex=0
  367. }
  368. }else{
  369. if(this.playSentIndex<this.resArr.wordsList.length-1){
  370. this.playSentIndex++
  371. }else{
  372. this.playSentIndex=0
  373. }
  374. }
  375. this.handleChangeTime(this.resArr.timeList[this.playSentIndex].s)
  376. },
  377. getCurTime(curTime) {
  378. if(this.isCompare){
  379. let time = curTime * 1000;
  380. if (time >= this.resArr.timeList[this.playSentIndex].e || time <= this.resArr.timeList[this.playSentIndex].s) {
  381. // this.curTime = this.resArr.timeList[this.playSentIndex].s;
  382. // this.$refs.audioLine.onTimeupdateTime(_this.currSent.bg / 1000, true);
  383. } else {
  384. this.curTime = curTime * 1000;
  385. }
  386. } else if(this.singleModel&&this.playSentIndex>-1&&!this.repeatAfter){
  387. let time = curTime * 1000;
  388. if (time >= this.resArr.timeList[this.playSentIndex].e || time <= this.resArr.timeList[this.playSentIndex].s) {
  389. this.curTime = this.resArr.timeList[this.playSentIndex].s;
  390. this.$refs.audioLine.onTimeupdateTime(this.resArr.timeList[this.playSentIndex].s / 1000, true);
  391. } else {
  392. this.curTime = curTime * 1000;
  393. }
  394. } else if((this.singleModel||this.autoNextSent)&&this.playSentIndex>-1&&this.repeatAfter){
  395. let time = curTime * 1000;
  396. this.curTime = curTime * 1000;
  397. // for(let i=0; i<this.resArr.timeList.length;i++){
  398. // if(this.curTime>this.resArr.timeList[i].s&&this.curTime<=this.resArr.timeList[i].e){
  399. // this.playSentIndex = i
  400. // break
  401. // }
  402. // }
  403. // if (time >= this.resArr.timeList[this.playSentIndex].s || time <= this.resArr.timeList[this.playSentIndex].e) {
  404. // this.curTime = this.resArr.timeList[this.playSentIndex].s;
  405. if(this.playSentIndex===0) this.ed = this.resArr.timeList[this.playSentIndex].e;
  406. this.showEd = true
  407. // this.$refs.audioLine.onTimeupdateTime(this.resArr.timeList[this.playSentIndex].s / 1000, true);
  408. // }
  409. }else{
  410. this.curTime = curTime * 1000;
  411. for(let i=0; i<this.resArr.timeList.length;i++){
  412. if(this.curTime>=this.resArr.timeList[i].s&&this.curTime<this.resArr.timeList[i].e){
  413. this.playSentIndex = i
  414. break
  415. }
  416. }
  417. }
  418. },
  419. returnCurrentTime(){
  420. return this.curTime
  421. },
  422. handleData() {
  423. let resArr = {
  424. wordsList: [],
  425. timeList: [],
  426. };
  427. let articleInfo = JSON.parse(JSON.stringify(this.articleInfo));
  428. articleInfo.art_corpus_data.sentList.forEach((item,index) => {
  429. let wordlist = []
  430. item.tokens.forEach((items,indexs)=>{
  431. let obj = {
  432. sent_id:item.id,
  433. sno: item.sno-1,
  434. pno: item.pno,
  435. text: item.text,
  436. tokens: items,
  437. wIndex: indexs,
  438. isShow: this.enFhList.indexOf(items[2])==-1,
  439. marginRight: indexs===item.tokens.length-1
  440. }
  441. wordlist.push(obj)
  442. })
  443. resArr.wordsList.push(wordlist)
  444. resArr.timeList.push(articleInfo.art_sound_srt_data.sents[index])
  445. });
  446. this.resArr = resArr;
  447. console.log(this.resArr)
  448. },
  449. handleChangeTime(time,ed,flag) {
  450. this.audio.pause();
  451. this.audioc.pause();
  452. if(flag){
  453. this.$refs.audioLine.PlayAudio()
  454. }else{
  455. if (time>=0) {
  456. this.curTime = time;
  457. for(let i=0; i<this.resArr.timeList.length;i++){
  458. if(this.curTime>=this.resArr.timeList[i].s&&this.curTime<this.resArr.timeList[i].e){
  459. this.playSentIndex = i
  460. break
  461. }
  462. }
  463. if(ed){
  464. this.ed = ed;
  465. }else if((this.singleModel||this.autoNextSent)&&this.playSentIndex>-1&&this.repeatAfter){
  466. this.ed = this.resArr.timeList[this.playSentIndex].e;
  467. }else{
  468. this.ed = undefined
  469. }
  470. this.$refs.audioLine.onTimeupdateTime(time / 1000, true);
  471. }
  472. }
  473. },
  474. emptyEd(flag) {
  475. this.ed = undefined;
  476. if(flag){
  477. this.showEd = false
  478. if(!this.microphoneStatus){
  479. this.microphone()
  480. }
  481. }
  482. },
  483. //经过每个词,高亮句子
  484. handleMouseover(pItem) {
  485. this.paraIndex = pItem.pno;
  486. this.sentIndex = pItem.sno;
  487. },
  488. handleMouseleave() {
  489. this.paraIndex = -1;
  490. this.sentIndex = -1;
  491. },
  492. mutualExclusive(val,val1){
  493. if(this[val]){
  494. this[val1] = false
  495. }
  496. },
  497. // 开始录音
  498. microphone() {
  499. let _this = this;
  500. this.audio.pause();
  501. this.audioc.pause();
  502. if(_this.$refs.audioLine.audio.playing){
  503. _this.$refs.audioLine.PlayAudio()
  504. }
  505. if (!this.microphoneStatus) {
  506. _this.hasMicro = "";
  507. // _this.$emit("getWavblob", null);
  508. // this.$emit("getSelectData", { type: "" });
  509. // 开始录音
  510. this.recorder.start();
  511. this.microphoneStatus = true;
  512. this.recordtime = 0;
  513. this.isPlayings = false;
  514. clearInterval(_this.timer);
  515. _this.timer = setInterval(() => {
  516. _this.recordtime++;
  517. }, 1000);
  518. this.$emit("handleParentPlay");
  519. let obj = {
  520. name: _this.fileName
  521. ? _this.fileName + _this.recordFile
  522. : "Recording " + _this.recordFile,
  523. id: _this.recordFile + Math.round(Math.random() * 10),
  524. };
  525. if (this.selectData) obj.selectData = this.selectData;
  526. _this.recordList.push(obj);
  527. _this.recordFile++;
  528. _this.selectIndex = _this.recordList.length - 1;
  529. } else {
  530. this.hasMicro = "normal";
  531. this.recorder.stop();
  532. clearInterval(_this.timer);
  533. let toltime = this.recorder.duration; // 录音总时长
  534. let fileSize = this.recorder.fileSize; // 录音总大小
  535. // 录音结束,获取取录音数据
  536. let wav = this.recorder.getWAVBlob(); // 获取 WAV 数据
  537. // this.wavblob = wav;
  538. this.microphoneStatus = false;
  539. let reader = new window.FileReader();
  540. reader.readAsDataURL(wav);
  541. reader.onloadend = () => {
  542. _this.recordList[_this.selectIndex].wavData = reader.result;
  543. _this.recordList[_this.selectIndex].toltime = Math.floor(toltime);
  544. _this.recordList[_this.selectIndex].fileSize = fileSize;
  545. _this.wavblob = _this.recordList[_this.selectIndex].wavData;
  546. _this.$emit("getWavblob", _this.wavblob);
  547. _this.$emit(
  548. "handleWav",
  549. JSON.parse(JSON.stringify(_this.recordList)),
  550. _this.tmIndex
  551. );
  552. if (this.recordList[this.selectIndex].selectData) {
  553. this.$emit(
  554. "getSelectData",
  555. this.recordList[this.selectIndex].selectData
  556. );
  557. }
  558. this.addRecord(reader.result)
  559. };
  560. }
  561. _this.$emit(
  562. "getRerordStatus",
  563. !_this.microphoneStatus && _this.recordList.length > 0
  564. );
  565. _this.$emit("getMicrophoneStatus", _this.microphoneStatus);
  566. },
  567. playmicrophone(totalTimes) {
  568. this.audioc.pause();
  569. if (this.hasMicro) {
  570. this.isPlayings = true;
  571. if (this.selectIndex || this.selectIndex == 0) {
  572. let totalTime = totalTimes;
  573. let _this = this;
  574. if (!this.audio.paused) {
  575. this.audio.pause();
  576. clearInterval(_this.timer);
  577. } else if (this.audio.paused && _this.oldIndex == _this.selectIndex) {
  578. _this.audio.play();
  579. if (_this.recordtime == 0) {
  580. _this.recordtime = totalTimes;
  581. _this.playtime = 0;
  582. }
  583. _this.timer = setInterval(() => {
  584. if (_this.playtime < totalTime) {
  585. _this.playtime++;
  586. _this.recordtime = totalTime - _this.playtime;
  587. } else {
  588. clearInterval(_this.timer);
  589. }
  590. }, 1000);
  591. } else {
  592. _this.audio.pause();
  593. _this.audio.load();
  594. _this.audio.src = _this.wavblob;
  595. _this.oldIndex = _this.selectIndex;
  596. _this.audio.play();
  597. _this.playtime = 0;
  598. _this.recordtime = totalTime;
  599. clearInterval(_this.timer);
  600. _this.timer = setInterval(() => {
  601. if (_this.playtime < totalTime) {
  602. _this.playtime++;
  603. _this.recordtime = totalTime - _this.playtime;
  604. } else {
  605. clearInterval(_this.timer);
  606. }
  607. }, 1000);
  608. }
  609. }
  610. }
  611. },
  612. // 格式化录音时长
  613. handleDateTime(time) {
  614. if (parseInt(time / 60) < 10) {
  615. time =
  616. ("0" + parseInt(time / 60)).substring(
  617. ("0" + parseInt(time / 60)).length - 2
  618. ) +
  619. ":" +
  620. ("0" + (time % 60)).substring(("0" + (time % 60)).length - 2);
  621. } else {
  622. time =
  623. parseInt(time / 60) +
  624. ":" +
  625. ("0" + (time % 60)).substring(("0" + (time % 60)).length - 2);
  626. }
  627. return time;
  628. },
  629. // 点击历史记录按钮
  630. lookHistory(index){
  631. this.historyFlag = true
  632. this.historyLoading = true
  633. this.audio.pause();
  634. this.audioc.pause();
  635. if(this.microphoneStatus){
  636. this.microphone()
  637. }
  638. if(this.$refs.audioLine.audio.playing){
  639. this.$refs.audioLine.PlayAudio()
  640. }
  641. this.playSentIndex = index
  642. let MethodName = "/PaperServer/Client/UserSentRec/RecListInUserSent";
  643. let data = {
  644. sent_id: this.articleInfo.art_corpus_data.sentList[index].id,
  645. got_rec_data_flag: false
  646. }
  647. getLogin(MethodName, data)
  648. .then((res) => {
  649. if(res.status===1){
  650. this.historySentRecordList = res.data.all
  651. this.historyLoading = false
  652. }
  653. }).catch(()=>{
  654. this.historyLoading = false
  655. })
  656. },
  657. closeHistory(){
  658. this.historyFlag = false
  659. },
  660. // 保存录音
  661. addRecord(wav){
  662. if(this.playSentIndex===-1){
  663. return
  664. }
  665. let MethodName = "/PaperServer/Client/UserSentRec/AddUserSentRec";
  666. let data = {
  667. sent_id: this.articleInfo.art_corpus_data.sentList[this.playSentIndex].id,
  668. rec_sound_data: wav
  669. }
  670. getLogin(MethodName, data)
  671. .then((res) => {
  672. if(this.repeatAfter){
  673. if(this.autoNextSent&&this.resArr.timeList[this.playSentIndex+1]){
  674. // this.playSentIndex = this.playSentIndex+1
  675. this.ed = this.resArr.timeList[this.playSentIndex+1].e
  676. this.handleChangeTime(this.resArr.timeList[this.playSentIndex+1].s)
  677. }
  678. }
  679. })
  680. },
  681. // 对比
  682. compare(){
  683. let _this = this;
  684. _this.isCompare = true
  685. // let curTime = _this.resArr.timeList[_this.playSentIndex].s
  686. // for(let i=0; i<this.resArr.timeList.length;i++){
  687. // if(curTime>this.resArr.timeList[i].s&&curTime<=this.resArr.timeList[i].e){
  688. // this.playSentIndex = i
  689. // break
  690. // }
  691. // }
  692. let playSentIndex = JSON.parse(JSON.stringify(_this.playSentIndex))
  693. if(playSentIndex===-1){
  694. this.$message.warning('请先选择要对比的句子')
  695. return
  696. }
  697. if(!this.wavblob){
  698. this.$message.warning('请先录音')
  699. return
  700. }
  701. this.audio.pause()
  702. if (!this.audioc.paused) {
  703. this.audioc.pause();
  704. } else {
  705. _this.audioc.pause();
  706. _this.audioc.load();
  707. _this.handleChangeTime(_this.resArr.timeList[playSentIndex].s,_this.resArr.timeList[playSentIndex].e)
  708. _this.timeC = setInterval(() => {
  709. if(_this.curTime>=_this.resArr.timeList[playSentIndex].e - 250){
  710. _this.curTime = _this.resArr.timeList[playSentIndex].s
  711. clearInterval(_this.timeC)
  712. _this.playWavdata()
  713. }
  714. }, 500);
  715. }
  716. },
  717. playWavdata(){
  718. let _this = this;
  719. _this.audioc.src = _this.wavblob;
  720. _this.audioc.play();
  721. _this.audioc.addEventListener("ended", function () {
  722. _this.isCompare = false
  723. });
  724. },
  725. pauseAudio() {
  726. let audio = document.getElementsByTagName("audio");
  727. audio.forEach((item) => {
  728. item.pause();
  729. });
  730. },
  731. pauseVideo() {
  732. let video = document.getElementsByTagName("video");
  733. video.forEach((item) => {
  734. item.pause();
  735. });
  736. },
  737. //语音全屏
  738. fullScreen() {
  739. this.pauseAudio();
  740. this.pauseVideo();
  741. this.isFull = true;
  742. this.goFullscreen();
  743. },
  744. goFullscreen() {
  745. let id = "screen-" + this.mathNum;
  746. var element = document.getElementById(id);
  747. if (element.requestFullscreen) {
  748. element.requestFullscreen();
  749. } else if (element.msRequestFullscreen) {
  750. element.msRequestFullscreen();
  751. } else if (element.mozRequestFullScreen) {
  752. element.mozRequestFullScreen();
  753. } else if (element.webkitRequestFullscreen) {
  754. element.webkitRequestFullscreen();
  755. }
  756. },
  757. exitFullscreen() {
  758. this.isFull = false;
  759. if (document.exitFullscreen) {
  760. document.exitFullscreen();
  761. } else if (document.msExitFullscreen) {
  762. document.msExitFullscreen();
  763. } else if (document.mozCancelFullScreen) {
  764. document.mozCancelFullScreen();
  765. } else if (document.webkitExitFullscreen) {
  766. document.webkitExitFullscreen();
  767. }
  768. },
  769. changeIsFull() {
  770. this.isFull = false;
  771. },
  772. },
  773. //生命周期 - 创建完成(可以访问当前this实例)
  774. created() {},
  775. //生命周期 - 挂载完成(可以访问DOM元素)
  776. mounted() {
  777. if (this.articleInfo) {
  778. this.handleData();
  779. }
  780. },
  781. beforeCreate() {}, //生命周期 - 创建之前
  782. beforeMount() {}, //生命周期 - 挂载之前
  783. beforeUpdate() {}, //生命周期 - 更新之前
  784. updated() {}, //生命周期 - 更新之后
  785. beforeDestroy() {
  786. clearInterval(this.timeC)
  787. }, //生命周期 - 销毁之前
  788. destroyed() {}, //生命周期 - 销毁完成
  789. activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
  790. };
  791. </script>
  792. <style lang='scss' scoped>
  793. //@import url(); 引入公共css类
  794. .NNPE-ArticleView {
  795. width: 100%;
  796. .nnpe-article-author{
  797. margin: 24px 0;
  798. }
  799. h2{
  800. display: flex;
  801. flex-flow: wrap;
  802. &.sentActive {
  803. background: rgba(24, 144, 255, 0.1);
  804. }
  805. &.overActive {
  806. background: rgba(0, 0, 0, 0.06);
  807. }
  808. .wordActive {
  809. color: #175DFF;
  810. }
  811. }
  812. .table-box {
  813. padding-top: 20px;
  814. padding-bottom: 64px;
  815. // background: #f7f7f7;
  816. // border-top: 1px solid rgba(0, 0, 0, 0.1);
  817. :last-child {
  818. :last-child.wordsList-box {
  819. padding-bottom: 40px;
  820. }
  821. }
  822. .wordsList-box {
  823. flex: 1;
  824. padding: 20px 0;
  825. .nnpe-sentence-box {
  826. display: flex;
  827. flex-flow: wrap;
  828. }
  829. > img {
  830. max-width: 50%;
  831. display: block;
  832. padding: 16px 0;
  833. margin: 0 auto;
  834. }
  835. }
  836. }
  837. .NNPE-detail {
  838. clear: both;
  839. // overflow: hidden;
  840. display: flex;
  841. position: relative;
  842. .history-btn{
  843. position: absolute;
  844. left: -110px;
  845. top: 50%;
  846. margin-top: -16px;
  847. z-index: 1;
  848. padding: 5px 16px;
  849. border-radius: 2px;
  850. background: #F2F3F5;
  851. color:#4E5969;
  852. font-size: 14px;
  853. line-height: 22px;
  854. cursor: pointer;
  855. }
  856. .para-index{
  857. color:#D0D3D9;
  858. font-size: 18px;
  859. font-weight: 700;
  860. line-height: 26px;
  861. padding-top: 20px;
  862. width: 40px;
  863. flex-shrink: 0;
  864. &-active{
  865. color: #2F3742;
  866. }
  867. }
  868. .NNPE-words {
  869. float: left;
  870. padding: 0;
  871. &.noPadding{
  872. padding:0;
  873. }
  874. &.sentActive {
  875. background: rgba(24, 144, 255, 0.1);
  876. }
  877. &.overActive {
  878. background: rgba(0, 0, 0, 0.06);
  879. }
  880. &.textLeft {
  881. text-align: left;
  882. }
  883. &.textCenter {
  884. text-align: center;
  885. }
  886. > span {
  887. float: left;
  888. cursor: pointer;
  889. &.NNPE-chs {
  890. // font-size: 24px;
  891. font-family: 'Smartisan';
  892. line-height: 150%;
  893. color: #000000;
  894. padding: 0 3px;
  895. &.wordActive {
  896. color: #175DFF;
  897. }
  898. &.marginRight{
  899. padding-right: 0;
  900. }
  901. &.marginLeft{
  902. padding-left: 0;
  903. }
  904. &.marginSingleRight{
  905. padding-right: 3px;
  906. }
  907. }
  908. &.padding {
  909. padding: 0 3px;
  910. cursor: pointer;
  911. }
  912. }
  913. }
  914. }
  915. .operate-box{
  916. position: fixed;
  917. border-top: 1px solid #EBEBEB;
  918. height: 192px;
  919. width: 1000px;
  920. background: #F2F3F5;
  921. left: 50%;
  922. bottom: 0;
  923. margin-left: -500px;
  924. padding-bottom: 24px;
  925. z-index: 1;
  926. &-inner{
  927. padding-top: 40px;
  928. height: 144px;
  929. &-content{
  930. width: 680px;
  931. margin: 0 auto;
  932. display: flex;
  933. justify-content: space-between;
  934. }
  935. .operate-item{
  936. text-align: center;
  937. cursor: pointer;
  938. .luyin-btn{
  939. display: block;
  940. width: 64px;
  941. height: 64px;
  942. padding: 10px;
  943. border-radius: 60px;
  944. background: #F2555A;
  945. box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 6px 30px 5px rgba(0, 0, 0, 0.05);
  946. }
  947. .luyin-gif{
  948. display: block;
  949. width: 64px;
  950. height: 64px;
  951. border-radius: 60px;
  952. box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 6px 30px 5px rgba(0, 0, 0, 0.05);
  953. }
  954. .svg-icon{
  955. display: block;
  956. width: 44px;
  957. height: 44px;
  958. margin: 0 auto;
  959. padding: 6px;
  960. }
  961. span{
  962. color: #929CA8;
  963. font-size: 12px;
  964. font-weight: 400;
  965. line-height: 20px;
  966. }
  967. }
  968. }
  969. }
  970. .table-box-white{
  971. .wordActive {
  972. color: #3459D2 !important;
  973. }
  974. .sentActive {
  975. background: #D9E2FC !important;
  976. }
  977. }
  978. .table-box-darkGreen{
  979. .wordActive {
  980. color: #299772 !important;
  981. }
  982. .sentActive {
  983. background: #ECEFED !important;
  984. }
  985. }
  986. .table-box-darkBlue{
  987. .wordActive {
  988. color: #5373E7 !important;
  989. }
  990. .sentActive {
  991. background:#1C2129 !important;
  992. }
  993. }
  994. .table-box-armyGreen{
  995. .wordActive {
  996. color: #30A47D !important;
  997. }
  998. .sentActive {
  999. background: #2A2F2C !important;
  1000. }
  1001. }
  1002. }
  1003. .audio-box{
  1004. display: flex;
  1005. align-items: center;
  1006. justify-content: space-between;
  1007. }
  1008. .aduioLine-box{
  1009. width: 516px;
  1010. height: 48px;
  1011. background: #FFFFFF;
  1012. border: 1px solid #EBEBEB;
  1013. border-radius: 30px;
  1014. display: flex;
  1015. align-items: center;
  1016. padding: 8px 24px;
  1017. .wrapper{
  1018. width: 24px;
  1019. height: 24px;
  1020. flex-shrink: 0;
  1021. color: #175DFF;
  1022. margin-left: 8px;
  1023. }
  1024. .Audio{
  1025. width: 430px;
  1026. }
  1027. }
  1028. .clear-box{
  1029. right: calc((100% - 1000px)/2);
  1030. position: fixed;
  1031. top: 150px;
  1032. width: 270px;
  1033. overflow: auto;
  1034. width: 176px;
  1035. padding: 8px;
  1036. border-radius: 8px;
  1037. border: 1px solid #EBEBEB;
  1038. background: #FFF;
  1039. box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 4px 6px 1px rgba(0, 0, 0, 0.06), 0px 3px 6px 2px rgba(0, 0, 0, 0.05);
  1040. margin-top: 24px;
  1041. h5{
  1042. margin: 0;
  1043. color: rgba(0, 0, 0, 0.40);
  1044. font-size: 14px;
  1045. font-weight: 400;
  1046. line-height: 22px;
  1047. }
  1048. .item{
  1049. display: flex;
  1050. align-items: center;
  1051. justify-content: space-between;
  1052. margin-top: 4px;
  1053. border-radius: 4px;
  1054. background:#F2F3F5;
  1055. padding: 4px 8px;
  1056. color: #2F3742;
  1057. font-size: 14px;
  1058. font-weight: 400;
  1059. line-height: 22px;
  1060. cursor: pointer;
  1061. &.red-item{
  1062. color: #F53F3F;
  1063. }
  1064. }
  1065. }
  1066. </style>