فهرست منبع

Merge branch 'master' of http://gcls-git.helxsoft.cn/GCLS/eep_page

dusenyao 3 روز پیش
والد
کامیت
ba7a082d1f

+ 1 - 0
src/icons/svg/align-center.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753267268868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9914" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M96 128h832v96H96zM96 576h832v96H96zM224 352h576v96H224zM224 800h576v96H224z" p-id="9915"></path></svg>

+ 1 - 0
src/icons/svg/align-left.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753267257902" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8928" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M96 128h832v96H96zM96 576h832v96H96zM96 352h576v96H96zM96 800h576v96H96z" p-id="8929"></path></svg>

+ 1 - 0
src/icons/svg/align-right.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753267280460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10899" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M96 128h832v96H96zM96 576h832v96H96zM352 352h576v96H352zM352 800h576v96H352z" p-id="10900"></path></svg>

+ 1 - 0
src/icons/svg/font-bold.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753267187987" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5983" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M768.96 575.072c-22.144-34.112-54.816-56.8-97.984-68.032v-2.176c22.88-10.88 42.112-23.04 57.696-36.48 15.616-12.704 27.584-26.144 35.936-40.288 16.32-29.76 24.128-60.96 23.392-93.632 0-63.872-19.776-115.232-59.328-154.08-39.2-38.464-97.824-58.048-175.84-58.784H215.232v793.728H579.52c62.432 0 114.496-20.864 156.256-62.624 42.112-39.936 63.52-94.176 64.224-162.752 0-41.376-10.336-79.68-31.04-114.88zM344.32 228.832h194.912c43.904 0.736 76.224 11.424 96.896 32.128 21.056 22.144 31.584 49.184 31.584 81.12s-10.528 58.432-31.584 79.488c-20.672 22.848-52.992 34.304-96.896 34.304H344.32V228.832z m304.352 536.256c-20.672 23.584-53.344 35.744-97.984 36.48H344.32v-238.432h206.336c44.64 0.704 77.312 12.512 97.984 35.392 20.672 23.232 31.04 51.168 31.04 83.84 0 31.904-10.336 59.488-31.008 82.72z" p-id="5984"></path></svg>

+ 1 - 0
src/icons/svg/font-italic.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753267205844" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6955" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M768 85.792h-288a32 32 0 0 0 0 64h96.32l-230.336 704H256a32 32 0 0 0 0 64h288a32 32 0 0 0 0-64h-93.728l230.528-704H768a32 32 0 0 0 0-64z" p-id="6956"></path></svg>

+ 1 - 0
src/icons/svg/strikethrough.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753268732245" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12869" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024 511.81H687.11c-38.48-16.41-94.03-35.49-167.45-57.37-77.09-22.34-126.25-39.09-146.36-50.27-45.8-24.57-68.14-56.98-68.14-97.18 0-45.82 18.98-79.32 56.98-101.66 33.5-20.11 79.32-29.07 138.52-29.07 64.8 0 115.07 13.41 150.82 42.45 34.64 27.93 56.98 70.39 67.05 128.48H809c-7.82-83.77-37.98-147.45-91.61-189.91C666 115.94 594.5 95.83 505.14 95.83c-82.68 0-150.82 17.89-203.34 53.64-59.2 37.98-88.25 92.73-88.25 161.98 0 67.05 30.16 118.43 91.61 154.18 19.87 10.38 61.41 26.15 123.58 46.19H0v93.09h681.64c35.63 26.24 54.75 59.59 54.75 100.93 0 42.43-20.11 75.95-60.32 100.55-40.23 24.57-93.84 36.86-158.66 36.86-71.5 0-125.11-15.64-161.98-44.68-40.23-32.41-64.8-83.8-72.61-153.07h-90.5c6.7 98.32 41.34 170.93 103.91 218.98 53.61 40.2 127.34 60.32 221.18 60.32 94.98 0 169.82-20.11 225.68-59.2 55.86-40.23 83.8-96.09 83.8-165.34 0-35.82-8.24-67.53-24.42-95.34H1024v-93.11z" p-id="12870"></path></svg>

+ 1 - 0
src/icons/svg/underline.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753268044512" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11869" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 811.296a312 312 0 0 0 312-312V89.6h-112v409.696a200 200 0 1 1-400 0V89.6h-112v409.696a312 312 0 0 0 312 312zM864 885.792H160a32 32 0 0 0 0 64h704a32 32 0 0 0 0-64z" p-id="11870"></path></svg>

+ 209 - 0
src/views/book/courseware/create/components/question/table/Table.vue

