Flex
2025-06-05 43f95dc1acae0b23febd0d602fbccbf9762c30f3
修改声音选择
已修改5个文件
已添加2个文件
523 ■■■■■ 文件已修改
easegen-front/src/assets/imgs/sound-active.png 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/assets/imgs/sound.png 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/locales/courseCenter/en.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/locales/courseCenter/zh.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/locales/en.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/locales/zh-CN.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/views/chooseTemplate/index.vue 519 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/assets/imgs/sound-active.png
easegen-front/src/assets/imgs/sound.png
easegen-front/src/locales/courseCenter/en.ts
@@ -113,6 +113,7 @@
    soundDriven:'Sound driven',
    template:'template',
    digitalPeople:'Digital people',
    sound:'Sound',
    myModel:'My Model',
easegen-front/src/locales/courseCenter/zh.ts
@@ -112,6 +112,7 @@
    soundDriven:'声音驱动',
    template:'模板',
    digitalPeople:'数字人',
    sound:'声音',
    myModel:'我的模型',
    name:'名称',
easegen-front/src/locales/en.ts
@@ -14,6 +14,7 @@
    loginOutMessage: 'Exit the system?',
    back: 'Back',
    ok: 'OK',
    originalSound:"Original Sound",
    save: 'Save',
    cancel: 'Cancel',
    close: 'Close',
easegen-front/src/locales/zh-CN.ts
@@ -14,6 +14,7 @@
    loginOutMessage: '是否退出本系统?',
    back: '返回',
    ok: '确定',
    originalSound:"原声",
    save: '保存',
    cancel: '取消',
    close: '关闭',
easegen-front/src/views/chooseTemplate/index.vue
@@ -174,9 +174,12 @@
          <div class="list">
            <div
              class="main-image-box"
              :style="{ width: viewSize.width + 'px', height: viewSize.height + 'px',position: 'relative' }"
              :style="{
                width: viewSize.width + 'px',
                height: viewSize.height + 'px',
                position: 'relative'
              }"
            >
              <!-- 背景(必显示) -->
              <el-image
                v-show="selectPPT.pictureUrl && selectPPT.digitalHuman.show==false"
@@ -376,7 +379,13 @@
        </div>
        <div v-if="selectPPT.driverType == 1" style="position: relative">
          <div class="middle-textarea">
            <Editor style="height: 196px; overflow-y: hidden;" v-model="selectPPT.pptRemark" :defaultConfig="editorConfig" mode="simple" @on-created="handleCreated" />
            <Editor
              style="height: 196px; overflow-y: hidden"
              v-model="selectPPT.pptRemark"
              :defaultConfig="editorConfig"
              mode="simple"
              @on-created="handleCreated"
            />
          </div>
          <div class="tool-box">
            <div class="tool-btn">
@@ -423,6 +432,7 @@
          </el-tooltip>
        </div>
      </div>
      <!-- 数字人 -->
      <div class="template-box template-right" v-if="showDigitalHumanTool">
        <div class="tabs-1">
          <div
@@ -505,7 +515,7 @@
            :key="index"
            :style="{
                width: '90%',
                maxWidth: '90%',
              maxWidth: '90%'
              }"
            @click="handleTemplateSelection(template)"
          >
