dusenyao пре 3 година
родитељ
комит
05a3327600

+ 1 - 1
src/components/Adult/preview/VoiceMatrix.vue

@@ -345,7 +345,7 @@
         @playing="playChange"
       />
       <span class="fullscreen" @click="fullScreen">
-        全屏模式
+        <span>全屏模式</span>
         <el-image :src="fullscreenSrc" />
       </span>
     </div>

+ 928 - 40
src/components/Adult/preview/VoiceMatrixFullscreen.vue

@@ -128,7 +128,253 @@
       </div>
     </div>
 
-    <div class="voicefull-content" />
+    <div class="voicefull-content">
+      <div class="voice-matrix-container">
+        <div
+          v-if="curQue.voiceMatrix.matrix.length > 0"
+          class="matrix"
+          :style="{
+            'grid-template': `36px repeat(${curQue.voiceMatrix.matrix.length}, auto) minmax(36px, 1fr) / 36px repeat(${curQue.voiceMatrix.matrix[0].length}, auto) minmax(36px, 1fr)`
+          }"
+          @mouseleave="clearSelectCell"
+        >
+          <!-- 顶部单元格 -->
+          <div class="matrix-top" @mouseenter="clearSelectCell" />
+          <template v-for="(row, i) in curQue.voiceMatrix.matrix[0]">
+            <div
+              :key="`top-${i}`"
+              :class="[
+                'matrix-top'
+              ]"
+              @mouseenter="checkboxMouseenter(selectColumn === i, 'column')"
+            >
+              <span
+                v-if="
+                  row.type !== 'connection' &&
+                    curQue.voiceMatrix.columnSelection
+                "
+                v-show="
+                  selectColumn === i ||
+                    (selectedLine.type === 'column' && selectedLine.index === i)
+                "
+                :class="[
+                  `matrix-checkbox-row-${themeColor}`,
+                  selectedLine.type === 'column' && selectedLine.index === i
+                    ? 'active'
+                    : ''
+                ]"
+                @click="selectRowOrColumn(i, 'column')"
+              />
+            </div>
+          </template>
+          <div class="matrix-top" @mouseenter="clearSelectCell" />
+          <!-- 主矩阵 -->
+          <template v-for="(row, i) in curQue.voiceMatrix.matrix">
+            <div
+              :key="`start-${i}`"
+              :class="[
+                'column-wrapper'
+              ]"
+              @mouseenter="checkboxMouseenter(selectRow === i, 'row')"
+            >
+              <span
+                v-if="curQue.voiceMatrix.rowSelection"
+                v-show="
+                  selectRow === i ||
+                    (selectedLine.type === 'row' && selectedLine.index === i)
+                "
+                :class="[
+                  `matrix-checkbox-column-${themeColor}`,
+                  selectedLine.type === 'row' && selectedLine.index === i
+                    ? 'active'
+                    : ''
+                ]"
+                @click="selectRowOrColumn(i, 'row')"
+              />
+            </div>
+            <!-- 单元格 -->
+            <template v-for="(column, j) in row">
+              <div
+                :key="`wrapper-${i}-${j}`"
+                :class="[
+                  'column-wrapper',
+                  (i === 0 && curQue.voiceMatrix.firstLineHighlight) ||
+                    (j === row.length - 1 &&
+                      curQue.voiceMatrix.lastColumnHighlight)
+                    ? `highlight-${themeColor}`
+                    : ''
+                ]"
+                @mouseenter="matrixCellMouseenter(i, j, column.type)"
+              >
+                <!-- 文本 -->
+                <div
+                  v-if="column.type === 'text'"
+                  :key="`column-${i}-${j}`"
+                  :class="[
+                    column.text.length === 0 ? 'space' : `column-${themeColor}`,
+                    (selectCell.row === i && selectCell.column === j) ||
+                      (selectedLine.type === 'column' &&
+                        selectedLine.index === j) ||
+                      (selectedLine.type === 'row' && selectedLine.index === i)
+                      ? 'selected'
+                      : '',
+                    playing &&
+                      column.lrc_data.begin_time / 1000 <= curTime &&
+                      (curTime < column.lrc_data.end_time / 1000 ||
+                        column.lrc_data.end_time === -1)
+                      ? 'playing'
+                      : '',
+                    column.isTitle ? 'title' : ''
+                  ]"
+                  @click="matrixCellClick(i, j)"
+                >
+                  <span>{{ column.text }}</span>
+                </div>
+                <!-- 连接线 -->
+                <div
+                  v-else-if="column.type === 'connection'"
+                  :key="`column-${i}-${j}`"
+                  :class="[
+                    'connection',
+                    i === 0 && curQue.voiceMatrix.firstLineHighlight
+                      ? `highlight-bc-${themeColor}`
+                      : ''
+                  ]"
+                />
+                <!-- 分词 -->
+                <div
+                  v-else-if="column.type === 'SentenceSegwordChs'"
+                  :key="`column-${i}-${j}`"
+                  :class="[
+                    `sentence-${themeColor}`,
+                    (selectCell.row === i && selectCell.column === j) ||
+                      (selectedLine.type === 'column' &&
+                        selectedLine.index === j) ||
+                      (selectedLine.type === 'row' && selectedLine.index === i)
+                      ? 'selected'
+                      : '',
+                    playing &&
+                      column.lrc_data.begin_time / 1000 <= curTime &&
+                      (curTime < column.lrc_data.end_time / 1000 ||
+                        column.lrc_data.end_time === -1)
+                      ? 'playing'
+                      : '',
+                    column.isTitle ? 'title' : ''
+                  ]"
+                  :style="{
+                    'grid-template-columns': `repeat(${column.sentence_data.wordsList.length}, auto)`
+                  }"
+                  @click="matrixCellClick(i, j)"
+                >
+                  <template v-for="(word, w) in column.sentence_data.wordsList">
+                    <span
+                      v-if="column.sentence_data.pyPosition === 'top'"
+                      :key="`pinyin-${w}`"
+                      class="pinyin"
+                    >
+                      {{ word.pinyin }}
+                    </span>
+                    <span v-else :key="`chs-${w}`" class="chs">
+                      {{ word.chs }}
+                    </span>
+                  </template>
+                  <template v-for="(word, w) in column.sentence_data.wordsList">
+                    <span
+                      v-if="column.sentence_data.pyPosition === 'top'"
+                      :key="`chs-${w}`"
+                      class="chs"
+                    >
+                      {{ word.chs }}
+                    </span>
+                    <span v-else :key="`pinyin-${w}`" class="pinyin">
+                      {{ word.pinyin }}
+                    </span>
+                  </template>
+                </div>
+                <!-- 拼音 + 英文 -->
+                <div
+                  v-else-if="column.type === 'PinyinEnglish'"
+                  :key="`column-${i}-${j}`"
+                  :class="[
+                    `pinyinEnglish-${themeColor}`,
+                    (selectCell.row === i && selectCell.column === j) ||
+                      (selectedLine.type === 'column' &&
+                        selectedLine.index === j) ||
+                      (selectedLine.type === 'row' && selectedLine.index === i)
+                      ? 'selected'
+                      : '',
+                    playing &&
+                      column.lrc_data.begin_time / 1000 <= curTime &&
+                      (curTime < column.lrc_data.end_time / 1000 ||
+                        column.lrc_data.end_time === -1)
+                      ? 'playing'
+                      : '',
+                    column.isTitle ? 'title' : ''
+                  ]"
+                  @click="matrixCellClick(i, j)"
+                >
+                  <div class="inside-wrapper">
+                    <div class="pinyin">
+                      {{ column.pinyin_english_data.pinyin }}
+                    </div>
+                    <div class="english">
+                      {{ column.pinyin_english_data.english }}
+                    </div>
+                  </div>
+                </div>
+                <!-- 文本中有括号 -->
+                <div
+                  v-else-if="column.type === 'textBrackets'"
+                  :key="`column-${i}-${j}`"
+                  :class="[
+                    `textBrackets-${themeColor}`,
+                    (selectCell.row === i && selectCell.column === j) ||
+                      (selectedLine.type === 'column' &&
+                        selectedLine.index === j) ||
+                      (selectedLine.type === 'row' && selectedLine.index === i)
+                      ? 'selected'
+                      : '',
+                    playing &&
+                      column.lrc_data.begin_time / 1000 <= curTime &&
+                      (curTime < column.lrc_data.end_time / 1000 ||
+                        column.lrc_data.end_time === -1)
+                      ? 'playing'
+                      : '',
+                    column.isTitle ? 'title' : ''
+                  ]"
+                  @click="matrixCellClick(i, j)"
+                >
+                  <span>
+                    <span class="brackets-text">{{
+                      column.text_brackets.brackets_outer
+                    }}</span>
+                    <span class="brackets">&nbsp;[&nbsp;</span>
+                    <span class="brackets-text">{{
+                      column.text_brackets.brackets_inner
+                    }}</span>
+                    <span class="brackets">&nbsp;]</span>
+                  </span>
+                </div>
+              </div>
+            </template>
+
+            <div
+              :key="`end-${i}`"
+              @mouseenter="clearSelectCell"
+            />
+          </template>
+          <!-- 底部格子 -->
+          <div class="matrix-bottom" @mouseenter="clearSelectCell" />
+          <template v-for="(row, i) in curQue.voiceMatrix.matrix[0]">
+            <div
+              :key="`bottom-${i}`"
+              @mouseenter="clearSelectCell"
+            />
+          </template>
+          <div class="matrix-bottom" @mouseenter="clearSelectCell" />
+        </div>
+      </div>
+    </div>
 
     <div
       class="voicefull-bottom"
