<template>
|
<div class="pages">
|
<div class="template-top">
|
<div class="top-left">
|
<div style="font-size: 16px" class="top-icon">
|
<ArrowLeft @click="goBack" style="width: 1em; height: 1em; cursor: pointer" />
|
</div>
|
<span class="back-text" @click="goBack">{{ t('courseCenter.goBack') }}</span>
|
<el-input
|
v-if="isEditing"
|
ref="inputRef"
|
v-model="editName"
|
style="width: 300px"
|
size="small"
|
:placeholder="t('common.inputText') + t('courseCenter.courseName')"
|
@blur="saveEdit"
|
@keydown.enter="saveEdit"
|
/>
|
|
<!-- 如果不在编辑,显示文本 -->
|
<div
|
v-else
|
@click="toggleEdit"
|
:prefix-icon="Edit"
|
style="display: flex; align-items: center; cursor: pointer"
|
>
|
<span>{{ courseInfo.name }}</span>
|
</div>
|
<span>{{ t('courseCenter.estimatedDuration') }}: {{ videoDuration }}</span>
|
<span>{{ t('courseCenter.wordCount') }}:{{ videoText }}</span>
|
</div>
|
<div class="top-right">
|
<span v-if="saveTime">{{ saveTime }} {{ t('courseCenter.saved') }}</span>
|
<el-button size="small" @click="saveSubmit('save')">{{ t('common.save') }}</el-button>
|
<el-button type="primary" size="small" @click="saveSubmit('')">{{
|
t('courseCenter.composeViode')
|
}}</el-button>
|
</div>
|
</div>
|
<div class="template-main">
|
<div class="template-box template-left">
|
<div class="page">
|
<div
|
>{{ t('courseCenter.page') }}:({{ PPTArr ? PPTArr.length : 1 }}){{
|
t('courseCenter.pageTitle')
|
}}</div
|
>
|
<div class="line"></div>
|
<el-upload
|
ref="uploadRef"
|
class="upload-demo"
|
accept=".pptx,.pdf"
|
:limit="1"
|
:headers="headers"
|
:action="`${config.base_url}/infra/file/upload`"
|
:on-exceed="handleExceed"
|
:on-change="handleChange"
|
:on-success="handleSuccess"
|
:on-error="handleError"
|
:show-file-list="false"
|
>
|
<template #trigger>
|
<el-button type="primary" :icon="Upload">
|
{{ t('courseCenter.upload') }} PPT/PDF
|
</el-button>
|
</template>
|
</el-upload>
|
</div>
|
<!-- 左侧场景页面缩略图 -->
|
<div v-if="showLeftList" style="height: calc(100% - 86px)">
|
<div class="image-list">
|
<draggable
|
:list="PPTArr"
|
:disabled="false"
|
item-key="name"
|
ghost-class="ghost"
|
chosen-class="chosen"
|
@start="state.dragging = true"
|
@end="state.dragging = false"
|
animation="300"
|
>
|
<template #item="{ element, index }">
|
<div class="mt-2 w-100%">
|
<div class="list" @click="choosePPt(element)">
|
<!-- 背景图片(必显示) -->
|
<el-image
|
class="background"
|
:src="element.pictureUrl"
|
fit="contain"
|
:style="{
|
width: thumViewSize.width + 'px',
|
height: thumViewSize.height + 'px',
|
top: '0px',
|
left: '0px'
|
}"
|
/>
|
<!-- 画中画(有则显示) -->
|
<el-image
|
v-if="element.innerPicture?.src"
|
class="ppt-bg"
|
:style="{
|
width: element.innerPicture.width * (thumViewSize.width / 800) + 'px',
|
height: element.innerPicture.height * (thumViewSize.height / 450) + 'px',
|
top: element.innerPicture.top * (thumViewSize.width / 800) + 'px',
|
left: element.innerPicture.marginLeft * (thumViewSize.height / 450) + 'px',
|
borderColor: element.isActive ? '#0683ee' : ''
|
}"
|
:src="element.innerPicture.src"
|
fit="contain"
|
/>
|
<!-- 数字人(有则显示) -->
|
<el-image
|
v-if="
|
element.digitalHuman?.show &&
|
element.digitalHuman?.host &&
|
element.digitalHuman?.host.fixVideoUrl !== null &&
|
element.digitalHuman?.host.pictureUrl !== null &&
|
element.digitalHuman?.host.videoUrl !== null
|
"
|
class="host"
|
:style="{
|
width: element.digitalHuman.w * (thumViewSize.width / 800) + 'px',
|
height: element.digitalHuman.h * (thumViewSize.height / 450) + 'px',
|
top: element.digitalHuman.y * (thumViewSize.width / 800) + 'px',
|
left: element.digitalHuman.x * (thumViewSize.height / 450) + 'px'
|
}"
|
:src="element.digitalHuman.host.pictureUrl"
|
/>
|
<div class="list-index" :style="element.isActive ? 'background: #409eff' : ''">
|
{{ index + 1 }}
|
</div>
|
<div class="icon-content">
|
<el-icon
|
size="20"
|
color="#FFA500"
|
style="margin-right: 5px"
|
@click.stop="copyDocument(element, index)"
|
>
|
<CopyDocument />
|
</el-icon>
|
<el-icon
|
size="20"
|
color="#FFA500"
|
style="margin-right: 5px"
|
@click.stop="deleteDocument(element)"
|
>
|
<Delete />
|
</el-icon>
|
<el-checkbox v-model="element.isChecked" size="large" />
|
</div>
|
</div>
|
</div>
|
</template>
|
</draggable>
|
</div>
|
<div class="page-btn">
|
<el-button type="primary" size="small" :icon="Delete" @click.stop="deleteMore" />
|
</div>
|
</div>
|
<div class="left-upload-setting" v-if="!showLeftList">
|
<div>ppt{{ t('courseCenter.analyzing') }}...</div>
|
<el-progress :percentage="percentagePPT" />
|
<el-button @click="cancelAnalyze">{{ t('common.cancel') }}</el-button>
|
<div>{{ t('courseCenter.analyzingTitle') }}</div>
|
</div>
|
</div>
|
<!-- 中间主画布 -->
|
<div class="template-box template-middle">
|
<div class="middle-top">
|
<el-select v-model="courseInfo.aspect" placeholder="Select" style="width: 140px">
|
<el-option
|
v-for="item in options"
|
:key="item.label"
|
:label="item.label"
|
:value="item.label"
|
/>
|
</el-select>
|
</div>
|
<div class="main-box relative">
|
<div class="list">
|
<div
|
class="main-image-box"
|
:style="{
|
width: viewSize.width + 'px',
|
height: viewSize.height + 'px',
|
position: 'relative'
|
}"
|
>
|
<!-- 背景(必显示) -->
|
<el-image
|
v-show="selectPPT.pictureUrl && selectPPT.digitalHuman.show == false"
|
class="background1"
|
:src="selectPPT.pictureUrl"
|
style="z-index: 2"
|
/>
|
<el-image
|
v-show="selectPPT.pictureUrl && selectPPT.digitalHuman.show == true"
|
class="background1"
|
:src="selectPPT.pictureUrl"
|
style="z-index: 1"
|
/>
|
|
<!-- 画中画 -->
|
<Vue3DraggableResizable
|
:key="selectPPT.digitalHuman.x + '-' + selectPPT.digitalHuman.y"
|
v-if="selectPPT.innerPicture && selectPPT.innerPicture.src"
|
:parent="true"
|
:initW="selectPPT.innerPicture.width"
|
:initH="selectPPT.innerPicture.height"
|
v-model:x="selectPPT.innerPicture.marginLeft"
|
v-model:y="selectPPT.innerPicture.top"
|
v-model:w="selectPPT.innerPicture.width"
|
v-model:h="selectPPT.innerPicture.height"
|
v-model:active="selectPPT.innerPicture.active"
|
:draggable="true"
|
:resizable="true"
|
@activated="print('PPT activated')"
|
@deactivated="print('PPT deactivated')"
|
@drag-start="print('PPT drag-start')"
|
@resize-start="print('PPT resize-start')"
|
@dragging="print('PPT dragging')"
|
@resizing="print('PPT resizing')"
|
@drag-end="print('PPT drag-end')"
|
@resize-end="print('PPT resize-end')"
|
style="z-index: 3"
|
>
|
<el-image class="ppt-bg" :src="selectPPT.innerPicture.src" />
|
<el-icon
|
v-if="selectPPT.innerPicture.active"
|
size="20"
|
color="#409eff"
|
style="position: absolute; top: 5px; right: 5px; z-index: 4"
|
@click.stop="deleteInnerPicture"
|
>
|
<Delete />
|
</el-icon>
|
</Vue3DraggableResizable>
|
<!-- 数字人 -->
|
<Vue3DraggableResizable
|
v-if="
|
selectPPT.digitalHuman.show == true &&
|
selectPPT.digitalHuman?.host &&
|
selectPPT.digitalHuman?.host.fixVideoUrl !== null &&
|
selectPPT.digitalHuman?.host.pictureUrl !== null &&
|
selectPPT.digitalHuman?.host.videoUrl !== null
|
"
|
:parent="false"
|
:lockAspectRatio="true"
|
:minW="350"
|
:initW="selectPPT.digitalHuman.w"
|
:initH="selectPPT.digitalHuman.h"
|
@drag-move="onDragMove"
|
v-model:x="selectPPT.digitalHuman.x"
|
v-model:y="selectPPT.digitalHuman.y"
|
v-model:w="selectPPT.digitalHuman.w"
|
v-model:h="selectPPT.digitalHuman.h"
|
v-model:active="selectPPT.digitalHuman.active"
|
:draggable="true"
|
:resizable="true"
|
@activated="print('activated')"
|
@deactivated="print('deactivated')"
|
@drag-start="print('drag-start')"
|
@resize-start="print('resize-start')"
|
@dragging="print('dragging')"
|
@resizing="print('resizing')"
|
@drag-end="print('drag-end')"
|
@resize-end="print('resize-end')"
|
style="z-index: 4"
|
>
|
<el-image
|
class="minddle-host-image"
|
:src="selectPPT.digitalHuman.host.pictureUrl"
|
/>
|
<el-icon
|
v-if="selectPPT.digitalHuman.active"
|
size="20"
|
color="#409eff"
|
style="position: absolute; top: 5px; right: 5px; z-index: 4"
|
@click.stop="deleteDigitalHuman"
|
>
|
<Delete />
|
</el-icon>
|
</Vue3DraggableResizable>
|
<Vue3DraggableResizable
|
v-if="
|
selectPPT.digitalHuman.show == false &&
|
selectPPT.digitalHuman?.host &&
|
selectPPT.digitalHuman?.host.fixVideoUrl !== null &&
|
selectPPT.digitalHuman?.host.pictureUrl !== null &&
|
selectPPT.digitalHuman?.host.videoUrl !== null
|
"
|
:parent="false"
|
:lockAspectRatio="true"
|
:minW="350"
|
:initW="selectPPT.digitalHuman.w"
|
:initH="selectPPT.digitalHuman.h"
|
@drag-move="onDragMove"
|
v-model:x="selectPPT.digitalHuman.x"
|
v-model:y="selectPPT.digitalHuman.y"
|
v-model:w="selectPPT.digitalHuman.w"
|
v-model:h="selectPPT.digitalHuman.h"
|
v-model:active="selectPPT.digitalHuman.active"
|
:draggable="true"
|
:resizable="true"
|
@activated="print('activated')"
|
@deactivated="print('deactivated')"
|
@drag-start="print('drag-start')"
|
@resize-start="print('resize-start')"
|
@dragging="print('dragging')"
|
@resizing="print('resizing')"
|
@drag-end="print('drag-end')"
|
@resize-end="print('resize-end')"
|
style="z-index: 1"
|
>
|
<el-image
|
class="minddle-host-image"
|
:src="selectPPT.digitalHuman.host.pictureUrl"
|
/>
|
<el-icon
|
v-if="selectPPT.digitalHuman.active"
|
size="20"
|
color="#409eff"
|
style="position: absolute; top: 5px; right: 5px; z-index: 4"
|
@click.stop="deleteDigitalHuman"
|
>
|
<Delete />
|
</el-icon>
|
</Vue3DraggableResizable>
|
</div>
|
</div>
|
<el-card
|
v-show="voiceData.show"
|
class="voice-card absolute right-10px bottom-10px w-300px"
|
>
|
<div class="flex flex-col">
|
<div class="flex flex-col p-10px border-b-solid border-b-1px border-gray-200">
|
<div class="flex items-baseline">
|
<span class="text-16px">{{ t('courseCenter.speedSpeech') }}</span>
|
<el-button
|
class="ml-10px"
|
type="info"
|
link
|
@click="voiceData.speechRate = voiceData.defaultSpeechRate"
|
>
|
{{ t('courseCenter.restoreDefault') }}
|
</el-button>
|
</div>
|
<el-slider
|
class="speech-slider px-10px mb-20px"
|
v-model="voiceData.speechRate"
|
:step="0.05"
|
:min="0.6"
|
:max="1.5"
|
:marks="voiceData.marks"
|
:show-spots="false"
|
/>
|
</div>
|
<div class="flex flex-col p-10px">
|
<div class="flex items-baseline">
|
<span class="text-16px">{{ t('courseCenter.enlargeVolume') }}</span>
|
<el-button
|
class="ml-10px"
|
type="info"
|
link
|
@click="voiceData.volume = voiceData.defaultVolume"
|
>
|
{{ t('courseCenter.restoreDefault') }}
|
</el-button>
|
</div>
|
<el-slider
|
class="speech-slider px-10px mb-20px"
|
v-model="voiceData.volume"
|
:step="0.1"
|
:min="1"
|
:max="2"
|
:marks="voiceData.marks2"
|
:show-spots="false"
|
/>
|
</div>
|
</div>
|
</el-card>
|
</div>
|
<div class="voice-main">
|
<el-text class="mx-1" type="primary" size="small">{{
|
t('courseCenter.oralBroadcastingContent')
|
}}</el-text>
|
<!-- 原本的声音选择器 -->
|
<!-- <div class="media-box">
|
<el-button type="primary" :icon="Mic" size="small" @click="openSelect">{{
|
selectPPT.selectAudio ? selectPPT.selectAudio.name : t('courseCenter.notSelect')
|
}}</el-button>
|
<el-button
|
type="success"
|
:icon="Headset"
|
size="small"
|
@click="voiceData.show = !voiceData.show"
|
/>
|
</div> -->
|
</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"
|
/>
|
</div>
|
<div class="tool-box">
|
<div class="tool-btn">
|
<div></div>
|
</div>
|
<el-button type="primary" :icon="VideoPlay" size="small" @click="createAudio">{{
|
t('courseCenter.tryListening')
|
}}</el-button>
|
</div>
|
<div class="audio-play" v-if="showAudioPlay">
|
<div>{{ t('courseCenter.listeningTrial') }}...</div>
|
<el-button @click="pauseAudio">{{ t('courseCenter.cancelTrialListening') }}</el-button>
|
</div>
|
<div class="audio-play" v-if="showAudioPlay1">
|
<div>生成中...</div>
|
</div>
|
</div>
|
<div v-else class="audio-upload" style="position: relative">
|
<div class="audio-play" v-if="startAudioPlay">
|
<div>{{ t('courseCenter.playing') }}...</div>
|
<el-button @click="cancelAudio">{{ t('courseCenter.cancelPlayback') }}</el-button>
|
</div>
|
<el-tooltip effect="dark" :content="t('courseCenter.toolTip')" placement="top">
|
<el-upload
|
v-model:file-list="selectPPT.fileList"
|
ref="uploadAudioRef"
|
class="upload-demo"
|
accept=".wav,.mp3"
|
:limit="1"
|
:headers="headers"
|
:action="`${config.base_url}/infra/file/upload`"
|
:on-success="handleAudioSuccess"
|
:on-change="handleAudioChange"
|
:on-preview="audioPlay"
|
:on-exceed="audioExceed"
|
:show-file-list="true"
|
>
|
<template #trigger>
|
<el-button type="primary" :icon="Upload">{{
|
t('courseCenter.uploadAudio')
|
}}</el-button>
|
</template>
|
</el-upload>
|
</el-tooltip>
|
</div>
|
</div>
|
<!-- 数字人 -->
|
<div class="template-box template-right" v-if="showDigitalHumanTool">
|
<div class="tabs-1">
|
<div
|
class="tabs-item"
|
v-for="item in tabs1"
|
:key="item.itemValue"
|
@click="tabs1Click(item)"
|
>
|
<div>{{ item.itemName }}</div>
|
<span v-if="tabs1ActiveNum == item.itemValue"></span>
|
</div>
|
</div>
|
<div class="tabs-2">
|
<div
|
:class="{ 'tabs-active': tabs2ActiveNum == item.itemValue }"
|
v-for="item in tabs2"
|
@click="tabs2Click(item)"
|
:key="item.itemValue"
|
>
|
{{ item.itemName }}
|
</div>
|
<div
|
:class="{ 'tabs-active': tabs3ActiveNum == item.itemValue }"
|
v-for="item in tabs3"
|
@click="tabs3Click(item)"
|
:key="item.itemValue"
|
>
|
{{ item.itemName }}
|
</div>
|
</div>
|
<div class="host-list">
|
<div
|
class="host-item"
|
v-for="(item, index) in hostList"
|
:key="index"
|
@click="chooseHost(item)"
|
>
|
<div class="background"></div>
|
<div class="host-name">{{ item.name }}</div>
|
<el-image
|
class="ppt-bg"
|
:src="item.pictureUrl"
|
:style="{
|
width: '100%',
|
height: '100%',
|
objectFit: 'contain',
|
maxWidth: '100%',
|
maxHeight: '100%'
|
}"
|
/>
|
</div>
|
<el-empty v-if="hostList.length == 0" description="暂无数据" />
|
<Pagination
|
small="true"
|
:total="total"
|
v-model:page="queryParams.pageNo"
|
v-model:limit="queryParams.pageSize"
|
@pagination="getList"
|
/>
|
</div>
|
</div>
|
<!-- 模板设置 -->
|
<div class="template-box template-right" v-if="showTemplateTool">
|
<div class="template-list">
|
<div class="tabs-1">
|
<div
|
class="tabs-item"
|
v-for="item in tabs4"
|
:key="item.itemValue"
|
@click="tabs4Click(item)"
|
>
|
<div>{{ item.itemName }}</div>
|
<span v-if="tabs4ActiveNum == item.itemValue"></span>
|
</div>
|
</div>
|
<div
|
class="template-item"
|
v-for="(template, index) in templates"
|
:key="index"
|
:style="{
|
width: '90%',
|
maxWidth: '90%'
|
}"
|
@click="handleTemplateSelection(template)"
|
>
|
<div class="list-index" :style="template.isActive ? 'background: #409eff' : ''">
|
{{ index + 1 }}
|
</div>
|
<el-image class="background" :src="template.previewImage" fit="contain" />
|
</div>
|
<el-empty v-if="templates.length == 0" description="暂无数据" />
|
</div>
|
<!-- <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"
|
size="small"
|
>
|
<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()"
|
size="small"
|
>
|
<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"
|
size="small"
|
>
|
<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" v-show="ChangeSoundTypeList?.value === 2 " >
|
<div class="SoundModelAreaBox" v-for="(value, key, index) in audioList" :key="index">
|
<div class="SoundClassTit">
|
<el-divider content-position="center"> {{ languageClass(key) }} </el-divider>
|
<!-- <el-divider content-position="center"> 123 </el-divider> -->
|
</div>
|
<div class="SoundClassContent">
|
<div
|
class="ModealBox"
|
v-for="(item, index) in value"
|
:key="index"
|
@click="handleSelect(key, item)"
|
@mouseenter="handleMouseenter(key, item)"
|
@mouseleave="handleMouseleave(key, 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>
|
</div>
|
<div class="ButtonArea" v-show="ChangeSoundTypeList?.value === 2" >
|
<el-button type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
|
</div>
|
</div>
|
</div>
|
<!-- 背景设置 -->
|
<div class="template-box template-right" v-if="showHeadImageTool">
|
<div class="image-setting">
|
<div>{{ t('courseCenter.uploadImage') }}</div>
|
<UploadImg v-model="selectPPT.pictureUrl" :limit="1" />
|
</div>
|
</div>
|
<!-- 画中画设置 -->
|
<div class="template-box template-right" v-if="showInnerPictureTool">
|
<div class="image-setting">
|
<div>{{ t('courseCenter.uploadImage') }}</div>
|
<UploadImg v-model="selectPPT.innerPicture.src" :limit="1" />
|
</div>
|
</div>
|
<div class="template-box template-tool">
|
<div
|
v-for="(item, index) in rightTools"
|
:key="index"
|
class="tool-item"
|
@click="handleChangeTool(item)"
|
>
|
<img :src="item.isActive ? item.activeUrl : item.url" alt="" />
|
<div class="tool-name" :style="item.isActive ? 'color:#0088fe' : ''">
|
{{ item.name }}
|
</div>
|
</div>
|
</div>
|
</div>
|
<uploadExplain ref="uploadExplainRef" @success="uploadSubmit" />
|
<AudioSelect ref="audioSelect" @success="selectAudio" />
|
<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)"
|
>
|
{{ item }}
|
</el-tag>
|
</el-dialog>
|
<el-dialog v-model="dialogVisible1" title="提示" width="500px" style="height: 150px">
|
<p style="margin-bottom: 20px; font-size: 18px">是否要将此模板应用到所有页面</p>
|
<span class="dialog-footer">
|
<el-button @click="handleTemplateSelection1(1)">应用当前页</el-button>
|
<el-button type="primary" @click="handleTemplateSelection1(2)">应用所有页</el-button>
|
</span>
|
</el-dialog>
|
<el-dialog v-model="dialogVisible2" title="提示" width="500px" style="height: 150px">
|
<p style="margin-bottom: 20px; font-size: 18px">是否要将此数字人应用到所有页面</p>
|
<span class="dialog-footer">
|
<el-button @click="chooseHost1(1)">应用当前页</el-button>
|
<el-button type="primary" @click="chooseHost1(2)">应用所有页</el-button>
|
</span>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
import { ref, reactive, onMounted, computed, watch, nextTick, shallowRef } from 'vue'
|
import draggable from 'vuedraggable'
|
import Vue3DraggableResizable from 'vue3-draggable-resizable'
|
import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css'
|
import { useFaceDetection } from '@/utils/HaveFace'
|
import { config } from '@/config/axios/config'
|
import { genFileId } from 'element-plus'
|
import type { UploadRawFile } from 'element-plus'
|
import { getAccessToken, getTenantId } from '@/utils/auth'
|
import * as pptTemplateApi from '@/api/pptTemplate'
|
import uploadExplain from './uploadExplain.vue'
|
import AudioSelect from './audioSelect.vue'
|
import mergeWarningDialog from './mergeWarningDialog.vue'
|
import ReplaceDialog from './replaceDialog.vue'
|
import template from '@/assets/imgs/template.png'
|
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'
|
import innerPictureActive from '@/assets/imgs/inner-picture-active.png'
|
import { TemplateApi } from '@/api/digitalcourse/template'
|
const { t } = useI18n()
|
import { useUserStore } from '@/store/modules/user'
|
import {
|
Edit,
|
ArrowLeft,
|
Upload,
|
Mic,
|
Headset,
|
Delete,
|
VideoPlay,
|
CopyDocument
|
} from '@element-plus/icons-vue'
|
import { generateUUID } from '@/utils'
|
import { useRoute, useRouter } from 'vue-router'
|
import { cloneDeep } from 'lodash-es'
|
import { Editor } from '@wangeditor/editor-for-vue'
|
import '@wangeditor/editor/dist/css/style.css'
|
import { Boot } from '@wangeditor/editor'
|
import TitleBlack from './title-black/index.js'
|
Boot.registerModule(TitleBlack)
|
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 { any } from 'vue-types'
|
|
const editorHtml = useEditorHtml()
|
const router = useRouter()
|
const route = useRoute()
|
const userStore = useUserStore()
|
const userId = computed(() => userStore.user.id)
|
const message = useMessage()
|
const dialogVisible1 = ref(false)
|
const dialogVisible2 = ref(false)
|
const isEditing = ref(false)
|
const inputRef = ref(null)
|
const editName = ref('')
|
const applyAllHost = ref(false)
|
//当前是否存在人脸
|
const IsHaveFace = ref(false)
|
//当前是否完成ppt人脸校验
|
const IsEndCheckFace = ref(true)
|
// 切换到编辑模式
|
const toggleEdit = () => {
|
isEditing.value = true
|
editName.value = courseInfo.value.name
|
nextTick(() => {
|
inputRef.value.focus()
|
})
|
}
|
const hostValue = ref({})
|
const chooseHost = (item) => {
|
if (PPTArr.value.length === 0) {
|
message.warning('请先上传ppt')
|
return false
|
}
|
|
// 数字人默认所有页,没有当前页选项
|
hostValue.value = item
|
chooseHost1(2)
|
// dialogVisible2.value = true
|
}
|
const chooseHost1 = (index) => {
|
if (index == 1) {
|
applyAllHost.value = false
|
applyHostToPages(hostValue.value)
|
dialogVisible2.value = false
|
} else if (index == 2) {
|
applyAllHost.value = true
|
applyHostToPages(hostValue.value)
|
dialogVisible2.value = false
|
}
|
}
|
const applyHostToPages = (host) => {
|
hostList.value.forEach((el) => {
|
el.isActive = el.id === host.id
|
})
|
|
const pagesToUpdate = applyAllHost.value ? PPTArr.value : [selectPPT.value]
|
|
pagesToUpdate.forEach((page) => {
|
page.digitalHuman.host = host
|
initHumanPositon(host, page.digitalHuman)
|
})
|
|
selectPPT.value = PPTArr.value[0]
|
}
|
const saveEdit = () => {
|
isEditing.value = false
|
courseInfo.value.name = editName.value
|
}
|
const print = (val) => {
|
console.log(val)
|
}
|
const viewSize = reactive({
|
width: 800,
|
height: (800 * 9) / 16
|
})
|
|
const thumViewSize = reactive({
|
width: 152,
|
height: (152 * 9) / 16
|
})
|
|
const digitalHumanSize = reactive({
|
width: 640,
|
height: 360
|
})
|
|
const scaleRatio = computed(() => ({
|
width: courseInfo.value.width / viewSize.width,
|
height: courseInfo.value.height / viewSize.height
|
}))
|
|
const courseInfo = ref({
|
id: 0,
|
accountId: userId.value,
|
aspect: '16:9',
|
name: '未命名草稿',
|
duration: 0,
|
status: 0,
|
pageMode: 2,
|
matting: 1,
|
width: 1920,
|
height: 1080
|
})
|
|
watch(
|
() => courseInfo.value.aspect,
|
(newAspect) => {
|
courseInfo.value.width = newAspect === '16:9' ? 1920 : 1080
|
courseInfo.value.height = newAspect === '16:9' ? 1080 : 1920
|
}
|
)
|
|
const state = reactive({
|
dragging: false
|
})
|
|
const TEMPLATE_PRESETS = ref([])
|
const templates = ref([])
|
const selectTemplate = ref([])
|
|
const tabs1 = [
|
{
|
itemName: t('courseCenter.model'),
|
itemValue: '0'
|
},
|
{
|
itemName: t('courseCenter.my'),
|
itemValue: '1'
|
}
|
]
|
const tabs1ActiveNum = ref('0')
|
const tabs2ActiveNum = ref('')
|
const tabs2 = [
|
{
|
itemName: t('courseCenter.all'),
|
itemValue: ''
|
},
|
{
|
itemName: t('courseCenter.man'),
|
itemValue: '1'
|
},
|
{
|
itemName: t('courseCenter.woman'),
|
itemValue: '2'
|
}
|
]
|
const tabs3ActiveNum = ref()
|
const tabs3 = [
|
{
|
itemName: t('courseCenter.standingPosture'),
|
itemValue: '1'
|
},
|
{
|
itemName: t('courseCenter.sittingPosture'),
|
itemValue: '2'
|
}
|
]
|
const tabs4 = [
|
{
|
itemName: t('模板库'),
|
itemValue: '1'
|
},
|
{
|
itemName: t('我的'),
|
itemValue: '2'
|
}
|
]
|
const tabs4ActiveNum = ref('1')
|
const tabs1Click = (item) => {
|
tabs1ActiveNum.value = item.itemValue
|
getList()
|
}
|
const tabs4Click = (item) => {
|
tabs4ActiveNum.value = item.itemValue
|
queryParams1.zg = tabs4ActiveNum.value
|
getList1()
|
}
|
|
const tabs2Click = (item) => {
|
tabs2ActiveNum.value = item.itemValue
|
getList()
|
}
|
|
const tabs3Click = (item) => {
|
tabs3ActiveNum.value = item.itemValue
|
getList()
|
}
|
|
const rightTools = reactive([
|
{
|
name: t('courseCenter.template'),
|
url: template,
|
activeUrl: templateActive,
|
isActive: false
|
},
|
{
|
name: t('courseCenter.digitalPeople'),
|
url: user,
|
activeUrl: userActive,
|
isActive: false
|
},
|
// 声音类型
|
{
|
name: t('courseCenter.sound'),
|
url: sound,
|
activeUrl: soundActive,
|
isActive: false
|
}
|
// {
|
// name: t('courseCenter.background'),
|
// url: bg,
|
// activeUrl: bgActive,
|
// isActive: false
|
// },
|
// {
|
// name: t('courseCenter.pictureInPicture'),
|
// url: innerPicture,
|
// activeUrl: innerPictureActive,
|
// isActive: false
|
// }
|
])
|
|
const showHeadImageTool = ref(false)
|
const showDigitalHumanTool = ref(false)
|
// 声音
|
const SoundTool = ref(false)
|
const showTemplateTool = ref(false)
|
const showInnerPictureTool = ref(false)
|
const applyAllTemplate = ref(false)
|
const templateSelection = ref({})
|
const handleTemplateSelection = (template) => {
|
console.log(template)
|
dialogVisible1.value = true
|
templateSelection.value = template
|
console.log(templateSelection.value)
|
}
|
|
const handleTemplateSelection1 = (index) => {
|
if (index == 2) {
|
applyAllTemplate.value = true
|
chooseTemplate(templateSelection.value)
|
dialogVisible1.value = false
|
} else if (index == 1) {
|
applyAllTemplate.value = false
|
chooseTemplate(templateSelection.value)
|
dialogVisible1.value = false
|
}
|
}
|
const handleChangeTool = (item) => {
|
rightTools.forEach((child) => {
|
if (child.name == item.name) {
|
child.isActive = true
|
} else {
|
child.isActive = false
|
}
|
})
|
if (item.name == t('courseCenter.digitalPeople')) {
|
tabs1ActiveNum.value = '0'
|
getList()
|
} else if (item.name == t('courseCenter.template')) {
|
tabs4ActiveNum.value = '1'
|
queryParams1.zg = tabs4ActiveNum.value
|
getList1()
|
} else if (item.name == t('courseCenter.sound')) {
|
// 声音的处理 selectAudio
|
|
if (selectLanguage.value === undefined) {
|
// 获取语言种类
|
getLanguageList()
|
}
|
if (changeAudio.value === undefined) {
|
//获取性别种类
|
getAudioType()
|
}
|
if (activeSoundType.value === undefined) {
|
// 获取声音类别
|
getVoiceType()
|
}
|
if (selectList.value === undefined) {
|
// 获取模型列表
|
getSoundModelList()
|
}
|
}
|
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>()
|
//获取性别字典
|
const getAudioType = () => {
|
let 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.CHANGE_SOUND_TYPE_LIST)
|
SoundTypeList.value = res
|
ChangeSoundTypeList.value = { ...res[0] }
|
}
|
if (ChangeSoundTypeList.value === undefined) {
|
// 获取可选的声音类型列表
|
GetSoundTypeList()
|
}
|
//可选的声音模型的声音类别列表
|
const SoundvoiceTypeList = ref()
|
// 当前选择的声音模型的声音类别
|
const activeSoundType = ref<any>()
|
//获取的声音模型的声音类别
|
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.language =
|
soundQueryParams.language === 'all_Language' ? '' : soundQueryParams.language
|
// 性别
|
soundQueryParams.gender = changeAudio?.value ?? ''
|
soundQueryParams.gender = Number(soundQueryParams.gender) === 3 ? '' : soundQueryParams.gender
|
// 声音类型
|
soundQueryParams.voiceType = activeSoundType?.value.value ?? ''
|
const data = await pptTemplateApi.videlPageList(soundQueryParams)
|
data.list.forEach((item) => {
|
item.isHover = false
|
item.isPlay = false
|
item.isSelect = false
|
})
|
let LanguageArr = {}
|
data.list.forEach((item) => {
|
if (LanguageArr?.[item.language] !== undefined) {
|
LanguageArr[item.language].push(item)
|
} else {
|
LanguageArr = { ...LanguageArr, [item.language]: [{ ...item }] }
|
}
|
})
|
console.log(LanguageArr)
|
|
audioList.value = LanguageArr
|
total.value = data.total
|
if (selectList.value !== undefined && selectList.value !== null) {
|
selectList.value = null //初始化
|
}
|
// 停止当前播放的音频
|
if (SoundcurrentAudio.value) {
|
SoundcurrentAudio.value.pause()
|
SoundcurrentAudio.value = null
|
}
|
|
// 重置当前播放状态
|
if (SoundcurrentlyPlaying.value) {
|
SoundcurrentlyPlaying.value.isPlay = false
|
SoundcurrentlyPlaying.value = null
|
}
|
} finally {
|
soundLoading.value = false
|
}
|
}
|
// 当前语种显示
|
const languageClass = (language) => {
|
let text = ''
|
languageList.value.forEach((element) => {
|
if (element.value === language) {
|
text = element.label
|
}
|
})
|
return text
|
}
|
// 语种选择
|
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 }
|
|
if (selectList.value !== undefined && selectList.value !== null) {
|
selectList.value = null //初始化
|
}
|
// 停止当前播放的音频
|
if (SoundcurrentAudio.value) {
|
SoundcurrentAudio.value.pause()
|
SoundcurrentAudio.value = null
|
}
|
|
// 重置当前播放状态
|
if (SoundcurrentlyPlaying.value) {
|
SoundcurrentlyPlaying.value.isPlay = false
|
SoundcurrentlyPlaying.value = null
|
}
|
}
|
})
|
getSoundModelList()
|
}
|
//选择声音模型
|
const selectList = ref()
|
const handleSelect = (key, item) => {
|
selectList.value = [item]
|
audioList.value[key].forEach((child) => {
|
if (child.id == item.id) {
|
child.isSelect = true
|
} else {
|
child.isSelect = false
|
}
|
})
|
}
|
// 用于存贮用户选择的声音信息
|
const AudioDetail = ref({
|
language: any, //语言
|
sex: any, //性别
|
type: any, //类型
|
model: any //声音模型
|
})
|
// 确定按钮点击处理函数
|
const submitForm = () => {
|
console.log(selectLanguage.value)
|
if (selectLanguage.value.value === 'all_Language') {
|
// message.warning('请将语种按钮由全部语种修改为您需要生成的声音的文本的语种类型')
|
message.warning('请先选择语种与声音类型')
|
return false
|
}
|
if (ChangeSoundTypeList.value.value === 2) {
|
//此时为通用
|
if (selectList.value === undefined || selectList.value === null) {
|
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)
|
}
|
// 记录用户选择的声音信息
|
AudioDetail.value = {
|
language: selectLanguage.value, //语言
|
sex: changeAudio.value, //性别
|
type: ChangeSoundTypeList.value, //类型
|
model: selectList.value !== undefined ? selectList.value : '' //声音模型
|
}
|
|
message.success('声音模型确定成功')
|
|
// SoundTool.value = false
|
|
// rightTools.forEach((child) => {
|
// if (child.name == '声音' || child.name == 'sound') {
|
// child.isActive = false
|
// }
|
// })
|
}
|
// 鼠标移入与移出
|
const handleMouseenter = (key, item) => {
|
audioList.value[key].forEach((child) => {
|
if (child.id == item.id) {
|
child.isHover = true
|
}
|
})
|
}
|
const handleMouseleave = (key, item) => {
|
audioList.value[key].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([])
|
const percentagePPT = ref(0)
|
const showLeftList = ref(true)
|
|
//是否进行过删除操作
|
const DeleteD = ref(false)
|
|
const selectPPT = ref({
|
pictureUrl: '',
|
innerPicture: {
|
name: '画中画',
|
src: '',
|
cover: '',
|
width: 0,
|
height: 0,
|
originWidth: 0,
|
originHeight: 0,
|
category: 1,
|
depth: 1,
|
top: 0,
|
marginLeft: 0,
|
businessId: generateUUID(),
|
entityType: 0,
|
entityId: '0'
|
},
|
pptRemark: '',
|
driverType: 1,
|
uploadAudioUrl: '',
|
fileList: [],
|
selectAudio: {
|
id: '',
|
code: '',
|
name: ''
|
},
|
digitalHuman: {
|
show: true,
|
x: viewSize.width - digitalHumanSize.width,
|
y: viewSize.height - digitalHumanSize.height,
|
w: digitalHumanSize.width,
|
h: digitalHumanSize.height,
|
active: false,
|
host: null
|
}
|
})
|
|
const videoText = ref(0)
|
const videoDuration = ref('')
|
|
watch(
|
() => PPTArr.value,
|
(val) => {
|
if (!val) return
|
|
videoText.value = val.reduce((prev, curr) => {
|
if (!curr.pptRemark) return prev
|
const plainText = curr.pptRemark.replace(/<[^>]+>/g, '')
|
return prev + plainText.length
|
}, 0)
|
|
let videoTime = (videoText.value / 200) * 60
|
videoDuration.value = formateVideoTime(Math.ceil(videoTime))
|
},
|
{ deep: true }
|
)
|
|
const formateVideoTime = (times: any) => {
|
let hours: any = parseInt(`${times / 60 / 60}`)
|
let restMinutes: any = parseInt(`${(times / 60) % 60}`)
|
let seconds: any = parseInt(`${times % 60}`)
|
|
if (hours < 10) hours = '0' + hours
|
if (restMinutes < 10) restMinutes = '0' + restMinutes
|
if (seconds < 10) seconds = '0' + seconds
|
|
return hours + ':' + restMinutes + ':' + seconds
|
}
|
|
const uploadRef = ref()
|
const headers = {
|
Accept: 'application/json, text/plain, */*',
|
Authorization: 'Bearer ' + getAccessToken(),
|
'tenant-id': getTenantId()
|
}
|
|
const uploadFileObj = reactive({
|
filename: '',
|
size: 0,
|
url: '',
|
md5: '16b4c5e61897159b11405883ebd6749c',
|
courseId: 23388,
|
docType: 1,
|
status: 0,
|
extInfo: '{"addMode":true,"docType":1,"pptNotes":true,"pptContent":false,"notesPolish":false}',
|
resolveType: 1
|
})
|
|
//智能讲稿组件begin
|
//添加ref
|
const rewriterRef = ref()
|
// 计算当前要传入的图片URL
|
const currentImageUrl = computed(() => {
|
if (selectPPT.value?.innerPicture?.src) {
|
return selectPPT.value.innerPicture.src
|
}
|
return selectPPT.value?.pictureUrl || ''
|
})
|
//ppt人脸校验方法
|
const PPtIsHaveFace = async () => {
|
IsEndCheckFace.value = false
|
//添加ppt中人脸校验
|
//向原始ppt添加数据,用作后续ppt中是否包含人脸的数据校验原始数据
|
const InitPpt = PPTArr.value.map((item) => {
|
return item.innerPicture.src
|
})
|
const { detectFacesInImages } = useFaceDetection()
|
IsHaveFace.value = await detectFacesInImages(InitPpt)
|
IsEndCheckFace.value = true
|
}
|
// 打开智能讲稿弹窗
|
const openScriptRewriter = () => {
|
if (!selectPPT.value?.pptRemark && !currentImageUrl.value) {
|
message.warning('请先选中页面')
|
return
|
}
|
rewriterRef.value.open()
|
}
|
// 处理讲稿内容
|
const handleRewritten = (rewrittenContent) => {
|
selectPPT.value.pptRemark = rewrittenContent
|
}
|
//智能讲稿组件end
|
const handleExceed = (files) => {
|
uploadRef.value!.clearFiles()
|
const file = files[0] as UploadRawFile
|
file.uid = genFileId()
|
uploadRef.value!.handleStart(file)
|
}
|
|
const handleChange = (files) => {
|
const extension = files.name.split('.').pop().toLowerCase()
|
uploadFileObj.docType = extension === 'pdf' ? 2 : 1
|
uploadFileObj.filename = files.name
|
uploadFileObj.size = files.size
|
}
|
|
const uploadExplainRef = ref()
|
const handleSuccess = (rawFile) => {
|
message.success('上传成功!')
|
uploadFileObj.url = rawFile.data
|
uploadExplainRef.value.open()
|
uploadRef.value!.clearFiles()
|
}
|
|
const handleError = (err) => {
|
message.error('上传失败,请重试')
|
console.error('Upload error:', err)
|
}
|
|
const uploadSubmit = (uploadForm) => {
|
pptTemplateApi.createPPT(uploadFileObj).then((res) => {
|
if (res) {
|
courseInfo.value.name = uploadFileObj.filename.split('.').slice(0, -1).join('.')
|
editName.value = courseInfo.value.name
|
schedulePPT(res)
|
}
|
})
|
}
|
|
const chooseTemplate = (currTemplate) => {
|
console.log('currTemplate', currTemplate)
|
selectTemplate.value = cloneDeep(currTemplate)
|
templates.value.forEach((item) => {
|
item.isActive = false
|
})
|
currTemplate.isActive = true
|
applyTemplate()
|
}
|
|
const schedulePPTTimer = ref()
|
const schedulePPT = (id) => {
|
percentagePPT.value = 0
|
if (schedulePPTTimer.value) {
|
clearInterval(schedulePPTTimer.value)
|
}
|
showLeftList.value = false
|
schedulePPTTimer.value = setInterval(() => {
|
pptTemplateApi.getSchedule(id).then((res) => {
|
if (res && typeof res == 'string') {
|
const progress = Number(res)
|
if (progress < 0) {
|
clearInterval(schedulePPTTimer.value)
|
showLeftList.value = true
|
message.error('PPT解析失败,请重试')
|
return
|
}
|
percentagePPT.value = parseInt(`${progress * 100}`)
|
} else if (res && res.length > 0) {
|
res.forEach((item) => {
|
item.isActive = false
|
item.isChecked = false
|
item.driverType = 1
|
item.selectAudio = {}
|
item.uploadAudioUrl = ''
|
item.fileList = []
|
item.businessId = generateUUID()
|
item.width = courseInfo.value.width
|
item.height = courseInfo.value.height
|
|
// 初始化独立的数字人配置
|
item.digitalHuman = {
|
show: true,
|
x: viewSize.width - digitalHumanSize.width,
|
y: viewSize.height - digitalHumanSize.height,
|
w: digitalHumanSize.width,
|
h: digitalHumanSize.height,
|
active: false,
|
host: selectHost.value
|
}
|
|
let ppturl = item.pictureUrl
|
if (selectTemplate.value.showBackground) {
|
item.pictureUrl = selectTemplate.value.bgImage
|
} else {
|
item.pictureUrl = ''
|
}
|
|
if (item.pictureUrl && selectTemplate.value.showPpt) {
|
item.innerPicture = {
|
name: '画中画',
|
src: ppturl,
|
cover: ppturl,
|
width: selectTemplate.value.pptW,
|
height: selectTemplate.value.pptH,
|
originWidth: selectTemplate.value.pptW,
|
originHeight: selectTemplate.value.pptH,
|
category: 1,
|
depth: 1,
|
top: selectTemplate.value.pptY,
|
marginLeft: selectTemplate.value.pptX,
|
businessId: generateUUID(),
|
entityType: 0,
|
entityId: '0'
|
}
|
} else if (!item.pictureUrl && selectTemplate.value.showPpt) {
|
item.pictureUrl = ppturl
|
item.innerPicture = {
|
src: '',
|
cover: '',
|
width: 0,
|
height: 0,
|
top: 0,
|
marginLeft: 0,
|
category: 1
|
}
|
}
|
})
|
|
PPTArr.value = res
|
PPTArr.value[0].isActive = true
|
selectPPT.value = PPTArr.value[0]
|
showLeftList.value = true
|
clearInterval(schedulePPTTimer.value)
|
|
//ppt人脸校验
|
PPtIsHaveFace()
|
|
//轮询保存课程
|
/**
|
* 后端数据库压力过大,暂时停止定时保存
|
*/
|
// saveInter()
|
}
|
})
|
}, 2000)
|
}
|
|
const cancelAnalyze = () => {
|
showLeftList.value = true
|
clearInterval(schedulePPTTimer.value)
|
}
|
|
const copyDocument = (item, index) => {
|
ElMessageBox.confirm('是否复制该页面?', '提示', {
|
confirmButtonText: '是',
|
cancelButtonText: '否',
|
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(() => {
|
ElMessage({
|
type: 'info',
|
message: '已取消复制'
|
})
|
})
|
}
|
|
const deleteDocument = (item) => {
|
ElMessageBox.confirm('是否删除该页面?', '提示', {
|
confirmButtonText: '是',
|
cancelButtonText: '否',
|
type: 'warning'
|
})
|
.then(() => {
|
PPTArr.value = PPTArr.value.filter((child) => child.id !== item.id)
|
//已经进行过删除操作
|
DeleteD.value = true
|
})
|
.catch(() => {
|
ElMessage({
|
type: 'info',
|
message: '已取消删除'
|
})
|
})
|
}
|
|
const deleteDigitalHuman = () => {
|
selectPPT.value.digitalHuman.show = false
|
}
|
|
const deleteInnerPicture = () => {
|
selectPPT.value.innerPicture.src = ''
|
}
|
|
const deleteMore = () => {
|
let selected = PPTArr.value.filter((child) => child.isChecked == true)
|
if (selected.length == 0) {
|
message.warning('请先选择要删除的ppt')
|
} else {
|
PPTArr.value = PPTArr.value.filter((child) => child.isChecked !== true)
|
}
|
}
|
|
const hostList = ref([])
|
const loading = ref(true)
|
const total = ref(0)
|
const queryParams = reactive({
|
pageNo: 1,
|
pageSize: 100,
|
type: '',
|
gender: '',
|
posture: ''
|
})
|
const queryParams1 = reactive({
|
pageNo: 1,
|
pageSize: 100,
|
zg: ''
|
})
|
const selectHost = ref(null)
|
|
const getList = async () => {
|
loading.value = true
|
try {
|
queryParams.type = tabs1ActiveNum.value
|
queryParams.gender = tabs2ActiveNum.value
|
queryParams.posture = tabs3ActiveNum.value
|
queryParams.status = 0
|
let data = await pptTemplateApi.pageList(queryParams)
|
data.list.forEach((item) => {
|
item.isActive = false
|
})
|
|
hostList.value = data.list
|
if (hostList.value.length > 0 && !selectHost.value) {
|
selectHost.value = hostList.value[0]
|
console.log('selectHost.value', selectHost.value)
|
if (selectHost.value !== null) {
|
selectHost.value.fixVideoUrl = null
|
selectHost.value.pictureUrl = null
|
selectHost.value.videoUrl = null
|
selectHost.value.id = null
|
}
|
// 更新当前选中PPT的数字人
|
if (selectPPT.value) {
|
selectPPT.value.digitalHuman.host = selectHost.value
|
}
|
}
|
|
total.value = data.total
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 所有数字人形象
|
const AllHumanList = ref<any[]>([])
|
// 获取所有数字人形象
|
const GetAllHumanList = () => {
|
let query = {
|
pageNo: 1,
|
pageSize: 100,
|
type: '0',
|
gender: tabs2ActiveNum.value,
|
posture: tabs3ActiveNum.value,
|
status: 0
|
}
|
GetHumanList(query)
|
query.type = '1'
|
GetHumanList(query)
|
}
|
// 获取单类数字人形象
|
const GetHumanList = async (query) => {
|
let data = await pptTemplateApi.pageList(query)
|
AllHumanList.value = [...AllHumanList.value, ...data.list]
|
}
|
|
const choosePPt = (item) => {
|
PPTArr.value.forEach((child) => {
|
child.isActive = child.id === item.id
|
})
|
selectPPT.value = item
|
}
|
|
// const chooseHost = (item) => {
|
// hostList.value.forEach((el) => {
|
// el.isActive = el.id === item.id
|
// })
|
//
|
// // 只更新当前选中的PPT页的数字人
|
// if (selectPPT.value) {
|
// selectPPT.value.digitalHuman.host = item
|
// initHumanPositon(item, selectPPT.value.digitalHuman)
|
// }
|
// }
|
// 初始化数字人位置
|
const initHumanPositon = (hostData, digitalHuman) => {
|
if (hostData.posture === 1) {
|
digitalHuman.x = viewSize.width - digitalHuman.w
|
digitalHuman.y = viewSize.height - digitalHuman.h
|
} else if (hostData.posture === 2) {
|
digitalHuman.x = viewSize.width - digitalHuman.w
|
digitalHuman.y = viewSize.height - digitalHuman.h
|
}
|
}
|
|
const audioSelect = ref()
|
const audioSelectData = ref()
|
|
const openSelect = () => {
|
audioSelect.value.open()
|
}
|
|
const selectAudio = (data) => {
|
console.log(data)
|
audioSelectData.value = data
|
if (data == undefined) {
|
selectPPT.value.selectAudio.name = ''
|
} else {
|
PPTArr.value.forEach((scene) => {
|
scene.selectAudio = data[0]
|
})
|
}
|
}
|
|
const coursesCreate = () => {
|
const params = {
|
accountId: userId.value
|
}
|
pptTemplateApi.coursesCreate(params).then((res) => {
|
if (res) {
|
courseInfo.value.id = res
|
}
|
})
|
}
|
|
const saveTime = ref()
|
|
const getSaveTime = () => {
|
const date = new Date()
|
let h: any = date.getHours()
|
let m: any = date.getMinutes()
|
let s: any = date.getSeconds()
|
|
if (h < 10) h = '0' + h
|
if (m < 10) m = '0' + m
|
if (s < 10) s = '0' + s
|
|
return h + ':' + m + ':' + s
|
}
|
|
const warningDialog = ref()
|
|
const voiceData = reactive({
|
show: false,
|
speechRate: 1,
|
volume: 1,
|
defaultSpeechRate: 1,
|
defaultVolume: 1,
|
marks: {
|
0.6: '0.6',
|
1: '1',
|
1.5: '1.5'
|
},
|
marks2: {
|
1: '1',
|
1.5: '1.5',
|
2: '2'
|
}
|
})
|
|
const removeHtmlTags = (html) => {
|
const parser = new DOMParser()
|
const doc = parser.parseFromString(html, 'text/html')
|
return doc.body.textContent || ''
|
}
|
|
const saveSubmit = async (type) => {
|
console.log('是否删除', DeleteD.value)
|
|
if (!PPTArr.value || PPTArr.value.length === 0) {
|
message.warning('场景为空,请先上传PPT!')
|
return false
|
}
|
|
if (
|
selectPPT.value.digitalHuman?.host === null ||
|
selectPPT.value.digitalHuman?.host.fixVideoUrl === null ||
|
selectPPT.value.digitalHuman?.host.pictureUrl === null ||
|
selectPPT.value.digitalHuman?.host.videoUrl === null
|
) {
|
message.error('请先选择数字人')
|
return
|
}
|
|
//人脸校验
|
while (!IsEndCheckFace.value) {} //一个空循环,主要为了避免极端情况下当用户点击保存按钮或者视频合成按钮时,人脸校验未完成的问题
|
if (IsHaveFace.value && !DeleteD.value) {
|
message.warning('当前ppt中存在人脸元素,为方便后续视频生成,请去除该元素')
|
return
|
}
|
|
//保存课程
|
let saveSubmitForm = {
|
accountId: courseInfo.value.accountId,
|
aspect: courseInfo.value.aspect,
|
duration: courseInfo.value.duration,
|
height: courseInfo.value.height,
|
matting: courseInfo.value.matting,
|
name: courseInfo.value.name,
|
pageMode: courseInfo.value.pageMode,
|
ppt: [],
|
scenes: [],
|
status: courseInfo.value.status,
|
width: courseInfo.value.width,
|
pageInfo: '',
|
thumbnail: '',
|
subtitlesStyle: '{}'
|
}
|
|
saveSubmitForm.id = courseInfo.value.id
|
|
const scenes = []
|
const pageInfo = {
|
docInfo: {
|
docType: 1,
|
fileName: uploadFileObj.filename,
|
fileSize: uploadFileObj.size
|
},
|
scenes: []
|
}
|
|
let thumbnail = ''
|
|
PPTArr.value.forEach((item, index) => {
|
try {
|
pageInfo.scenes.push(item.businessId)
|
if (index === 0) {
|
thumbnail = item.pictureUrl
|
}
|
|
item.pptRemark = removeHtmlTags(item.pptRemark)
|
|
const formatItem = {
|
background: {
|
backgroundType: item.backgroundType,
|
entityId: '',
|
width: courseInfo.value.width,
|
height: courseInfo.value.height,
|
depth: 0,
|
src: item.pictureUrl,
|
cover: item.pictureUrl,
|
originWidth: item.width,
|
originHeight: item.height,
|
color: '#ffffff',
|
pptRemark: item.pptRemark
|
},
|
hasPerson: item.digitalHuman?.show ? 1 : 2,
|
components: [
|
{
|
name: item.digitalHuman?.host?.name,
|
src: item.digitalHuman?.host?.pictureUrl,
|
cover: item.digitalHuman?.host?.pictureUrl,
|
width: item.digitalHuman?.w * scaleRatio.value.width,
|
height: item.digitalHuman?.h * scaleRatio.value.height,
|
originWidth: item.digitalHuman?.w * scaleRatio.value.width,
|
originHeight: item.digitalHuman?.h * scaleRatio.value.height,
|
category: 2,
|
depth: 0,
|
top: item.digitalHuman?.y * scaleRatio.value.height,
|
marginLeft: item.digitalHuman?.x * scaleRatio.value.width,
|
entityId: item.digitalHuman?.host?.code,
|
entityType: item.digitalHuman?.host?.type,
|
businessId: generateUUID(),
|
digitbotType: tabs1ActiveNum.value,
|
matting: 1,
|
marker: 1,
|
status: item.digitalHuman?.show ? 0 : 1
|
},
|
...(item.innerPicture?.src
|
? [
|
{
|
...cloneDeep(item.innerPicture),
|
width: item.innerPicture.width * scaleRatio.value.width,
|
height: item.innerPicture.height * scaleRatio.value.height,
|
top: item.innerPicture.top * scaleRatio.value.height,
|
marginLeft: item.innerPicture.marginLeft * scaleRatio.value.width,
|
category: 1,
|
id: undefined
|
}
|
]
|
: [])
|
],
|
driverType: item.driverType,
|
duration: '',
|
orderNo: index + 1,
|
textDriver: {
|
pitch: '',
|
speed: '',
|
speech_rate: voiceData.speechRate,
|
volume: voiceData.volume,
|
smartSpeed: '',
|
textJson: item.pptRemark
|
},
|
audioDriver: {
|
fileName: item.fileList && item.fileList[0]?.name,
|
audioId: '',
|
audioUrl: item.uploadAudioUrl,
|
useVideoBackgroundAudio: ''
|
},
|
voice: {
|
voiceId: audioSelectData.value == undefined ? null : audioSelectData.value[0].id,
|
entityId: item.selectAudio && item.selectAudio.code,
|
tonePitch: '',
|
voiceType: item.selectAudio && item.selectAudio.voiceType,
|
speechRate: '',
|
name: item.selectAudio && item.selectAudio.name,
|
language: selectLanguage.value?.value
|
},
|
businessId: item.businessId
|
}
|
|
scenes.push(formatItem)
|
} catch (error) {
|
console.error(`处理第 ${index + 1} 个场景时出错:`, error)
|
throw error
|
}
|
})
|
|
saveSubmitForm.pageInfo = JSON.stringify(pageInfo)
|
saveSubmitForm.thumbnail = thumbnail
|
saveSubmitForm.scenes = cloneDeep(scenes)
|
|
if (type == 'save') {
|
if (DeleteD.value) {
|
//如果进行过ppt删除操作则需要进行二次查看
|
await PPtIsHaveFace()
|
if (IsHaveFace.value) {
|
message.warning('当前ppt中存在人脸元素,为方便后续视频生成,请去除该元素')
|
return
|
}
|
}
|
|
|
|
try {
|
const res = await pptTemplateApi.coursesSave(JSON.stringify(saveSubmitForm))
|
if (res) {
|
message.success('保存成功!')
|
saveTime.value = getSaveTime()
|
return true
|
}
|
return false
|
} catch (error) {
|
console.error('保存课程时出错:', error)
|
message.error('保存失败,请重试')
|
return false
|
}
|
} else {
|
try {
|
if (
|
ChangeSoundTypeList.value?.value === undefined ||
|
selectLanguage.value?.value === undefined
|
) {
|
message.error('请先选择语种与声音类型')
|
return
|
}
|
|
if (ChangeSoundTypeList.value.value === 2) {
|
//此时为通用
|
if (selectList.value === undefined || selectList.value === null) {
|
message.warning('请选择声音模型,并点击确定按钮确定所选声音模型')
|
return false
|
}
|
|
if (audioSelectData.value == undefined) {
|
message.warning('请点击确定按钮确定所选声音模型')
|
return false
|
}
|
}
|
|
const saveResult = await saveSubmit('save')
|
if (!saveResult) {
|
message.error('保存失败,请重试后再合成视频')
|
return
|
}
|
|
let warningStrArr = []
|
for (let i = 0; i < PPTArr.value.length; i++) {
|
const item = PPTArr.value[i]
|
console.log(item)
|
// 校验背景宽高
|
if (!item.width || !item.height) {
|
message.warning('背景尺寸无效,请检查宽高设置,或者重新选择模板')
|
return
|
}
|
if (item.driverType == 1) {
|
const plainText = item.pptRemark ? item.pptRemark.replace(/<[^>]+>/g, '') : ''
|
if (!plainText || plainText.trim() === '') {
|
warningStrArr.push(
|
`场景<span style="color: red; font-weight: bold;">${i + 1}</span>无有效的口播内容`
|
)
|
} else if (plainText.length > 2000) {
|
warningStrArr.push(
|
`场景<span style="color: red; font-weight: bold;">${i + 1}</span>口播内容超过2000字,请减少或拆分场景`
|
)
|
}
|
}
|
}
|
|
if (warningStrArr.length > 0) {
|
warningDialog.value.open(warningStrArr.map((warning) => `<div>${warning}</div>`).join(''))
|
return
|
}
|
|
try {
|
const res = await pptTemplateApi.megerMedia(saveSubmitForm)
|
if (res) {
|
message.success('合成视频任务提交成功,请到我的视频中查看!')
|
}
|
} catch (error) {
|
console.error('合成视频失败:', error)
|
message.error('合成视频失败,请重试')
|
}
|
} catch (error) {
|
console.error('保存或合成过程出错:', error)
|
message.error('操作失败,请重试')
|
}
|
}
|
}
|
|
const applyTemplate = (ppt = null) => {
|
const template = selectTemplate.value
|
console.log('template', selectTemplate.value)
|
const pptList = applyAllTemplate.value ? PPTArr.value : [selectPPT.value]
|
|
pptList.forEach((item) => {
|
const originalPPT = item.innerPicture?.src || item.pictureUrl
|
console.log(template)
|
item.pictureUrl = template.bgImage
|
item.digitalHuman.show = template.showDigitalHuman
|
item.digitalHuman.w = template.humanW
|
item.digitalHuman.h = template.humanH
|
item.digitalHuman.x = template.humanX
|
item.digitalHuman.y = template.humanY
|
PPTArr.value.forEach((otherItem) => {
|
if (otherItem.templateId === item.templateId) {
|
otherItem.width = item.width
|
otherItem.height = item.height
|
}
|
})
|
if (template.showPpt) {
|
item.innerPicture = {
|
name: '画中画',
|
src: originalPPT,
|
cover: template.bgImage,
|
width: template.pptW,
|
height: template.pptH,
|
top: template.pptY,
|
marginLeft: template.pptX,
|
category: 1,
|
depth: 1,
|
businessId: generateUUID(),
|
entityType: 1,
|
originHeight: courseInfo.value.height,
|
originWidth: courseInfo.value.width,
|
entityId: 1,
|
templateId: template.id
|
}
|
}
|
})
|
}
|
|
const replaceDialog = ref(null)
|
|
const openReplaceDialog = () => {
|
replaceDialog.value.open()
|
}
|
|
const escapeRegExp = (string) => {
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
}
|
|
const handleReplacement = (replacements) => {
|
PPTArr.value.forEach((item) => {
|
if (item.pptRemark) {
|
replacements.forEach((replacement) => {
|
const fromEscaped = escapeRegExp(replacement.from)
|
const regExp = new RegExp(fromEscaped, 'g')
|
item.pptRemark = item.pptRemark.replace(regExp, replacement.to)
|
})
|
}
|
})
|
message.success('批量替换成功!')
|
}
|
|
const showAudioPlay = ref(false)
|
const showAudioPlay1 = ref(false)
|
const startAudioPlay = ref(false)
|
const textareaRef = ref()
|
const selectTextarea = ref('')
|
|
const audioExceed = () => {
|
message.warning('最多上传一个声音驱动文件!')
|
}
|
|
const currentAudio = ref()
|
|
const createAudio = async () => {
|
|
const text = editorRef.value.getText()
|
if (!text) {
|
message.warning('请输入需要试听文本的内容…')
|
return false
|
}
|
|
if (
|
ChangeSoundTypeList.value?.value === undefined ||
|
selectLanguage.value?.value === undefined ||
|
selectLanguage.value.value === 'all_Language'
|
) {
|
message.error('请先选择语种与声音类型')
|
return
|
}
|
|
console.log(ChangeSoundTypeList.value.value)
|
console.log(selectList.value)
|
|
if (ChangeSoundTypeList.value.value === 2) {
|
//此时为通用
|
if (selectList.value === undefined || selectList.value === null) {
|
message.warning('请选择声音模型,并点击确定按钮确定所选声音模型')
|
return false
|
}
|
|
if (audioSelectData.value == undefined) {
|
message.warning('请点击确定按钮确定所选声音模型')
|
return false
|
}
|
}
|
|
if (ChangeSoundTypeList.value.value === 1) {
|
if (
|
selectPPT.value.digitalHuman.host === null ||
|
selectPPT.value.digitalHuman.host.id === undefined ||
|
selectPPT.value.digitalHuman.host.id === null
|
) {
|
message.warning('请选择数字人')
|
return false
|
}
|
}
|
|
const truncatedText = text.length > 100 ? text.substring(0, 100) : text
|
|
const params = {
|
text: truncatedText,
|
humanId: selectPPT.value.digitalHuman?.host?.id || null,
|
voiceId: audioSelectData.value == undefined ? null : audioSelectData.value[0].id,
|
language: selectLanguage.value?.value
|
}
|
|
if (ChangeSoundTypeList.value.value === 2) {
|
//此时选取了声音模型
|
params.humanId = null
|
} else if (ChangeSoundTypeList.value.value === 1) {
|
params.voiceId = null
|
}
|
|
try {
|
showAudioPlay1.value = true
|
const res = await pptTemplateApi.createAudio(params)
|
|
if (res && !res.error) {
|
showAudioPlay1.value = false
|
showAudioPlay.value = true
|
|
currentAudio.value = new Audio(res)
|
currentAudio.value.addEventListener('ended', () => {
|
showAudioPlay.value = false
|
currentAudio.value = null
|
})
|
|
currentAudio.value.play()
|
} else {
|
showAudioPlay1.value = false
|
}
|
} catch (error) {
|
console.error('API 请求失败:', error)
|
showAudioPlay1.value = false
|
}
|
}
|
|
const pauseAudio = () => {
|
currentAudio.value.pause()
|
currentAudio.value = null
|
showAudioPlay.value = false
|
}
|
|
const currentAudioFile = ref()
|
|
const audioPlay = (file) => {
|
if (!file.response.data) {
|
message.error('未获取到文件')
|
return
|
}
|
|
if (currentAudioFile.value) {
|
currentAudioFile.value.pause()
|
currentAudioFile.value.currentTime = 0
|
}
|
|
const audio = new Audio(file.response.data)
|
currentAudioFile.value = audio
|
|
audio.addEventListener('ended', () => {
|
cancelAudio()
|
})
|
|
startAudioPlay.value = true
|
audio.play()
|
}
|
|
const cancelAudio = () => {
|
if (currentAudioFile.value) {
|
currentAudioFile.value.pause()
|
currentAudioFile.value.currentTime = 0
|
currentAudioFile.value = null
|
}
|
startAudioPlay.value = false
|
}
|
|
const goBack = () => {
|
if (PPTArr.value.length == 0) {
|
pptTemplateApi.coursesDelete(courseInfo.value.id).then((res) => {
|
router.go(-1)
|
})
|
} else {
|
router.go(-1)
|
}
|
}
|
|
const editorRef = shallowRef()
|
const editorConfig = { placeholder: '请输入内容...' }
|
|
const handleCreated = (editor) => {
|
editorRef.value = editor
|
}
|
|
const dialogVisible = ref(false)
|
const textList = ref([])
|
|
const handleWord = () => {
|
editorRef.value.focus()
|
selectTextarea.value = editorRef.value.getSelectionText()
|
if (!selectTextarea.value) {
|
message.warning('请先选中需指定读法的文本')
|
return false
|
}
|
if (selectTextarea.value.length > 1) {
|
message.warning('只能选择一个字')
|
return false
|
}
|
let textPinyin = polyphonic(selectTextarea.value, { toneType: 'num', type: 'array' })[0]
|
if (textPinyin.length > 1) {
|
textList.value = textPinyin
|
dialogVisible.value = true
|
} else {
|
message.warning(`${selectTextarea.value}不是多音字`)
|
}
|
}
|
|
const handleTag = (name) => {
|
dialogVisible.value = false
|
const node = {
|
type: 'text-value',
|
textVal: selectTextarea.value,
|
children: [{ text: name }]
|
}
|
editorRef.value.restoreSelection()
|
editorRef.value.insertNode(node)
|
editorRef.value.move(1)
|
}
|
const onDragMove = (evt, data) => {
|
console.log(evt)
|
console.log(data)
|
|
// 限制坐标
|
if (data.x < -100) {
|
data.x = -100 // 可以设置最小坐标为 -100
|
}
|
if (data.y < -100) {
|
data.y = -100 // 可以设置最小坐标为 -100
|
}
|
}
|
|
const getCourseDetail = async (id) => {
|
const res = await pptTemplateApi.coursesDetail(id)
|
console.log('res当前信息', res)
|
if (res) {
|
courseInfo.value = res
|
if (res.scenes && res.scenes.length > 0) {
|
res.scenes.forEach((item) => {
|
item.isActive = false
|
item.isChecked = false
|
item.pictureUrl = item.background.src
|
item.pptRemark = editorHtml.parseElemHtml(item.background.pptRemark)
|
item.backgroundType = item.background.backgroundType
|
item.width = item.background.width
|
item.height = item.background.height
|
|
const hostInfo = item.components.find((p) => p.category == 2)
|
if (hostInfo) {
|
item.digitalHuman = {
|
show: hostInfo.status === 0,
|
x: hostInfo.marginLeft / scaleRatio.value.width,
|
y: hostInfo.top / scaleRatio.value.height,
|
w: hostInfo.width / scaleRatio.value.width,
|
h: hostInfo.height / scaleRatio.value.height,
|
active: false,
|
host: {
|
...AllHumanList.value.find((h) => h.code === hostInfo.entityId),
|
code: hostInfo.entityId,
|
type: hostInfo.entityType
|
}
|
}
|
}
|
|
const innerPicture = item.components.find((p) => p.category == 1)
|
if (innerPicture) {
|
item.innerPicture = {
|
...innerPicture,
|
width: innerPicture.width / scaleRatio.value.width,
|
height: innerPicture.height / scaleRatio.value.height,
|
top: innerPicture.top / scaleRatio.value.height,
|
marginLeft: innerPicture.marginLeft / scaleRatio.value.width
|
}
|
}
|
})
|
|
PPTArr.value = res.scenes
|
PPTArr.value[0].isActive = true
|
selectPPT.value = PPTArr.value[0]
|
|
PPTArr.value.forEach((scene, index) => {
|
if (res.scenes[index].voice) {
|
scene.selectAudio = res.scenes[index].voice
|
scene.selectAudio.code = res.scenes[index].voice.entityId
|
scene.selectAudio.id = res.scenes[index].voice.voiceId
|
}
|
scene.uploadAudioUrl = res.scenes[index].audioDriver?.audioUrl
|
})
|
|
if (PPTArr.value[0].audioDriver?.fileName && PPTArr.value[0].audioDriver?.audioUrl) {
|
selectPPT.value.fileList = [
|
{
|
name: PPTArr.value[0].audioDriver?.fileName,
|
url: PPTArr.value[0].audioDriver?.audioUrl
|
}
|
]
|
}
|
|
// 设置音频选择数据
|
const firstScene = res.scenes[0]
|
if (firstScene.voice) {
|
audioSelectData.value = [
|
{
|
id: firstScene.voice.voiceId,
|
entityId: firstScene.voice.entityId,
|
name: firstScene.voice.name
|
}
|
]
|
}
|
}
|
|
const pageInfo = res.pageInfo ? JSON.parse(res.pageInfo) : ''
|
uploadFileObj.filename = pageInfo ? pageInfo.docInfo.fileName : ''
|
uploadFileObj.size = pageInfo ? pageInfo.docInfo.fileSize : ''
|
}
|
}
|
const getList1 = async () => {
|
const data = await TemplateApi.getTemplatePage(queryParams1)
|
if (data) {
|
TEMPLATE_PRESETS.value = data.list.map((item) => ({
|
...item,
|
showBackground: item.showBackground === 1,
|
showDigitalHuman: item.showDigitalHuman === 1,
|
showPpt: item.showPpt === 1
|
}))
|
templates.value = TEMPLATE_PRESETS.value.map((template) => cloneDeep(template))
|
selectTemplate.value = cloneDeep(templates.value[0])
|
}
|
}
|
onMounted(async () => {
|
let data = await TemplateApi.getTemplatePage(queryParams1)
|
TEMPLATE_PRESETS.value = data.list.map((item) => ({
|
...item,
|
showBackground: item.showBackground === 1,
|
showDigitalHuman: item.showDigitalHuman === 1,
|
showPpt: item.showPpt === 1
|
}))
|
|
templates.value = TEMPLATE_PRESETS.value.map((template) => cloneDeep(template))
|
selectTemplate.value = cloneDeep(templates.value[0])
|
|
await getList()
|
|
GetAllHumanList()
|
|
if (route.query.id) {
|
await getCourseDetail(route.query.id)
|
} else {
|
coursesCreate()
|
}
|
})
|
|
onUnmounted(() => {
|
if (schedulePPTTimer.value) {
|
clearInterval(schedulePPTTimer.value)
|
}
|
if (currentAudioFile.value) {
|
currentAudioFile.value.removeEventListener('ended', cancelAudio)
|
currentAudioFile.value = null
|
}
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.pages {
|
height: 100%;
|
background-color: #f5f7fa;
|
}
|
|
.minddle-host-image {
|
z-index: 5;
|
width: 100%;
|
height: 100%;
|
}
|
|
.template-top {
|
display: flex;
|
height: 60px;
|
padding: 0 30px;
|
line-height: 60px;
|
background-color: #fff;
|
border: 1px solid #ebeef5;
|
box-shadow: 0 3px 6px rgb(175 175 175 / 16%);
|
justify-content: space-between;
|
|
.top-left {
|
display: flex;
|
align-items: center;
|
|
.top-icon {
|
display: flex;
|
align-items: center;
|
}
|
|
.back-text {
|
margin-right: 20px;
|
margin-left: 10px;
|
cursor: pointer;
|
}
|
|
span {
|
margin: 0 25px;
|
}
|
}
|
|
.top-right {
|
span {
|
margin: 0 20px;
|
}
|
}
|
}
|
|
.template-main {
|
display: flex;
|
height: calc(100% - 82px);
|
padding: 10px;
|
justify-content: space-around;
|
|
.template-left {
|
position: relative;
|
width: 180px;
|
background-color: #fff;
|
border: 1px solid #ebeef5;
|
box-shadow: 0 3px 6px rgb(175 175 175 / 16%);
|
|
.page {
|
margin: 0;
|
|
div {
|
padding: 5px 10px;
|
margin: 0;
|
line-height: 30px;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.line {
|
width: 30px;
|
height: 3px;
|
padding: 0;
|
margin: 0;
|
background-color: aqua;
|
}
|
|
.upload-demo {
|
text-align: center;
|
}
|
}
|
|
.left-upload-setting {
|
display: flex;
|
height: calc(100% - 86px);
|
padding: 0 20px;
|
text-align: center;
|
flex-direction: column;
|
justify-content: center;
|
align-items: center;
|
|
div {
|
line-height: 40px;
|
}
|
|
::v-deep(.el-progress-bar) {
|
width: 180px;
|
}
|
|
.el-button {
|
margin: 20px 0;
|
}
|
}
|
|
.image-list {
|
height: calc(100% - 70px);
|
padding: 10px;
|
overflow: hidden auto;
|
border-bottom: 1px solid #ebeef5;
|
|
.list {
|
position: relative;
|
height: calc(152px * 9 / 16);
|
margin: 20px 0;
|
box-sizing: content-box;
|
|
.list-index {
|
position: absolute;
|
top: 10px;
|
left: 10px;
|
z-index: 100;
|
width: 25px;
|
height: 25px;
|
line-height: 25px;
|
color: #fff;
|
text-align: center;
|
background: #122121;
|
border-radius: 5px;
|
}
|
|
.background {
|
position: absolute;
|
width: 100%;
|
height: 100%;
|
}
|
.background1 {
|
position: absolute;
|
width: 100%;
|
height: 100%;
|
background-color: rgba(0, 0, 0, 0);
|
}
|
|
.ppt-bg {
|
z-index: 2;
|
}
|
|
.host {
|
position: absolute;
|
z-index: 3;
|
}
|
|
.icon-content {
|
position: absolute;
|
top: 0;
|
right: 10px;
|
display: flex;
|
cursor: pointer;
|
align-items: center;
|
z-index: 4;
|
}
|
}
|
}
|
|
.page-btn {
|
position: absolute;
|
bottom: 10px;
|
width: 85%;
|
padding: 0 10px;
|
}
|
}
|
|
.template-middle {
|
display: flex;
|
width: 56%;
|
background-color: #fff;
|
box-shadow: 0 3px 6px rgb(175 175 175 / 16%);
|
flex-grow: 1;
|
flex-direction: column;
|
justify-content: flex-start;
|
|
.middle-top {
|
padding: 5px 20px;
|
}
|
|
.main-box {
|
display: flex;
|
padding: 10px 20px;
|
border: 1px solid #ebeef5;
|
justify-content: center;
|
|
.main-image-box {
|
position: relative;
|
border: 1px solid #ebeef5;
|
box-sizing: content-box;
|
}
|
|
.list {
|
position: relative;
|
display: flex;
|
width: 95%;
|
justify-content: center;
|
}
|
|
.ppt-bg {
|
z-index: 2;
|
width: 100%;
|
height: 100%;
|
}
|
|
.host {
|
position: absolute;
|
right: 0;
|
bottom: 0;
|
width: 300px;
|
}
|
}
|
|
.voice-main {
|
display: flex;
|
justify-content: space-between;
|
padding: 10px;
|
|
.media-box {
|
display: flex;
|
align-items: center;
|
}
|
}
|
|
.audio-upload {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
height: 200px;
|
}
|
|
.middle-textarea {
|
padding: 5px 20px;
|
}
|
|
.tool-box {
|
display: flex;
|
padding: 10px;
|
border-top: 1px solid #ebeef5;
|
justify-content: space-between;
|
|
.tool-btn {
|
display: flex;
|
align-items: center;
|
}
|
}
|
|
.audio-play {
|
position: absolute;
|
top: 0;
|
left: 0;
|
display: flex;
|
width: 100%;
|
height: 100%;
|
padding: 20px 0;
|
line-height: 40px;
|
color: #fff;
|
background: #000;
|
opacity: 0.5;
|
align-items: center;
|
flex-direction: column;
|
}
|
}
|
|
.template-right {
|
position: relative;
|
width: 20%;
|
background-color: #fff;
|
box-shadow: 0 3px 6px rgb(175 175 175 / 16%);
|
|
.tabs-1 {
|
display: flex;
|
justify-content: space-around;
|
padding: 10px 30px;
|
border-bottom: 1px solid #ebeef5;
|
|
.tabs-item {
|
width: 50px;
|
font-size: 14px;
|
text-align: center;
|
cursor: pointer;
|
|
span {
|
display: block;
|
width: 50px;
|
height: 2px;
|
margin-top: 5px;
|
background: #409eff;
|
}
|
}
|
}
|
|
.tabs-2 {
|
display: flex;
|
padding: 10px;
|
justify-content: space-around;
|
|
div {
|
width: 60px;
|
height: 30px;
|
line-height: 30px;
|
text-align: center;
|
cursor: pointer;
|
border-radius: 5px;
|
}
|
|
.tabs-active {
|
color: #fff !important;
|
background-color: #409eff;
|
}
|
}
|
|
.apply-all {
|
position: absolute;
|
bottom: 80px;
|
display: flex;
|
width: 100%;
|
justify-content: center;
|
}
|
|
.host-list {
|
height: 80%;
|
overflow-y: auto;
|
border-top: 1px solid #ebeef5;
|
|
.host-item {
|
position: relative;
|
display: inline-block;
|
width: 95%;
|
margin: 5px 0;
|
margin-left: 10px;
|
cursor: pointer;
|
|
.background {
|
position: absolute;
|
top: 0;
|
left: 0;
|
z-index: 1;
|
width: 100%;
|
height: 100%;
|
background-color: #f0f1fa;
|
}
|
|
.host-name {
|
position: absolute;
|
bottom: 10px;
|
left: 5px;
|
z-index: 100;
|
width: 30px;
|
height: 20px;
|
font-size: 10px;
|
line-height: 20px;
|
text-align: center;
|
background: rgb(225 225 225 / 70%);
|
border-radius: 5px;
|
}
|
|
.ppt-bg {
|
z-index: 2;
|
width: 100%;
|
height: 100%;
|
}
|
}
|
}
|
}
|
|
.image-setting {
|
padding: 10px 20px;
|
|
.img-setting {
|
display: flex;
|
align-items: center;
|
line-height: 40px;
|
|
.setting-label {
|
width: 120px;
|
}
|
|
::v-deep(.el-input) {
|
width: 170px;
|
margin-left: 10px;
|
}
|
}
|
}
|
|
.template-list {
|
height: 90%;
|
overflow-y: auto;
|
border-top: 1px solid #ebeef5;
|
|
.template-item {
|
position: relative;
|
display: inline-block;
|
width: 224px;
|
height: 126px;
|
margin: 5px 0;
|
margin-left: 10px;
|
cursor: pointer;
|
.list-index {
|
position: absolute;
|
top: 10px;
|
left: 10px;
|
z-index: 100;
|
width: 25px;
|
height: 25px;
|
line-height: 25px;
|
color: #fff;
|
text-align: center;
|
background: #122121;
|
border-radius: 5px;
|
}
|
.ppt-bg {
|
position: absolute;
|
z-index: 2;
|
}
|
|
.human-image {
|
position: absolute;
|
z-index: 3;
|
}
|
}
|
}
|
|
.background {
|
position: absolute;
|
top: 0;
|
left: 0;
|
z-index: 1;
|
width: 100%;
|
height: 100%;
|
//background-color: #f0f1fa;
|
}
|
|
.template-tool {
|
width: 60px;
|
padding: 10px;
|
background-color: #fff;
|
box-shadow: 0 3px 6px rgb(175 175 175 / 16%);
|
|
.tool-item {
|
display: flex;
|
padding: 10px 20px;
|
cursor: pointer;
|
flex-direction: column;
|
align-items: center;
|
|
img {
|
width: 32px;
|
height: 32px;
|
}
|
|
.tool-name {
|
width: 60px;
|
margin-top: 6px;
|
font-size: 14px;
|
line-height: 10px;
|
text-align: center;
|
}
|
}
|
}
|
}
|
|
::v-deep(.el-pagination) {
|
position: absolute;
|
bottom: 0;
|
}
|
|
::-webkit-scrollbar {
|
width: 4px;
|
}
|
|
::-webkit-scrollbar-thumb {
|
background-color: #888;
|
border-radius: 6px;
|
}
|
|
::-webkit-scrollbar-track {
|
background-color: #f2f2f2;
|
border-radius: 6px;
|
}
|
|
.voice-card {
|
z-index: 1000 !important;
|
}
|
|
.voice-card :deep(.el-card__body) {
|
padding: 0;
|
}
|
|
.speech-slider {
|
&:deep(.el-slider__bar) {
|
display: none;
|
}
|
|
&:deep(.el-slider__runway) {
|
height: 2px;
|
}
|
|
&:deep(.el-slider__button-wrapper) {
|
top: -17px;
|
}
|
|
&:deep(.el-slider__marks-stop) {
|
top: -5px;
|
width: 12px;
|
height: 12px;
|
background-color: #1989fa;
|
}
|
}
|
.dialog-footer {
|
float: right;
|
}
|
|
// 声音部分
|
.SoundArea {
|
margin-top: 16px;
|
height: 100%;
|
.SoundClassArea {
|
width: 100%;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
> div {
|
width: 20%;
|
margin: 0 8px;
|
::v-deep .el-select--small .el-select__wrapper {
|
height: 28px;
|
line-height: 28px;
|
}
|
}
|
}
|
.SoundModelArea {
|
width: 100%;
|
height: 86%;
|
margin: 10px 0;
|
overflow-y: scroll;
|
|
.SoundModelAreaBox {
|
width: 100%;
|
.SoundClassTit {
|
width: 80%;
|
margin: 0 auto;
|
}
|
.SoundClassContent {
|
width: 100%;
|
display: flex;
|
flex-wrap: wrap;
|
align-content: flex-start;
|
> .ModealBox {
|
width: 47%;
|
margin: 10px 1%;
|
display: flex;
|
justify-content: space-around;
|
align-items: center;
|
position: relative;
|
> .ImgBox {
|
width: 26%;
|
margin: 0 auto;
|
img {
|
width: 100%;
|
}
|
}
|
> .TextArea {
|
width: 48%;
|
p {
|
font-size: 12px;
|
margin: 4px 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>
|