@@ -519,6 +529,90 @@
<!--        <div class="apply-all">-->
<!--          <el-checkbox v-model="applyAllTemplate" :label="t('courseCenter.uploadAudio')" />-->
<!--        </div>-->
      </div>
      <!-- 声音 -->
      <div class="template-box template-right" v-if="SoundTool">
        <div class="SoundArea">
          <div class="SoundClassArea">
            <!-- 语种 -->
            <div>
              <el-select
                v-model="selectLanguage.value"
                placeholder="请选择语种"
                @change="LanguageChange"
              >
                <el-option
                  v-for="item in languageList"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </div>
            <!-- 性别 -->
            <div>
              <el-select v-model="changeAudio" placeholder="请选择性别" @change="AudioChange()">
                <el-option
                  v-for="item in audioType"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </div>
            <!-- 类型 -->
            <div>
              <el-select
                v-model="ChangeSoundTypeList.value"
                placeholder="请选择声音类型"
                @change="SoundTypeChange"
              >
                <el-option
                  v-for="item in SoundTypeList"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </div>
          </div>
          <div class="SoundModelArea" v-loading="soundLoading">
            <div
              class="ModealBox"
              v-for="(item, index) in audioList"
              :key="index"
              @click="handleSelect(item)"
              @mouseenter="handleMouseenter(item)"
              @mouseleave="handleMouseleave(item)"
              :class="item.isSelect ? 'slectModel' : ''"
            >
              <div class="ImgBox">
                <img :src="item.avatarUrl" alt="" />
              </div>
              <div class="TextArea">
                <p> {{ item.name }} </p>
                <p> {{ item.introduction }} </p>
              </div>
              <img
                class="play-img"
                v-if="item.isHover && !item.isPlay"
                src="@/assets/imgs/play.png"
                alt=""
                @click.stop="playAudio(item)"
              />
              <img
                class="play-img"
                v-if="item.isHover && item.isPlay"
                src="@/assets/imgs/pause.png"
                alt=""
                @click.stop="SoundpauseAudio(item)"
              />
            </div>
          </div>
          <div class="ButtonArea">
            <el-button type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
          </div>
        </div>
      </div>
      <!-- 背景设置 -->
      <div class="template-box template-right" v-if="showHeadImageTool">
@@ -553,8 +647,20 @@
    <mergeWarningDialog ref="warningDialog" />
    <ReplaceDialog ref="replaceDialog" :ppt-arr="PPTArr" @submit="handleReplacement" />
    <!-- 多音字 -->
    <el-dialog v-model="dialogVisible" title="点击需要纠正的多音字,选择正确的发音" width="500" @close="dialogVisible = false">
      <el-tag v-for="(item, index) in textList" :key="index" type="primary" effect="dark" style="margin-right: 10px;cursor: pointer;" @click="handleTag(item)">
    <el-dialog
      v-model="dialogVisible"
      title="点击需要纠正的多音字,选择正确的发音"
      width="500"
      @close="dialogVisible = false"
    >
      <el-tag
        v-for="(item, index) in textList"
        :key="index"
        type="primary"
        effect="dark"
        style="margin-right: 10px; cursor: pointer"
        @click="handleTag(item)"
      >
        {{ item }}
      </el-tag>
    </el-dialog>