@@ -0,0 +1,209 @@
+<template>
+  <ModuleBase :type="data.type">
+    <template #content>
+      <div class="fun-type">
+        <a
+          v-for="{ value, label } in tableTypeList"
+          :key="value"
+          :class="[data.mode === value ? 'active' : '']"
+          @click="data.mode = value"
+          >{{ label }}</a
+        >
+      </div>
+      <template v-if="data.mode === 'short'">
+        <div class="table-rich-toolbar">
+          <el-select v-model="data.styles.fontFamily" placeholder="请选择" style="width: 130px">
+            <el-option v-for="{ value, label } in fontFamilyList" :key="value" :label="label" :value="value" />
+          </el-select>
+          <el-select v-model="data.styles.fontSize" placeholder="请选择" style="width: 130px">
+            <el-option
+              v-for="(value, index) in 16"
+              :key="index"
+              :label="6 + value * 2 + 'pt'"
+              :value="6 + value * 2 + 'pt'"
+            />
+          </el-select>
+          <el-form :model="property" inline>
+            <el-form-item label="文字颜色">
+              <el-color-picker v-model="data.styles.fontColor" />
+            </el-form-item>
+            <el-form-item label="背景色">
+              <el-color-picker v-model="data.styles.bgColor" />
+            </el-form-item>
+          </el-form>
+          <span
+            :class="[data.styles.isUnderline ? 'active' : '']"
+            @click="data.styles.isUnderline = !data.styles.isUnderline"
+          >
+            <SvgIcon icon-class="underline" size="20" />
+          </span>
+          <span :class="[data.styles.isBold ? 'active' : '']" @click="data.styles.isBold = !data.styles.isBold">
+            <SvgIcon icon-class="font-bold" size="20" />
+          </span>
+          <span :class="[data.styles.isItalic ? 'active' : '']" @click="data.styles.isItalic = !data.styles.isItalic">
+            <SvgIcon icon-class="font-italic" size="20" />
+          </span>
+          <span
+            :class="[data.styles.isStrikethrough ? 'active' : '']"
+            @click="data.styles.isStrikethrough = !data.styles.isStrikethrough"
+          >
+            <SvgIcon icon-class="strikethrough" size="20" />
+          </span>
+          <span :class="[data.styles.textAlign === 'left' ? 'active' : '']" @click="data.styles.textAlign = 'left'">
+            <SvgIcon icon-class="align-left" size="20" />
+          </span>
+          <span :class="[data.styles.textAlign === 'center' ? 'active' : '']" @click="data.styles.textAlign = 'center'">
+            <SvgIcon icon-class="align-center" size="20" />
+          </span>
+          <span :class="[data.styles.textAlign === 'right' ? 'active' : '']" @click="data.styles.textAlign = 'right'">
+            <SvgIcon icon-class="align-right" size="20" />
+          </span>
+        </div>
+      </template>
+      <div class="option-list">
+        <div v-for="(item, i) in data.option_list" :key="i" class="table-node">
+          <div v-for="li in item" :key="li.mark" class="table-item">
+            <!-- eslint-disable max-len -->
+            <RichText
+              ref="richText"
+              v-model="li.content"
+              :inline="true"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+              v-if="data.mode === 'normal'"
+            />
+            <el-input v-else v-model="li.content"></el-input>
+          </div>
+        </div>
+      </div>
+    </template>
+  </ModuleBase>
+</template>
+
+<script>
+import ModuleMixin from '../../common/ModuleMixin';
+
+import { getTableData, getOption, tableTypeList, fontFamilyList } from '@/views/book/courseware/data/table';
+
+export default {
+  name: 'Table',
+  components: {},
+  mixins: [ModuleMixin],
+  data() {
+    return {
+      data: getTableData(),
+      tableTypeList,
+      fontFamilyList,
+    };
+  },
+  watch: {
+    'data.property.row_count': {
+      handler(val) {
+        if (val < this.data.option_list.length) {
+          this.data.option_list = this.data.option_list.slice(0, val);
+        } else {
+          const diff = val - this.data.option_list.length;
+          for (let i = 0; i < diff; i++) {
+            this.data.option_list.push(Array.from({ length: this.data.property.column_count }, getOption));
+          }
+        }
+      },
+    },
+    'data.property.column_count': {
+      handler(val) {
+        this.data.option_list = this.data.option_list.map((row) => {
+          if (val < row.length) {
+            return row.slice(0, val);
+          }
+          const diff = val - row.length;
+          return row.concat(Array.from({ length: diff }, getOption));
+        });
+      },
+    },
+  },
+  methods: {
+    // 思维导图数据
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.option-list {
+  margin-bottom: 12px;
+
+  .table-node {
+    display: flex;
+    row-gap: 16px;
+
+    .table-item {
+      flex: 1;
+      min-width: 75px;
+      padding: 8px;
+      border: 1px solid $border-color;
+
+      &:not(:last-child) {
+        border-right: 0;
+      }
+
+      :deep p {
+        margin: 0;
+      }
+    }
+  }
+}
+
+.fun-type {
+  display: flex;
+  gap: 5px;
+  width: 100%;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #e5e6eb;
+
+  a {
+    padding: 5px 10px;
+    font-weight: normal;
+    color: #1d2129;
+    cursor: pointer;
+    background: #f2f3f5;
+    border: 1px solid #f2f3f5;
+    border-radius: 2px;
+
+    &.active {
+      color: #165dff;
+      background: #e7eeff;
+      border-color: #165dff;
+    }
+  }
+}
+
+.table-rich-toolbar {
+  display: flex;
+  gap: 5px;
+  padding: 5px 0;
+
+  /* align-items: center; */
+
+  :deep .el-form-item--small.el-form-item {
+    margin: 0 5px 0 0;
+  }
+
+  :deep .el-form-item__label {
+    padding-right: 3px;
+  }
+
+  > span {
+    height: 30px;
+    padding: 5px 6px;
+    color: #222f3e;
+    cursor: pointer;
+    border-radius: 5px;
+
+    &:hover {
+      background: #cce2fa;
+    }
+
+    &.active {
+      background: #a6ccf7;
+    }
+  }
+}
+</style>

+ 65 - 0
src/views/book/courseware/create/components/question/table/TableSetting.vue

@@ -0,0 +1,65 @@
+<template>
+  <div>
+    <el-form :model="property" label-width="72px">
+      <SerailNumber :property="property" />
+
+      <el-form-item label="宽度">
+        <el-input-number v-model="property.width" :min="1" :step="50" />
+      </el-form-item>
+      <el-form-item label="高度">
+        <el-input-number v-model="property.height" :min="1" :step="50" />
+      </el-form-item>
+      <el-form-item label="行数">
+        <el-input-number v-model="property.row_count" :min="1" />
+      </el-form-item>
+      <el-form-item label="列数">
+        <el-input-number v-model="property.column_count" :min="1" />
+      </el-form-item>
+      <el-form-item label="自动换行">
+        <el-radio-group v-model="property.auto_wrap">
+          <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">{{ label }}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-divider />
+      <el-form-item label="首行颜色">
+        <el-color-picker v-model="property.first_line_color" />
+      </el-form-item>
+      <el-form-item label="首列颜色">
+        <el-color-picker v-model="property.first_column_color" />
+      </el-form-item>
+      <el-form-item label="边框颜色">
+        <el-color-picker v-model="property.border_color" />
+      </el-form-item>
+      <el-form-item label="装饰颜色">
+        <el-color-picker v-model="property.decoration_color" />
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
+
+import { getTableProperty, switchOption, alignTypeList } from '@/views/book/courseware/data/table';
+
+export default {
+  name: 'TableSetting',
+  mixins: [SettingMixin],
+  data() {
+    return {
+      property: getTableProperty(),
+      switchOption,
+      alignTypeList,
+    };
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.el-form {
+  @include setting-base;
+}
+</style>

+ 11 - 0
src/views/book/courseware/data/bookType.js

@@ -65,6 +65,8 @@ import VideoInteraction from '../create/components/question/video_interaction/Vi
 import VideoInteractionSetting from '../create/components/question/video_interaction/VideoInteractionSetting.vue';
 import ThreeModel from '../create/components/base/3d_model/3DModel.vue';
 import ThreeModelSetting from '../create/components/base/3d_model/3DModelSetting.vue';
+import Table from '../create/components/question/table/Table.vue';
+import TableSetting from '../create/components/question/table/TableSetting.vue';
 
 // 预览组件页面列表
 import AudioPreview from '@/views/book/courseware/preview/components/audio/AudioPreview.vue';
@@ -101,6 +103,7 @@ import H5GamesPreview from '../preview/components/h5_games/H5GamesPreview.vue';
 import DrawingPreview from '../preview/components/drawing/DrawingPreview.vue';
 import VideoInteractionPreview from '../preview/components/video_interaction/VideoInteractionPreview.vue';
 import ThreeModelPreview from '../preview/components/3d_model/3DModelPreview.vue';
+import TablePreview from '../preview/components/table/TablePreview.vue';
 
 export const bookTypeOption = [
   {
@@ -354,6 +357,14 @@ export const bookTypeOption = [
         set: ImageTextSetting,
         preview: ImageTextPreview,
       },
+        {
+        value: 'table',
+        label: '表格',
+        icon: '',
+        component: Table,
+        set: TableSetting,
+        preview: TablePreview,
+      },
       {
         value: 'pinyin_base',
         label: '拼音组件',

+ 75 - 0
src/views/book/courseware/data/table.js

@@ -0,0 +1,75 @@
+import {
+  displayList,
+  serialNumberTypeList,
+  serialNumberPositionList,
+  switchOption,
+} from '@/views/book/courseware/data/common';
+import { getRandomNumber } from '@/utils';
+export { switchOption };
+
+export const tableTypeList = [
+  { value: 'short', label: '精简模式' },
+  { value: 'normal', label: '常规模式' },
+];
+
+export const fontFamilyList = [
+  { value: '楷体,微软雅黑', label: '楷体' },
+  { value: '黑体,微软雅黑', label: '黑体' },
+  { value: '宋体,微软雅黑', label: '宋体' },
+  { value: 'arial,helvetica,sans-serif', label: 'Arial' },
+  { value: 'times new roman,times,serif', label: 'Times New Roman' },
+  { value: 'League', label: '拼音' },
+];
+
+
+export function getTableProperty() {
+  return {
+    serial_number: 1,
+    sn_type: serialNumberTypeList[0].value,
+    sn_position: serialNumberPositionList[3].value,
+    sn_display_mode: displayList[0].value,
+
+    height: 400,
+    width: 600,
+    row_count: 2,
+    column_count: 3,
+    auto_wrap: switchOption[0].value, // 自动换行
+    first_line_color: '#fff', // 首行颜色
+    first_column_color: '#fff', // 首列颜色
+    border_color: '#e6e6e6', // 边框颜色
+    decoration_color: '#e7b576', // 装饰颜色
+  };
+}
+
+export function getOption() {
+  return {
+    content: '',
+    mark: getRandomNumber(),
+    
+  };
+}
+
+export function getTableData() {
+  return {
+    type: 'table',
+    title: '表格',
+    option_list: Array.from({ length: 2 }, () => Array.from({ length: 3 }, getOption)),
+    record_list: [],
+    mode: tableTypeList[0].value,
+    styles: {
+      fontFamily: 'Arial',
+      fontSize: '12pt',
+      fontColor: '#1d2129',
+      bgColor: '',
+      isUnderline: false,
+      isBold: false,
+      isItalic: false,
+      isStrikethrough: false,
+      textAlign: ''
+    },
+    property: getTableProperty(),
+    mind_map: {
+      node_list: [{ name: '表格' }],
+    },
+  };
+}

+ 393 - 0
src/views/book/courseware/preview/components/table/TablePreview.vue

@@ -0,0 +1,393 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="table-preview" :style="getAreaStyle()">
+    <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
+
+    <div class="main">
+      <table
+        border
+        :style="{
+          width: data.property.width + 'px',
+          height: data.property.height + 'px',
+          border: '1px solid ' + data.property.border_color,
+        }"
+      >
+        <tr v-for="(row, i) in data.option_list" :key="`tr-${i}`">
+          <template v-for="col in row">
+            <td
+              :key="col.mark"
+              :style="{
+                border: '1px solid ' + data.property.border_color,
+              }"
+            >
+              <div :style="[tdStyle]" class="cell-wrap" v-html="col.content"></div>
+            </td>
+          </template>
+        </tr>
+      </table>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getTableData } from '@/views/book/courseware/data/table';
+import { getRandomNumber } from '@/utils/index.js';
+
+import PreviewMixin from '../common/PreviewMixin';
+
+export default {
+  name: 'TablePreview',
+  components: {},
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      data: getTableData(),
+      cid: getRandomNumber(),
+      themeColor: 'red',
+      curTime: 0,
+      playing: false,
+      stopAudio: true,
+      unWatch: null,
+      lrcArray: [],
+      fileName: '',
+      // 底色行、列
+      selectRow: -1,
+      selectColumn: -1,
+      // 行、列选中
+      selectedLine: {
+        type: '',
+        index: 0,
+      },
+      // 点击选中
+      selectCell: {
+        row: -1,
+        column: -1,
+      },
+      isRepeat: false,
+      // 跟读所需属性
+      wavblob: null,
+      isRecord: false,
+      matrixSelectLrc: null,
+    };
+  },
+  computed: {
+    tdStyle() {
+      let obj = {};
+      if (this.data.mode === 'short') {
+        let styles = this.data.styles;
+        obj = {
+          fontFamily: styles.fontFamily,
+          fontSize: styles.fontSize,
+          fontColor: styles.fontColor,
+          backgroundColor: styles.bgColor,
+          textDecoration: styles.isUnderline ? 'underline' : styles.isStrikethrough ? 'line-through' : '',
+          fontWeight: styles.isBold ? 'bold' : '',
+          fontStyle: styles.isItalic ? 'italic' : '',
+          textAlign: styles.textAlign,
+        };
+      }
+      return obj;
+    },
+  },
+  watch: {},
+  created() {},
+  mounted() {},
+  beforeDestroy() {},
+  methods: {
+    handleWav(data) {
+      this.data.record_list = data;
+    },
+    // 鼠标移入移出
+    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.data.option_list[row][column].lrc_data);
+      // 设置录音文件名
+      this.setRecordingFileName(row, column);
+    },
+
+    setRecordingFileName(row, column) {
+      return `录音第${column}列第${row}行`;
+    },
+
+    /**
+     * 判断 click 点击是否语音矩阵可操作区域
+     * @param {PointerEvent} event
+     */
+    restoreAudioStatus(event) {
+      const whitePath = [
+        'column-green',
+        'column-red',
+        'column-brown',
+        'matrix-checkbox-column-',
+        'matrix-checkbox-row-',
+        'audio-simple-image',
+        'audio-simple-repeat',
+        'luyin-box',
+      ];
+      const operable = event.composedPath().some((item) => {
+        const className = item.className;
+        if (!className || typeof className !== 'string') 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.data.option_list[index].forEach((item, i) => {
+          if (i >= index) return;
+        });
+      }
+      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 = [];
+      const { type, index } = this.selectedLine;
+
+      if (type.length > 0 && index >= 0 && type === 'row') {
+        this.data.option_list[index].forEach((item) => {
+          const 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.data.option_list.forEach((item) => {
+          const data = this.getLrcData(item[index]);
+          if (data) this.lrcArray.push(data);
+        });
+        if (this.lrcArray.length > 0) this.lrcPlay(this.lrcArray[0], 0);
+        return;
+      }
+
+      const { row, column } = this.selectCell;
+      if (row >= 0 && column >= 0) {
+        this.handleChangeTime(this.data.option_list[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;
+        const 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();
+            const 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 = [];
+          }
+        });
+      });
+    },
+
+    playChange(playing) {
+      this.playing = playing;
+      // 子组件通信,同时只能播放一个音频
+      if (playing) Bus.$emit('audioPause', this.cid);
+    },
+
+    pauseOtherAudio() {
+      Bus.$emit('audioPause', this.cid);
+      this.stopAudio = true;
+    },
+
+    // 暂停音频播放
+    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;
+      const arr = [];
+      if (type.length > 0 && index >= 0 && type === 'row') {
+        this.data.option_list[index].forEach((item) => {
+          const data = this.getLrcData(item);
+          if (data) arr.push(data);
+        });
+        this.matrixSelectLrc = arr;
+        return;
+      }
+
+      if (type.length > 0 && index >= 0 && type === 'column') {
+        this.data.option_list.forEach((item) => {
+          const data = this.getLrcData(item[index]);
+          if (data) arr.push(data);
+        });
+        this.matrixSelectLrc = arr;
+        return;
+      }
+
+      if (type === 'cell' && row >= 0 && column >= 0) {
+        const lrcData = this.data.option_list[row][column].lrc_data;
+        if (lrcData.end_time === -1) lrcData.end_time = this.mp3Duration;
+        this.matrixSelectLrc = [lrcData];
+      }
+    },
+
+    getLrcData({ content, lrc_data }) {
+      if (content.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;
+        const 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;
+            if (this.isRepeat) {
+              this.handleChangeTime({ begin_time, end_time });
+            }
+          }
+        });
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+$select-color: #1890ff;
+$border-color: #e6e6e6;
+
+.table-preview {
+  @include preview-base;
+
+  .main {
+    color: #262626;
+
+    table {
+      border-spacing: 3px;
+      border-collapse: separate;
+    }
+  }
+}
+
+.NNPE-tableList-tr-last {
+  .voice-matrix {
+    padding-bottom: 0;
+  }
+}
+</style>
+
+<style lang="scss">
+.voice-matrix {
+  &-audio {
+    .audioLine {
+      border-radius: 8px 8px 0 0 !important;
+    }
+
+    .el-slider {
+      width: 100% !important;
+    }
+  }
+
+  .luyin-box {
+    .el-select .el-input {
+      width: 136px;
+    }
+  }
+}
+</style>