VoiceMatrix.vue 36 KB

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