@@ -594,6 +700,8 @@
import templateActive from '@/assets/imgs/template-active.png'
import user from '@/assets/imgs/user.png'
import userActive from '@/assets/imgs/user-active.png'
import sound from '@/assets/imgs/sound.png'
import soundActive from '@/assets/imgs/sound-active.png'
import bg from '@/assets/imgs/bg.png'
import bgActive from '@/assets/imgs/bg-active.png'
import innerPicture from '@/assets/imgs/inner-picture.png'
@@ -622,6 +730,8 @@
import { polyphonic } from 'pinyin-pro'
import { useEditorHtml } from '@/hooks/web/useEditorHtml'
import { ElMessage, ElMessageBox } from 'element-plus'
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
import { measureMemory } from 'vm'
const editorHtml = useEditorHtml()
const router = useRouter()
@@ -670,7 +780,7 @@
  const pagesToUpdate = applyAllHost.value ? PPTArr.value : [selectPPT.value]
  pagesToUpdate.forEach(page => {
  pagesToUpdate.forEach((page) => {
    page.digitalHuman.host = host
    initHumanPositon(host, page.digitalHuman)
  })
@@ -812,6 +922,13 @@
    activeUrl: userActive,
    isActive: false
  },
  // 声音类型
  {
    name: t('courseCenter.sound'),
    url: sound,
    activeUrl: soundActive,
    isActive: false
  }
  // {
  //   name: t('courseCenter.background'),
  //   url: bg,
@@ -828,6 +945,8 @@
const showHeadImageTool = ref(false)
const showDigitalHumanTool = ref(false)
// 声音
const SoundTool = ref(false)
const showTemplateTool = ref(false)
const showInnerPictureTool = ref(false)
const applyAllTemplate = ref(false)
@@ -865,11 +984,235 @@
    tabs4ActiveNum.value = '2'
    queryParams1.zg=tabs4ActiveNum.value
    getList1()
  } else if (item.name == t('courseCenter.sound')) {
    // 声音的处理       selectAudio
    // 获取语言种类
    getLanguageList()
    //获取性别种类
    getAudioType()
    // 获取声音类别
    getVoiceType()
    // 获取模型列表
    getSoundModelList()
    // 获取可选的声音类型列表
    GetSoundTypeList()
  }
  showHeadImageTool.value = item.name === t('courseCenter.background')
  showTemplateTool.value = item.name === t('courseCenter.template')
  showDigitalHumanTool.value = item.name === t('courseCenter.digitalPeople')
  SoundTool.value = item.name === t('courseCenter.sound')
  showInnerPictureTool.value = item.name === t('courseCenter.pictureInPicture')
}
// 当前选择的语种
const selectLanguage = ref<any>()
// 可选的语种列表
const languageList = ref()
// 获取语言字典
const getLanguageList = () => {
  let res = getStrDictOptions(DICT_TYPE.DIGITALCOURSE_VOICES_LANGUAGE)
  languageList.value = res
  selectLanguage.value = { ...languageList.value[0] }
}
// 可选的性别列表
const audioType = ref()
// 当前选择的性别
const changeAudio = ref<any>({ value: '' })
//获取性别字典
const getAudioType = () => {
  const list = getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)
  audioType.value = list
  changeAudio.value = list[0].value
}
// 可选的声音类别列表
const SoundTypeList = ref()
// 当前选择的声音类别
const ChangeSoundTypeList = ref()
// 获取可选的声音类型列表
const GetSoundTypeList = () => {
  const res = getIntDictOptions(DICT_TYPE.DIGITALCOURSE_VOICES_TYPE)
  SoundTypeList.value = res
  ChangeSoundTypeList.value = { ...res[0] }
}
//可选的声音模型的声音类别列表
const SoundvoiceTypeList = ref()
// 当前选择的声音模型的声音类别
const activeSoundType = ref<any>({ value: '' })
//获取的声音模型的声音类别
const getVoiceType = () => {
  const list1 = getIntDictOptions(DICT_TYPE.DIGITALCOURSE_VOICES_TYPE)
  SoundvoiceTypeList.value = list1
  activeSoundType.value = { ...list1[0] }
}
// 获取声音模型列表请求参数
const soundQueryParams = reactive({
  pageNo: 1,
  pageSize: 32,
  language: '',
  gender: '',
  voiceType: '',
  status: 0 // 状态:0正常,1异常
})
// 可选择的声音模型列表
const audioList = ref()
// 是否正在加载模型列表
const soundLoading = ref(false)
// 获取声音模型列表
const getSoundModelList = async () => {
  try {
    soundLoading.value = true
    // 语言类型
    soundQueryParams.language = selectLanguage?.value.value ?? ''
    // 性别
    soundQueryParams.gender = changeAudio?.value ?? ''
    // 声音类型
    soundQueryParams.voiceType = activeSoundType?.value.value ?? ''
    const data = await pptTemplateApi.videlPageList(soundQueryParams)
    data.list.forEach((item) => {
      item.isHover = false
      item.isPlay = false
      item.isSelect = false
    })
    audioList.value = data.list
    total.value = data.total
  } finally {
    soundLoading.value = false
  }
}
// 语种选择
const LanguageChange = (event) => {
  languageList.value.forEach((element) => {
    if (element.value === event) {
      selectLanguage.value = { ...element }
    }
  })
  getSoundModelList()
}
// 性别选择
const AudioChange = () => {
  getSoundModelList()
}
// 类别选择
const SoundTypeChange = (event) => {
  SoundTypeList.value.forEach((element) => {
    if (element.value === event) {
      ChangeSoundTypeList.value = { ...element }
    }
  })
  getSoundModelList()
}
//选择声音模型
const selectList = ref()
const handleSelect = (item) => {
  selectList.value = [item]
  audioList.value.forEach((child) => {
    if (child.id == item.id) {
      child.isSelect = true
    } else {
      child.isSelect = false
    }
  })
}
// 确定按钮点击处理函数
const submitForm = () => {
  if (ChangeSoundTypeList.value.value === 2) {
    //此时为通用
    if (activeSoundType.value === 1 && selectList.value.length === 0) {
      message.warning('请选择声音模型')
      return false
    }
    selectAudio(selectList.value)
  } else if (ChangeSoundTypeList.value.value === 1) {
    // 清除选中的音频
    selectList.value = null
    // 清除列表中所有选中状态
    if (audioList.value) {
      audioList.value.forEach((item) => {
        item.isSelect = false
      })
    }
    // 停止当前播放的音频
    if (SoundcurrentAudio.value) {
      SoundcurrentAudio.value.pause()
      SoundcurrentAudio.value = null
    }
    // 重置当前播放状态
    if (SoundcurrentlyPlaying.value) {
      SoundcurrentlyPlaying.value.isPlay = false
      SoundcurrentlyPlaying.value = null
    }
    selectAudio(undefined)
  }
}
// 鼠标移入与移出
const handleMouseenter = (item) => {
  audioList.value.forEach((child) => {
    if (child.id == item.id) {
      child.isHover = true
    }
  })
}
const handleMouseleave = (item) => {
  audioList.value.forEach((child) => {
    if (child.id == item.id) {
      child.isHover = false
    }
  })
}
// 音频管理
const SoundcurrentAudio = ref<HTMLAudioElement | null>(null)
const SoundcurrentlyPlaying = ref<any>(null)
const playAudio = async (item: any) => {
  // 如果点击的是当前正在播放的项目,则暂停
  if (SoundcurrentlyPlaying.value && SoundcurrentlyPlaying.value.id === item.id) {
    SoundpauseAudio(item)
    return
  }
  // 停止当前播放的音频
  if (SoundcurrentAudio.value) {
    SoundcurrentAudio.value.pause()
    SoundcurrentAudio.value = null
  }
  // 如果之前有播放的项目,重置其状态
  if (SoundcurrentlyPlaying.value) {
    SoundcurrentlyPlaying.value.isPlay = false
  }
  // 设置新的播放项目
  SoundcurrentlyPlaying.value = item
  item.isPlay = true
  // 创建新的音频对象并播放
  SoundcurrentAudio.value = new Audio(item.auditionUrl)
  SoundcurrentAudio.value.play()
  // 添加播放结束事件监听
  SoundcurrentAudio.value.addEventListener('ended', () => {
    item.isPlay = false
    SoundcurrentlyPlaying.value = null
    SoundcurrentAudio.value = null
  })
}
const SoundpauseAudio = (item: any) => {
  if (
    SoundcurrentAudio.value &&
    SoundcurrentlyPlaying.value &&
    SoundcurrentlyPlaying.value.id === item.id
  ) {
    SoundcurrentAudio.value.pause()
    item.isPlay = false
    SoundcurrentlyPlaying.value = null
  }
}
const PPTArr = ref([])
@@ -1154,45 +1497,42 @@
}
const copyDocument = (item, index) => {
  ElMessageBox.confirm(
    '是否复制该页面?',
    '提示',
    {
  ElMessageBox.confirm('是否复制该页面?', '提示', {
      confirmButtonText: '是',
      cancelButtonText: '否',
      type: 'warning',
    }).then(() => {
    type: 'warning'
  })
    .then(() => {
    let copyItem = cloneDeep(item)
    copyItem.id = generateUUID()
    copyItem.isActive = false
    // 深拷贝数字人配置
    copyItem.digitalHuman = {...item.digitalHuman}
    PPTArr.value.splice(index + 1, 0, copyItem)
  }).catch(() => {
    })
    .catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消复制',
        message: '已取消复制'
    })
  })
}
const deleteDocument = (item) => {
  ElMessageBox.confirm(
    '是否删除该页面?',
    '提示',
    {
  ElMessageBox.confirm('是否删除该页面?', '提示', {
      confirmButtonText: '是',
      cancelButtonText: '否',
      type: 'warning',
    }
  ).then(() => {
    type: 'warning'
  })
    .then(() => {
    PPTArr.value = PPTArr.value.filter((child) => child.id !== item.id)
    //已经进行过删除操作
    DeleteD.value = true
  }).catch(() => {
    })
    .catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消删除',
        message: '已取消删除'
    })
  })
}
@@ -1295,6 +1635,7 @@
}
const selectAudio = (data) => {
  console.log(data)
  audioSelectData.value = data
  if (data == undefined) {
    selectPPT.value.selectAudio.name = ''
@@ -1354,18 +1695,16 @@
const removeHtmlTags = (html) => {
  const parser = new DOMParser()
  const doc = parser.parseFromString(html, 'text/html')
  return doc.body.textContent || ""
  return doc.body.textContent || ''
}
const saveSubmit = async (type) => {
  console.log( "是否删除", DeleteD.value )
  console.log('是否删除', DeleteD.value)
  if (!PPTArr.value || PPTArr.value.length === 0) {
    message.warning('场景为空,请先上传PPT!')
    return false
  }
  //人脸校验
  while(!IsEndCheckFace.value){} //一个空循环,主要为了避免极端情况下当用户点击保存按钮或者视频合成按钮时,人脸校验未完成的问题
@@ -1373,8 +1712,6 @@
    message.warning('当前ppt中存在人脸元素,为方便后续视频生成,请去除该元素')
    return
  }
  //保存课程
  let saveSubmitForm = {
@@ -1453,7 +1790,9 @@
            marker: 1,
            status: item.digitalHuman?.show ? 0 : 1
          },
          ...(item.innerPicture?.src ? [{
          ...(item.innerPicture?.src
            ? [
                {
            ...cloneDeep(item.innerPicture),
            width: item.innerPicture.width * scaleRatio.value.width,
            height: item.innerPicture.height * scaleRatio.value.height,
@@ -1461,7 +1800,9 @@
            marginLeft: item.innerPicture.marginLeft * scaleRatio.value.width,
            category: 1,
            id: undefined
          }] : [])
                }
              ]
            : [])
        ],
        driverType: item.driverType,
        duration: '',
@@ -1472,7 +1813,7 @@
          speech_rate: voiceData.speechRate,
          volume: voiceData.volume,
          smartSpeed: '',
          textJson: item.pptRemark,
          textJson: item.pptRemark
        },
        audioDriver: {
          fileName: item.fileList && item.fileList[0]?.name,
@@ -1503,7 +1844,6 @@
  saveSubmitForm.scenes = cloneDeep(scenes)
  if (type == 'save') {
    if( DeleteD.value ){
      //如果进行过ppt删除操作则需要进行二次查看
      await PPtIsHaveFace()
@@ -1528,6 +1868,11 @@
    }
  } else {
    try {
      if (ChangeSoundTypeList.value?.value === undefined || selectLanguage.value?.value === undefined) {
        message.error('请先选择语种与声音类型')
        return
      }
      const saveResult = await saveSubmit('save')
      if (!saveResult) {
        message.error('保存失败,请重试后再合成视频')
@@ -1538,8 +1883,8 @@
      for (let i = 0; i < PPTArr.value.length; i++) {
        const item = PPTArr.value[i]
        console.log(item)
        console.log( "宽度", item.width )
        console.log( "高度", item.height )
        console.log('宽度', item.width)
        console.log('高度', item.height)
        // 校验背景宽高
        if (!item.width || !item.height) {
          message.warning('背景尺寸无效,请检查宽高设置,或者重新选择模板')
@@ -1606,7 +1951,7 @@
          originHeight: courseInfo.value.height,
          originWidth: courseInfo.value.width,
          entityId: 1,
          templateId: template.id,
          templateId: template.id
        }
      }
    } else {
@@ -1665,6 +2010,11 @@
const currentAudio = ref()
const createAudio = async () => {
  if (ChangeSoundTypeList.value?.value === undefined || selectLanguage.value?.value === undefined) {
    message.error('请先选择语种与声音类型')
    return
  }
  const text = editorRef.value.getText()
  if (!text) {
    message.warning('请输入需要试听文本的内容…')
@@ -1677,7 +2027,7 @@
    text: truncatedText,
    humanId: selectPPT.value.digitalHuman?.host?.id || null,
    // voiceId: audioSelectData.value == undefined ? null : audioSelectData.value[0].id,
    voiceId: 'zh-CN',
    voiceId: 'zh-CN'
  }
  try {
@@ -1799,12 +2149,12 @@
  // 限制坐标
  if (data.x < -100) {
    data.x = -100; // 可以设置最小坐标为 -100
    data.x = -100 // 可以设置最小坐标为 -100
  }
  if (data.y < -100) {
    data.y = -100; // 可以设置最小坐标为 -100
    data.y = -100 // 可以设置最小坐标为 -100
  }
};
}
const getCourseDetail = async (id) => {
  const res = await pptTemplateApi.coursesDetail(id)
@@ -1831,7 +2181,7 @@
            h: hostInfo.height / scaleRatio.value.height,
            active: false,
            host: {
              ...hostList.value.find(h => h.code === hostInfo.entityId),
              ...hostList.value.find((h) => h.code === hostInfo.entityId),
              code: hostInfo.entityId,
              type: hostInfo.entityType
            }
@@ -1901,7 +2251,6 @@
    }))
    templates.value = TEMPLATE_PRESETS.value.map((template) => cloneDeep(template))
    selectTemplate.value = cloneDeep(templates.value[0])
  }
}
onMounted(async () => {
@@ -2455,4 +2804,88 @@
.dialog-footer{
  float: right;
}
// 声音部分
.SoundArea {
  margin-top: 10px;
  height: 100%;
  .SoundClassArea {
    width: 100%;
    display: flex;
    justify-content: space-around;
    align-items: center;
    > div {
      width: 30%;
    }
  }
  .SoundModelArea {
    width: 100%;
    height: 86%;
    margin: 10px 0;
    overflow-y: scroll;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    > .ModealBox {
      width: 30%;
      margin: 10px 1%;
      position: relative;
      > .ImgBox {
        width: 70%;
        margin: 0 auto;
        img {
          width: 100%;
        }
      }
      > .TextArea {
        width: 100%;
        p {
          margin: 3px 0;
          padding-left: 6px;
          box-sizing: border-box;
          text-align: left;
          word-wrap: break-word;
        }
      }
      > .play-img {
        width: 32px;
        height: 32px;
        cursor: pointer;
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        bottom: 0;
        margin: auto;
        z-index: +10;
      }
    }
    .ModealBox:hover {
      background-color: #000;
      opacity: 0.5;
      border: 2px solid #0183f4;
      > .TextArea {
        p {
          color: #fff;
        }
      }
    }
    > .slectModel {
      border: 2px solid #1989fa;
      border-radius: 6px;
    }
  }
  .ButtonArea {
    width: 90%;
    margin: 0 auto;
    display: flex;
    justify-content: space-around;
    align-items: center;
    button {
      width: 90%;
      padding: 20px 0;
      box-sizing: border-box;
    }
  }
}
</style>