VoiceMatrix.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369
  1. <template>
  2. <div v-if="curQue" class="voice-matrix">
  3. <div class="voice-matrix-audio">
  4. <div v-if="curQue.voiceMatrix.isAudioNumber" class="audio-number">
  5. <span
  6. :class="[
  7. themeColor.length === 0 || themeColor === 'red'
  8. ? 'serial-number'
  9. : `serial-number-${themeColor}`,
  10. ]"
  11. >
  12. {{ curQue.voiceMatrix.audioSerialNumber }}
  13. </span>
  14. </div>
  15. <div v-show="hasSelectedCell" class="audio-simple">
  16. <img
  17. class="audio-simple-image"
  18. :src="playing ? voicePlaySrc : voicePauseSrc"
  19. @click="playAudio"
  20. />
  21. <span
  22. :class="[
  23. 'Repeat-16',
  24. 'audio-simple-repeat',
  25. isRepeat ? '' : 'disabled',
  26. ]"
  27. @click="isRepeat = !isRepeat"
  28. />
  29. </div>
  30. <audio-line
  31. v-show="!hasSelectedCell"
  32. ref="audioLine"
  33. audio-id="voiceMatrixAudio"
  34. :mp3="mp3Url"
  35. :get-cur-time="getCurTime"
  36. :stop-audio="stopAudio"
  37. :mp3-source="mp3Source"
  38. :base-size-phone="baseSizePhone"
  39. @handleChangeStopAudio="handleChangeStopAudio"
  40. @playChange="playChange"
  41. />
  42. </div>
  43. <!-- 语音矩阵 -->
  44. <div
  45. class="voice-matrix-container"
  46. :style="{ fontSize: baseSizePhone + 'px' }"
  47. >
  48. <div
  49. v-if="curQue.voiceMatrix.matrix.length > 0"
  50. class="matrix"
  51. :style="{
  52. 'grid-template': `36px repeat(${curQue.voiceMatrix.matrix.length}, auto) minmax(36px, 1fr) / 36px repeat(${curQue.voiceMatrix.matrix[0].length}, auto) minmax(36px, 1fr)`,
  53. }"
  54. @mouseleave="clearSelectCell"
  55. >
  56. <!-- 顶部单元格 -->
  57. <div class="matrix-top" @mouseenter="clearSelectCell" />
  58. <template v-for="(row, i) in curQue.voiceMatrix.matrix[0]">
  59. <div
  60. :key="`top-${i}`"
  61. :class="[
  62. 'matrix-top',
  63. curQue.voiceMatrix.columnSelection &&
  64. (selectColumn === i ||
  65. (selectedLine.type === 'column' && selectedLine.index === i))
  66. ? 'read'
  67. : '',
  68. ]"
  69. @mouseenter="checkboxMouseenter(selectColumn === i, 'column')"
  70. >
  71. <span
  72. v-if="
  73. row.type !== 'connection' && curQue.voiceMatrix.columnSelection
  74. "
  75. :class="[
  76. `matrix-checkbox-row-${themeColor}`,
  77. selectedLine.type === 'column' && selectedLine.index === i
  78. ? 'active'
  79. : '',
  80. ]"
  81. @click="selectRowOrColumn(i, 'column')"
  82. />
  83. </div>
  84. </template>
  85. <div class="matrix-top" @mouseenter="clearSelectCell" />
  86. <!-- 主矩阵 -->
  87. <template v-for="(row, i) in curQue.voiceMatrix.matrix">
  88. <div
  89. :key="`start-${i}`"
  90. :class="[
  91. 'column-wrapper',
  92. curQue.voiceMatrix.rowSelection &&
  93. (selectRow === i ||
  94. (selectedLine.type === 'row' && selectedLine.index === i))
  95. ? 'read'
  96. : '',
  97. ]"
  98. @mouseenter="checkboxMouseenter(selectRow === i, 'row')"
  99. >
  100. <span
  101. v-if="curQue.voiceMatrix.rowSelection"
  102. :class="[
  103. `matrix-checkbox-column-${themeColor}`,
  104. selectedLine.type === 'row' && selectedLine.index === i
  105. ? 'active'
  106. : '',
  107. ]"
  108. @click="selectRowOrColumn(i, 'row')"
  109. />
  110. </div>
  111. <!-- 单元格 -->
  112. <template v-for="(column, j) in row">
  113. <div
  114. :key="`wrapper-${i}-${j}`"
  115. :class="[
  116. 'column-wrapper',
  117. (curQue.voiceMatrix.rowSelection && selectRow === i) ||
  118. (curQue.voiceMatrix.columnSelection && selectColumn === j) ||
  119. (curQue.voiceMatrix.columnSelection &&
  120. selectedLine.type === 'column' &&
  121. selectedLine.index === j) ||
  122. (curQue.voiceMatrix.rowSelection &&
  123. selectedLine.type === 'row' &&
  124. selectedLine.index === i)
  125. ? 'read'
  126. : '',
  127. (i === 0 && curQue.voiceMatrix.firstLineHighlight) ||
  128. (j === row.length - 1 && curQue.voiceMatrix.lastColumnHighlight)
  129. ? `highlight-${themeColor}`
  130. : '',
  131. ]"
  132. @mouseenter="matrixCellMouseenter(i, j, column.type)"
  133. >
  134. <!-- 文本 -->
  135. <div
  136. v-if="column.type === 'text'"
  137. :key="`column-${i}-${j}`"
  138. :class="[
  139. column.text.length === 0 ? 'space' : `column-${themeColor}`,
  140. (selectCell.row === i && selectCell.column === j) ||
  141. (selectedLine.type === 'column' &&
  142. selectedLine.index === j) ||
  143. (selectedLine.type === 'row' && selectedLine.index === i)
  144. ? 'selected'
  145. : '',
  146. playing &&
  147. column.lrc_data.begin_time / 1000 <= curTime &&
  148. (curTime < column.lrc_data.end_time / 1000 ||
  149. column.lrc_data.end_time === -1)
  150. ? 'playing'
  151. : '',
  152. column.isTitle ? 'title' : '',
  153. ]"
  154. @click="matrixCellClick(i, j)"
  155. >
  156. <span>{{ column.text }}</span>
  157. </div>
  158. <!-- 连接线 -->
  159. <div
  160. v-else-if="column.type === 'connection'"
  161. :key="`column-${i}-${j}`"
  162. :class="[
  163. 'connection',
  164. i === 0 && curQue.voiceMatrix.firstLineHighlight
  165. ? `highlight-bc-${themeColor}`
  166. : '',
  167. ]"
  168. />
  169. <!-- 分词 -->
  170. <div
  171. v-else-if="column.type === 'SentenceSegwordChs'"
  172. :key="`column-${i}-${j}`"
  173. :class="[
  174. `sentence-${themeColor}`,
  175. (selectCell.row === i && selectCell.column === j) ||
  176. (selectedLine.type === 'column' &&
  177. selectedLine.index === j) ||
  178. (selectedLine.type === 'row' && selectedLine.index === i)
  179. ? 'selected'
  180. : '',
  181. playing &&
  182. column.lrc_data.begin_time / 1000 <= curTime &&
  183. (curTime < column.lrc_data.end_time / 1000 ||
  184. column.lrc_data.end_time === -1)
  185. ? 'playing'
  186. : '',
  187. column.isTitle ? 'title' : '',
  188. ]"
  189. :style="{
  190. 'grid-template-columns': `repeat(${column.sentence_data.wordsList.length}, auto)`,
  191. }"
  192. @click="matrixCellClick(i, j)"
  193. >
  194. <template
  195. v-for="({ pinyin, chs }, w) in column.sentence_data.wordsList"
  196. >
  197. <span
  198. :key="`${
  199. column.sentence_data.pyPosition === 'top'
  200. ? 'pinyin'
  201. : 'chs'
  202. }-${w}`"
  203. :class="
  204. column.sentence_data.pyPosition === 'top'
  205. ? 'pinyin'
  206. : 'chs'
  207. "
  208. >
  209. {{
  210. column.sentence_data.pyPosition === "top" ? pinyin : chs
  211. }}
  212. </span>
  213. </template>
  214. <template
  215. v-for="({ pinyin, chs }, w) in column.sentence_data.wordsList"
  216. >
  217. <span
  218. :key="`${
  219. column.sentence_data.pyPosition === 'top'
  220. ? 'chs'
  221. : 'pinyin'
  222. }-${w}`"
  223. :class="
  224. column.sentence_data.pyPosition === 'top'
  225. ? 'chs'
  226. : 'pinyin'
  227. "
  228. >
  229. {{
  230. column.sentence_data.pyPosition === "top" ? chs : pinyin
  231. }}
  232. </span>
  233. </template>
  234. </div>
  235. <!-- 拼音 + 英文 -->
  236. <div
  237. v-else-if="column.type === 'PinyinEnglish'"
  238. :key="`column-${i}-${j}`"
  239. :class="[
  240. `pinyinEnglish-${themeColor}`,
  241. (selectCell.row === i && selectCell.column === j) ||
  242. (selectedLine.type === 'column' &&
  243. selectedLine.index === j) ||
  244. (selectedLine.type === 'row' && selectedLine.index === i)
  245. ? 'selected'
  246. : '',
  247. playing &&
  248. column.lrc_data.begin_time / 1000 <= curTime &&
  249. (curTime < column.lrc_data.end_time / 1000 ||
  250. column.lrc_data.end_time === -1)
  251. ? 'playing'
  252. : '',
  253. column.isTitle ? 'title' : '',
  254. ]"
  255. @click="matrixCellClick(i, j)"
  256. >
  257. <div class="inside-wrapper">
  258. <div class="pinyin">
  259. {{ column.pinyin_english_data.pinyin }}
  260. </div>
  261. <div class="english">
  262. {{ column.pinyin_english_data.english }}
  263. </div>
  264. </div>
  265. </div>
  266. <!-- 文本中有括号 -->
  267. <div
  268. v-else-if="column.type === 'textBrackets'"
  269. :key="`column-${i}-${j}`"
  270. :class="[
  271. `textBrackets-${themeColor}`,
  272. (selectCell.row === i && selectCell.column === j) ||
  273. (selectedLine.type === 'column' &&
  274. selectedLine.index === j) ||
  275. (selectedLine.type === 'row' && selectedLine.index === i)
  276. ? 'selected'
  277. : '',
  278. playing &&
  279. column.lrc_data.begin_time / 1000 <= curTime &&
  280. (curTime < column.lrc_data.end_time / 1000 ||
  281. column.lrc_data.end_time === -1)
  282. ? 'playing'
  283. : '',
  284. column.isTitle ? 'title' : '',
  285. ]"
  286. @click="matrixCellClick(i, j)"
  287. >
  288. <span>
  289. <span
  290. :class="[
  291. getBracketsOuterTypeClass(
  292. column.text_brackets.brackets_outer_type
  293. ),
  294. ]"
  295. >
  296. {{ column.text_brackets.brackets_outer }}
  297. </span>
  298. <span class="brackets">&nbsp;[&nbsp;</span>
  299. <span
  300. :class="[
  301. column.text_brackets.brackets_inner_type === 'pinyin'
  302. ? 'pinyin'
  303. : 'english',
  304. ]"
  305. >
  306. {{ column.text_brackets.brackets_inner }}
  307. </span>
  308. <span class="brackets">&nbsp;]</span>
  309. </span>
  310. </div>
  311. </div>
  312. </template>
  313. <div
  314. :key="`end-${i}`"
  315. :class="[
  316. curQue.voiceMatrix.rowSelection &&
  317. (selectRow === i ||
  318. (selectedLine.type === 'row' && selectedLine.index === i))
  319. ? 'read'
  320. : '',
  321. ]"
  322. @mouseenter="clearSelectCell"
  323. />
  324. </template>
  325. <!-- 底部格子 -->
  326. <div class="matrix-bottom" @mouseenter="clearSelectCell" />
  327. <template v-for="(row, i) in curQue.voiceMatrix.matrix[0]">
  328. <div
  329. :key="`bottom-${i}`"
  330. :class="[
  331. 'matrix-bottom',
  332. curQue.voiceMatrix.columnSelection &&
  333. (selectColumn === i ||
  334. (selectedLine.type === 'column' && selectedLine.index === i))
  335. ? 'read'
  336. : '',
  337. ]"
  338. @mouseenter="clearSelectCell"
  339. />
  340. </template>
  341. <div class="matrix-bottom" @mouseenter="clearSelectCell" />
  342. </div>
  343. </div>
  344. <!-- 录音 -->
  345. <div class="voice-luyin">
  346. <Soundrecord
  347. v-if="refresh"
  348. ref="luyin"
  349. type="promax"
  350. class="luyin-box"
  351. :file-name="fileName"
  352. :select-data="selectData"
  353. :answer-record-list="curQue.Bookanswer.recordList"
  354. :task-model="TaskModel"
  355. :base-size-phone="baseSizePhone"
  356. @getWavblob="getWavblob"
  357. @getSelectData="getSelectData"
  358. @handleParentPlay="pauseOtherAudio"
  359. @sentPause="sentPause"
  360. @handleWav="handleWav"
  361. />
  362. <AudioCompare
  363. :style="{ flex: 1 }"
  364. :theme-color="themeColor"
  365. :wavblob="wavblob"
  366. :url="mp3Url"
  367. :is-record="isRecord"
  368. :sent-pause="sentPause"
  369. :matrix-select-lrc="matrixSelectLrc"
  370. :get-cur-time="getCurTime"
  371. :cur-time="curTime"
  372. :handle-change-stop-audio="handleChangeStopAudio"
  373. @playing="playChange"
  374. />
  375. <span ref="fullscreen" class="fullscreen" @click="fullScreen">
  376. <span>黑板模式</span>
  377. <el-image :src="fullscreenSrc" />
  378. </span>
  379. </div>
  380. <div :id="`screen-${cid}`" class="voice-full-screen">
  381. <VoiceFullscreen
  382. v-if="isFull"
  383. :theme-color="themeColor"
  384. :cur-que="curQue"
  385. :mp3="mp3Url"
  386. :matrix-select-lrc="matrixSelectLrc"
  387. :record-list="curQue.Bookanswer.recordList"
  388. @exitFullscreen="exitFullscreen"
  389. @changeIsFull="changeIsFull"
  390. @handleWav="handleWav"
  391. />
  392. </div>
  393. </div>
  394. </template>
  395. <script>
  396. import Bus from "./components/Bus.js";
  397. import AudioLine from "./AudioLine.vue";
  398. import Soundrecord from "./Soundrecord.vue";
  399. import AudioCompare from "./AudioCompareMatrix.vue";
  400. import VoiceFullscreen from "./VoiceMatrixFullscreen.vue";
  401. export default {
  402. components: {
  403. AudioLine,
  404. Soundrecord,
  405. AudioCompare,
  406. VoiceFullscreen,
  407. },
  408. props: ["curQue", "themeColor", "TaskModel", "baseSizePhone"],
  409. data() {
  410. return {
  411. // 组件id
  412. cid: Math.random().toString(36).substr(2, 10),
  413. isFull: false,
  414. curTime: 0,
  415. playing: false,
  416. stopAudio: true,
  417. unWatch: null,
  418. lrcArray: [],
  419. fileName: "",
  420. // 底色行、列
  421. selectRow: -1,
  422. selectColumn: -1,
  423. // 行、列选中
  424. selectedLine: {
  425. type: "",
  426. index: 0,
  427. },
  428. // 点击选中
  429. selectCell: {
  430. row: -1,
  431. column: -1,
  432. },
  433. isRepeat: false,
  434. // 跟读所需属性
  435. wavblob: null,
  436. isRecord: false,
  437. matrixSelectLrc: null,
  438. refresh: true,
  439. };
  440. },
  441. computed: {
  442. mp3Url() {
  443. let mp3_list = this.curQue.mp3_list[0];
  444. if (mp3_list === undefined) return "";
  445. return mp3_list.url.match(/^\[FID##/)
  446. ? mp3_list.temporary_url
  447. : mp3_list.url;
  448. },
  449. mp3Source() {
  450. let mp3_list = this.curQue.mp3_list[0];
  451. if (mp3_list === undefined) return "";
  452. return "source" in mp3_list ? mp3_list.source : "";
  453. },
  454. mp3Duration() {
  455. let mp3_list = this.curQue.mp3_list[0];
  456. if (mp3_list === undefined) return 0;
  457. return mp3_list.media_duration * 1000;
  458. },
  459. hasSelectedCell() {
  460. let { type, index } = this.selectedLine;
  461. let { row, column } = this.selectCell;
  462. return (type.length > 0 && index >= 0) || (row >= 0 && column >= 0);
  463. },
  464. selectData() {
  465. let { type, index } = this.selectedLine;
  466. let { row, column } = this.selectCell;
  467. return {
  468. type: type.length > 0 && index >= 0 ? type : "cell",
  469. index,
  470. row,
  471. column,
  472. };
  473. },
  474. voicePauseSrc() {
  475. const themeColor = this.themeColor;
  476. if (themeColor.length === 0 || themeColor === "red") {
  477. return require("../../../assets/NPC/play-red.png");
  478. }
  479. return require(`../../../assets/NPC/play-${themeColor}.png`);
  480. },
  481. voicePlaySrc() {
  482. const themeColor = this.themeColor;
  483. if (themeColor.length === 0 || themeColor === "red") {
  484. return require("../../../assets/NPC/icon-voice-play-red.png");
  485. }
  486. return require(`../../../assets/NPC/icon-voice-play-${themeColor}.png`);
  487. },
  488. fullscreenSrc() {
  489. return require("../../../assets/NPC/full-screen-red.png");
  490. },
  491. },
  492. watch: {
  493. hasSelectedCell() {
  494. this.handleParentPlay();
  495. },
  496. isFull: {
  497. handler(newVal, oldVal) {
  498. this.refresh = false;
  499. if (!newVal) {
  500. this.$nextTick(() => {
  501. // 重新渲染组件
  502. this.refresh = true;
  503. });
  504. }
  505. },
  506. deep: true,
  507. },
  508. },
  509. created() {
  510. Bus.$on("audioPause", (id) => {
  511. if (this.cid === id) return;
  512. this.$nextTick(() => {
  513. if (this.$refs.luyin?.microphoneStatus) this.$refs.luyin.microphone();
  514. this.handleParentPlay();
  515. });
  516. });
  517. if (!this.curQue.Bookanswer) {
  518. let bookanswer = {
  519. recordList: [],
  520. };
  521. this.$set(this.curQue, "Bookanswer", bookanswer);
  522. }
  523. },
  524. mounted() {
  525. // 如果一行内有两个语音矩阵,隐藏 全屏模式 文字
  526. if (
  527. Number(
  528. window.getComputedStyle(this.$refs.fullscreen).width.replace("px", "")
  529. ) < 80
  530. ) {
  531. // this.$refs.fullscreen.children[0].hidden = true;
  532. }
  533. },
  534. beforeDestroy() {},
  535. methods: {
  536. // 鼠标移入移出
  537. matrixCellMouseenter(i, j, type) {
  538. if (type === "connection") {
  539. this.selectRow = -1;
  540. this.selectColumn = -1;
  541. } else {
  542. this.selectRow = i;
  543. this.selectColumn = j;
  544. }
  545. },
  546. clearSelectCell() {
  547. this.selectRow = -1;
  548. this.selectColumn = -1;
  549. },
  550. // 单击单元格
  551. matrixCellClick(row, column) {
  552. if (this.playing) this.handleParentPlay();
  553. if (this.unWatch) this.unWatch();
  554. this.lrcArray = [];
  555. if (row === this.selectCell.row && column === this.selectCell.column) {
  556. this.selectCell = { row: -1, column: -1 };
  557. return;
  558. }
  559. this.selectedLine = { type: "", index: -1 };
  560. this.selectCell = { row, column };
  561. this.handleChangeTime(
  562. this.curQue.voiceMatrix.matrix[row][column].lrc_data
  563. );
  564. // 设置录音文件名
  565. this.setRecordingFileName(row, column);
  566. },
  567. setRecordingFileName(row, column) {
  568. let { type, text, sentence_data, pinyin_english_data, text_brackets } =
  569. this.curQue.voiceMatrix.matrix[row][column];
  570. if (type === "text") this.fileName = text;
  571. if (type === "SentenceSegwordChs") this.fileName = sentence_data.sentence;
  572. if (type === "PinyinEnglish") this.fileName = pinyin_english_data.pinyin;
  573. if (type === "textBrackets") {
  574. this.fileName = `${text_brackets.brackets_outer}[${text_brackets.brackets_inner}]`;
  575. }
  576. },
  577. checkboxMouseenter(isSelected, type) {
  578. if (!isSelected) return this.clearSelectCell();
  579. if (type === "row") this.selectColumn = -1;
  580. if (type === "column") this.selectRow = -1;
  581. },
  582. // 选中行、列
  583. selectRowOrColumn(index, type) {
  584. this.handleParentPlay();
  585. this.lrcArray = [];
  586. this.selectCell = { row: -1, column: -1 };
  587. if (this.unWatch) this.unWatch();
  588. if (
  589. this.selectedLine.type === type &&
  590. this.selectedLine.index === index
  591. ) {
  592. this.selectedLine = { type: "", index: -1 };
  593. return;
  594. }
  595. this.selectedLine = { type, index };
  596. let number = index;
  597. if (type === "column") {
  598. this.curQue.voiceMatrix.matrix[index].forEach(({ type }, i) => {
  599. if (i >= index) return;
  600. if (type === "connection") number -= 1;
  601. });
  602. }
  603. this.fileName = `第 ${number + 1} ${type === "row" ? "行" : "列"}`;
  604. },
  605. getBracketsOuterTypeClass(type) {
  606. if (type === "pinyin") return "pinyin";
  607. if (type === "chs") return "chs";
  608. if (type === "english") return "english";
  609. return "chs";
  610. },
  611. playAudio() {
  612. if (!this.hasSelectedCell) return;
  613. if (this.playing) return this.handleParentPlay();
  614. if (this.lrcArray.length > 0) return this.$refs.audioLine.PlayAudio();
  615. if (this.unWatch) this.unWatch();
  616. this.lrcArray = [];
  617. let { type, index } = this.selectedLine;
  618. if (type.length > 0 && index >= 0 && type === "row") {
  619. this.curQue.voiceMatrix.matrix[index].forEach((item) => {
  620. let data = this.getLrcData(item);
  621. if (data && "begin_time" in data) this.lrcArray.push(data);
  622. });
  623. if (this.lrcArray.length > 0) this.lrcPlay(this.lrcArray[0], 0);
  624. return;
  625. }
  626. if (type.length > 0 && index >= 0 && type === "column") {
  627. this.curQue.voiceMatrix.matrix.forEach((item) => {
  628. let data = this.getLrcData(item[index]);
  629. if (data && "begin_time" in data) this.lrcArray.push(data);
  630. });
  631. if (this.lrcArray.length > 0) this.lrcPlay(this.lrcArray[0], 0);
  632. return;
  633. }
  634. let { row, column } = this.selectCell;
  635. if (row >= 0 && column >= 0) {
  636. this.handleChangeTime(
  637. this.curQue.voiceMatrix.matrix[row][column].lrc_data
  638. );
  639. }
  640. },
  641. lrcPlay({ begin_time, end_time }, index) {
  642. this.handleParentPlay();
  643. this.$nextTick(() => {
  644. this.$refs.audioLine.onTimeupdateTime(begin_time / 1000);
  645. this.$refs.audioLine.PlayAudio();
  646. if (end_time === -1) return;
  647. let end = end_time / 1000 - 0.01;
  648. this.unWatch = this.$watch("curTime", (val) => {
  649. if (val >= end) {
  650. if (!this.hasSelectedCell) return this.unWatch();
  651. this.handleParentPlay();
  652. this.$refs.audioLine.onTimeupdateTime(end);
  653. this.unWatch();
  654. let i = index + 1;
  655. if (i < this.lrcArray.length) {
  656. return this.lrcPlay(this.lrcArray[i], i);
  657. }
  658. if (this.isRepeat) {
  659. return this.lrcPlay(this.lrcArray[0], 0);
  660. }
  661. this.lrcArray = [];
  662. }
  663. });
  664. });
  665. },
  666. playChange(playing) {
  667. this.playing = playing;
  668. // 子组件通信,同时只能播放一个音频
  669. if (playing) Bus.$emit("audioPause", this.cid);
  670. },
  671. pauseOtherAudio() {
  672. Bus.$emit("audioPause", this.cid);
  673. this.stopAudio = true;
  674. },
  675. // 暂停音频播放
  676. handleParentPlay() {
  677. this.stopAudio = true;
  678. },
  679. // 音频播放时改变布尔值
  680. handleChangeStopAudio() {
  681. this.stopAudio = false;
  682. },
  683. getCurTime(curTime) {
  684. this.curTime = curTime;
  685. },
  686. getWavblob(wavblob) {
  687. this.wavblob = wavblob;
  688. },
  689. getSelectData({ type, index, row, column }) {
  690. if (type === "") return;
  691. if (index === 0 && row === -1 && column === -1) {
  692. this.matrixSelectLrc = null;
  693. return;
  694. }
  695. let arr = [];
  696. if (type.length > 0 && index >= 0 && type === "row") {
  697. this.curQue.voiceMatrix.matrix[index].forEach((item) => {
  698. let data = this.getLrcData(item);
  699. if (data) arr.push(data);
  700. });
  701. this.matrixSelectLrc = arr;
  702. return;
  703. }
  704. if (type.length > 0 && index >= 0 && type === "column") {
  705. this.curQue.voiceMatrix.matrix.forEach((item) => {
  706. let data = this.getLrcData(item[index]);
  707. if (data) arr.push(data);
  708. });
  709. this.matrixSelectLrc = arr;
  710. return;
  711. }
  712. if (type === "cell" && row >= 0 && column >= 0) {
  713. let lrcData = this.curQue.voiceMatrix.matrix[row][column].lrc_data;
  714. if (lrcData.end_time === -1) lrcData.end_time = this.mp3Duration;
  715. this.matrixSelectLrc = [lrcData];
  716. }
  717. },
  718. getLrcData({ type, text, lrc_data }) {
  719. if (
  720. type === "SentenceSegwordChs" ||
  721. type === "PinyinEnglish" ||
  722. type === "textBrackets" ||
  723. (type === "text" && text.length > 0)
  724. ) {
  725. if (lrc_data.end_time === -1) {
  726. return {
  727. begin_time: lrc_data.begin_time,
  728. end_time: this.mp3Duration,
  729. text: lrc_data.text,
  730. };
  731. }
  732. return lrc_data;
  733. }
  734. return false;
  735. },
  736. sentPause(isRecord) {
  737. this.isRecord = isRecord;
  738. },
  739. pauseAudio() {
  740. let audio = document.getElementsByTagName("audio");
  741. if (
  742. audio &&
  743. audio.length > 0 &&
  744. window.location.href.indexOf("GCLS-Learn") == -1
  745. ) {
  746. audio.forEach((item) => {
  747. item.pause();
  748. });
  749. }
  750. },
  751. fullScreen() {
  752. this.pauseAudio();
  753. this.isFull = true;
  754. // this.goFullscreen();
  755. },
  756. goFullscreen() {
  757. let element = document.getElementById(`screen-${this.cid}`);
  758. if (element.requestFullscreen) {
  759. element.requestFullscreen();
  760. } else if (element.msRequestFullscreen) {
  761. element.msRequestFullscreen();
  762. } else if (element.mozRequestFullScreen) {
  763. element.mozRequestFullScreen();
  764. } else if (element.webkitRequestFullscreen) {
  765. element.webkitRequestFullscreen();
  766. }
  767. },
  768. exitFullscreen() {
  769. this.isFull = false;
  770. if (document.exitFullscreen) {
  771. document.exitFullscreen();
  772. } else if (document.msExitFullscreen) {
  773. document.msExitFullscreen();
  774. } else if (document.mozCancelFullScreen) {
  775. document.mozCancelFullScreen();
  776. } else if (document.webkitExitFullscreen) {
  777. document.webkitExitFullscreen();
  778. }
  779. },
  780. changeIsFull() {
  781. this.isFull = false;
  782. },
  783. handleChangeTime({ begin_time, end_time }) {
  784. if (this.unWatch) this.unWatch();
  785. this.handleParentPlay();
  786. this.$nextTick(() => {
  787. this.$refs.audioLine.onTimeupdateTime(begin_time / 1000);
  788. setTimeout(() => {
  789. this.$refs.audioLine.PlayAudio();
  790. });
  791. // 监听是否已到结束时间,为了选中效果 - 0.01
  792. if (end_time === -1) return;
  793. let end = end_time / 1000 - 0.01;
  794. this.unWatch = this.$watch("curTime", (val) => {
  795. if (val >= end) {
  796. this.handleParentPlay();
  797. this.$refs.audioLine.onTimeupdateTime(end);
  798. this.unWatch();
  799. this.unWatch = null;
  800. if (this.isRepeat) {
  801. this.handleChangeTime({ begin_time, end_time });
  802. }
  803. }
  804. });
  805. });
  806. },
  807. handleWav(list, tmIndex = 0) {
  808. this.$set(this.curQue.Bookanswer, "recordList", list);
  809. },
  810. },
  811. };
  812. </script>
  813. <style lang="scss" scoped>
  814. $select-color: #de4444;
  815. $border-color: rgba(0, 0, 0, 0.1);
  816. $select-color-green: #24b99e;
  817. $select-color-green-bc: rgba(36, 185, 158, 0.25);
  818. $select-color-green-hover: #3dd4b8;
  819. $select-color-green-active: #1fa189;
  820. $select-color-brown: #bd8865;
  821. $select-color-brown-bc: rgba(189, 136, 101, 0.25);
  822. $select-color-brown-hover: #d6a687;
  823. $select-color-brown-active: #a37557;
  824. .voice-matrix {
  825. height: 100%;
  826. width: 100%;
  827. // padding-bottom: 24px;
  828. color: #262626;
  829. &-audio {
  830. display: flex;
  831. height: 42px;
  832. border: 1px solid $border-color;
  833. border-radius: 8px 8px 0 0;
  834. .audio-number {
  835. padding: 11px 0 0 12px;
  836. %serial-number,
  837. .serial-number {
  838. display: inline-block;
  839. width: 16px;
  840. height: 16px;
  841. text-align: center;
  842. line-height: 16px;
  843. font-size: 12px;
  844. background-color: $select-color;
  845. font-family: "robot";
  846. // color: #fff;
  847. border-radius: 50%;
  848. }
  849. .serial-number-green {
  850. @extend %serial-number;
  851. background-color: $select-color-green;
  852. }
  853. .serial-number-brown {
  854. @extend %serial-number;
  855. background-color: $select-color-brown;
  856. }
  857. }
  858. .audio-simple {
  859. flex-grow: 1;
  860. line-height: 46px;
  861. height: 100%;
  862. display: flex;
  863. align-items: center;
  864. justify-content: space-between;
  865. img {
  866. cursor: pointer;
  867. width: 16px;
  868. height: 16px;
  869. margin-left: 12px;
  870. }
  871. .Repeat-16 {
  872. display: inline-block;
  873. width: 16px;
  874. height: 16px;
  875. margin-right: 12px;
  876. cursor: pointer;
  877. }
  878. }
  879. }
  880. // 语音矩阵
  881. &-container {
  882. overflow: auto;
  883. font-size: 16px;
  884. height: calc(100% - 80px);
  885. // background-color: #f5f5f5;
  886. border-left: 1px solid $border-color;
  887. border-right: 1px solid $border-color;
  888. word-break: break-word;
  889. .matrix {
  890. display: inline-grid;
  891. width: 100%;
  892. height: 100%;
  893. %matrix-checkbox {
  894. position: relative;
  895. top: calc(50% - 5px);
  896. display: block;
  897. width: 14px;
  898. height: 14px;
  899. border: 1.5px solid #b0b0b0;
  900. border-radius: 4px;
  901. margin: 0 auto;
  902. cursor: pointer;
  903. &.active {
  904. border-color: $select-color;
  905. &::after {
  906. box-sizing: content-box;
  907. content: "";
  908. border: 1px solid $select-color;
  909. border-left: 0;
  910. border-top: 0;
  911. height: 7px;
  912. left: 4px;
  913. position: absolute;
  914. width: 3px;
  915. transform: rotate(45deg) scaleY(1);
  916. transition: transform 0.15s ease-in 0.05s;
  917. transform-origin: center;
  918. }
  919. }
  920. }
  921. .matrix-checkbox-row-,
  922. .matrix-checkbox-row-red {
  923. @extend %matrix-checkbox;
  924. }
  925. .matrix-checkbox-row-green {
  926. @extend %matrix-checkbox;
  927. &.active {
  928. border-color: $select-color-green-active;
  929. &::after {
  930. border-color: $select-color-green-active;
  931. }
  932. }
  933. }
  934. .matrix-checkbox-row-brown {
  935. @extend %matrix-checkbox;
  936. &.active {
  937. border-color: $select-color-brown-active;
  938. &::after {
  939. border-color: $select-color-brown-active;
  940. }
  941. }
  942. }
  943. %matrix-checkbox-column,
  944. .matrix-checkbox-column-,
  945. .matrix-checkbox-column-red {
  946. @extend %matrix-checkbox;
  947. top: calc(50% - 7px);
  948. right: -2px;
  949. }
  950. .matrix-checkbox-column-green {
  951. @extend %matrix-checkbox-column;
  952. &.active {
  953. border-color: $select-color-green-active;
  954. &::after {
  955. border-color: $select-color-green-active;
  956. }
  957. }
  958. }
  959. .matrix-checkbox-column-brown {
  960. @extend %matrix-checkbox-column;
  961. &.active {
  962. border-color: $select-color-brown-active;
  963. &::after {
  964. border-color: $select-color-brown-active;
  965. }
  966. }
  967. }
  968. .read {
  969. background-color: #eaeaea;
  970. }
  971. .highlight-,
  972. .highlight-red {
  973. color: $select-color;
  974. }
  975. .highlight-green {
  976. color: $select-color-green;
  977. }
  978. .highlight-brown {
  979. color: $select-color-brown;
  980. }
  981. .column-wrapper {
  982. padding: 4px;
  983. %column {
  984. width: 100%;
  985. height: 100%;
  986. min-height: 24px;
  987. // background-color: #fff;
  988. border: 1px solid $border-color;
  989. border-radius: 8px;
  990. transition: 0.2s;
  991. cursor: pointer;
  992. user-select: none;
  993. &:hover {
  994. border-color: #8c8c8c;
  995. }
  996. &.selected {
  997. color: $select-color;
  998. border-color: $select-color;
  999. }
  1000. &.playing {
  1001. background-color: #fee;
  1002. }
  1003. &.title {
  1004. background-color: transparent;
  1005. border-color: transparent;
  1006. }
  1007. > span {
  1008. display: inline-block;
  1009. white-space: nowrap;
  1010. padding: 4px 12px;
  1011. line-height: 1.4;
  1012. }
  1013. }
  1014. %column-red,
  1015. .column-,
  1016. .column-red {
  1017. @extend %column;
  1018. position: relative;
  1019. font-family: "GB-PINYINOK-B", "FZJCGFKTK";
  1020. &::before {
  1021. display: inline-block;
  1022. content: "";
  1023. vertical-align: middle;
  1024. }
  1025. }
  1026. .column-green {
  1027. @extend %column-red;
  1028. &.selected {
  1029. color: $select-color-green;
  1030. border-color: $select-color-green;
  1031. }
  1032. &.playing {
  1033. background-color: $select-color-green-bc;
  1034. }
  1035. }
  1036. .column-brown {
  1037. @extend %column-red;
  1038. &.selected {
  1039. color: $select-color-brown;
  1040. border-color: $select-color-brown;
  1041. }
  1042. &.playing {
  1043. background-color: $select-color-brown-bc;
  1044. }
  1045. }
  1046. %sentence,
  1047. .sentence-,
  1048. .sentence-red {
  1049. @extend %column;
  1050. display: inline-grid;
  1051. padding: 4px 12px;
  1052. line-height: 24px;
  1053. column-gap: 8px;
  1054. justify-items: center;
  1055. justify-content: start;
  1056. > span {
  1057. padding: 0;
  1058. }
  1059. .pinyin {
  1060. font-family: "GB-PINYINOK-B";
  1061. opacity: 0.45;
  1062. font-size: 12px;
  1063. line-height: 20px;
  1064. }
  1065. .chs {
  1066. font-family: "FZJCGFKTK";
  1067. font-size: 16px;
  1068. line-height: 24px;
  1069. }
  1070. }
  1071. .sentence-green {
  1072. @extend %sentence;
  1073. &.selected {
  1074. color: $select-color-green;
  1075. border-color: $select-color-green;
  1076. }
  1077. &.playing {
  1078. background-color: $select-color-green-bc;
  1079. }
  1080. }
  1081. .sentence-brown {
  1082. @extend %sentence;
  1083. &.selected {
  1084. color: $select-color-brown;
  1085. border-color: $select-color-brown;
  1086. }
  1087. &.playing {
  1088. background-color: $select-color-brown-bc;
  1089. }
  1090. }
  1091. .connection {
  1092. position: relative;
  1093. top: calc(50% - 1px);
  1094. height: 2px;
  1095. width: 16px;
  1096. margin: 0 -4px;
  1097. border-radius: 4px;
  1098. background-color: #252525;
  1099. &.highlight-bc-,
  1100. &.highlight-bc-red {
  1101. background-color: $select-color;
  1102. }
  1103. &.highlight-bc-green {
  1104. background-color: $select-color-green;
  1105. }
  1106. &.highlight-bc-brown {
  1107. background-color: $select-color-brown;
  1108. }
  1109. }
  1110. // 拼音 + 文字
  1111. %pinyinEnglish,
  1112. .pinyinEnglish-,
  1113. .pinyinEnglish-red {
  1114. @extend %column;
  1115. .inside-wrapper {
  1116. padding: 4px 12px;
  1117. white-space: nowrap;
  1118. .pinyin {
  1119. font-family: "GB-PINYINOK-B";
  1120. font-size: 16px;
  1121. line-height: 24px;
  1122. }
  1123. .english {
  1124. font-family: "robot";
  1125. opacity: 0.45;
  1126. font-size: 12px;
  1127. line-height: 20px;
  1128. }
  1129. }
  1130. }
  1131. .pinyinEnglish-green {
  1132. @extend %pinyinEnglish;
  1133. &.selected {
  1134. color: $select-color-green;
  1135. border-color: $select-color-green;
  1136. }
  1137. &.playing {
  1138. background-color: $select-color-green-bc;
  1139. }
  1140. }
  1141. .pinyinEnglish-brown {
  1142. @extend %pinyinEnglish;
  1143. &.selected {
  1144. color: $select-color-brown;
  1145. border-color: $select-color-brown;
  1146. }
  1147. &.playing {
  1148. background-color: $select-color-brown-bc;
  1149. }
  1150. }
  1151. // 文本中有括号
  1152. %textBrackets,
  1153. .textBrackets-,
  1154. .textBrackets-red {
  1155. @extend %column;
  1156. .chs {
  1157. font-family: "FZJCGFKTK";
  1158. }
  1159. .pinyin {
  1160. font-family: "GB-PINYINOK-B";
  1161. }
  1162. .english {
  1163. font-family: "robot";
  1164. }
  1165. .brackets {
  1166. font-size: 16px;
  1167. font-family: "FZJCGFKTK";
  1168. }
  1169. }
  1170. .textBrackets-green {
  1171. @extend %textBrackets;
  1172. &.selected {
  1173. color: $select-color-green;
  1174. border-color: $select-color-green;
  1175. }
  1176. &.playing {
  1177. background-color: $select-color-green-bc;
  1178. }
  1179. }
  1180. .textBrackets-brown {
  1181. @extend %textBrackets;
  1182. &.selected {
  1183. color: $select-color-brown;
  1184. border-color: $select-color-brown;
  1185. }
  1186. &.playing {
  1187. background-color: $select-color-brown-bc;
  1188. }
  1189. }
  1190. }
  1191. }
  1192. .matrix-audio {
  1193. width: 228px;
  1194. height: 40px;
  1195. padding: 4px 4px 4px 16px;
  1196. margin: 24px 24px 0 0;
  1197. // background-color: #fff;
  1198. border: 1px solid $border-color;
  1199. border-radius: 8px;
  1200. }
  1201. }
  1202. .voice-luyin {
  1203. display: flex;
  1204. border: 1px solid $border-color;
  1205. border-radius: 0 0 8px 8px;
  1206. align-items: center;
  1207. padding: 3px 16px;
  1208. height: 40px;
  1209. .fullscreen {
  1210. cursor: pointer;
  1211. .el-image {
  1212. width: 16px;
  1213. height: 16px;
  1214. margin-left: 8px;
  1215. vertical-align: text-bottom;
  1216. }
  1217. }
  1218. }
  1219. }
  1220. </style>
  1221. <style lang="scss" scoped>
  1222. .NNPE-tableList-tr-last {
  1223. .voice-matrix {
  1224. padding-bottom: 0;
  1225. }
  1226. }
  1227. </style>
  1228. <style lang="scss">
  1229. .voice-matrix {
  1230. &-audio {
  1231. .audioLine {
  1232. border-radius: 8px 8px 0 0 !important;
  1233. }
  1234. .el-slider {
  1235. width: 100% !important;
  1236. }
  1237. }
  1238. .luyin-box {
  1239. .el-select .el-input {
  1240. width: 126px;
  1241. }
  1242. }
  1243. }
  1244. </style>