VoiceMatrix.vue 38 KB

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