VoiceMatrix.vue 38 KB

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