|
|
@@ -2,11 +2,13 @@
|
|
|
<Modal
|
|
|
:visible="visible"
|
|
|
:width="1120"
|
|
|
- :close-button="true"
|
|
|
+ :content-style="{ maxWidth: 'calc(100vw - 32px)', boxSizing: 'border-box' }"
|
|
|
+ :close-button="!working"
|
|
|
:close-on-click-mask="!working"
|
|
|
+ :close-on-esc="!working"
|
|
|
@update:visible="value => emit('update:visible', value)"
|
|
|
>
|
|
|
- <div class="ai-dialog">
|
|
|
+ <div :class="['ai-dialog', workflowStep === 1 ? 'setup-mode' : 'review-mode']">
|
|
|
<div class="header">
|
|
|
<div>
|
|
|
<div class="title">{{ pageTitle }}</div>
|
|
|
@@ -69,60 +71,7 @@
|
|
|
|
|
|
<div class="extra">
|
|
|
<div class="label">还有什么特别要求?<span>选填</span></div>
|
|
|
- <TextArea v-model:value="extraPrompt" :rows="2" placeholder="例如:多安排节奏游戏,语言适合一年级学生" :disabled="working" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="template-summary-card">
|
|
|
- <div class="template-real-cover" :style="templateCoverStyle(selectedTemplate.id)"></div>
|
|
|
- <div class="template-summary-copy">
|
|
|
- <span>{{ templateSelectionMode === "auto" ? "系统将自动匹配" : "当前选择" }}</span>
|
|
|
- <strong>{{ selectedTemplate.name }}</strong>
|
|
|
- <small>{{ selectedTemplate.scene }} · {{ selectedTemplate.description }}</small>
|
|
|
- </div>
|
|
|
- <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
|
|
|
- {{ templatePickerExpanded ? "收起" : "更换模板" }}
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div v-if="templatePickerExpanded" class="style-selector compact-template-selector">
|
|
|
- <div class="template-selector-head">
|
|
|
- <div>
|
|
|
- <div class="label">选择模板</div>
|
|
|
- <div class="template-help">优先展示适合当前课程的模板。</div>
|
|
|
- </div>
|
|
|
- <button
|
|
|
- v-if="templateSelectionMode === 'manual'"
|
|
|
- type="button"
|
|
|
- class="auto-template-button"
|
|
|
- :disabled="working"
|
|
|
- @click="useRecommendedTemplate"
|
|
|
- >
|
|
|
- 恢复智能推荐
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <div class="style-options">
|
|
|
- <button
|
|
|
- v-for="template in visibleTemplates"
|
|
|
- :key="template.id"
|
|
|
- type="button"
|
|
|
- :class="['style-option', { active: selectedTemplateId === template.id }]"
|
|
|
- :disabled="working"
|
|
|
- @click="selectTemplate(template.id)"
|
|
|
- >
|
|
|
- <span class="template-real-cover option" :style="templateCoverStyle(template.id)"></span>
|
|
|
- <span class="template-card-title">
|
|
|
- <strong>{{ template.name }}</strong>
|
|
|
- <em v-if="outline && template.id === recommendedTemplate.id">系统推荐</em>
|
|
|
- </span>
|
|
|
- <span>{{ template.description }}</span>
|
|
|
- <small>{{ template.scene }}</small>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <button v-if="!showAllTemplates" type="button" class="show-all-templates" @click="showAllTemplates = true">查看全部模板</button>
|
|
|
- <div v-if="outline && selectedTemplateFit.level === 'low'" class="template-fit-warning">
|
|
|
- <span>当前模板不太适合这节课:{{ selectedTemplateFit.reason }}</span>
|
|
|
- <button type="button" :disabled="working" @click="useRecommendedTemplate">切换为系统推荐</button>
|
|
|
- </div>
|
|
|
+ <TextArea v-model:value="extraPrompt" :rows="3" placeholder="例如:多安排节奏游戏,语言适合一年级学生" :disabled="working" />
|
|
|
</div>
|
|
|
|
|
|
<button type="button" class="advanced-toggle" @click="advancedSettingsExpanded = !advancedSettingsExpanded">
|
|
|
@@ -177,7 +126,19 @@
|
|
|
</div>
|
|
|
<div v-if="draftQuality && !draftQuality.passed" class="quality-actions">
|
|
|
<button type="button" :disabled="working" @click="autoOptimizeDraft">自动优化全部页面</button>
|
|
|
- <button v-if="selectedTemplateFit.level === 'low'" type="button" :disabled="working" @click="useRecommendedTemplateForDraft">切换推荐模板</button>
|
|
|
+ <button v-if="selectedTemplateFit.level === 'low'" type="button" :disabled="working" @click="useRecommendedTemplateForDraft">
|
|
|
+ 切换推荐模板
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div v-if="selectedDraftPage" class="selected-draft-actions">
|
|
|
+ <div>
|
|
|
+ <span>当前页面</span><strong>第{{ selectedDraftPageIndex + 1 }}页 · {{ selectedDraftPage.title }}</strong>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <button type="button" :disabled="working" @click="compactDraftPage(selectedDraftPageIndex)">精简文字</button>
|
|
|
+ <button type="button" :disabled="working || !selectedDraftPage.image" @click="removeDraftImage(selectedDraftPageIndex)">移除图片</button>
|
|
|
+ <button type="button" :disabled="working" @click="regenerateDraftPage(selectedDraftPageIndex)">重新生成本页</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="preview-grid">
|
|
|
<button
|
|
|
@@ -191,20 +152,17 @@
|
|
|
<div class="preview-page-no">{{ formatPageNo(index + 1) }}</div>
|
|
|
</button>
|
|
|
</div>
|
|
|
- <div v-if="selectedDraftPage" class="selected-draft-actions">
|
|
|
- <div><span>当前页面</span><strong>第{{ selectedDraftPageIndex + 1 }}页 · {{ selectedDraftPage.title }}</strong></div>
|
|
|
- <div>
|
|
|
- <button type="button" :disabled="working" @click="compactDraftPage(selectedDraftPageIndex)">精简文字</button>
|
|
|
- <button type="button" :disabled="working || !selectedDraftPage.image" @click="removeDraftImage(selectedDraftPageIndex)">移除图片</button>
|
|
|
- <button type="button" :disabled="working" @click="regenerateDraftPage(selectedDraftPageIndex)">重新生成本页</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="workflowStep === 2 && outline" class="outline">
|
|
|
<div class="sample-stage-head">
|
|
|
- <div><strong>确认课件的整体样式</strong><span>查看封面、正文和课堂活动三类页面,一次确认即可。</span></div>
|
|
|
- <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">更换模板</button>
|
|
|
+ <div>
|
|
|
+ <strong>确认课件的整体样式</strong>
|
|
|
+ <span>当前模板:{{ selectedTemplate.name }}。查看三类代表页面,一次确认即可。</span>
|
|
|
+ </div>
|
|
|
+ <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
|
|
|
+ {{ templatePickerExpanded ? "收起模板" : "更换模板" }}
|
|
|
+ </button>
|
|
|
</div>
|
|
|
|
|
|
<div class="sample-thumbnail-grid">
|
|
|
@@ -232,10 +190,17 @@
|
|
|
@click="selectTemplate(template.id, true)"
|
|
|
>
|
|
|
<span class="template-real-cover option" :style="templateCoverStyle(template.id)"></span>
|
|
|
- <span><strong>{{ template.name }}</strong><small>{{ template.id === recommendedTemplate.id ? "系统推荐 · " : "" }}{{ template.scene }}</small></span>
|
|
|
+ <span
|
|
|
+ ><strong>{{ template.name }}</strong
|
|
|
+ ><small>{{ template.id === recommendedTemplate.id ? "系统推荐 · " : "" }}{{ template.scene }}</small></span
|
|
|
+ >
|
|
|
</button>
|
|
|
</div>
|
|
|
<button v-if="!showAllTemplates" type="button" class="show-all-templates" @click="showAllTemplates = true">查看全部模板</button>
|
|
|
+ <div v-if="selectedTemplateFit.level === 'low'" class="template-fit-warning">
|
|
|
+ <span>当前模板不太适合这节课:{{ selectedTemplateFit.reason }}</span>
|
|
|
+ <button type="button" :disabled="working" @click="useRecommendedTemplate">切换为系统推荐</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="sampleSlide" class="sample-review simplified-sample-review">
|
|
|
@@ -243,32 +208,32 @@
|
|
|
<div class="sample-eyebrow">当前查看</div>
|
|
|
<div class="sample-title">第{{ samplePageNo }}页 · {{ samplePageTitle }}</div>
|
|
|
<div class="sample-hint">{{ samplePageReason }}</div>
|
|
|
- <div class="current-template-card">
|
|
|
- <div>
|
|
|
- <span>当前模板</span>
|
|
|
- <strong>{{ selectedTemplate.name }}</strong>
|
|
|
- <small>切换模板只改变视觉,不改变教学内容</small>
|
|
|
- <small v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</small>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</div>
|
|
|
<div class="sample-feedback">
|
|
|
<div class="feedback-label">这一页哪里需要调整?</div>
|
|
|
<div class="feedback-chips">
|
|
|
- <button v-for="item in sampleFeedbackOptions" :key="item" type="button" :class="{ active: sampleFeedbackTags.includes(item) }" :disabled="working" @click="toggleSampleFeedback(item)">
|
|
|
+ <button
|
|
|
+ v-for="item in sampleFeedbackOptions"
|
|
|
+ :key="item"
|
|
|
+ type="button"
|
|
|
+ :class="{ active: sampleFeedbackTags.includes(item) }"
|
|
|
+ :disabled="working"
|
|
|
+ @click="toggleSampleFeedback(item)"
|
|
|
+ >
|
|
|
{{ item }}
|
|
|
</button>
|
|
|
</div>
|
|
|
- <textarea v-model="sampleFeedbackText" :disabled="working" placeholder="也可以直接说明你希望怎么调整" @input="invalidateVisualApproval"></textarea>
|
|
|
+ <textarea
|
|
|
+ v-model="sampleFeedbackText"
|
|
|
+ :disabled="working"
|
|
|
+ placeholder="也可以直接说明你希望怎么调整"
|
|
|
+ @input="invalidateVisualApproval"
|
|
|
+ ></textarea>
|
|
|
</div>
|
|
|
<Button @click="regenerateSamplePage" :disabled="working">调整当前页面</Button>
|
|
|
</div>
|
|
|
<div class="sample-canvas">
|
|
|
- <ThumbnailSlide
|
|
|
- :slide="sampleSlide"
|
|
|
- :size="420"
|
|
|
- :show-background-error="true"
|
|
|
- @background-load-state="handleSampleBackgroundLoadState"
|
|
|
- />
|
|
|
+ <ThumbnailSlide :slide="sampleSlide" :size="420" :show-background-error="true" @background-load-state="handleSampleBackgroundLoadState" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -277,25 +242,25 @@
|
|
|
</button>
|
|
|
<div v-if="outlineExpanded" class="outline-editor-panel">
|
|
|
<div class="page-nav">
|
|
|
- <button
|
|
|
- v-for="page in outline.pages"
|
|
|
- :key="page.pageNo"
|
|
|
- class="nav-item"
|
|
|
- :class="{ active: selectedPageNo === page.pageNo }"
|
|
|
- type="button"
|
|
|
- @click="selectedPageNo = page.pageNo"
|
|
|
- >
|
|
|
- <span class="nav-no">{{ formatPageNo(page.pageNo) }}</span>
|
|
|
- <span class="nav-title">{{ page.title || "未命名页面" }}</span>
|
|
|
- </button>
|
|
|
+ <button
|
|
|
+ v-for="page in outline.pages"
|
|
|
+ :key="page.pageNo"
|
|
|
+ class="nav-item"
|
|
|
+ :class="{ active: selectedPageNo === page.pageNo }"
|
|
|
+ type="button"
|
|
|
+ @click="selectedPageNo = page.pageNo"
|
|
|
+ >
|
|
|
+ <span class="nav-no">{{ formatPageNo(page.pageNo) }}</span>
|
|
|
+ <span class="nav-title">{{ page.title || "未命名页面" }}</span>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="selectedPage" class="page-editor simplified-page-editor">
|
|
|
- <div class="editor-head">
|
|
|
- <div class="title-editor">
|
|
|
- <input v-model="selectedPage.title" class="page-title" :disabled="working" />
|
|
|
+ <div class="editor-head">
|
|
|
+ <div class="title-editor">
|
|
|
+ <input v-model="selectedPage.title" class="page-title" :disabled="working" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
<div class="field-card">
|
|
|
<div class="field-title">学生看到的内容</div>
|
|
|
@@ -318,21 +283,21 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else-if="workflowStep === 1 && !outline" class="empty">完成生成设置后,系统会先生成大纲和可调整的视觉样张。</div>
|
|
|
-
|
|
|
<div class="footer">
|
|
|
<Button @click="emit('update:visible', false)" :disabled="working">取消</Button>
|
|
|
<template v-if="workflowStep === 1">
|
|
|
<Button v-if="outline" @click="workflowStep = 2" :disabled="working">继续选择样式</Button>
|
|
|
- <Button type="primary" @click="handleOutline" :disabled="working">{{ outline ? "按当前设置重新生成" : "生成大纲和视觉样稿" }}</Button>
|
|
|
+ <Button type="primary" @click="handleOutline" :disabled="working">{{ outline ? "按当前设置重新生成" : "生成大纲并选择样式" }}</Button>
|
|
|
</template>
|
|
|
<template v-else-if="workflowStep === 2">
|
|
|
<Button @click="workflowStep = 1" :disabled="working">返回修改需求</Button>
|
|
|
- <Button type="primary" @click="confirmSamplesAndGenerate" :disabled="working || sampleBackgroundState.status === 'error'">样式满意,生成完整课件</Button>
|
|
|
+ <Button type="primary" @click="confirmSamplesAndGenerate" :disabled="working || sampleBackgroundState.status === 'error'"
|
|
|
+ >确认样式并生成课件</Button
|
|
|
+ >
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<Button @click="backToOutline" :disabled="working">返回调整</Button>
|
|
|
- <Button type="primary" @click="applyDraft" :disabled="working || !draftQuality?.passed">应用到当前课件</Button>
|
|
|
+ <Button type="primary" @click="applyDraft" :disabled="working || !draftQuality?.passed">确认并应用到当前课件</Button>
|
|
|
</template>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -392,9 +357,9 @@ const outline = ref<AiCoursewareOutline>()
|
|
|
const taskInfo = ref<AiCoursewareTaskInfo>()
|
|
|
const workflowStep = ref<1 | 2 | 3>(1)
|
|
|
const workflowSteps = [
|
|
|
- { value: 1, label: "说明需求" },
|
|
|
- { value: 2, label: "选择样式" },
|
|
|
- { value: 3, label: "预览课件" }
|
|
|
+ { value: 1, label: "设置课程" },
|
|
|
+ { value: 2, label: "确认样式" },
|
|
|
+ { value: 3, label: "检查课件" }
|
|
|
] as const
|
|
|
const taskMode = ref<"full" | "sample">("full")
|
|
|
const coursewareTemplates = AI_COURSEWARE_TEMPLATES
|
|
|
@@ -433,9 +398,7 @@ const working = computed(() => outlineLoading.value || taskInfo.value?.status ==
|
|
|
const selectedTemplate = computed(() => getAiCoursewareTemplate(selectedTemplateId.value))
|
|
|
const selectedStyle = computed(() => selectedTemplate.value.baseStyle)
|
|
|
const templateRecommendation = computed(() =>
|
|
|
- outline.value
|
|
|
- ? recommendAiCoursewareTemplate(outline.value)
|
|
|
- : { id: defaultTemplateId, reason: "生成大纲后将根据课程内容自动推荐" }
|
|
|
+ outline.value ? recommendAiCoursewareTemplate(outline.value) : { id: defaultTemplateId, reason: "生成大纲后将根据课程内容自动推荐" }
|
|
|
)
|
|
|
const recommendedTemplate = computed(() => getAiCoursewareTemplate(templateRecommendation.value.id))
|
|
|
const visibleTemplates = computed(() => {
|
|
|
@@ -449,9 +412,10 @@ const visibleTemplates = computed(() => {
|
|
|
})
|
|
|
.slice(0, 4)
|
|
|
})
|
|
|
-const selectedTemplateFit = computed(() => outline.value
|
|
|
- ? scoreAiCoursewareTemplate(outline.value, selectedTemplateId.value)
|
|
|
- : { templateId: selectedTemplateId.value, score: 80, level: "usable" as const, label: "可以使用" as const, reason: "生成大纲后评估" }
|
|
|
+const selectedTemplateFit = computed(() =>
|
|
|
+ outline.value
|
|
|
+ ? scoreAiCoursewareTemplate(outline.value, selectedTemplateId.value)
|
|
|
+ : { templateId: selectedTemplateId.value, score: 80, level: "usable" as const, label: "可以使用" as const, reason: "生成大纲后评估" }
|
|
|
)
|
|
|
const progressValue = computed(() => taskInfo.value?.progress || 0)
|
|
|
const progressSteps = [
|
|
|
@@ -465,9 +429,9 @@ const currentProgressLabel = computed(() => {
|
|
|
const matched = [...progressSteps].reverse().find(step => progressValue.value >= step.threshold)
|
|
|
return shortProgressLabel(taskInfo.value?.message) || matched?.label || "处理中"
|
|
|
})
|
|
|
-const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "课件大纲")
|
|
|
+const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "AI生成课件")
|
|
|
const pageMeta = computed(() => {
|
|
|
- if (!outline.value) return "根据当前教材、单元和章节生成9至15页音乐课件"
|
|
|
+ if (!outline.value) return `根据当前教材生成${preferences.minPages}至${preferences.maxPages}页音乐课件`
|
|
|
return [...[outline.value.gradeName, outline.value.unitName].filter(Boolean), `共${outline.value.pages.length}页`].join(" · ")
|
|
|
})
|
|
|
const selectedPage = computed(() => {
|
|
|
@@ -495,11 +459,27 @@ const representativeSamples = computed(() => {
|
|
|
}
|
|
|
add(pages[0], "封面主视觉")
|
|
|
add(
|
|
|
- pages.find(page => ["hero_focus", "media_focus", "split_content", "diagram_focus", "story_stage", "full_image_question", "split_image", "observe_question"].includes(page.visualPlan?.layout || "") && page.pageNo !== pages[0].pageNo),
|
|
|
+ pages.find(
|
|
|
+ page =>
|
|
|
+ [
|
|
|
+ "hero_focus",
|
|
|
+ "media_focus",
|
|
|
+ "split_content",
|
|
|
+ "diagram_focus",
|
|
|
+ "story_stage",
|
|
|
+ "full_image_question",
|
|
|
+ "split_image",
|
|
|
+ "observe_question"
|
|
|
+ ].includes(page.visualPlan?.layout || "") && page.pageNo !== pages[0].pageNo
|
|
|
+ ),
|
|
|
"内容主视觉"
|
|
|
)
|
|
|
add(
|
|
|
- pages.find(page => ["activity", "summary", "action_demo", "group_mission", "performance_stage", "activity_steps", "practice_check"].includes(page.visualPlan?.layout || "")),
|
|
|
+ pages.find(page =>
|
|
|
+ ["activity", "summary", "action_demo", "group_mission", "performance_stage", "activity_steps", "practice_check"].includes(
|
|
|
+ page.visualPlan?.layout || ""
|
|
|
+ )
|
|
|
+ ),
|
|
|
"课堂活动"
|
|
|
)
|
|
|
pages.forEach(page => {
|
|
|
@@ -530,10 +510,12 @@ const previewSlides = computed(() => {
|
|
|
}).slides
|
|
|
})
|
|
|
const sampleSlide = computed(() => previewSlides.value?.[samplePageIndex.value])
|
|
|
-const representativeSampleSlides = computed(() => representativeSamples.value.map(sample => {
|
|
|
- const pageIndex = outline.value?.pages.findIndex(page => page.pageNo === sample.pageNo) ?? -1
|
|
|
- return { ...sample, slide: pageIndex >= 0 ? previewSlides.value?.[pageIndex] : undefined }
|
|
|
-}))
|
|
|
+const representativeSampleSlides = computed(() =>
|
|
|
+ representativeSamples.value.map(sample => {
|
|
|
+ const pageIndex = outline.value?.pages.findIndex(page => page.pageNo === sample.pageNo) ?? -1
|
|
|
+ return { ...sample, slide: pageIndex >= 0 ? previewSlides.value?.[pageIndex] : undefined }
|
|
|
+ })
|
|
|
+)
|
|
|
|
|
|
watch(selectedTemplateId, () => {
|
|
|
paletteVariant.value = 0
|
|
|
@@ -660,7 +642,9 @@ async function fetchTask(taskId: string) {
|
|
|
else await loadDraft(taskId)
|
|
|
} else if (taskInfo.value.status === "FAILED") {
|
|
|
clearPollTimer()
|
|
|
- ElMessage.error(taskInfo.value.message || "AI课件生成失败")
|
|
|
+ const message = taskInfo.value.message || "AI课件生成失败"
|
|
|
+ clearTask()
|
|
|
+ ElMessage.error(message)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -808,11 +792,13 @@ async function confirmSamplesAndGenerate() {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- const backgroundUrls = Array.from(new Set(
|
|
|
- representativeSampleSlides.value
|
|
|
- .map(sample => sample.slide?.background?.type === "image" ? resolveTemplateAssetUrl(sample.slide.background.image) : "")
|
|
|
- .filter(Boolean)
|
|
|
- ))
|
|
|
+ const backgroundUrls = Array.from(
|
|
|
+ new Set(
|
|
|
+ representativeSampleSlides.value
|
|
|
+ .map(sample => (sample.slide?.background?.type === "image" ? resolveTemplateAssetUrl(sample.slide.background.image) : ""))
|
|
|
+ .filter(Boolean)
|
|
|
+ )
|
|
|
+ )
|
|
|
|
|
|
try {
|
|
|
await Promise.all(backgroundUrls.map(preloadImage))
|
|
|
@@ -898,7 +884,12 @@ function autoOptimizeDraft() {
|
|
|
draftOutline.value.pages.forEach((page, index) => {
|
|
|
if (!page.visualPlan) page.visualPlan = {}
|
|
|
const points = [...new Set(page.studentContent?.points || page.bullets || [])]
|
|
|
- .map(item => String(item || "").replace(/\s+/g, " ").trim().slice(0, 24))
|
|
|
+ .map(item =>
|
|
|
+ String(item || "")
|
|
|
+ .replace(/\s+/g, " ")
|
|
|
+ .trim()
|
|
|
+ .slice(0, 24)
|
|
|
+ )
|
|
|
.filter(Boolean)
|
|
|
.slice(0, 4)
|
|
|
if (!page.studentContent) page.studentContent = { headline: "", points: [] }
|
|
|
@@ -911,7 +902,8 @@ function autoOptimizeDraft() {
|
|
|
page.visualPlan.mainVisual ||= page.imageKeywords?.[0] || `${page.title}的课堂主视觉`
|
|
|
}
|
|
|
if (index > 0 && !page.classroomAction?.teacherInstruction && !page.teacherTips && !page.speakerNotes) {
|
|
|
- if (!page.classroomAction) page.classroomAction = { teacherInstruction: "", studentTask: page.studentContent.headline || page.title, interactionType: "" }
|
|
|
+ if (!page.classroomAction)
|
|
|
+ page.classroomAction = { teacherInstruction: "", studentTask: page.studentContent.headline || page.title, interactionType: "" }
|
|
|
page.classroomAction.teacherInstruction = `引导学生完成“${page.classroomAction.studentTask || page.title}”,根据学生的语言、动作或声音表现及时反馈。`
|
|
|
}
|
|
|
})
|
|
|
@@ -978,22 +970,29 @@ function shortProgressLabel(message?: string) {
|
|
|
if (message.includes("失败")) return "生成失败"
|
|
|
return message.length > 4 ? message.slice(0, 4) : message
|
|
|
}
|
|
|
-
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.ai-dialog {
|
|
|
- height: 82vh;
|
|
|
+ max-height: calc(100vh - 64px);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
color: #1f2933;
|
|
|
}
|
|
|
+.ai-dialog.review-mode {
|
|
|
+ height: 82vh;
|
|
|
+}
|
|
|
.header,
|
|
|
.footer {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
+.header,
|
|
|
+.workflow-steps,
|
|
|
+.footer {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
.title {
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
@@ -1052,7 +1051,7 @@ function shortProgressLabel(message?: string) {
|
|
|
}
|
|
|
.setup-stage {
|
|
|
min-height: 0;
|
|
|
- flex: 1;
|
|
|
+ flex: 0 1 auto;
|
|
|
overflow: auto;
|
|
|
padding-right: 4px;
|
|
|
}
|
|
|
@@ -1162,43 +1161,6 @@ function shortProgressLabel(message?: string) {
|
|
|
color: #33404d;
|
|
|
}
|
|
|
}
|
|
|
-.template-summary-card {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: 116px minmax(0, 1fr) auto;
|
|
|
- align-items: center;
|
|
|
- gap: 14px;
|
|
|
- margin-top: 14px;
|
|
|
- padding: 12px;
|
|
|
- border: 1px solid #dbe7f5;
|
|
|
- border-radius: 9px;
|
|
|
- background: #f7faff;
|
|
|
- button {
|
|
|
- height: 34px;
|
|
|
- padding: 0 12px;
|
|
|
- border: 1px solid $themeColor;
|
|
|
- border-radius: 6px;
|
|
|
- background: #fff;
|
|
|
- color: $themeColor;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
-}
|
|
|
-.template-summary-copy {
|
|
|
- min-width: 0;
|
|
|
- span,
|
|
|
- strong,
|
|
|
- small {
|
|
|
- display: block;
|
|
|
- }
|
|
|
- span,
|
|
|
- small {
|
|
|
- color: #7b8794;
|
|
|
- font-size: 12px;
|
|
|
- }
|
|
|
- strong {
|
|
|
- margin: 3px 0;
|
|
|
- font-size: 16px;
|
|
|
- }
|
|
|
-}
|
|
|
.template-real-cover {
|
|
|
display: block;
|
|
|
width: 116px;
|
|
|
@@ -1593,34 +1555,106 @@ function shortProgressLabel(message?: string) {
|
|
|
font-size: 9px;
|
|
|
}
|
|
|
&.story {
|
|
|
- .mini-band { width: 100%; height: 100%; opacity: 0.22; }
|
|
|
- .mini-title { left: 10%; top: 12px; width: 46%; }
|
|
|
- .mini-content { left: 10%; background: rgba(255, 255, 255, 0.9); }
|
|
|
- .mini-accent { right: 12%; bottom: 13px; width: 30px; height: 30px; opacity: 0.85; }
|
|
|
+ .mini-band {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ opacity: 0.22;
|
|
|
+ }
|
|
|
+ .mini-title {
|
|
|
+ left: 10%;
|
|
|
+ top: 12px;
|
|
|
+ width: 46%;
|
|
|
+ }
|
|
|
+ .mini-content {
|
|
|
+ left: 10%;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ }
|
|
|
+ .mini-accent {
|
|
|
+ right: 12%;
|
|
|
+ bottom: 13px;
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ opacity: 0.85;
|
|
|
+ }
|
|
|
}
|
|
|
&.activity {
|
|
|
- .mini-band { left: 7%; top: 12px; width: 18%; height: 42px; border-radius: 50%; }
|
|
|
- .mini-title { left: 32%; width: 48%; }
|
|
|
- .mini-content.first { left: 32%; top: 29px; width: 58%; }
|
|
|
- .mini-content.second { left: 32%; top: 47px; width: 45%; }
|
|
|
+ .mini-band {
|
|
|
+ left: 7%;
|
|
|
+ top: 12px;
|
|
|
+ width: 18%;
|
|
|
+ height: 42px;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ .mini-title {
|
|
|
+ left: 32%;
|
|
|
+ width: 48%;
|
|
|
+ }
|
|
|
+ .mini-content.first {
|
|
|
+ left: 32%;
|
|
|
+ top: 29px;
|
|
|
+ width: 58%;
|
|
|
+ }
|
|
|
+ .mini-content.second {
|
|
|
+ left: 32%;
|
|
|
+ top: 47px;
|
|
|
+ width: 45%;
|
|
|
+ }
|
|
|
}
|
|
|
&.technology {
|
|
|
background: var(--template-dark, #102744);
|
|
|
- .mini-band { width: 100%; height: 3px; opacity: 0.8; }
|
|
|
- .mini-title { left: 10%; background: var(--template-secondary); }
|
|
|
- .mini-content { left: 10%; width: 76%; background: rgba(255, 255, 255, 0.15); }
|
|
|
- .mini-accent { border-radius: 3px; }
|
|
|
+ .mini-band {
|
|
|
+ width: 100%;
|
|
|
+ height: 3px;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ .mini-title {
|
|
|
+ left: 10%;
|
|
|
+ background: var(--template-secondary);
|
|
|
+ }
|
|
|
+ .mini-content {
|
|
|
+ left: 10%;
|
|
|
+ width: 76%;
|
|
|
+ background: rgba(255, 255, 255, 0.15);
|
|
|
+ }
|
|
|
+ .mini-accent {
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
}
|
|
|
&.culture {
|
|
|
- .mini-band { left: 78%; width: 14%; height: 100%; opacity: 0.18; }
|
|
|
- .mini-title { left: 10%; width: 44%; }
|
|
|
- .mini-content { left: 10%; width: 58%; background: var(--template-soft); box-shadow: none; }
|
|
|
+ .mini-band {
|
|
|
+ left: 78%;
|
|
|
+ width: 14%;
|
|
|
+ height: 100%;
|
|
|
+ opacity: 0.18;
|
|
|
+ }
|
|
|
+ .mini-title {
|
|
|
+ left: 10%;
|
|
|
+ width: 44%;
|
|
|
+ }
|
|
|
+ .mini-content {
|
|
|
+ left: 10%;
|
|
|
+ width: 58%;
|
|
|
+ background: var(--template-soft);
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
}
|
|
|
&.academic {
|
|
|
- .mini-band { width: 4%; }
|
|
|
- .mini-title { left: 12%; width: 54%; }
|
|
|
- .mini-content { left: 12%; width: 76%; border-radius: 0; box-shadow: none; }
|
|
|
- .mini-accent { border-radius: 1px; }
|
|
|
+ .mini-band {
|
|
|
+ width: 4%;
|
|
|
+ }
|
|
|
+ .mini-title {
|
|
|
+ left: 12%;
|
|
|
+ width: 54%;
|
|
|
+ }
|
|
|
+ .mini-content {
|
|
|
+ left: 12%;
|
|
|
+ width: 76%;
|
|
|
+ border-radius: 0;
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+ .mini-accent {
|
|
|
+ border-radius: 1px;
|
|
|
+ }
|
|
|
}
|
|
|
em.fit-usable {
|
|
|
background: #fff7e6;
|
|
|
@@ -1655,55 +1689,11 @@ function shortProgressLabel(message?: string) {
|
|
|
.template-fit-warning.compact {
|
|
|
margin: 0 0 10px;
|
|
|
}
|
|
|
-.current-template-card {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- gap: 12px;
|
|
|
+.template-load-error {
|
|
|
margin-bottom: 10px;
|
|
|
- padding: 9px 10px;
|
|
|
- border: 1px solid #dbe7f5;
|
|
|
- border-radius: 7px;
|
|
|
- background: #f5f9ff;
|
|
|
- span,
|
|
|
- small {
|
|
|
- display: block;
|
|
|
- }
|
|
|
- span {
|
|
|
- color: #7b8794;
|
|
|
- font-size: 10px;
|
|
|
- }
|
|
|
- strong {
|
|
|
- display: block;
|
|
|
- margin: 2px 0;
|
|
|
- color: #1f2933;
|
|
|
- font-size: 13px;
|
|
|
- }
|
|
|
- small {
|
|
|
- color: #7b8794;
|
|
|
- font-size: 10px;
|
|
|
- }
|
|
|
- .template-debug-info {
|
|
|
- margin-top: 3px;
|
|
|
- color: #50647a;
|
|
|
- }
|
|
|
- .template-load-error {
|
|
|
- max-width: 620px;
|
|
|
- margin-top: 4px;
|
|
|
- color: #b42318;
|
|
|
- overflow-wrap: anywhere;
|
|
|
- }
|
|
|
- button {
|
|
|
- flex-shrink: 0;
|
|
|
- height: 30px;
|
|
|
- padding: 0 10px;
|
|
|
- border: 1px solid $themeColor;
|
|
|
- border-radius: 6px;
|
|
|
- background: #fff;
|
|
|
- color: $themeColor;
|
|
|
- font-size: 12px;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
+ color: #b42318;
|
|
|
+ font-size: 12px;
|
|
|
+ overflow-wrap: anywhere;
|
|
|
}
|
|
|
.sample-template-picker {
|
|
|
display: grid;
|
|
|
@@ -1888,8 +1878,6 @@ function shortProgressLabel(message?: string) {
|
|
|
}
|
|
|
.quality-list {
|
|
|
margin-top: 12px;
|
|
|
- max-height: 150px;
|
|
|
- overflow: auto;
|
|
|
border: 1px solid #edf1f5;
|
|
|
border-radius: 8px;
|
|
|
}
|
|
|
@@ -1978,6 +1966,9 @@ function shortProgressLabel(message?: string) {
|
|
|
}
|
|
|
}
|
|
|
.selected-draft-actions {
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 2;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
@@ -1987,6 +1978,7 @@ function shortProgressLabel(message?: string) {
|
|
|
border: 1px solid #dbe7f5;
|
|
|
border-radius: 8px;
|
|
|
background: #f7faff;
|
|
|
+ box-shadow: 0 4px 12px rgba(31, 41, 51, 0.06);
|
|
|
span,
|
|
|
strong {
|
|
|
display: block;
|
|
|
@@ -2279,21 +2271,67 @@ function shortProgressLabel(message?: string) {
|
|
|
line-height: 1.5;
|
|
|
background: #fff;
|
|
|
}
|
|
|
-.empty {
|
|
|
- flex: 1;
|
|
|
- margin-top: 14px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- border: 1px dashed #d9d9d9;
|
|
|
- color: #7b8794;
|
|
|
-}
|
|
|
.task {
|
|
|
min-height: 20px;
|
|
|
}
|
|
|
.footer {
|
|
|
margin-top: 14px;
|
|
|
+ padding-top: 12px;
|
|
|
+ border-top: 1px solid #edf1f5;
|
|
|
+ background: #fff;
|
|
|
justify-content: flex-end;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
+
|
|
|
+@media (max-width: 900px) {
|
|
|
+ .ai-dialog.review-mode {
|
|
|
+ height: calc(100vh - 64px);
|
|
|
+ }
|
|
|
+ .setup-primary-grid,
|
|
|
+ .outline-editor-panel {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .sample-review {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .sample-canvas {
|
|
|
+ justify-self: center;
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
+ .sample-thumbnail-grid {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+ .compact-template-panel .sample-template-picker {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+ .selected-draft-actions {
|
|
|
+ align-items: flex-start;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 640px) {
|
|
|
+ .workflow-step {
|
|
|
+ gap: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ .step-no {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ }
|
|
|
+ .sample-thumbnail-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .compact-template-panel .sample-template-picker {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .preview-head,
|
|
|
+ .sample-stage-head {
|
|
|
+ align-items: flex-start;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .footer {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|