|
|
@@ -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 || ""
|
|
|
+ const type = `${page.type || ""} ${page.visual?.layout || ""}`
|
|
|
const elements = baseElements(palette, options)
|
|
|
|
|
|
if (type.includes("cover") || page.pageNo === 1) {
|
|
|
@@ -77,16 +77,22 @@ function coverLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palet
|
|
|
elements.push(textElement({ left: 88, top: 264, width: 820, height: 38, content: escapeHtml(subtitle), fontSize: 23, color: palette.text }))
|
|
|
}
|
|
|
elements.push(...mainVisual(page, palette, { left: 940, top: 112, width: 520, height: 470, mode: "hero" }))
|
|
|
- elements.push(...bulletCards(normalizeBullets(page, 3), palette, { left: 88, top: 390, width: 720, itemHeight: 72, fontSize: 28, mark: "♪" }))
|
|
|
- elements.push(textElement({ left: 92, top: 746, width: 900, height: 42, content: "课堂导入:从声音、画面和问题进入音乐情境", fontSize: 22, color: palette.muted }))
|
|
|
+ elements.push(...bulletCards(studentPoints(page, 3), palette, { left: 88, top: 390, width: 720, itemHeight: 72, fontSize: 28, mark: "♪" }))
|
|
|
+ const intro = activityTask(page) || "从声音、画面和问题进入音乐情境"
|
|
|
+ elements.push(textElement({ left: 92, top: 746, width: 900, height: 42, content: escapeHtml(intro), fontSize: 22, color: palette.muted }))
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
function contentLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
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(...mainVisual(page, palette, { left: 960, top: 178, width: 470, height: 470, mode: "photo" }))
|
|
|
- elements.push(...bulletCards(normalizeBullets(page, 4), palette, { left: 98, top: 190, width: 760, itemHeight: 86, fontSize: 27, mark: "•" }))
|
|
|
+ elements.push(...bulletCards(studentPoints(page, 4), palette, { left: 98, top: headline && headline !== page.title ? 300 : 190, width: 760, itemHeight: 78, fontSize: 26, mark: "•" }))
|
|
|
addPrompt(elements, page, palette, 98, 706, 1220)
|
|
|
return elements
|
|
|
}
|
|
|
@@ -94,7 +100,7 @@ function contentLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, pal
|
|
|
function goalLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
- const goals = normalizeBullets(page, 3)
|
|
|
+ const goals = studentPoints(page, 3)
|
|
|
const colors = [palette.primary, palette.accent, palette.secondary]
|
|
|
goals.forEach((goal, index) => {
|
|
|
const left = 100 + index * 470
|
|
|
@@ -111,18 +117,18 @@ function listenLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, pale
|
|
|
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: 360, height: 46, content: "听赏任务", fontSize: 34, color: palette.primary, bold: true }))
|
|
|
+ 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(normalizeBullets(page, 3), palette, { left: 132, top: 442, width: 520, itemHeight: 58, fontSize: 24, mark: "听" }))
|
|
|
+ elements.push(...bulletCards(studentPoints(page, 3), palette, { left: 132, top: 442, width: 520, 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 || "听一听:音乐中出现了哪些有特点的声音?")
|
|
|
+ addPrompt(elements, page, palette, 112, 716, 1200, page.interaction?.question || activityTask(page) || "听一听:音乐中出现了哪些有特点的声音?")
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
function knowledgeLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
- const items = normalizeBullets(page, 4)
|
|
|
+ const items = studentPoints(page, 4)
|
|
|
items.forEach((item, index) => {
|
|
|
const left = 100 + (index % 2) * 520
|
|
|
const top = 210 + Math.floor(index / 2) * 190
|
|
|
@@ -137,11 +143,11 @@ function knowledgeLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, p
|
|
|
function interactionLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
- const question = page.interaction?.question || normalizeBullets(page, 1)[0] || "请同学们参与课堂互动"
|
|
|
+ const question = page.interaction?.question || activityTask(page) || studentHeadline(page) || "请同学们参与课堂互动"
|
|
|
elements.push(shapeElement({ left: 96, top: 178, width: 930, height: 210, fill: palette.primary, radius: 28, shadow: softShadow() }))
|
|
|
elements.push(textElement({ left: 140, top: 222, width: 820, height: 104, content: escapeHtml(question), fontSize: 38, color: "#FFFFFF", bold: true, lineHeight: 1.28 }))
|
|
|
elements.push(...mainVisual(page, palette, { left: 1090, top: 170, width: 330, height: 330, mode: "compact" }))
|
|
|
- elements.push(...stepCards(normalizeBullets(page, 3), palette, 112, 486))
|
|
|
+ elements.push(...stepCards(activitySteps(page, 3), palette, 112, 486))
|
|
|
addPrompt(elements, page, palette, 112, 732, 1200, page.interaction?.answer ? `参考:${page.interaction.answer}` : "")
|
|
|
return elements
|
|
|
}
|
|
|
@@ -149,7 +155,7 @@ function interactionLayout(page: AiCoursewarePage, outline: AiCoursewareOutline,
|
|
|
function summaryLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
- const items = normalizeBullets(page, 4)
|
|
|
+ const items = studentPoints(page, 4)
|
|
|
elements.push(shapeElement({ left: 110, top: 190, width: 960, height: 470, fill: "#FFFFFF", radius: 26, shadow: softShadow() }))
|
|
|
items.forEach((item, index) => {
|
|
|
const top = 236 + index * 96
|
|
|
@@ -166,7 +172,7 @@ function homeworkLayout(page: AiCoursewarePage, outline: AiCoursewareOutline, pa
|
|
|
addTitle(elements, page, outline, palette)
|
|
|
elements.push(shapeElement({ left: 110, top: 188, width: 590, height: 500, fill: "#FFFFFF", radius: 28, shadow: softShadow() }))
|
|
|
elements.push(textElement({ left: 150, top: 236, width: 460, height: 44, content: "课后练习", fontSize: 36, color: palette.primary, bold: true }))
|
|
|
- elements.push(...bulletCards(normalizeBullets(page, 4), palette, { left: 150, top: 324, width: 490, itemHeight: 66, fontSize: 25, mark: "做" }))
|
|
|
+ elements.push(...bulletCards(activitySteps(page, 4), palette, { left: 150, top: 324, width: 490, itemHeight: 66, fontSize: 25, mark: "做" }))
|
|
|
elements.push(...mainVisual(page, palette, { left: 825, top: 188, width: 520, height: 500, mode: "photo" }))
|
|
|
return elements
|
|
|
}
|
|
|
@@ -202,9 +208,10 @@ function fallbackVisualElements(page: AiCoursewarePage, palette: Palette, rect:
|
|
|
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.23, width: rect.width * 0.28, height: 100, content: visual.icon, fontSize: rect.mode === "compact" ? 76 : 96, color: visual.color, bold: true }))
|
|
|
- elements.push(...soundWave(rect.left + rect.width * 0.22, rect.top + rect.height * 0.62, palette, rect.width * 0.5))
|
|
|
- elements.push(textElement({ left: rect.left + rect.width * 0.22, top: rect.top + rect.height * 0.76, width: rect.width * 0.56, height: 42, content: visual.label, fontSize: rect.mode === "compact" ? 22 : 28, color: visual.color, bold: true }))
|
|
|
+ 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 }))
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
@@ -238,20 +245,36 @@ function addPrompt(elements: PPTElement[], page: AiCoursewarePage, palette: Pale
|
|
|
elements.push(textElement({ left: left + 28, top: top + 20, width: width - 56, height: 34, content: escapeHtml(text), fontSize: 23, color: palette.text, lineHeight: 1.3 }))
|
|
|
}
|
|
|
|
|
|
-function soundWave(left: number, top: number, palette: Palette, width = 330): PPTElement[] {
|
|
|
+function soundWave(left: number, top: number, palette: Palette, width = 330, maxHeight = 140): PPTElement[] {
|
|
|
const elements: PPTElement[] = []
|
|
|
- const heights = [46, 76, 112, 70, 132, 92, 54]
|
|
|
+ const heights = [46, 76, 112, 70, 132, 92, 54].map(height => (height / 140) * maxHeight)
|
|
|
heights.forEach((height, index) => {
|
|
|
const barWidth = Math.max(18, width / 18)
|
|
|
const gap = Math.max(20, width / 10)
|
|
|
- elements.push(shapeElement({ left: left + index * gap, top: top + (140 - height) / 2, width: barWidth, height, fill: index % 2 === 0 ? palette.primary : palette.accent, radius: 10 }))
|
|
|
+ elements.push(shapeElement({ left: left + index * gap, top: top + (maxHeight - height) / 2, width: barWidth, height, fill: index % 2 === 0 ? palette.primary : palette.accent, radius: 10 }))
|
|
|
})
|
|
|
return elements
|
|
|
}
|
|
|
|
|
|
-function normalizeBullets(page: AiCoursewarePage, limit: number): string[] {
|
|
|
- const bullets = (page.bullets || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
- const result = bullets.length ? bullets.slice(0, limit) : [page.subtitle || page.title || "课堂内容"]
|
|
|
+function studentHeadline(page: AiCoursewarePage) {
|
|
|
+ return String(page.studentContent?.headline || page.subtitle || "").trim()
|
|
|
+}
|
|
|
+
|
|
|
+function studentPoints(page: AiCoursewarePage, limit: number): string[] {
|
|
|
+ const structuredPoints = (page.studentContent?.points || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
+ const legacyPoints = (page.bullets || []).filter(Boolean).map(item => item.trim()).filter(Boolean)
|
|
|
+ const source = structuredPoints.length ? structuredPoints : legacyPoints
|
|
|
+ const result = source.length ? source.slice(0, limit) : [page.subtitle || page.title || "课堂内容"]
|
|
|
+ return result.map(item => trimText(item, 42))
|
|
|
+}
|
|
|
+
|
|
|
+function activityTask(page: AiCoursewarePage) {
|
|
|
+ return String(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 result = steps.length ? steps.slice(0, limit) : studentPoints(page, limit)
|
|
|
return result.map(item => trimText(item, 46))
|
|
|
}
|
|
|
|
|
|
@@ -295,13 +318,14 @@ function buildPalette(style: string): Palette {
|
|
|
}
|
|
|
|
|
|
function visualConfig(page: AiCoursewarePage, palette: Palette) {
|
|
|
- const type = page.type || ""
|
|
|
- if (type.includes("interaction") || type.includes("activity")) return { icon: "?", label: "课堂互动", color: palette.accent, light: "#EEF2FF" }
|
|
|
- if (type.includes("summary")) return { icon: "✓", label: "课堂小结", color: "#2E7D32", light: "#EAF7EC" }
|
|
|
- if (type.includes("goal")) return { icon: "◎", label: "学习目标", color: "#7B1FA2", light: "#F3E8FF" }
|
|
|
- if (type.includes("listen")) return { icon: "♪", label: "听一听", color: palette.secondary, light: "#E5F6F3" }
|
|
|
- if (type.includes("homework")) return { icon: "✎", label: "课后练习", color: "#6D4C41", light: "#F3ECE8" }
|
|
|
- return { icon: "♫", label: "音乐课堂", color: palette.primary, light: palette.warm }
|
|
|
+ const type = `${page.type || ""} ${page.visual?.layout || ""}`
|
|
|
+ const visualLabel = trimText(page.visual?.mainVisual || page.visual?.style || "", 8)
|
|
|
+ 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" }
|
|
|
+ if (type.includes("listen")) return { icon: "♪", label: visualLabel || "听一听", color: palette.secondary, light: "#E5F6F3" }
|
|
|
+ if (type.includes("homework")) return { icon: "✎", label: visualLabel || "课后练习", color: "#6D4C41", light: "#F3ECE8" }
|
|
|
+ return { icon: "♫", label: visualLabel || "音乐课堂", color: palette.primary, light: palette.warm }
|
|
|
}
|
|
|
|
|
|
function textElement(params: {
|