VoiceMatrix.vue 38 KB

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