@@ -140,7 +386,6 @@
           <soundrecorddiff
             ref="Soundrecorddiff"
             :bg-index="bgIndex"
-            @handleWav="handleWav"
             @getWavblob="getWavblob"
             @handleParentPlay="handleParentPlay"
             @sentPause="sentPause"
@@ -281,8 +526,15 @@ export default {
     }
   },
   created() {
-    document.addEventListener("keyup", e => {
-      if (this.isKeyboard) {
+    document.addEventListener("keyup", ({ key }) => {
+      if (!this.isKeyboard) return;
+      if (key === "ArrowUp") {}
+      if (key === "ArrowLeft") {}
+      if (key === "ArrowDown") {}
+      if (key === "ArrowRight") {}
+      if (key === "Enter") {}
+      if (key === " ") {
+        console.log(2);
       }
     });
 
@@ -317,6 +569,261 @@ export default {
   },
   // 方法集合
   methods: {
+    /** 语音矩阵方法开始 **/
+    // 鼠标移入移出
+    matrixCellMouseenter(i, j, type) {
+      if (type === "connection") {
+        this.selectRow = -1;
+        this.selectColumn = -1;
+      } else {
+        this.selectRow = i;
+        this.selectColumn = j;
+      }
+    },
+
+    clearSelectCell() {
+      this.selectRow = -1;
+      this.selectColumn = -1;
+    },
+
+    // 单击单元格
+    matrixCellClick(row, column) {
+      if (this.playing) this.handleParentPlay();
+      if (this.unWatch) this.unWatch();
+      this.lrcArray = [];
+      if (row === this.selectCell.row && column === this.selectCell.column) {
+        this.selectCell = { row: -1, column: -1 };
+        return;
+      }
+      this.selectedLine = { type: "", index: -1 };
+      this.selectCell = { row, column };
+      this.handleChangeTime(
+        this.curQue.voiceMatrix.matrix[row][column].lrc_data
+      );
+      // 设置录音文件名
+      this.setRecordingFileName(row, column);
+    },
+
+    setRecordingFileName(row, column) {
+      let {
+        type,
+        text,
+        sentence_data,
+        pinyin_english_data,
+        text_brackets
+      } = this.curQue.voiceMatrix.matrix[row][column];
+      if (type === "text") this.fileName = text;
+      if (type === "SentenceSegwordChs") this.fileName = sentence_data.sentence;
+      if (type === "PinyinEnglish") this.fileName = pinyin_english_data.pinyin;
+      if (type === "textBrackets") {
+        this.fileName = `${text_brackets.brackets_outer}[${text_brackets.brackets_inner}]`;
+      }
+    },
+
+    // 判断 click 点击是否语音矩阵可操作区域
+    restoreAudioStatus(event) {
+      const whitePath = [
+        "column-green",
+        "column-red",
+        "column-brown",
+        "matrix-checkbox-column-",
+        "matrix-checkbox-row-",
+        "audio-simple-image",
+        "audio-simple-repeat",
+        "luyin-box"
+      ];
+
+      let operable = event.path.some(item => {
+        let className = item.className;
+        if (!className) return false;
+        return whitePath.some(path => className.includes(path));
+      });
+      if (!operable) {
+        this.selectedLine = { type: "", index: -1 };
+        this.selectCell = { row: -1, column: -1 };
+        if (this.playing) this.handleParentPlay();
+        if (this.unWatch) this.unWatch();
+      }
+    },
+
+    checkboxMouseenter(isSelected, type) {
+      if (!isSelected) return this.clearSelectCell();
+      if (type === "row") this.selectColumn = -1;
+      if (type === "column") this.selectRow = -1;
+    },
+
+    // 选中行、列
+    selectRowOrColumn(index, type) {
+      this.handleParentPlay();
+      this.lrcArray = [];
+      this.selectCell = { row: -1, column: -1 };
+      if (this.unWatch) this.unWatch();
+      if (
+        this.selectedLine.type === type &&
+        this.selectedLine.index === index
+      ) {
+        this.selectedLine = { type: "", index: -1 };
+        return;
+      }
+      this.selectedLine = { type, index };
+      let number = index;
+      if (type === "column") {
+        this.curQue.voiceMatrix.matrix[index].forEach(({ type }, i) => {
+          if (i >= index) return;
+          if (type === "connection") number -= 1;
+        });
+      }
+      this.fileName = `第 ${number + 1} ${type === "row" ? "行" : "列"}`;
+    },
+
+    playAudio() {
+      if (!this.hasSelectedCell) return;
+      if (this.playing) return this.handleParentPlay();
+      if (this.lrcArray.length > 0) return this.$refs.audioLine.PlayAudio();
+      if (this.unWatch) this.unWatch();
+
+      this.lrcArray = [];
+      let { type, index } = this.selectedLine;
+      if (type.length > 0 && index >= 0 && type === "row") {
+        this.curQue.voiceMatrix.matrix[index].forEach(item => {
+          let data = this.getLrcData(item);
+          if (data) this.lrcArray.push(data);
+        });
+        if (this.lrcArray.length > 0) this.lrcPlay(this.lrcArray[0], 0);
+        return;
+      }
+
+      if (type.length > 0 && index >= 0 && type === "column") {
+        this.curQue.voiceMatrix.matrix.forEach(item => {
+          let data = this.getLrcData(item[index]);
+          if (data) this.lrcArray.push(data);
+        });
+        if (this.lrcArray.length > 0) this.lrcPlay(this.lrcArray[0], 0);
+        return;
+      }
+
+      let { row, column } = this.selectCell;
+      if (row >= 0 && column >= 0) {
+        this.handleChangeTime(
+          this.curQue.voiceMatrix.matrix[row][column].lrc_data
+        );
+      }
+    },
+
+    lrcPlay({ begin_time, end_time }, index) {
+      this.handleParentPlay();
+      this.$nextTick(() => {
+        this.$refs.audioLine.onTimeupdateTime(begin_time / 1000);
+        this.$refs.audioLine.PlayAudio();
+        if (end_time === -1) return;
+        let end = end_time / 1000 - 0.01;
+        this.unWatch = this.$watch("curTime", val => {
+          if (val >= end) {
+            if (!this.hasSelectedCell) return this.unWatch();
+            this.handleParentPlay();
+            this.$refs.audioLine.onTimeupdateTime(end);
+            this.unWatch();
+            let i = index + 1;
+            if (i < this.lrcArray.length) {
+              return this.lrcPlay(this.lrcArray[i], i);
+            }
+            if (this.isRepeat) {
+              return this.lrcPlay(this.lrcArray[0], 0);
+            }
+            this.lrcArray = [];
+          }
+        });
+      });
+    },
+
+    // 暂停音频播放
+    handleParentPlay() {
+      this.stopAudio = true;
+    },
+    // 音频播放时改变布尔值
+    handleChangeStopAudio() {
+      this.stopAudio = false;
+    },
+
+    getCurTime(curTime) {
+      this.curTime = curTime;
+    },
+
+    getWavblob(wavblob) {
+      this.wavblob = wavblob;
+    },
+
+    getSelectData({ type, index, row, column }) {
+      if (type === "") return;
+      let arr = [];
+      if (type.length > 0 && index >= 0 && type === "row") {
+        this.curQue.voiceMatrix.matrix[index].forEach(item => {
+          let data = this.getLrcData(item);
+          if (data) arr.push(data);
+        });
+        this.matrixSelectLrc = arr;
+        return;
+      }
+
+      if (type.length > 0 && index >= 0 && type === "column") {
+        this.curQue.voiceMatrix.matrix.forEach(item => {
+          let data = this.getLrcData(item[index]);
+          if (data) arr.push(data);
+        });
+        this.matrixSelectLrc = arr;
+        return;
+      }
+
+      if (type === "cell" && row >= 0 && column >= 0) {
+        let lrcData = this.curQue.voiceMatrix.matrix[row][column].lrc_data;
+        if (lrcData.end_time === -1) lrcData.end_time = this.mp3Duration;
+        this.matrixSelectLrc = [lrcData];
+      }
+    },
+
+    getLrcData({ type, text, lrc_data }) {
+      if (
+        type === "SentenceSegwordChs" ||
+        type === "PinyinEnglish" ||
+        type === "textBrackets" ||
+        (type === "text" && text.length > 0)
+      ) {
+        if (lrc_data.end_time === -1) {
+          return {
+            begin_time: lrc_data.begin_time,
+            end_time: this.mp3Duration,
+            text: lrc_data.text
+          };
+        }
+        return lrc_data;
+      }
+      return false;
+    },
+
+    sentPause(isRecord) {
+      this.isRecord = isRecord;
+    },
+
+    handleChangeTime({ begin_time, end_time }) {
+      if (this.unWatch) this.unWatch();
+      this.handleParentPlay();
+      this.$nextTick(() => {
+        this.$refs.audioLine.onTimeupdateTime(begin_time / 1000);
+        this.$refs.audioLine.PlayAudio();
+        // 监听是否已到结束时间,为了选中效果 - 0.01
+        if (end_time === -1) return;
+        let end = end_time / 1000 - 0.01;
+        this.unWatch = this.$watch("curTime", val => {
+          if (val >= end) {
+            this.handleParentPlay();
+            this.$refs.audioLine.onTimeupdateTime(end);
+            this.unWatch();
+            this.unWatch = null;
+          }
+        });
+      });
+    },
+    /** 语音矩阵方法结束 **/
     setTopShow(bool) {
       this.isTopShow = bool;
     },
@@ -363,33 +870,27 @@ export default {
     getMicrophoneStatus(bool) {
       this.isRecording = bool;
     },
-    getWavblob(wavblob) {
-      this.wavblob = wavblob;
-    },
-    sentPause(isRecord) {
-      this.isRecord = isRecord;
-    },
-    getCurTime(curTime) {
-      if (this.isRepeat) {
-        let time = curTime * 1000;
-        if (time > this.ed || time < this.bg) {
-          this.curTime = this.bg;
-          this.$refs.audioLineSent.onTimeupdateTime(this.bg / 1000);
-        } else {
-          this.curTime = curTime * 1000;
-        }
-      } else if (this.isAuto) {
-        let time = curTime * 1000;
-        if (time > this.ed) {
-          this.curTime = this.bg;
-          this.$refs.audioLineSent.onTimeupdateTime(this.bg / 1000);
-        } else {
-          this.curTime = curTime * 1000;
-        }
-      } else {
-        this.curTime = curTime * 1000;
-      }
-    },
+    // getCurTime(curTime) {
+    //   if (this.isRepeat) {
+    //     let time = curTime * 1000;
+    //     if (time > this.ed || time < this.bg) {
+    //       this.curTime = this.bg;
+    //       this.$refs.audioLineSent.onTimeupdateTime(this.bg / 1000);
+    //     } else {
+    //       this.curTime = curTime * 1000;
+    //     }
+    //   } else if (this.isAuto) {
+    //     let time = curTime * 1000;
+    //     if (time > this.ed) {
+    //       this.curTime = this.bg;
+    //       this.$refs.audioLineSent.onTimeupdateTime(this.bg / 1000);
+    //     } else {
+    //       this.curTime = curTime * 1000;
+    //     }
+    //   } else {
+    //     this.curTime = curTime * 1000;
+    //   }
+    // },
     getCurCompareTime(curTime) {
       this.curTime = curTime * 1000;
     },
@@ -412,21 +913,25 @@ export default {
     changeFullScreen() {
       this.pauseAudio();
       this.$emit("changeIsFull");
-    },
-    handleWav(data) {},
-    // 录音时暂停音频播放
-    handleParentPlay() {
-      this.stopAudio = true;
-    },
-    // 音频播放时改变布尔值
-    handleChangeStopAudio() {
-      this.stopAudio = false;
     }
   }
 };
 </script>
 
 <style lang="scss" scoped>
+$select-color: #de4444;
+$border-color: #e6e6e6;
+
+$select-color-green: #24b99e;
+$select-color-green-bc: rgba(36, 185, 158, 0.25);
+$select-color-green-hover: #3dd4b8;
+$select-color-green-active: #1fa189;
+
+$select-color-brown: #bd8865;
+$select-color-brown-bc: rgba(189, 136, 101, 0.25);
+$select-color-brown-hover: #d6a687;
+$select-color-brown-active: #a37557;
+
 .voicefull {
   width: 100%;
   height: 100vh;
@@ -710,6 +1215,389 @@ export default {
     width: 100%;
     display: flex;
     align-items: center;
+
+    // 语音矩阵
+    .voice-matrix-container {
+      height: 100%;
+      width: 100%;
+      border-left: 1px solid $border-color;
+      border-right: 1px solid $border-color;
+      word-break: break-word;
+
+      .matrix {
+        display: inline-grid;
+        width: 100%;
+        height: 100%;
+
+        %matrix-checkbox {
+          position: relative;
+          top: calc(50% - 5px);
+          display: block;
+          width: 21px;
+          height: 21px;
+          border: 1.5px solid #b0b0b0;
+          border-radius: 4px;
+          margin: 0 auto;
+          cursor: pointer;
+
+          &.active {
+            border-color: $select-color;
+
+            &::after {
+              box-sizing: content-box;
+              content: "";
+              border: 1px solid $select-color;
+              border-left: 0;
+              border-top: 0;
+              height: 7px;
+              left: 4px;
+              position: absolute;
+              width: 3px;
+              transform: rotate(45deg) scaleY(1);
+              transition: transform 0.15s ease-in 0.05s;
+              transform-origin: center;
+            }
+          }
+        }
+
+        .matrix-checkbox-row-,
+        .matrix-checkbox-row-red {
+          @extend %matrix-checkbox;
+        }
+
+        .matrix-checkbox-row-green {
+          @extend %matrix-checkbox;
+
+          &.active {
+            border-color: $select-color-green-active;
+
+            &::after {
+              border-color: $select-color-green-active;
+            }
+          }
+        }
+
+        .matrix-checkbox-row-brown {
+          @extend %matrix-checkbox;
+
+          &.active {
+            border-color: $select-color-brown-active;
+
+            &::after {
+              border-color: $select-color-brown-active;
+            }
+          }
+        }
+
+        %matrix-checkbox-column,
+        .matrix-checkbox-column-,
+        .matrix-checkbox-column-red {
+          @extend %matrix-checkbox;
+
+          top: calc(50% - 7px);
+          right: -2px;
+        }
+
+        .matrix-checkbox-column-green {
+          @extend %matrix-checkbox-column;
+
+          &.active {
+            border-color: $select-color-green-active;
+
+            &::after {
+              border-color: $select-color-green-active;
+            }
+          }
+        }
+
+        .matrix-checkbox-column-brown {
+          @extend %matrix-checkbox-column;
+
+          &.active {
+            border-color: $select-color-brown-active;
+
+            &::after {
+              border-color: $select-color-brown-active;
+            }
+          }
+        }
+
+        .read {
+          background-color: #eaeaea;
+        }
+
+        .highlight-,
+        .highlight-red {
+          color: $select-color;
+        }
+
+        .highlight-green {
+          color: $select-color-green;
+        }
+
+        .highlight-brown {
+          color: $select-color-brown;
+        }
+
+        .column-wrapper {
+          padding: 4px;
+
+          %column {
+            width: 100%;
+            height: 100%;
+            min-height: 32px;
+            border-radius: 8px;
+            transition: 0.2s;
+            cursor: pointer;
+            user-select: none;
+
+            &:hover {
+              border-color: #8c8c8c;
+            }
+
+            &.selected {
+              color: $select-color;
+              border-color: $select-color;
+            }
+
+            &.playing {
+              background-color: #fee;
+            }
+
+            &.title {
+              background-color: transparent;
+              border-color: transparent;
+            }
+
+            > span {
+              display: inline-block;
+              padding: 4px 12px;
+              line-height: 24px;
+            }
+          }
+
+          %column-red,
+          .column-,
+          .column-red {
+            @extend %column;
+
+            position: relative;
+            font-family: "GB-PINYINOK-B", "FZJCGFKTK";
+
+            &::before {
+              display: inline-block;
+              content: "";
+              vertical-align: middle;
+            }
+          }
+
+          .column-green {
+            @extend %column-red;
+
+            &.selected {
+              color: $select-color-green;
+              border-color: $select-color-green;
+            }
+
+            &.playing {
+              background-color: $select-color-green-bc;
+            }
+          }
+
+          .column-brown {
+            @extend %column-red;
+
+            &.selected {
+              color: $select-color-brown;
+              border-color: $select-color-brown;
+            }
+
+            &.playing {
+              background-color: $select-color-brown-bc;
+            }
+          }
+
+          %sentence,
+          .sentence-,
+          .sentence-red {
+            @extend %column;
+
+            display: inline-grid;
+            padding: 4px 12px;
+            line-height: 24px;
+            column-gap: 8px;
+            justify-items: center;
+            justify-content: start;
+
+            > span {
+              padding: 0;
+            }
+
+            .pinyin {
+              font-family: "GB-PINYINOK-B";
+              opacity: 0.45;
+              font-size: 12px;
+              line-height: 20px;
+            }
+
+            .chs {
+              font-family: "FZJCGFKTK";
+              font-size: 16px;
+              line-height: 24px;
+            }
+          }
+
+          .sentence-green {
+            @extend %sentence;
+
+            &.selected {
+              color: $select-color-green;
+              border-color: $select-color-green;
+            }
+
+            &.playing {
+              background-color: $select-color-green-bc;
+            }
+          }
+
+          .sentence-brown {
+            @extend %sentence;
+
+            &.selected {
+              color: $select-color-brown;
+              border-color: $select-color-brown;
+            }
+
+            &.playing {
+              background-color: $select-color-brown-bc;
+            }
+          }
+
+          .connection {
+            position: relative;
+            top: calc(50% - 1px);
+            height: 2px;
+            width: 16px;
+            margin: 0 -4px;
+            border-radius: 4px;
+            background-color: #252525;
+
+            &.highlight-bc-,
+            &.highlight-bc-red {
+              background-color: $select-color;
+            }
+
+            &.highlight-bc-green {
+              background-color: $select-color-green;
+            }
+
+            &.highlight-bc-brown {
+              background-color: $select-color-brown;
+            }
+          }
+          // 拼音 + 文字
+          %pinyinEnglish,
+          .pinyinEnglish-,
+          .pinyinEnglish-red {
+            @extend %column;
+
+            .inside-wrapper {
+              padding: 4px 12px;
+
+              .pinyin {
+                font-family: "GB-PINYINOK-B";
+                font-size: 16px;
+                line-height: 24px;
+              }
+
+              .english {
+                font-family: "robot";
+                opacity: 0.45;
+                font-size: 12px;
+                line-height: 20px;
+              }
+            }
+          }
+
+          .pinyinEnglish-green {
+            @extend %pinyinEnglish;
+
+            &.selected {
+              color: $select-color-green;
+              border-color: $select-color-green;
+            }
+
+            &.playing {
+              background-color: $select-color-green-bc;
+            }
+          }
+
+          .pinyinEnglish-brown {
+            @extend %pinyinEnglish;
+
+            &.selected {
+              color: $select-color-brown;
+              border-color: $select-color-brown;
+            }
+
+            &.playing {
+              background-color: $select-color-brown-bc;
+            }
+          }
+
+          %textBrackets,
+          .textBrackets-,
+          .textBrackets-red {
+            @extend %column;
+
+            .brackets-text {
+              font-family: "GB-PINYINOK-B";
+            }
+
+            .brackets {
+              font-size: 16px;
+              font-family: "FZJCGFKTK";
+            }
+          }
+
+          .textBrackets-green {
+            @extend %textBrackets;
+
+            &.selected {
+              color: $select-color-green;
+              border-color: $select-color-green;
+            }
+
+            &.playing {
+              background-color: $select-color-green-bc;
+            }
+          }
+
+          .textBrackets-brown {
+            @extend %textBrackets;
+
+            &.selected {
+              color: $select-color-brown;
+              border-color: $select-color-brown;
+            }
+
+            &.playing {
+              background-color: $select-color-brown-bc;
+            }
+          }
+        }
+      }
+
+      .matrix-audio {
+        width: 228px;
+        height: 40px;
+        padding: 4px 4px 4px 16px;
+        margin: 24px 24px 0 0;
+        background-color: #fff;
+        border: 1px solid $border-color;
+        border-radius: 8px;
+      }
+    }
   }
 
   &-bottom {