VoiceMatrix.vue 38 KB

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