| | |
| | | @blur="saveEdit" |
| | | @keydown.enter="saveEdit" |
| | | /> |
| | | |
| | | |
| | | <!-- 如果不在编辑,显示文本 --> |
| | | <div |
| | | v-else |
| | |
| | | </div> |
| | | <div class="top-right"> |
| | | <span v-if="saveTime">{{ saveTime }} {{ t('courseCenter.saved') }}</span> |
| | | <!-- 保存按钮 --> |
| | | <el-button size="small" @click="saveSubmit('save')" :loading="SaveLoading" >{{ t('common.save') }}</el-button> |
| | | <!-- 合成视频 --> |
| | | <el-button type="primary" size="small" @click="saveSubmit('')" :loading="MakeLoading" >{{ |
| | | <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 class="icon-content"> |
| | | <el-icon |
| | | size="20" |
| | | color="#FFA500" |
| | | color="#ffffff" |
| | | style="margin-right: 5px" |
| | | @click.stop="copyDocument(element, index)" |
| | | > |
| | |
| | | </el-icon> |
| | | <el-icon |
| | | size="20" |
| | | color="#FFA500" |
| | | color="#ffffff" |
| | | style="margin-right: 5px" |
| | | @click.stop="deleteDocument(element)" |
| | | > |
| | |
| | | class="minddle-host-image" |
| | | :src="selectHost ? selectHost.pictureUrl : ''" |
| | | /> |
| | | |
| | | |
| | | <el-icon |
| | | v-if="PPTpositon.active" |
| | | size="20" |
| | |
| | | class="minddle-host-image" |
| | | :src="selectHost ? selectHost.pictureUrl : ''" |
| | | /> |
| | | |
| | | |
| | | <el-icon |
| | | v-if="PPTpositon.active" |
| | | size="20" |
| | |
| | | <div class="template-box template-right" v-if="showHeadImageTool"> |
| | | <div class="image-setting"> |
| | | <!-- 上传图片成功后,将当前场景的背景修改为上传的图片url--> |
| | | |
| | | |
| | | <div>{{ t('courseCenter.uploadImage') }}</div> |
| | | <UploadImg v-model="selectPPT.pictureUrl" :limit="1" /> |
| | | </div> |
| | |
| | | <div class="template-box template-right" v-if="showInnerPictureTool"> |
| | | <div class="image-setting"> |
| | | <!-- 上传图片成功后,将当前场景的画中画修改为上传的图片url--> |
| | | |
| | | |
| | | <div>{{ t('courseCenter.uploadImage') }}</div> |
| | | <UploadImg v-model="selectPPT.innerPicture.src" :limit="1" /> |
| | | </div> |
| | |
| | | <script lang="ts" setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import draggable from 'vuedraggable' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | import Vue3DraggableResizable from 'vue3-draggable-resizable' |
| | | import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css' |
| | | import { config } from '@/config/axios/config' |
| | |
| | | import { polyphonic } from 'pinyin-pro'; |
| | | //编辑器内容转换ssml |
| | | import { useEditorHtml } from '@/hooks/web/useEditorHtml'; |
| | | //引入人脸识别方法 |
| | | import { useFaceDetection } from '@/utils/HaveFace' |
| | | import {coursesDelete} from "@/api/pptTemplate"; |
| | | import { tr } from 'element-plus/es/locale' |
| | | const editorHtml = useEditorHtml() |
| | | const router = useRouter() // 路由 |
| | | const route = useRoute() // |
| | |
| | | const message = useMessage() |
| | | const isEditing = ref(false) |
| | | const inputRef = ref(null) |
| | | //保存的加载动画 |
| | | const SaveLoading = ref(false) |
| | | //视频合成的加载动画 |
| | | const MakeLoading = ref(false) |
| | | // 切换到编辑模式 |
| | | const toggleEdit = () => { |
| | | isEditing.value = true |
| | |
| | | const onDragMove = (evt, data) => { |
| | | console.log(evt) |
| | | console.log(data) |
| | | |
| | | |
| | | // 限制坐标 |
| | | if (data.x < -100) { |
| | | data.x = -100; // 可以设置最小坐标为 -100 |
| | |
| | | courseInfo.value.name = editName.value |
| | | } |
| | | let humanId = 0 |
| | | |
| | | |
| | | //课程基本信息 |
| | | const courseInfo = ref({ |
| | | id: 0, |
| | |
| | | courseInfo.value.height = newAspect === '16:9' ? 1080 : 1920 |
| | | } |
| | | ) |
| | | |
| | | |
| | | const editName = ref(courseInfo.value.name) |
| | | const viewSize = reactive({ |
| | | width: 800, |
| | |
| | | width: courseInfo.value.width / viewSize.width, // 1920/800 = 2.4 |
| | | height: courseInfo.value.height / viewSize.height // 1080/450 = 2.4 |
| | | })) |
| | | |
| | | |
| | | const PPTpositon = reactive({ |
| | | x: viewSize.width - digitalHumanSize.width, |
| | | y: viewSize.height - digitalHumanSize.height, |
| | |
| | | depth: 0, |
| | | active: false |
| | | }) |
| | | |
| | | |
| | | const componentsInfo = reactive({ |
| | | width: PPTpositon.w / 5, |
| | | height: PPTpositon.h / 4, |
| | |
| | | const showImageSet = ref(false) |
| | | //是否将模板应用到所有页面 |
| | | const applyAllTemplate = ref(false) |
| | | |
| | | |
| | | const xScale = viewSize.width / thumViewSize.width |
| | | // const yScale = viewSize.height / thumViewSize.height |
| | | //左侧ppt数字人位置 |
| | |
| | | const state = reactive({ |
| | | dragging: false |
| | | }) |
| | | |
| | | |
| | | //预设模板 |
| | | const TEMPLATE_PRESETS = ref([]) |
| | | const templates = ref([]) |
| | | |
| | | |
| | | const selectTemplate = ref([]) |
| | | |
| | | |
| | | //数字人tab |
| | | const tabs1 = [ |
| | | { |
| | |
| | | showInnerPictureTool.value = true |
| | | } |
| | | } |
| | | |
| | | |
| | | const PPTArr = ref() |
| | | //ppt解析进度 |
| | | const percentagePPT = ref(0) |
| | | const showLeftList = ref(true) |
| | | |
| | | |
| | | const selectPPT = ref({ |
| | | id:"", |
| | | pictureUrl: '', |
| | | innerPicture: { |
| | | //定义画中画对象,属性与数字人相同 |
| | |
| | | file.uid = genFileId() |
| | | uploadRef.value!.handleStart(file) |
| | | } |
| | | |
| | | |
| | | // 上传相关的处理函数 |
| | | const handleChange = (files) => { |
| | | // 获取文件扩展名 |
| | | const extension = files.name.split('.').pop().toLowerCase() |
| | | |
| | | |
| | | // 设置文档类型 1:ppt 2:pdf |
| | | uploadFileObj.docType = extension === 'pdf' ? 2 : 1 |
| | | uploadFileObj.filename = files.name |
| | | uploadFileObj.size = files.size |
| | | } |
| | | |
| | | |
| | | const uploadExplainRef = ref() |
| | | const handleSuccess = (rawFile) => { |
| | | message.success('上传成功!') |
| | |
| | | uploadExplainRef.value.open() |
| | | uploadRef.value!.clearFiles() |
| | | } |
| | | |
| | | |
| | | const handleError = (err) => { |
| | | message.error('上传失败,请重试') |
| | | console.error('Upload error:', err) |
| | | } |
| | | |
| | | |
| | | //上传音频 |
| | | const uploadAudioRef = ref() |
| | | const handleAudioSuccess = (rawFile) => { |
| | |
| | | } |
| | | percentagePPT.value = parseInt(`${progress * 100}`) |
| | | } else if (res && res.length > 0) { |
| | | console.log('解析成功', res) |
| | | console.log('courseInfo', courseInfo.value) |
| | | res.forEach((item) => { |
| | | item.isActive = false |
| | |
| | | console.log('PPTArr.value', PPTArr.value) |
| | | PPTArr.value[0].isActive = true |
| | | selectPPT.value = PPTArr.value[0] |
| | | console.log('selectPPT.value', selectPPT.value) |
| | | showLeftList.value = true |
| | | clearInterval(schedulePPTTimer.value) |
| | | //轮询保存课程 |
| | |
| | | // 计算总字数 - 修改为去除SSML标签后再计算长度 |
| | | videoText.value = val.reduce((prev, curr) => { |
| | | if (!curr.pptRemark) return prev; |
| | | |
| | | |
| | | // 去除所有SSML标签,只保留文本内容 |
| | | const plainText = curr.pptRemark; |
| | | return prev + plainText.length; |
| | |
| | | clearInterval(schedulePPTTimer.value) |
| | | } |
| | | const copyDocument = (item, index) => { |
| | | ElMessageBox.confirm( |
| | | '是否复制这页PPT?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '是', |
| | | cancelButtonText: '否', |
| | | type: 'warning', |
| | | } |
| | | ) |
| | | .then(() => { |
| | | let copyItem = cloneDeep(item) |
| | | copyItem.id = generateUUID() |
| | | copyItem.isActive = false |
| | | PPTArr.value.splice(index + 1, 0, copyItem) |
| | | }) |
| | | .catch(() => { |
| | | ElMessage({ |
| | | type: 'info', |
| | | message: '已取消复制', |
| | | }) |
| | | }) |
| | | |
| | | let copyItem = cloneDeep(item) |
| | | copyItem.id = generateUUID() |
| | | copyItem.isActive = false |
| | | PPTArr.value.splice(index + 1, 0, copyItem) |
| | | } |
| | | const deleteDocument = (item) => { |
| | | ElMessageBox.confirm( |
| | | '是否删除这页PPT?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '是', |
| | | cancelButtonText: '否', |
| | | type: 'warning', |
| | | } |
| | | ) |
| | | .then(() => { |
| | | PPTArr.value = PPTArr.value.filter((child) => child.id !== item.id) |
| | | }).catch(() => { |
| | | ElMessage({ |
| | | type: 'info', |
| | | message: '已取消删除', |
| | | }) |
| | | }) |
| | | |
| | | PPTArr.value = PPTArr.value.filter((child) => child.id !== item.id) |
| | | } |
| | | const deleteDigitalHuman = () => { |
| | | selectPPT.value.showDigitalHuman = false |
| | |
| | | scene.selectAudio = data[0] |
| | | }) |
| | | } |
| | | |
| | | |
| | | } |
| | | //生成课程id |
| | | const coursesCreate = () => { |
| | |
| | | accountId: userId.value |
| | | } |
| | | pptTemplateApi.coursesCreate(params).then((res) => { |
| | | console.log(res) |
| | | if (res) { |
| | | courseInfo.value.id = res |
| | | } |
| | | }) |
| | | } |
| | | |
| | | |
| | | //ppt人脸校验 |
| | | const PPtIsHaveFace = async ()=>{ |
| | | //添加ppt中人脸校验 |
| | | //向原始ppt添加数据,用作后续ppt中是否包含人脸的数据校验原始数据 |
| | | const InitPpt = PPTArr.value.map( (item)=>{ |
| | | return item.innerPicture.src |
| | | } ) |
| | | const { detectFacesInImages } = useFaceDetection() |
| | | const IsHaveFace = await detectFacesInImages(InitPpt) |
| | | return IsHaveFace |
| | | } |
| | | |
| | | |
| | | |
| | | //获取保存时间 |
| | | const saveTime = ref() |
| | | const getSaveTime = () => { |
| | |
| | | return h + ':' + m + ':' + s |
| | | } |
| | | const warningDialog = ref() |
| | | |
| | | |
| | | // 语速 音量 |
| | | const voiceData = reactive({ |
| | | show: false, |
| | |
| | | const doc = parser.parseFromString(html, 'text/html'); |
| | | return doc.body.textContent || ""; |
| | | } |
| | | |
| | | //传入 save 则代表保存,空字符传则是合成视频 |
| | | |
| | | const saveSubmit = async (type) => { |
| | | if( type.length === 0 ){ |
| | | //此时为视频合成 |
| | | MakeLoading.value = true |
| | | SaveLoading.value = true |
| | | }else{ |
| | | //此时为保存 |
| | | SaveLoading.value = true |
| | | } |
| | | |
| | | // 检查场景是否为空 |
| | | if (!PPTArr.value || PPTArr.value.length === 0) { |
| | | message.warning('场景为空,请先上传PPT!') |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return false |
| | | } |
| | | |
| | | //保存课程 |
| | | let saveSubmitForm = { |
| | | accountId: courseInfo.value.accountId, |
| | |
| | | // }else{ |
| | | // Reflect.set(saveSubmitForm, "courseMediaId", courseInfo.value.id); |
| | | // } |
| | | |
| | | |
| | | //组装数据 |
| | | const scenes: any = [] |
| | | const pageInfo = { |
| | |
| | | matting: 1, |
| | | marker: 1 |
| | | } |
| | | |
| | | let pageNum = 1 |
| | | if (PPTArr.value && PPTArr.value.length > 0) { |
| | | console.log('开始处理PPTArr数据') |
| | |
| | | pageNum++ |
| | | } |
| | | console.log(item) |
| | | |
| | | |
| | | const innerPictureCom = item.innerPicture |
| | | console.log('innerPictureCom:', JSON.stringify(innerPictureCom)) |
| | | console.log(item.pptRemark) |
| | | item.pptRemark = removeHtmlTags(item.pptRemark) |
| | | // item.pptRemark = editorRef.value.getText() |
| | | // item.pptRemark=item.pptRemark.replace(/<[^>]+>/g, '') |
| | |
| | | scenes.push(formatItem) |
| | | } catch (error) { |
| | | console.error(`处理第 ${index + 1} 个场景时出错:`, error) |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | //抛出异常 |
| | | throw error |
| | | } |
| | | }) |
| | | } |
| | | |
| | | console.log('pageInfo:', JSON.stringify(pageInfo)) |
| | | console.log('thumbnail:', thumbnail) |
| | | |
| | | try { |
| | | saveSubmitForm.pageInfo = JSON.stringify(pageInfo) |
| | | saveSubmitForm.thumbnail = thumbnail |
| | | saveSubmitForm.scenes = cloneDeep(scenes) |
| | | console.log('saveSubmitForm:', cloneDeep(saveSubmitForm)) |
| | | } catch (error) { |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | console.error('保存表单数据时出错:', error) |
| | | } |
| | | |
| | | if (type == 'save') { |
| | | //反正怎么走都会走save这一步,那就只在这一步进行一次人脸校验,如果后续合成视频按钮不再走保存,请将这一步也一并进行更改 |
| | | //主要因为wangEditor过于敏感 |
| | | const isHaveFace = await PPtIsHaveFace() |
| | | if( isHaveFace ){ |
| | | message.warning('当前ppt中包含人脸元素, 为方便后续视频生成 ,请去除该元素') |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return false |
| | | } |
| | | |
| | | try { |
| | | const res = await pptTemplateApi.coursesSave(stringifySafely(saveSubmitForm)) |
| | | if (res) { |
| | | message.success('保存成功!') |
| | | saveTime.value = getSaveTime() |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return true // 返回保存成功标志 |
| | | } |
| | | return false |
| | | } catch (error) { |
| | | console.error('保存课程时出错:', error) |
| | | message.error('保存失败,请重试') |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return false |
| | | } |
| | | |
| | | } else { |
| | | // 合成视频前先保存 |
| | | try { |
| | | const saveResult = await saveSubmit('save') |
| | | if (!saveResult) { |
| | | message.error('保存失败,请重试后再合成视频') |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return |
| | | } |
| | | |
| | | // 校验场景数据 |
| | | let warningStrArr: any = [] |
| | | for (let i = 0; i < PPTArr.value.length; i++) { |
| | | const item = PPTArr.value[i] |
| | | console.log(item) |
| | | // 校验背景宽高 |
| | | if (!item.width || !item.height) { |
| | | message.warning('背景尺寸无效,请检查宽高设置,或者重新选择模板') |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return |
| | | } |
| | | if (item.driverType == 1) { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (warningStrArr.length > 0) { |
| | | warningDialog.value.open(warningStrArr.map((warning) => `<div>${warning}</div>`).join('')) |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | return |
| | | } |
| | | |
| | | // 合成视频 |
| | | try { |
| | | const res = await pptTemplateApi.megerMedia(saveSubmitForm) |
| | | if (res) { |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | message.success('合成视频任务提交成功,请到我的视频中查看!') |
| | | } |
| | | } catch (error) { |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | console.error('合成视频失败:', error) |
| | | message.error('合成视频失败,请重试') |
| | | } |
| | | } catch (error) { |
| | | //关闭视频合成与保存按钮的loading动画 |
| | | MakeLoading.value = false |
| | | SaveLoading.value = false |
| | | console.error('保存或合成过程出错:', error) |
| | | message.error('操作失败,请重试') |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | function stringifySafely(obj) { |
| | | const seen = new WeakSet() |
| | | return JSON.stringify(obj, (key, value) => { |
| | |
| | | message.warning('最多上传一个声音驱动文件!') |
| | | } |
| | | const currentAudio = ref() |
| | | |
| | | |
| | | const handlePptRemarkSelection = () => { |
| | | if (textareaRef.value) { |
| | | const textarea = textareaRef.value.$el.querySelector('textarea') |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | //富文本编辑器 -start |
| | | // 编辑器实例,必须用 shallowRef |
| | | const editorRef = shallowRef() |
| | |
| | | const createAudio = async () => { |
| | | // 获取编辑器文本内容 |
| | | const text = editorRef.value.getText(); |
| | | |
| | | |
| | | // 检查文本是否为空 |
| | | if (!text) { |
| | | message.warning('请输入需要试听文本的内容…'); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // 截取文本长度不超过 100 |
| | | const truncatedText = text.length > 100 ? text.substring(0, 100) : text; |
| | | console.log(audioSelectData.value) |
| | |
| | | try { |
| | | // 显示音频播放加载状态 |
| | | showAudioPlay1.value = true; |
| | | |
| | | |
| | | // 调用 API 创建音频 |
| | | const res = await pptTemplateApi.createAudio(params); |
| | | |
| | | |
| | | // 检查响应是否有效且无错误 |
| | | if (res && !res.error) { |
| | | console.log(res); |
| | | // 隐藏加载状态,显示音频播放状态 |
| | | showAudioPlay1.value = false; |
| | | showAudioPlay.value = true; |
| | | |
| | | |
| | | // 初始化 Audio 对象 |
| | | currentAudio.value = new Audio(res); |
| | | |
| | | |
| | | // 添加播放结束事件监听器 |
| | | currentAudio.value.addEventListener('ended', () => { |
| | | showAudioPlay.value = false; |
| | | currentAudio.value = null; |
| | | }); |
| | | |
| | | |
| | | // 播放音频 |
| | | currentAudio.value.play(); |
| | | } else { |
| | |
| | | try { |
| | | // 显示音频播放加载状态 |
| | | showAudioPlay1.value = true; |
| | | |
| | | |
| | | // 调用 API 创建音频 |
| | | const res = await pptTemplateApi.createAudio(params); |
| | | |
| | | |
| | | // 检查响应是否有效且无错误 |
| | | if (res && !res.error) { |
| | | console.log(res); |
| | | // 隐藏加载状态,显示音频播放状态 |
| | | showAudioPlay1.value = false; |
| | | showAudioPlay.value = true; |
| | | |
| | | |
| | | // 初始化 Audio 对象 |
| | | currentAudio.value = new Audio(res); |
| | | |
| | | |
| | | // 添加播放结束事件监听器 |
| | | currentAudio.value.addEventListener('ended', () => { |
| | | showAudioPlay.value = false; |
| | | currentAudio.value = null; |
| | | }); |
| | | |
| | | |
| | | // 播放音频 |
| | | currentAudio.value.play(); |
| | | } else { |
| | |
| | | } |
| | | } |
| | | // 构建请求参数 |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | | //取消试听 |
| | | const pauseAudio = () => { |
| | |
| | | currentAudioFile.value.pause() |
| | | currentAudioFile.value.currentTime = 0 // 重置播放位置 |
| | | } |
| | | |
| | | |
| | | // 创建新的 Audio 实例 |
| | | const audio = new Audio(file.response.data) |
| | | currentAudioFile.value = audio |
| | | |
| | | |
| | | // 监听播放结束事件 |
| | | audio.addEventListener('ended', () => { |
| | | cancelAudio() |
| | | }) |
| | | |
| | | |
| | | // 开始播放 |
| | | startAudioPlay.value = true |
| | | audio.play() |
| | |
| | | const hostInfo = res.scenes[0].components.find((component) => component.category === 2) |
| | | // 先在当前数字人列表中查找 |
| | | let foundHost = hostList.value.find(item => item.code === hostInfo.entityId) |
| | | |
| | | |
| | | // 如果在当前列表中没找到,且当前是公共数字人列表,则切换到我的数字人列表重新获取 |
| | | if (!foundHost && tabs1ActiveNum.value === '0') { |
| | | // 保存公共数字人列表的第一个数字人作为默认值 |
| | | const defaultPublicHost = hostList.value[0] |
| | | |
| | | |
| | | // 切换到"我的"数字人 |
| | | tabs1ActiveNum.value = '1' |
| | | // 重新获取数字人列表 |
| | | getList().then(() => { |
| | | // 在新列表中查找 |
| | | foundHost = hostList.value.find(item => item.code === hostInfo.entityId) |
| | | |
| | | |
| | | // 如果在"我的"数字人中也没找到,则使用默认公共数字人 |
| | | if (!foundHost) { |
| | | tabs1ActiveNum.value = '0' // 切回公共数字人tab |
| | | foundHost = defaultPublicHost // 使用之前保存的默认公共数字人 |
| | | message.warning('未找到原数字人,已使用默认公共数字人替代') |
| | | } |
| | | |
| | | |
| | | // 设置选中的数字人 |
| | | selectHost.value = foundHost || hostList.value[0] |
| | | }) |
| | |
| | | // 设置选中的数字人 |
| | | selectHost.value = foundHost || hostList.value[0] |
| | | } |
| | | |
| | | |
| | | // 设置选中的数字人 |
| | | selectHost.value = foundHost || hostList.value[0] |
| | | if (hostInfo) { |
| | |
| | | const pageInfo = res.pageInfo ? JSON.parse(res.pageInfo) : '' |
| | | uploadFileObj.filename = pageInfo ? pageInfo.docInfo.fileName : '' |
| | | uploadFileObj.size = pageInfo ? pageInfo.docInfo.fileSize : '' |
| | | |
| | | |
| | | //应用模板 这里用户可能已经调整了模板,所以这里不应用模板 |
| | | // applyTemplate() |
| | | } |
| | |
| | | currTemplate.isActive = true |
| | | applyTemplate() |
| | | } |
| | | |
| | | |
| | | const applyTemplate = (ppt = null) => { |
| | | const template = selectTemplate.value |
| | | const pptList = applyAllTemplate.value ? PPTArr.value : [selectPPT.value] |
| | | |
| | | |
| | | // 数字人是统一生效的,先处理 |
| | | console.log(template) |
| | | pptList.forEach((item) => { |
| | |
| | | item.pictureUrl = originalPPT |
| | | item.innerPicture.src = '' |
| | | } |
| | | |
| | | |
| | | item.showDigitalHuman = template.showDigitalHuman |
| | | // 添加同步宽高的逻辑 |
| | | const targetTemplate = selectTemplate.value |
| | |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | |
| | | // 数字人位置也需要缩放 |
| | | PPTpositon.w = selectTemplate.value.humanW |
| | | PPTpositon.h = selectTemplate.value.humanH |
| | | PPTpositon.x = selectTemplate.value.humanX |
| | | PPTpositon.y = selectTemplate.value.humanY |
| | | } |
| | | |
| | | |
| | | 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) { |
| | |
| | | }) |
| | | message.success('批量替换成功!') |
| | | } |
| | | |
| | | |
| | | onMounted(async () => { |
| | | let data = await TemplateApi.getTemplatePage(queryParams) |
| | | TEMPLATE_PRESETS.value = data.list.map((item) => { |
| | |
| | | height: 100%; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | |
| | | .minddle-host-image { |
| | | z-index: 5; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | |
| | | .template-top { |
| | | display: flex; |
| | | height: 60px; |
| | |
| | | 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 { |
| | | // height: 30px; |
| | | padding: 5px 10px; |
| | |
| | | line-height: 30px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | |
| | | .line { |
| | | width: 30px; |
| | | height: 3px; |
| | |
| | | margin: 0; |
| | | background-color: aqua; |
| | | } |
| | | |
| | | |
| | | .upload-demo { |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | |
| | | .left-upload-setting { |
| | | display: flex; |
| | | height: calc(100% - 86px); |
| | |
| | | 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; |
| | |
| | | background: #122121; |
| | | border-radius: 5px; |
| | | } |
| | | |
| | | |
| | | // 确保背景图片填充整个容器 |
| | | .background { |
| | | position: absolute; |
| | |
| | | height: 100%; |
| | | background-color: rgba(0, 0, 0, 0); |
| | | } |
| | | |
| | | |
| | | .ppt-bg { |
| | | z-index: 2; |
| | | // width: 152px; |
| | | // height: 100%; |
| | | } |
| | | |
| | | |
| | | .host { |
| | | position: absolute; |
| | | z-index: 3; |
| | | } |
| | | |
| | | |
| | | .icon-content { |
| | | position: absolute; |
| | | top: 0; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | .page-btn { |
| | | position: absolute; |
| | | bottom: 10px; |
| | |
| | | padding: 0 10px; |
| | | } |
| | | } |
| | | |
| | | |
| | | .template-middle { |
| | | display: flex; |
| | | width: 56%; |
| | |
| | | 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; |
| | | // width: 760px; |
| | |
| | | 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; |
| | |
| | | width: 300px; |
| | | } |
| | | } |
| | | |
| | | |
| | | .voice-main { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 10px; |
| | | |
| | | |
| | | .voice-item { |
| | | width: 180px; |
| | | height: 30px; |
| | |
| | | cursor: pointer; |
| | | background-color: #c9c9c9; |
| | | border-radius: 12px; |
| | | |
| | | |
| | | span { |
| | | display: inline-block; |
| | | width: 50%; |
| | |
| | | line-height: 30px; |
| | | text-align: center; |
| | | } |
| | | |
| | | |
| | | .active-item { |
| | | color: #fff; |
| | | background-color: #409eff; |
| | | } |
| | | } |
| | | |
| | | |
| | | .media-box { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | |
| | | .mic { |
| | | 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; |
| | |
| | | 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: 30px; |
| | | font-size: 14px; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | |
| | | |
| | | span { |
| | | display: block; |
| | | width: 30px; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | .tabs-2 { |
| | | display: flex; |
| | | padding: 10px; |
| | | justify-content: space-around; |
| | | |
| | | |
| | | div { |
| | | width: 60px; |
| | | height: 30px; |
| | |
| | | cursor: pointer; |
| | | border-radius: 5px; |
| | | } |
| | | |
| | | |
| | | .tabs-active { |
| | | color: #fff !important; |
| | | background-color: #409eff; |
| | | } |
| | | } |
| | | |
| | | |
| | | .apply-all { |
| | | position: absolute; |
| | | bottom: 80px; |
| | |
| | | width: 100%; |
| | | justify-content: center; |
| | | } |
| | | |
| | | |
| | | .host-list { |
| | | height: 80%; |
| | | overflow-y: auto; |
| | | border-top: 1px solid #ebeef5; |
| | | |
| | | |
| | | .host-item { |
| | | position: relative; |
| | | display: inline-block; |
| | |
| | | margin: 5px 0; |
| | | margin-left: 10px; |
| | | cursor: pointer; |
| | | |
| | | |
| | | .background { |
| | | position: absolute; |
| | | top: 0; |
| | |
| | | height: 100%; |
| | | background-color: #f0f1fa; /* 设置底色 */ |
| | | } |
| | | |
| | | |
| | | .host-name { |
| | | position: absolute; |
| | | bottom: 10px; |
| | |
| | | background: rgb(225 225 225 / 70%); |
| | | border-radius: 5px; |
| | | } |
| | | |
| | | |
| | | .ppt-bg { |
| | | z-index: 2; /* 图片在背景之上 */ |
| | | width: 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; |
| | |
| | | position: absolute; |
| | | z-index: 2; /* 图片在背景之上 */ |
| | | } |
| | | |
| | | |
| | | .human-image { |
| | | position: absolute; |
| | | z-index: 3; /* 图片在背景之上 */ |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | .background { |
| | | position: absolute; |
| | | top: 0; |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | ::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; // 添加更高的z-index确保在最顶层 |
| | | } |
| | | |
| | | |
| | | .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; |
| | |
| | | background-color: #1989fa; |
| | | } |
| | | } |
| | | </style> |
| | | </style> |