|
|
@@ -168,13 +168,62 @@
|
|
|
>
|
|
|
{{ isGeneratingVideos ? '生成中...' : '一键生成所有视频' }}
|
|
|
</button>
|
|
|
- <button
|
|
|
- class="toolbar-btn"
|
|
|
- @click="generateAllVoiceovers"
|
|
|
- :disabled="isGeneratingVoiceovers || isAnyVoiceoverGenerating"
|
|
|
- >
|
|
|
- {{ isGeneratingVoiceovers ? '生成中...' : '一键配音' }}
|
|
|
- </button>
|
|
|
+ <div class="voiceover-split-button">
|
|
|
+ <button
|
|
|
+ class="toolbar-btn voiceover-main-btn"
|
|
|
+ @click="generateAllVoiceovers"
|
|
|
+ :disabled="
|
|
|
+ isGeneratingVoiceovers ||
|
|
|
+ isAnyVoiceoverGenerating ||
|
|
|
+ selectedVoiceoverTypes.length === 0
|
|
|
+ "
|
|
|
+ :aria-busy="isGeneratingVoiceovers"
|
|
|
+ >
|
|
|
+ {{ isGeneratingVoiceovers ? '生成中...' : '一键配音' }}
|
|
|
+ </button>
|
|
|
+ <el-popover
|
|
|
+ v-model:visible="voiceoverMenuVisible"
|
|
|
+ trigger="click"
|
|
|
+ placement="bottom-end"
|
|
|
+ :width="220"
|
|
|
+ :teleported="true"
|
|
|
+ :disabled="isGeneratingVoiceovers || isAnyVoiceoverGenerating"
|
|
|
+ popper-class="voiceover-type-popper"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <button
|
|
|
+ class="toolbar-btn voiceover-menu-btn"
|
|
|
+ :class="{ active: voiceoverMenuVisible }"
|
|
|
+ :disabled="isGeneratingVoiceovers || isAnyVoiceoverGenerating"
|
|
|
+ aria-label="选择配音类型"
|
|
|
+ aria-haspopup="dialog"
|
|
|
+ :aria-expanded="voiceoverMenuVisible"
|
|
|
+ >
|
|
|
+ <span class="dropdown-triangle" aria-hidden="true"></span>
|
|
|
+ </button>
|
|
|
+ </template>
|
|
|
+ <div class="voiceover-type-menu" role="group" aria-label="配音类型">
|
|
|
+ <div class="voiceover-menu-title">
|
|
|
+ <span>选择配音类型</span>
|
|
|
+ <span
|
|
|
+ >{{ selectedVoiceoverTypes.length }}/{{ voiceoverTypeOptions.length }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <el-checkbox-group v-model="selectedVoiceoverTypes">
|
|
|
+ <el-checkbox
|
|
|
+ v-for="option in voiceoverTypeOptions"
|
|
|
+ :key="option.value"
|
|
|
+ :value="option.value"
|
|
|
+ >
|
|
|
+ {{ option.label }}
|
|
|
+ </el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ <div v-if="selectedVoiceoverTypes.length === 0" class="voiceover-menu-tip">
|
|
|
+ 请至少选择一种配音类型
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 可滚动的内容区域 -->
|
|
|
@@ -1101,6 +1150,17 @@ const isGeneratingImages = ref(false)
|
|
|
const isGeneratingVoiceovers = ref(false)
|
|
|
const isGeneratingVideos = ref(false)
|
|
|
|
|
|
+// 一键配音类型
|
|
|
+const voiceoverTypeOptions = [
|
|
|
+ { value: 'digital', label: '数字人对话', default: true },
|
|
|
+ { value: 'quest', label: '互动提问', default: true },
|
|
|
+ { value: 'poem', label: '诗词', default: true }
|
|
|
+]
|
|
|
+const selectedVoiceoverTypes = ref(
|
|
|
+ voiceoverTypeOptions.filter((option) => option.default).map((option) => option.value)
|
|
|
+)
|
|
|
+const voiceoverMenuVisible = ref(false)
|
|
|
+
|
|
|
// 数字人列表
|
|
|
const digitalHumans = ref([])
|
|
|
|
|
|
@@ -2294,13 +2354,17 @@ const generateVoiceover = async (sectionIndex, dialogueIndex) => {
|
|
|
|
|
|
// 步骤3:一键生成所有语音
|
|
|
const generateAllVoiceovers = async () => {
|
|
|
+ const selectedTypes = new Set(selectedVoiceoverTypes.value)
|
|
|
+ if (selectedTypes.size === 0) return
|
|
|
+
|
|
|
+ voiceoverMenuVisible.value = false
|
|
|
isGeneratingVoiceovers.value = true
|
|
|
try {
|
|
|
for (let i = 0; i < scriptData.sections.length; i++) {
|
|
|
for (let j = 0; j < scriptData.sections[i].dialogues.length; j++) {
|
|
|
const dialogue = scriptData.sections[i].dialogues[j]
|
|
|
- // 只处理没有语音URL的对话
|
|
|
- if (!dialogue.voiceoverUrl) {
|
|
|
+ // 只处理已勾选类型且尚未生成语音的对话
|
|
|
+ if (selectedTypes.has(dialogue.type) && !dialogue.voiceoverUrl) {
|
|
|
await generateVoiceover(i, j)
|
|
|
}
|
|
|
}
|
|
|
@@ -2361,7 +2425,7 @@ const validateScript = () => {
|
|
|
(dialogue.type !== 'poem' && dialogue.type !== 'poem_reading' && dialogue.type !== 'write_reading' && dialogue.type !== 'recite_reading' && !dialogue.voiceoverUrl)
|
|
|
) {
|
|
|
errorMessages.value.push(
|
|
|
- `环节${sectionIndex + 1}:对话${dialogueIndex + 1}:存在完成内容!`
|
|
|
+ `环节${sectionIndex + 1}:对话${dialogueIndex + 1}:存在未完成内容!`
|
|
|
)
|
|
|
isValid = false
|
|
|
}
|
|
|
@@ -3549,6 +3613,129 @@ onUnmounted(() => {
|
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
|
|
}
|
|
|
|
|
|
+.voiceover-split-button {
|
|
|
+ display: inline-flex;
|
|
|
+ margin: 0 12px;
|
|
|
+ vertical-align: middle;
|
|
|
+ border-radius: 10px;
|
|
|
+ transition:
|
|
|
+ box-shadow 0.3s ease,
|
|
|
+ transform 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-split-button:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-split-button .toolbar-btn {
|
|
|
+ margin: 0;
|
|
|
+ transform: none;
|
|
|
+ box-shadow: none;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-main-btn {
|
|
|
+ border-radius: 10px 0 0 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 40px;
|
|
|
+ padding: 0;
|
|
|
+ margin-left: -2px !important;
|
|
|
+ border-radius: 0 10px 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn::before {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ width: 1px;
|
|
|
+ height: 22px;
|
|
|
+ content: '';
|
|
|
+ background: #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn:hover::before,
|
|
|
+.voiceover-menu-btn.active::before {
|
|
|
+ background: rgba(255, 255, 255, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn.active {
|
|
|
+ color: white;
|
|
|
+ background: #409eff;
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-split-button:focus-within {
|
|
|
+ outline: 2px solid rgba(64, 158, 255, 0.45);
|
|
|
+ outline-offset: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-split-button:has(.toolbar-btn:disabled):hover {
|
|
|
+ transform: none;
|
|
|
+ box-shadow: none;
|
|
|
+}
|
|
|
+
|
|
|
+.dropdown-triangle {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-top: 6px solid currentColor;
|
|
|
+ border-right: 5px solid transparent;
|
|
|
+ border-left: 5px solid transparent;
|
|
|
+ transition: transform 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-btn.active .dropdown-triangle {
|
|
|
+ transform: rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+:global(.voiceover-type-popper.el-popover) {
|
|
|
+ padding: 12px 14px;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-title {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #606266;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-type-menu :deep(.el-checkbox-group) {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-type-menu :deep(.el-checkbox) {
|
|
|
+ height: 36px;
|
|
|
+ margin-right: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-type-menu :deep(.el-checkbox__label) {
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.voiceover-menu-tip {
|
|
|
+ padding-top: 6px;
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #e6a23c;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+
|
|
|
/* 脚本编辑器 */
|
|
|
.script-section {
|
|
|
margin-top: 40px;
|