|
@@ -104,7 +104,7 @@
|
|
|
style="width: 300px"
|
|
style="width: 300px"
|
|
|
>
|
|
>
|
|
|
<el-option label="课程通用" value=13 />
|
|
<el-option label="课程通用" value=13 />
|
|
|
- <el-option label="诗词课" value=101 />
|
|
|
|
|
|
|
+ <el-option label="诗词课" value=256 />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -558,7 +558,7 @@
|
|
|
重新生成
|
|
重新生成
|
|
|
</button>
|
|
</button>
|
|
|
|
|
|
|
|
- <button class="primary-btn" :disabled="!canProceed" @click="nextStep">
|
|
|
|
|
|
|
+ <button class="primary-btn" @click="nextStep">
|
|
|
预览完整脚本
|
|
预览完整脚本
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
@@ -568,16 +568,24 @@
|
|
|
<div v-else-if="currentStep === 3" class="step-content">
|
|
<div v-else-if="currentStep === 3" class="step-content">
|
|
|
<div class="preview-container">
|
|
<div class="preview-container">
|
|
|
<h3>课程脚本预览</h3>
|
|
<h3>课程脚本预览</h3>
|
|
|
- <br />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="scrollable-content">
|
|
|
|
|
+ <br />
|
|
|
|
|
|
|
|
- <div
|
|
|
|
|
- class="validation-result"
|
|
|
|
|
- :class="{ valid: isValidationPassed, invalid: !isValidationPassed }"
|
|
|
|
|
- >
|
|
|
|
|
- {{ validationMessage }}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div v-if="isValidationPassed" class="validation-result valid">
|
|
|
|
|
+ {{ validationMessage }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(error, index) in errorMessages"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="validation-result invalid"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ error }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div class="preview-content">
|
|
|
|
|
|
|
+ <div class="preview-content">
|
|
|
<div
|
|
<div
|
|
|
v-for="(section, sectionIndex) in scriptData.sections"
|
|
v-for="(section, sectionIndex) in scriptData.sections"
|
|
|
:key="sectionIndex"
|
|
:key="sectionIndex"
|
|
@@ -667,17 +675,18 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="preview-actions">
|
|
<div class="preview-actions">
|
|
|
<button class="secondary-btn" @click="currentStep = 2">返回修改</button>
|
|
<button class="secondary-btn" @click="currentStep = 2">返回修改</button>
|
|
|
- <button
|
|
|
|
|
|
|
+<!-- <button
|
|
|
class="primary-btn"
|
|
class="primary-btn"
|
|
|
:disabled="!isValidationPassed"
|
|
:disabled="!isValidationPassed"
|
|
|
@click="showVideoPreview"
|
|
@click="showVideoPreview"
|
|
|
>
|
|
>
|
|
|
预览视频
|
|
预览视频
|
|
|
- </button>
|
|
|
|
|
|
|
+ </button>-->
|
|
|
<button class="primary-btn" :disabled="!isValidationPassed" @click="saveScript">
|
|
<button class="primary-btn" :disabled="!isValidationPassed" @click="saveScript">
|
|
|
保存课程脚本
|
|
保存课程脚本
|
|
|
</button>
|
|
</button>
|
|
@@ -938,8 +947,7 @@ const canProceed = computed(() => {
|
|
|
((dialogue.type === 'digital' || dialogue.type === 'quest' || dialogue.type === 'poem') &&
|
|
((dialogue.type === 'digital' || dialogue.type === 'quest' || dialogue.type === 'poem') &&
|
|
|
dialogue.roleName &&
|
|
dialogue.roleName &&
|
|
|
dialogue.content.trim() &&
|
|
dialogue.content.trim() &&
|
|
|
- dialogue.voiceoverUrl) ||
|
|
|
|
|
- (dialogue.type === 'user' && dialogue.roleName && dialogue.content.trim())
|
|
|
|
|
|
|
+ dialogue.voiceoverUrl)
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
default:
|
|
default:
|
|
@@ -962,6 +970,7 @@ const isAnyVoiceoverGenerating = computed(() => {
|
|
|
// 校验结果
|
|
// 校验结果
|
|
|
const isValidationPassed = ref(false)
|
|
const isValidationPassed = ref(false)
|
|
|
const validationMessage = ref('')
|
|
const validationMessage = ref('')
|
|
|
|
|
+const errorMessages = ref([])
|
|
|
|
|
|
|
|
// 视频预览状态
|
|
// 视频预览状态
|
|
|
const showVideoPreviewModal = ref(false)
|
|
const showVideoPreviewModal = ref(false)
|
|
@@ -1415,7 +1424,7 @@ const generateAllVoiceovers = async () => {
|
|
|
for (let j = 0; j < scriptData.sections[i].dialogues.length; j++) {
|
|
for (let j = 0; j < scriptData.sections[i].dialogues.length; j++) {
|
|
|
const dialogue = scriptData.sections[i].dialogues[j]
|
|
const dialogue = scriptData.sections[i].dialogues[j]
|
|
|
// 只处理没有语音URL的对话
|
|
// 只处理没有语音URL的对话
|
|
|
- if (!dialogue.voiceoverUrl) {
|
|
|
|
|
|
|
+ if (!dialogue.voiceoverUrl && dialogue.type !== 'poem') {
|
|
|
await generateVoiceover(i, j)
|
|
await generateVoiceover(i, j)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1442,32 +1451,29 @@ const parseMarkdown = (text) => {
|
|
|
// 步骤4:校验脚本
|
|
// 步骤4:校验脚本
|
|
|
const validateScript = () => {
|
|
const validateScript = () => {
|
|
|
let isValid = true
|
|
let isValid = true
|
|
|
- let message = '校验通过'
|
|
|
|
|
-
|
|
|
|
|
- if (!scriptData.sections.every((s) => s.name.trim())) {
|
|
|
|
|
- isValid = false
|
|
|
|
|
- message = '存在空的环节名称'
|
|
|
|
|
- } else if (!scriptData.sections.every((s) => s.backgroundImage.url)) {
|
|
|
|
|
- isValid = false
|
|
|
|
|
- message = '存在未生成的背景图'
|
|
|
|
|
- } else if (
|
|
|
|
|
- !scriptData.sections.every((s) =>
|
|
|
|
|
- s.dialogues.every(
|
|
|
|
|
- (d) =>
|
|
|
|
|
- ((d.type === 'digital' || d.type === 'quest' || d.type === 'poem') &&
|
|
|
|
|
- d.roleName &&
|
|
|
|
|
- d.content.trim() &&
|
|
|
|
|
- d.voiceoverUrl) ||
|
|
|
|
|
- (d.type === 'user' && d.roleName && d.content.trim())
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- ) {
|
|
|
|
|
- isValid = false
|
|
|
|
|
- message = '存在未完成的对话或语音'
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ errorMessages.value = []
|
|
|
|
|
+
|
|
|
|
|
+ // 检查环节名称、背景图
|
|
|
|
|
+ scriptData.sections.forEach((section, sectionIndex) => {
|
|
|
|
|
+ if (!section.name.trim() || !section.backgroundImage.url) {
|
|
|
|
|
+ errorMessages.value.push(`环节${sectionIndex + 1}:名称或背景图未配置!`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查对话
|
|
|
|
|
+ section.dialogues.forEach((dialogue, dialogueIndex) => {
|
|
|
|
|
+ // 只检查数字人、提问和诗词类型的对话
|
|
|
|
|
+ if (dialogue.type === 'digital' || dialogue.type === 'quest' || dialogue.type === 'poem') {
|
|
|
|
|
+ if (!dialogue.roleName || !dialogue.content.trim() || (dialogue.type !== 'poem' && !dialogue.voiceoverUrl)) {
|
|
|
|
|
+ errorMessages.value.push(`环节${sectionIndex + 1}:对话${dialogueIndex + 1}:存在完成内容!`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
isValidationPassed.value = isValid
|
|
isValidationPassed.value = isValid
|
|
|
- validationMessage.value = message
|
|
|
|
|
|
|
+ validationMessage.value = errorMessages.value.length > 0 ? '校验失败' : '校验通过'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 步骤4:保存脚本
|
|
// 步骤4:保存脚本
|
|
@@ -1486,12 +1492,10 @@ const saveScript = async () => {
|
|
|
|
|
|
|
|
// 下一步操作
|
|
// 下一步操作
|
|
|
const nextStep = () => {
|
|
const nextStep = () => {
|
|
|
- if (canProceed.value) {
|
|
|
|
|
- if (currentStep.value === 2) {
|
|
|
|
|
- validateScript()
|
|
|
|
|
- }
|
|
|
|
|
- currentStep.value++
|
|
|
|
|
|
|
+ if (currentStep.value === 2) {
|
|
|
|
|
+ validateScript()
|
|
|
}
|
|
}
|
|
|
|
|
+ currentStep.value++
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 显示视频预览
|
|
// 显示视频预览
|
|
@@ -1751,6 +1755,12 @@ onUnmounted(() => {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* 错误信息项样式 */
|
|
|
|
|
+.error-item {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.dialogue-type-tag.digital {
|
|
.dialogue-type-tag.digital {
|
|
|
background-color: #409eff;
|
|
background-color: #409eff;
|
|
|
}
|
|
}
|
|
@@ -2642,32 +2652,36 @@ onUnmounted(() => {
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.preview-content {
|
|
|
|
|
|
|
+.scrollable-content {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
- margin-top: 20px;
|
|
|
|
|
- margin-bottom: 30px;
|
|
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
padding-right: 10px;
|
|
padding-right: 10px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.preview-content::-webkit-scrollbar {
|
|
|
|
|
|
|
+.scrollable-content::-webkit-scrollbar {
|
|
|
width: 6px;
|
|
width: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.preview-content::-webkit-scrollbar-track {
|
|
|
|
|
|
|
+.scrollable-content::-webkit-scrollbar-track {
|
|
|
background: #f1f1f1;
|
|
background: #f1f1f1;
|
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.preview-content::-webkit-scrollbar-thumb {
|
|
|
|
|
|
|
+.scrollable-content::-webkit-scrollbar-thumb {
|
|
|
background: #c1c1c1;
|
|
background: #c1c1c1;
|
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.preview-content::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
|
|
+.scrollable-content::-webkit-scrollbar-thumb:hover {
|
|
|
background: #a1a1a1;
|
|
background: #a1a1a1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.preview-content {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.preview-actions {
|
|
.preview-actions {
|
|
|
margin-top: auto;
|
|
margin-top: auto;
|
|
|
display: flex;
|
|
display: flex;
|