|
|
@@ -39,7 +39,7 @@ export function aiCoursewareToSlides(outline: AiCoursewareOutline, options: AiCo
|
|
|
}
|
|
|
|
|
|
function buildSlide(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette, options: AiCoursewareVisualOptions): Slide {
|
|
|
- const type = `${page.type || ""} ${page.visual?.layout || ""}`
|
|
|
+ const type = pageKind(page)
|
|
|
const elements = baseElements(palette, options)
|
|
|
|
|
|
if (type.includes("cover") || page.pageNo === 1) {
|
|
|
@@ -64,7 +64,7 @@ function buildSlide(page: AiCoursewarePage, outline: AiCoursewareOutline, palett
|
|
|
id: nanoid(10),
|
|
|
elements,
|
|
|
background: backgroundConfig(palette, options),
|
|
|
- remark: page.teacherTips || ""
|
|
|
+ remark: slideRemark(page)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -88,8 +88,8 @@ function contentLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, pal
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
const headline = studentHeadline(page)
|
|
|
if (headline && headline !== page.title) {
|
|
|
- elements.push(shapeElement({ left: 96, top: 178, width: 740, height: 86, fill: palette.primary, radius: 24, shadow: softShadow() }))
|
|
|
- elements.push(textElement({ left: 134, top: 204, width: 664, height: 38, content: escapeHtml(headline), fontSize: 30, color: "#FFFFFF", bold: true }))
|
|
|
+ elements.push(shapeElement({ left: 96, top: 178, width: 760, height: 78, fill: palette.panelAlt, radius: 18 }))
|
|
|
+ elements.push(textElement({ left: 130, top: 202, width: 692, height: 34, content: escapeHtml(headline), fontSize: 29, color: palette.text, bold: true }))
|
|
|
}
|
|
|
elements.push(...mainVisual(page, palette, { left: 960, top: 178, width: 470, height: 470, mode: "photo" }))
|
|
|
elements.push(...bulletCards(studentPoints(page, 4), palette, { left: 98, top: headline && headline !== page.title ? 300 : 190, width: 760, itemHeight: 78, fontSize: 26, mark: "•" }))
|
|
|
@@ -116,12 +116,13 @@ function goalLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palett
|
|
|
function listenLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
- elements.push(shapeElement({ left: 92, top: 190, width: 640, height: 438, fill: "#FFFFFF", radius: 28, shadow: softShadow() }))
|
|
|
- elements.push(textElement({ left: 132, top: 232, width: 520, height: 46, content: escapeHtml(studentHeadline(page) || "听赏任务"), fontSize: 34, color: palette.primary, bold: true }))
|
|
|
- elements.push(...soundWave(150, 328, palette))
|
|
|
- elements.push(...bulletCards(studentPoints(page, 3), palette, { left: 132, top: 442, width: 520, itemHeight: 58, fontSize: 24, mark: "听" }))
|
|
|
+ const task = activityTask(page) || studentHeadline(page) || "听一听:音乐中出现了哪些有特点的声音?"
|
|
|
+ elements.push(shapeElement({ left: 92, top: 184, width: 660, height: 150, fill: palette.primary, radius: 24, shadow: softShadow() }))
|
|
|
+ elements.push(textElement({ left: 132, top: 224, width: 570, height: 64, content: escapeHtml(task), fontSize: 34, color: "#FFFFFF", bold: true, lineHeight: 1.25 }))
|
|
|
+ elements.push(...soundWave(128, 382, palette, 530, 118))
|
|
|
+ elements.push(...bulletCards(studentPoints(page, 3), palette, { left: 96, top: 548, width: 620, itemHeight: 58, fontSize: 24, mark: "听" }))
|
|
|
elements.push(...mainVisual(page, palette, { left: 835, top: 178, width: 560, height: 500, mode: "photo" }))
|
|
|
- addPrompt(elements, page, palette, 112, 716, 1200, page.interaction?.question || activityTask(page) || "听一听:音乐中出现了哪些有特点的声音?")
|
|
|
+ addPrompt(elements, page, palette, 112, 716, 1200, page.interaction?.question || page.classroomAction?.studentTask || "听完后,请说一说你听到了什么。")
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
@@ -182,9 +183,9 @@ function baseElements(palette: Palette, options: AiCoursewareVisualOptions): PPT
|
|
|
if (options.backgroundImage) {
|
|
|
elements.push(shapeElement({ left: 0, top: 0, width: SLIDE_WIDTH, height: SLIDE_HEIGHT, fill: "rgba(255, 255, 255, 0.78)", opacity: 0.94 }))
|
|
|
}
|
|
|
- elements.push(shapeElement({ left: 0, top: 0, width: 18, height: SLIDE_HEIGHT, fill: palette.primary, radius: 0 }))
|
|
|
- elements.push(shapeElement({ left: 1260, top: 78, width: 250, height: 250, fill: palette.panelAlt, opacity: 0.7, radius: 100 }))
|
|
|
- elements.push(shapeElement({ left: 1370, top: 675, width: 120, height: 120, fill: palette.warm, opacity: 0.5, radius: 60 }))
|
|
|
+ elements.push(shapeElement({ left: 0, top: 0, width: 12, height: SLIDE_HEIGHT, fill: palette.primary, radius: 0 }))
|
|
|
+ elements.push(shapeElement({ left: 1260, top: 72, width: 210, height: 210, fill: palette.panelAlt, opacity: 0.38, radius: 100 }))
|
|
|
+ elements.push(shapeElement({ left: 1380, top: 690, width: 90, height: 90, fill: palette.warm, opacity: 0.38, radius: 44 }))
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
@@ -196,22 +197,30 @@ function addTitle(elements: PPTElement[], page: AiCoursewarePage, outline: AiCou
|
|
|
function mainVisual(page: AiCoursewarePage, palette: Palette, rect: { left: number; top: number; width: number; height: number; mode: "hero" | "photo" | "compact" }): PPTElement[] {
|
|
|
if (page.image?.url) {
|
|
|
return [
|
|
|
- shapeElement({ left: rect.left - 18, top: rect.top + 20, width: rect.width, height: rect.height, fill: palette.warm, opacity: 0.62, radius: 34 }),
|
|
|
+ shapeElement({ left: rect.left - 16, top: rect.top + 18, width: rect.width, height: rect.height, fill: palette.warm, opacity: 0.48, radius: 30 }),
|
|
|
imageElement({ left: rect.left, top: rect.top, width: rect.width, height: rect.height, src: page.image.url, radius: rect.mode === "compact" ? 28 : 34 })
|
|
|
]
|
|
|
}
|
|
|
- return fallbackVisualElements(page, palette, rect)
|
|
|
+ return teachingVisualElements(page, palette, rect)
|
|
|
}
|
|
|
|
|
|
-function fallbackVisualElements(page: AiCoursewarePage, palette: Palette, rect: { left: number; top: number; width: number; height: number; mode: "hero" | "photo" | "compact" }): PPTElement[] {
|
|
|
+function teachingVisualElements(page: AiCoursewarePage, palette: Palette, rect: { left: number; top: number; width: number; height: number; mode: "hero" | "photo" | "compact" }): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
const visual = visualConfig(page, palette)
|
|
|
- elements.push(shapeElement({ left: rect.left, top: rect.top, width: rect.width, height: rect.height, fill: visual.light, radius: 34, shadow: softShadow() }))
|
|
|
- elements.push(shapeElement({ left: rect.left + rect.width * 0.18, top: rect.top + rect.height * 0.13, width: rect.width * 0.64, height: rect.height * 0.64, fill: "#FFFFFF", opacity: 0.88, radius: 90 }))
|
|
|
- elements.push(textElement({ left: rect.left + rect.width * 0.36, top: rect.top + rect.height * 0.18, width: rect.width * 0.28, height: 100, content: visual.icon, fontSize: rect.mode === "compact" ? 68 : 90, color: visual.color, bold: true }))
|
|
|
- elements.push(...soundWave(rect.left + rect.width * 0.24, rect.top + rect.height * 0.58, palette, rect.width * 0.48, rect.mode === "compact" ? 82 : 110))
|
|
|
- elements.push(shapeElement({ left: rect.left + rect.width * 0.26, top: rect.top + rect.height * 0.78, width: rect.width * 0.48, height: 44, fill: "#FFFFFF", opacity: 0.82, radius: 22 }))
|
|
|
- elements.push(textElement({ left: rect.left + rect.width * 0.3, top: rect.top + rect.height * 0.8, width: rect.width * 0.4, height: 30, content: visual.label, fontSize: rect.mode === "compact" ? 18 : 22, color: visual.color, bold: true }))
|
|
|
+ const title = visualMain(page) || visual.label
|
|
|
+ const chips = visualElements(page).slice(0, rect.mode === "compact" ? 2 : 4)
|
|
|
+ elements.push(shapeElement({ left: rect.left, top: rect.top, width: rect.width, height: rect.height, fill: visual.light, radius: 30, shadow: softShadow() }))
|
|
|
+ elements.push(shapeElement({ left: rect.left + rect.width * 0.11, top: rect.top + rect.height * 0.12, width: rect.width * 0.78, height: rect.height * 0.42, fill: "#FFFFFF", opacity: 0.92, radius: 34 }))
|
|
|
+ elements.push(textElement({ left: rect.left + rect.width * 0.43, top: rect.top + rect.height * 0.18, width: rect.width * 0.16, height: 76, content: visual.icon, fontSize: rect.mode === "compact" ? 54 : 74, color: visual.color, bold: true }))
|
|
|
+ elements.push(textElement({ left: rect.left + rect.width * 0.12, top: rect.top + rect.height * 0.42, width: rect.width * 0.76, height: 44, content: escapeHtml(title), fontSize: rect.mode === "compact" ? 18 : 24, color: visual.color, bold: true }))
|
|
|
+ elements.push(...soundWave(rect.left + rect.width * 0.2, rect.top + rect.height * 0.58, palette, rect.width * 0.58, rect.mode === "compact" ? 56 : 78))
|
|
|
+ chips.forEach((chip, index) => {
|
|
|
+ const chipWidth = rect.mode === "compact" ? rect.width * 0.66 : rect.width * 0.38
|
|
|
+ const chipLeft = rect.left + rect.width * 0.12 + (rect.mode === "compact" ? 0 : (index % 2) * rect.width * 0.43)
|
|
|
+ const chipTop = rect.top + rect.height * 0.76 + Math.floor(index / (rect.mode === "compact" ? 1 : 2)) * 44
|
|
|
+ elements.push(shapeElement({ left: chipLeft, top: chipTop, width: chipWidth, height: 32, fill: "#FFFFFF", opacity: 0.9, radius: 16 }))
|
|
|
+ elements.push(textElement({ left: chipLeft + 14, top: chipTop + 7, width: chipWidth - 28, height: 20, content: escapeHtml(trimText(chip, 12)), fontSize: rect.mode === "compact" ? 15 : 18, color: palette.text }))
|
|
|
+ })
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
@@ -269,15 +278,31 @@ function studentPoints(page: AiCoursewarePage, limit: number): string[] {
|
|
|
}
|
|
|
|
|
|
function activityTask(page: AiCoursewarePage) {
|
|
|
- return String(page.activity?.task || "").trim()
|
|
|
+ return String(page.classroomAction?.studentTask || page.activity?.task || "").trim()
|
|
|
}
|
|
|
|
|
|
function activitySteps(page: AiCoursewarePage, limit: number): string[] {
|
|
|
- const steps = (page.activity?.steps || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
+ const steps = [
|
|
|
+ page.classroomAction?.studentTask,
|
|
|
+ ...(page.activity?.steps || [])
|
|
|
+ ].filter(Boolean).map(item => String(item).trim()).filter(Boolean)
|
|
|
const result = steps.length ? steps.slice(0, limit) : studentPoints(page, limit)
|
|
|
return result.map(item => trimText(item, 46))
|
|
|
}
|
|
|
|
|
|
+function pageKind(page: AiCoursewarePage) {
|
|
|
+ return `${page.type || ""} ${page.visualPlan?.layout || ""} ${page.visual?.layout || ""}`.toLowerCase()
|
|
|
+}
|
|
|
+
|
|
|
+function slideRemark(page: AiCoursewarePage) {
|
|
|
+ return [page.speakerNotes, page.teacherTips, page.classroomAction?.teacherInstruction, page.teachingPurpose]
|
|
|
+ .filter(Boolean)
|
|
|
+ .map(item => String(item).trim())
|
|
|
+ .filter(Boolean)
|
|
|
+ .filter((item, index, array) => array.indexOf(item) === index)
|
|
|
+ .join("\n")
|
|
|
+}
|
|
|
+
|
|
|
function backgroundConfig(palette: Palette, options: AiCoursewareVisualOptions): SlideBackground {
|
|
|
if (options.backgroundImage) {
|
|
|
return {
|
|
|
@@ -318,8 +343,8 @@ function buildPalette(style: string): Palette {
|
|
|
}
|
|
|
|
|
|
function visualConfig(page: AiCoursewarePage, palette: Palette) {
|
|
|
- const type = `${page.type || ""} ${page.visual?.layout || ""}`
|
|
|
- const visualLabel = trimText(page.visual?.mainVisual || page.visual?.style || "", 8)
|
|
|
+ const type = pageKind(page)
|
|
|
+ const visualLabel = trimText(visualMain(page) || page.visual?.style || "", 10)
|
|
|
if (type.includes("interaction") || type.includes("activity")) return { icon: "?", label: visualLabel || "课堂互动", color: palette.accent, light: "#EEF2FF" }
|
|
|
if (type.includes("summary")) return { icon: "✓", label: visualLabel || "课堂小结", color: "#2E7D32", light: "#EAF7EC" }
|
|
|
if (type.includes("goal")) return { icon: "◎", label: visualLabel || "学习目标", color: "#7B1FA2", light: "#F3E8FF" }
|
|
|
@@ -328,6 +353,16 @@ function visualConfig(page: AiCoursewarePage, palette: Palette) {
|
|
|
return { icon: "♫", label: visualLabel || "音乐课堂", color: palette.primary, light: palette.warm }
|
|
|
}
|
|
|
|
|
|
+function visualMain(page: AiCoursewarePage) {
|
|
|
+ return String(page.visualPlan?.mainVisual || page.visual?.mainVisual || "").trim()
|
|
|
+}
|
|
|
+
|
|
|
+function visualElements(page: AiCoursewarePage) {
|
|
|
+ const elements = (page.visualPlan?.elements || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
+ const keywords = (page.visualPlan?.imageKeywords || page.visual?.imageKeywords || page.imageKeywords || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
+ return elements.length ? elements : keywords
|
|
|
+}
|
|
|
+
|
|
|
function textElement(params: {
|
|
|
left: number
|
|
|
top: number
|