Practicechs.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. <!-- -->
  2. <template>
  3. <div class="NNPE-ArticleView" v-if="curQue">
  4. <div class="aduioLine-box aduioLine-practice">
  5. <div
  6. class="aduioLine-content"
  7. v-if="
  8. curQue.mp3_list &&
  9. curQue.mp3_list.length > 0 &&
  10. curQue.mp3_list[0].url
  11. "
  12. >
  13. <AudioLine
  14. audioId="artPraAudio"
  15. :mp3="curQue.mp3_list[0].url"
  16. :getCurTime="getCurTime"
  17. ref="audioLine"
  18. :stopAudio="stopAudio"
  19. :width="555"
  20. @handleChangeStopAudio="handleChangeStopAudio"
  21. />
  22. </div>
  23. <div class="aduioLine-right">
  24. <span
  25. :class="['Repeat-16', isRepeat ? '' : 'disabled']"
  26. @click="changeRepeat"
  27. ></span>
  28. <span
  29. :class="['pinyin-16', isShowPY ? '' : 'disabled']"
  30. @click="changePinyin"
  31. ></span>
  32. </div>
  33. </div>
  34. <template v-if="resObj">
  35. <!-- -->
  36. <div class="NPC-sentences-list">
  37. <div
  38. :class="['NNPE-detail-box', sentIndex == index ? 'active' : '']"
  39. v-for="(item, index) in resObj.sentList"
  40. :key="'detail' + index"
  41. >
  42. <div
  43. :class="['NNPE-detail']"
  44. @click="
  45. handleChangeTime(
  46. curQue.wordTime &&
  47. curQue.wordTime[index] &&
  48. curQue.wordTime[index].bg,
  49. index
  50. )
  51. "
  52. >
  53. <div
  54. class="NNPE-words"
  55. v-for="(pItem, pIndex) in item"
  56. :key="'wordsList' + pIndex"
  57. :class="[pItem.wordIndex == 0 ? 'textLeft' : 'textCenter']"
  58. >
  59. <template v-if="!pItem.width">
  60. <template v-if="pItem.isShow">
  61. <template
  62. v-if="
  63. item[pIndex + 1].chs &&
  64. chsFhList.indexOf(item[pIndex + 1].chs) > -1
  65. "
  66. >
  67. <span class="NNPE-words-box">
  68. <template v-if="curQue.pyPosition == 'top'">
  69. <span
  70. v-if="isShowPY"
  71. class="NNPE-pinyin"
  72. :class="[
  73. pItem.className ? pItem.className : '',
  74. sentIndex == index ? 'wordBlank' : '',
  75. noFont.indexOf(pItem.pinyin) > -1 ? 'noFont' : '',
  76. ]"
  77. >{{ pItem.pinyin }}</span
  78. >
  79. </template>
  80. <span
  81. class="NNPE-chs"
  82. :class="[
  83. pItem.padding && isShowPY ? 'padding' : '',
  84. sentIndex == index ? 'wordBlank' : '',
  85. ]"
  86. >
  87. <template>
  88. <span
  89. v-for="(wItem, wIndex) in pItem.leg"
  90. :key="'ci' + wIndex + pIndex + index"
  91. :class="[
  92. pItem.timeList[wIndex] &&
  93. curTime >= pItem.timeList[wIndex].wordBg &&
  94. curTime <= curQue.wordTime[index].ed
  95. ? 'active'
  96. : '',
  97. sentIndex == index ? 'wordBlank' : '',
  98. ]"
  99. >{{ pItem.chs[wIndex] }}</span
  100. >
  101. </template>
  102. </span>
  103. <template v-if="curQue.pyPosition == 'bottom'">
  104. <span
  105. v-if="isShowPY"
  106. class="NNPE-pinyin"
  107. :class="[
  108. pItem.className ? pItem.className : '',
  109. sentIndex == index ? 'wordBlank' : '',
  110. noFont.indexOf(pItem.pinyin) > -1 ? 'noFont' : '',
  111. ]"
  112. >{{ pItem.pinyin }}</span
  113. >
  114. </template>
  115. </span>
  116. <span class="NNPE-words-box">
  117. <template v-if="curQue.pyPosition == 'top'">
  118. <span
  119. v-if="isShowPY"
  120. :class="[
  121. 'NNPE-pinyin',
  122. sentIndex == index ? 'wordBlank' : '',
  123. noFont.indexOf(item[pIndex + 1].pinyin) > -1
  124. ? 'noFont'
  125. : '',
  126. ]"
  127. style="text-align: left"
  128. >{{ item[pIndex + 1].pinyin }}</span
  129. >
  130. </template>
  131. <span class="NNPE-chs" style="text-align: left">
  132. <span
  133. :class="[
  134. pItem.timeList[pItem.leg - 1] &&
  135. curQue.wordTime &&
  136. curTime >= pItem.timeList[pItem.leg - 1].wordBg &&
  137. curTime <= curQue.wordTime[index].ed
  138. ? 'active'
  139. : '',
  140. sentIndex == index ? 'wordBlank' : '',
  141. ]"
  142. >
  143. {{ item[pIndex + 1].chs }}</span
  144. >
  145. </span>
  146. <template v-if="curQue.pyPosition == 'bottom'">
  147. <span
  148. v-if="isShowPY"
  149. :class="[
  150. 'NNPE-pinyin',
  151. sentIndex == index ? 'wordBlank' : '',
  152. noFont.indexOf(item[pIndex + 1].pinyin) > -1
  153. ? 'noFont'
  154. : '',
  155. ]"
  156. style="text-align: left"
  157. >{{ item[pIndex + 1].pinyin }}</span
  158. >
  159. </template>
  160. </span>
  161. </template>
  162. <template v-else>
  163. <template v-if="curQue.pyPosition == 'top'">
  164. <span
  165. v-if="isShowPY"
  166. class="NNPE-pinyin"
  167. :class="[
  168. pItem.padding ? 'padding' : '',
  169. pItem.className ? pItem.className : '',
  170. sentIndex == index ? 'wordBlank' : '',
  171. noFont.indexOf(pItem.pinyin) > -1 ? 'noFont' : '',
  172. ]"
  173. >{{ pItem.pinyin }}</span
  174. >
  175. </template>
  176. <span
  177. class="NNPE-chs"
  178. :class="[
  179. pItem.padding && isShowPY ? 'padding' : '',
  180. sentIndex == index ? 'wordBlank' : '',
  181. ]"
  182. >
  183. <template>
  184. <span
  185. v-for="(wItem, wIndex) in pItem.leg"
  186. :key="'ci' + wIndex + pIndex + index"
  187. :class="[
  188. pItem.timeList[wIndex] &&
  189. curQue.wordTime &&
  190. curQue.wordTime[index] &&
  191. curTime >= pItem.timeList[wIndex].wordBg &&
  192. curTime <= curQue.wordTime[index].ed
  193. ? 'active'
  194. : '',
  195. sentIndex == index ? 'wordBlank' : '',
  196. ]"
  197. >{{ pItem.chs[wIndex] }}</span
  198. >
  199. </template>
  200. </span>
  201. <template v-if="curQue.pyPosition == 'bottom'">
  202. <span
  203. v-if="isShowPY"
  204. class="NNPE-pinyin"
  205. :class="[
  206. pItem.padding ? 'padding' : '',
  207. pItem.className ? pItem.className : '',
  208. sentIndex == index ? 'wordBlank' : '',
  209. noFont.indexOf(pItem.pinyin) > -1 ? 'noFont' : '',
  210. ]"
  211. >{{ pItem.pinyin }}</span
  212. >
  213. </template>
  214. </template>
  215. </template>
  216. </template>
  217. <template v-else>
  218. <span
  219. :style="{
  220. height: pItem.height + 'px',
  221. width: pItem.width + 'px',
  222. }"
  223. ></span>
  224. </template>
  225. </div>
  226. </div>
  227. <div
  228. v-show="
  229. (curQue.wordTime &&
  230. curQue.wordTime[index] &&
  231. curTime >= curQue.wordTime[index].bg &&
  232. curTime <= curQue.wordTime[index].ed) ||
  233. sentIndex == index
  234. "
  235. class="Soundrecord-content"
  236. >
  237. <Soundrecord
  238. @handleWav="handleWav"
  239. type="promax"
  240. class="luyin-box"
  241. @handleParentPlay="handleParentPlay"
  242. />
  243. <div>
  244. <AudioLine
  245. :mp3="curQue.mp3_list[0].url"
  246. :getCurTime="getCurTime"
  247. :ref="'audioLine' + index"
  248. :audioId="'artPraAudioId' + index"
  249. :stopAudio="stopAudio"
  250. :width="555"
  251. :hideSlider="true"
  252. :bg="curQue.wordTime[index].bg"
  253. :ed="curQue.wordTime[index].ed"
  254. @handleChangeStopAudio="handleChangeStopAudio"
  255. />
  256. </div>
  257. </div>
  258. </div>
  259. </div>
  260. </template>
  261. </div>
  262. </template>
  263. <script>
  264. import { timeStrToSen } from "@/utils/index";
  265. import AudioLine from "../AudioLine.vue";
  266. import Soundrecord from "../../preview/Soundrecord.vue"; // 录音模板
  267. export default {
  268. name: "ArticleView",
  269. props: ["curQue", "noFont"],
  270. components: {
  271. AudioLine,
  272. Soundrecord,
  273. },
  274. data() {
  275. return {
  276. resObj: null,
  277. curTime: 0, //单位s
  278. chsFhList: [",", "。", "“", ":", "》", "《", "?", "!", ";"],
  279. enFhList: [",", ".", ";", "?", "!", ":", ">", "<"],
  280. stopAudio: false,
  281. sentIndex: 0,
  282. isShowPY: true,
  283. isRepeat: false,
  284. currSent: null, //当前句子的时间
  285. };
  286. },
  287. computed: {
  288. isPlaying: function () {
  289. let playing = false;
  290. if (this.$refs.audioLine) {
  291. playing = this.$refs.audioLine.audio.isPlaying;
  292. }
  293. return playing;
  294. },
  295. },
  296. watch: {},
  297. //方法集合
  298. methods: {
  299. pauseOther() {},
  300. getCurTime(curTime) {
  301. let _this = this;
  302. if (_this.isRepeat) {
  303. let time = curTime * 1000;
  304. console.log(time, _this.currSent.ed);
  305. if (time > _this.currSent.ed || time < _this.currSent.bg) {
  306. _this.curTime = _this.currSent.bg;
  307. this.$refs.audioLine.onTimeupdateTime(_this.currSent.bg / 1000);
  308. } else {
  309. _this.curTime = curTime * 1000;
  310. }
  311. } else {
  312. _this.curTime = curTime * 1000;
  313. _this.getSentIndex(_this.curTime);
  314. }
  315. },
  316. getSentIndex(curTime) {
  317. for (let i = 0; i < this.curQue.wordTime.length; i++) {
  318. let bg = this.curQue.wordTime[i].bg;
  319. let ed = this.curQue.wordTime[i].ed;
  320. if (curTime >= bg && curTime <= ed) {
  321. this.sentIndex = i;
  322. break;
  323. }
  324. }
  325. },
  326. handleData() {
  327. let resArr = [],
  328. timeArr = [];
  329. let curQue = JSON.parse(JSON.stringify(this.curQue));
  330. let wordTimeList = curQue.wordTime;
  331. curQue.detail.forEach((dItem, dIndex) => {
  332. dItem.wordsList.forEach((sItem, sIndex) => {
  333. let sentArr = [];
  334. sItem.forEach((wItem, wIndex) => {
  335. let startIndex =
  336. wIndex == 0
  337. ? 0
  338. : sentArr[wIndex - 1].startIndex +
  339. sentArr[wIndex - 1].chs.length;
  340. let endIndex =
  341. wIndex == 0
  342. ? wItem.chs.length
  343. : sentArr[wIndex - 1].endIndex + wItem.chs.length;
  344. // this.judgePad(sItem, wItem, wIndex);
  345. this.mergeWordSymbol(sItem, wItem, wIndex);
  346. let obj = {
  347. paraIndex: dIndex, //段落索引
  348. sentIndex: sIndex, //在段落中句子索引
  349. wordIndex: wIndex, //单词的索引
  350. pinyin: wItem.pinyin,
  351. chs: wItem.chs,
  352. padding: true,
  353. className: wItem.className,
  354. isShow: wItem.isShow,
  355. startIndex: startIndex,
  356. endIndex: endIndex,
  357. leg: wItem.chs.length,
  358. timeList: [],
  359. };
  360. sentArr.push(obj);
  361. });
  362. resArr.push(sentArr);
  363. });
  364. timeArr.push(dItem.timeList);
  365. });
  366. if (wordTimeList && wordTimeList.length > 0) {
  367. this.mergeWordTime(resArr, wordTimeList);
  368. }
  369. let timeList = [];
  370. timeArr.forEach((item) => {
  371. item.forEach((aItem) => {
  372. if (timeList.indexOf(aItem) < 0) {
  373. timeList.push(aItem);
  374. }
  375. });
  376. });
  377. this.resObj = { sentList: resArr, timeList: timeList };
  378. console.log(this.resObj);
  379. },
  380. mergeWordTime(resArr, wordTimeList) {
  381. resArr.forEach((item, index) => {
  382. let wordsResultList = wordTimeList[index].wordsResultList;
  383. item.forEach((wItem) => {
  384. let startIndex = wItem.startIndex;
  385. let endIndex = wItem.endIndex;
  386. wItem.timeList = wordsResultList.slice(startIndex, endIndex);
  387. });
  388. });
  389. },
  390. //词和标点合一起
  391. mergeWordSymbol(sItem, wItem, curIndex) {
  392. let leg = sItem.length;
  393. if (curIndex < leg - 1) {
  394. let nextIndex = curIndex + 1;
  395. if (this.chsFhList.indexOf(wItem.chs) > -1) {
  396. wItem.isShow = false;
  397. } else {
  398. wItem.isShow = true;
  399. }
  400. }
  401. },
  402. //判断是否有padding
  403. judgePad(sItem, wItem, curIndex) {
  404. let leg = sItem.length;
  405. if (curIndex < leg - 1) {
  406. let nextIndex = curIndex + 1;
  407. let chs = sItem[nextIndex].chs;
  408. if (
  409. this.chsFhList.indexOf(chs) > -1 ||
  410. this.chsFhList.indexOf(wItem.chs) > -1
  411. ) {
  412. wItem.padding = false;
  413. } else {
  414. wItem.padding = true;
  415. }
  416. if (this.enFhList.indexOf(wItem.pinyin) > -1) {
  417. wItem.className = "textLeft";
  418. }
  419. }
  420. },
  421. //转化时间
  422. handleTimeList(list) {
  423. let listRes = [];
  424. list.forEach((item) => {
  425. let res = timeStrToSen(item);
  426. listRes.push(res);
  427. });
  428. return listRes;
  429. },
  430. //计算总时间
  431. countWordTime(sentArr) {
  432. let total = 0;
  433. sentArr.forEach((item) => {
  434. total += item.endTime;
  435. });
  436. return total;
  437. },
  438. //点击播放某个句子
  439. handleChangeTime(time, index) {
  440. let _this = this;
  441. if (this.isRepeat) {
  442. _this.currSent = _this.curQue.wordTime[index];
  443. }
  444. _this.sentIndex = index;
  445. if (time) {
  446. _this.curTime = time;
  447. _this.$refs.audioLine.onTimeupdateTime(time / 1000, true);
  448. }
  449. },
  450. handleWav(data) {},
  451. // 录音时暂停音频播放
  452. handleParentPlay() {
  453. this.stopAudio = true;
  454. },
  455. // 音频播放时改变布尔值
  456. handleChangeStopAudio() {
  457. this.stopAudio = false;
  458. },
  459. //拼音的显示隐藏
  460. changePinyin() {
  461. this.isShowPY = !this.isShowPY;
  462. },
  463. // 单句是否重复播放
  464. changeRepeat() {
  465. let _this = this;
  466. _this.isRepeat = !_this.isRepeat;
  467. this.currSent = _this.curQue.wordTime[_this.sentIndex];
  468. },
  469. },
  470. //生命周期 - 创建完成(可以访问当前this实例)
  471. created() {},
  472. //生命周期 - 挂载完成(可以访问DOM元素)
  473. mounted() {
  474. console.log(this.curQue);
  475. if (this.curQue) {
  476. this.handleData();
  477. }
  478. },
  479. beforeCreate() {}, //生命周期 - 创建之前
  480. beforeMount() {}, //生命周期 - 挂载之前
  481. beforeUpdate() {}, //生命周期 - 更新之前
  482. updated() {}, //生命周期 - 更新之后
  483. beforeDestroy() {}, //生命周期 - 销毁之前
  484. destroyed() {}, //生命周期 - 销毁完成
  485. activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
  486. };
  487. </script>
  488. <style lang='scss' scoped>
  489. //@import url(); 引入公共css类
  490. .NNPE-ArticleView {
  491. width: 100%;
  492. .NPC-sentences-list {
  493. padding: 16px 0;
  494. }
  495. .aduioLine-content {
  496. flex: 1;
  497. }
  498. .NNPE-detail-box {
  499. width: 100%;
  500. box-sizing: border-box;
  501. margin-bottom: 8px;
  502. box-sizing: border-box;
  503. padding: 8px 24px 8px;
  504. &.active {
  505. background: rgba(222, 68, 68, 0.15);
  506. }
  507. }
  508. .aduioLine-practice {
  509. display: flex;
  510. justify-content: flex-start;
  511. align-items: center;
  512. .aduioLine-right {
  513. display: flex;
  514. justify-content: space-between;
  515. align-items: center;
  516. width: 72px;
  517. height: 40px;
  518. box-sizing: border-box;
  519. padding: 0 12px;
  520. border-left: 1px solid rgba(0, 0, 0, 0.1);
  521. > span {
  522. width: 16px;
  523. height: 16px;
  524. cursor: pointer;
  525. }
  526. }
  527. }
  528. .NNPE-detail {
  529. clear: both;
  530. overflow: hidden;
  531. .NNPE-words {
  532. float: left;
  533. &-box {
  534. float: left;
  535. > span {
  536. display: block;
  537. &.NNPE-pinyin {
  538. font-family: "GB-PINYINOK-B";
  539. font-weight: normal;
  540. font-size: 14px;
  541. line-height: 20px;
  542. color: rgba(0, 0, 0, 0.45);
  543. height: 20px;
  544. &.noFont {
  545. font-family: initial;
  546. }
  547. &.textLeft {
  548. text-align: left;
  549. }
  550. &.wordBlank {
  551. color: rgba(0, 0, 0, 0.85);
  552. }
  553. }
  554. &.NNPE-chs {
  555. font-family: "FZJCGFKTK";
  556. font-size: 20px;
  557. line-height: 28px;
  558. color: rgba(0, 0, 0, 0.45);
  559. .active {
  560. color: #de4444;
  561. }
  562. &.wordBlank {
  563. color: rgba(0, 0, 0, 0.85);
  564. }
  565. }
  566. // &.padding {
  567. // padding-right: 6px;
  568. // }
  569. }
  570. }
  571. &.textLeft {
  572. text-align: left;
  573. }
  574. &.textCenter {
  575. text-align: center;
  576. }
  577. > span {
  578. display: block;
  579. &.NNPE-pinyin {
  580. font-family: "GB-PINYINOK-B";
  581. font-weight: normal;
  582. font-size: 14px;
  583. line-height: 20px;
  584. color: rgba(0, 0, 0, 0.45);
  585. height: 20px;
  586. &.noFont {
  587. font-family: initial;
  588. }
  589. &.textLeft {
  590. text-align: left;
  591. }
  592. &.wordBlank {
  593. color: rgba(0, 0, 0, 0.85);
  594. }
  595. }
  596. &.NNPE-chs {
  597. font-family: "FZJCGFKTK";
  598. font-size: 20px;
  599. line-height: 28px;
  600. color: rgba(0, 0, 0, 0.45);
  601. .active {
  602. color: #de4444;
  603. }
  604. &.wordBlank {
  605. color: rgba(0, 0, 0, 0.85);
  606. }
  607. }
  608. &.padding {
  609. padding: 0 3px;
  610. }
  611. }
  612. }
  613. }
  614. .Soundrecord-content {
  615. margin-top: 8px;
  616. display: flex;
  617. justify-content: flex-start;
  618. align-items: center;
  619. }
  620. .luyin-box {
  621. width: 280px;
  622. box-sizing: border-box;
  623. padding: 8px 12px;
  624. background: #ffffff;
  625. border: 1px solid rgba(0, 0, 0, 0.1);
  626. box-sizing: border-box;
  627. border-radius: 8px;
  628. height: auto;
  629. margin-right: 16px;
  630. }
  631. }
  632. </style>