yonge 1 неделя назад
Родитель
Сommit
5e9d78feca

+ 1 - 1
dist/assets/index-CuS6y5Pe.js → dist/assets/index-AV00OC_I.js

@@ -1,4 +1,4 @@
-import{cF as dw,cG as hw,aG as vw,cH as pw,cI as gw,cJ as mw,cK as yw,ah as Yt,cL as Sw,cM as bw,cN as xw,cO as Cw,aM as ww,aF as Tw,cP as Iw,aB as c1,bg as _w,cQ as Aw,cR as Ew,cS as Pw,cT as Mw,cU as Rw,cV as Dw,cW as Lw,aE as kw,cX as Ow,s as Ae,bl as Bw,al as je,ag as ze,af as ve,ak as Z,cY as Nw,cZ as Fw,c_ as $w,c$ as zw,bk as Uw,cw as ul,aS as Cv,aq as Ye,d0 as Hw,d1 as Vw,a5 as $e,d2 as Gw,d3 as Ww,d4 as jw,d5 as Kw,d6 as Xw,d7 as Yw,d8 as Zw,d9 as Qw,da as Jw,db as qw,dc as eT,v as tT,dd as rT,de as nT,df as iT,aW as aT,dg as oT,dh as sT,di as lT,dj as uT,dk as cT,ay as sr,dl as fT,dm as dT,dn as hT,dp as vT,A as pT,dq as gT,dr as mT,bq as yT,ds as ST,dt as bT,du as xT,ap as CT,ac as f1,ai as dt,dv as wT,au as Ce,dw as TT,X as d1,y as IT,dx as _T,aP as AT,dy as ET,w as _r,dz as PT,dA as MT,H as RT,dB as DT,bA as hn,aD as LT,ae as ie,bN as ea,$ as wc,dC as kT,bM as ta,dD as OT,aC as BT,aO as NT,R as fe,dE as FT,br as rh,b3 as Lr,aj as Hs,b1 as lt,b2 as Ua,an as wv,dF as $T,dG as zT,dH as UT,dI as HT,dJ as VT,dK as GT,dL as WT,Q as jT,dM as KT,dN as XT,dO as YT,at as Mt,dP as ZT,aR as QT,aY as JT,ad as cl,aZ as qT,dQ as eI,dR as tI,dS as rI,P as le,a6 as nI,dT as iI,dU as aI,dV as oI,dW as sI,a7 as lI,dX as uI,dY as cI,dZ as fI,d_ as dI,d$ as hI,b4 as vI,aA as Tv,e0 as pI,e1 as gI,E as gr,x as mI,e2 as yI,e3 as SI,e4 as bI,am as _n,e5 as xI,az as hr,aU as nh,e6 as CI,as as jr,e7 as wI,e8 as Iv,cE as mr,by as ih,ct as ah,bI as TI,bH as Ve,bu as oh,bv as _v,bx as Wt,bB as Tc,bL as di,e9 as wu,bE as hi,ea as II,bJ as ra,bC as _I,bw as Bt,eb as AI,ec as ui,cu as EI,ed as PI,ee as MI,ef as RI,eg as DI,bK as Zp,eh as LI,bF as Qp,cB as Ic,cA as kI,ei as Hu,ej as Il}from"./index-De-JLCO5.js";/**
+import{cF as dw,cG as hw,aG as vw,cH as pw,cI as gw,cJ as mw,cK as yw,ah as Yt,cL as Sw,cM as bw,cN as xw,cO as Cw,aM as ww,aF as Tw,cP as Iw,aB as c1,bg as _w,cQ as Aw,cR as Ew,cS as Pw,cT as Mw,cU as Rw,cV as Dw,cW as Lw,aE as kw,cX as Ow,s as Ae,bl as Bw,al as je,ag as ze,af as ve,ak as Z,cY as Nw,cZ as Fw,c_ as $w,c$ as zw,bk as Uw,cw as ul,aS as Cv,aq as Ye,d0 as Hw,d1 as Vw,a5 as $e,d2 as Gw,d3 as Ww,d4 as jw,d5 as Kw,d6 as Xw,d7 as Yw,d8 as Zw,d9 as Qw,da as Jw,db as qw,dc as eT,v as tT,dd as rT,de as nT,df as iT,aW as aT,dg as oT,dh as sT,di as lT,dj as uT,dk as cT,ay as sr,dl as fT,dm as dT,dn as hT,dp as vT,A as pT,dq as gT,dr as mT,bq as yT,ds as ST,dt as bT,du as xT,ap as CT,ac as f1,ai as dt,dv as wT,au as Ce,dw as TT,X as d1,y as IT,dx as _T,aP as AT,dy as ET,w as _r,dz as PT,dA as MT,H as RT,dB as DT,bA as hn,aD as LT,ae as ie,bN as ea,$ as wc,dC as kT,bM as ta,dD as OT,aC as BT,aO as NT,R as fe,dE as FT,br as rh,b3 as Lr,aj as Hs,b1 as lt,b2 as Ua,an as wv,dF as $T,dG as zT,dH as UT,dI as HT,dJ as VT,dK as GT,dL as WT,Q as jT,dM as KT,dN as XT,dO as YT,at as Mt,dP as ZT,aR as QT,aY as JT,ad as cl,aZ as qT,dQ as eI,dR as tI,dS as rI,P as le,a6 as nI,dT as iI,dU as aI,dV as oI,dW as sI,a7 as lI,dX as uI,dY as cI,dZ as fI,d_ as dI,d$ as hI,b4 as vI,aA as Tv,e0 as pI,e1 as gI,E as gr,x as mI,e2 as yI,e3 as SI,e4 as bI,am as _n,e5 as xI,az as hr,aU as nh,e6 as CI,as as jr,e7 as wI,e8 as Iv,cE as mr,by as ih,ct as ah,bI as TI,bH as Ve,bu as oh,bv as _v,bx as Wt,bB as Tc,bL as di,e9 as wu,bE as hi,ea as II,bJ as ra,bC as _I,bw as Bt,eb as AI,ec as ui,cu as EI,ed as PI,ee as MI,ef as RI,eg as DI,bK as Zp,eh as LI,bF as Qp,cB as Ic,cA as kI,ei as Hu,ej as Il}from"./index-Dfvg3Po0.js";/**
 * vue v3.4.34
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/assets/index-BIm5POJ0.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/assets/index-CQ5ffcXX.css


+ 1 - 1
dist/assets/index-kFrDVL_E.js → dist/assets/index-CfTnRDko.js

@@ -1 +1 @@
-import{u as s,af as r}from"./index-CuS6y5Pe.js";import{a5 as c,cB as n,R as p,ae as t,af as _,al as u,ag as i,bH as f}from"./index-De-JLCO5.js";const l={class:"pptScreen"},m=c({__name:"pptScreen",setup(d){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",l,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),P=f(m,[["__scopeId","data-v-678905e9"]]);export{P as default};
+import{u as s,af as r}from"./index-AV00OC_I.js";import{a5 as c,cB as n,R as p,ae as t,af as _,al as u,ag as i,bH as f}from"./index-Dfvg3Po0.js";const l={class:"pptScreen"},m=c({__name:"pptScreen",setup(d){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",l,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),P=f(m,[["__scopeId","data-v-678905e9"]]);export{P as default};

+ 1 - 1
dist/assets/index-WkGUE7x5.js → dist/assets/index-CnpK3sR9.js

@@ -1 +1 @@
-import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,en as t,ae as r,af as c,bM as _,bN as d,ak as s,bH as p}from"./index-De-JLCO5.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,en as t,ae as r,af as c,bM as _,bN as d,ak as s,bH as p}from"./index-Dfvg3Po0.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};

+ 1 - 1
dist/assets/index-Bns18hje.js → dist/assets/index-CyI7BBbx.js

@@ -1 +1 @@
-import{u as c,af as l}from"./index-CuS6y5Pe.js";import{w as p,bA as d,a5 as g,cB as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-De-JLCO5.js";import"./shapes-BEljt_NO.js";const S=()=>{function o(a){if(window.parent===window)return;const{api:e,playState:n}=a.data||{};e==="headerTogge"?window.parent.postMessage({api:"headerTogge",playState:n},"*"):e==="changeTogge"?window.parent.postMessage({api:"changeTogge"},"*"):["clickTempo","clickViewFigner"].includes(e)&&window.parent.postMessage({api:e},"*")}p(()=>{window.addEventListener("message",o)}),d(()=>{window.removeEventListener("message",o)})},_=g({__name:"mobileScreen",setup(o){S();const a=m(),e=t(!0),n=t(!1),i=c();return a.setScreenMode("mobileScreen"),i.initPPTData().then(()=>{e.value=!1}),(v,s)=>(r(),u("div",{class:"mobileScreen",onClick:s[0]||(s[0]=b=>n.value=!1)},[e.value?w("",!0):(r(),f(l,{key:0}))]))}}),B=k(_,[["__scopeId","data-v-90b2692a"]]);export{B as default};
+import{u as c,af as l}from"./index-AV00OC_I.js";import{w as p,bA as d,a5 as g,cB as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-Dfvg3Po0.js";import"./shapes-BYgDLQBI.js";const S=()=>{function o(a){if(window.parent===window)return;const{api:e,playState:n}=a.data||{};e==="headerTogge"?window.parent.postMessage({api:"headerTogge",playState:n},"*"):e==="changeTogge"?window.parent.postMessage({api:"changeTogge"},"*"):["clickTempo","clickViewFigner"].includes(e)&&window.parent.postMessage({api:e},"*")}p(()=>{window.addEventListener("message",o)}),d(()=>{window.removeEventListener("message",o)})},_=g({__name:"mobileScreen",setup(o){S();const a=m(),e=t(!0),n=t(!1),i=c();return a.setScreenMode("mobileScreen"),i.initPPTData().then(()=>{e.value=!1}),(v,s)=>(r(),u("div",{class:"mobileScreen",onClick:s[0]||(s[0]=b=>n.value=!1)},[e.value?w("",!0):(r(),f(l,{key:0}))]))}}),B=k(_,[["__scopeId","data-v-90b2692a"]]);export{B as default};

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/assets/index-DQmLC3GK.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/assets/index-DaIPj8X3.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/assets/index-Dfvg3Po0.js


+ 1 - 1
dist/assets/index-D_L77Dkm.js → dist/assets/index-DgNDSzoq.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Bmc1oCHR.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,aS as p,bM as m,bN as f,bH as u}from"./index-De-JLCO5.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),h={class:"login"},b={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",h,[o("div",b,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),S=u(v,[["__scopeId","data-v-340bbdaa"]]);export{S as default};
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-DiO6wLJJ.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,aS as p,bM as m,bN as f,bH as u}from"./index-Dfvg3Po0.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),h={class:"login"},b={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",h,[o("div",b,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),S=u(v,[["__scopeId","data-v-340bbdaa"]]);export{S as default};

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/assets/index-DiO6wLJJ.js


+ 1 - 1
dist/assets/shapes-BEljt_NO.js → dist/assets/shapes-BYgDLQBI.js

@@ -1,4 +1,4 @@
-import{cE as k,bB as $1}from"./index-De-JLCO5.js";import{ag as Z,ah as H1,ai as U,aj as n0,ak as c0,al as i0,am as j1,an as K1,ao as Y1,ap as R}from"./index-CuS6y5Pe.js";var z1={exports:{}};/*!
+import{cE as k,bB as $1}from"./index-Dfvg3Po0.js";import{ag as Z,ah as H1,ai as U,aj as n0,ak as c0,al as i0,am as j1,an as K1,ao as Y1,ap as R}from"./index-AV00OC_I.js";var z1={exports:{}};/*!
  * clipboard.js v2.0.11
  * https://clipboardjs.com/
  *

+ 1 - 1
dist/index.html

@@ -10,7 +10,7 @@
     <meta name="description" content="ppt编辑器" />
     <meta name="keywords" content="ppt,powerpoint,office powerpoint,在线ppt,幻灯片,演示文稿,ppt在线制作,Vue3,TypeScript" />
     <title>PPT</title>
-    <script type="module" crossorigin src="./assets/index-De-JLCO5.js"></script>
+    <script type="module" crossorigin src="./assets/index-Dfvg3Po0.js"></script>
     <link rel="stylesheet" crossorigin href="./assets/index-AavUEaYy.css">
   </head>
   <body>

+ 14 - 0
src/api/aiCourseware.ts

@@ -29,6 +29,20 @@ export interface AiCoursewarePage {
   title: string
   subtitle?: string
   bullets: string[]
+  studentContent?: {
+    headline?: string
+    points?: string[]
+  }
+  activity?: {
+    task?: string
+    steps?: string[]
+  }
+  visual?: {
+    layout?: string
+    style?: string
+    mainVisual?: string
+    imageKeywords?: string[]
+  }
   designRequirement?: string
   teacherTips?: string
   interaction?: {

+ 53 - 29
src/utils/aiCoursewareToSlides.ts

@@ -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: {

+ 73 - 8
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -56,15 +56,42 @@
 
           <div class="field-grid">
             <div class="field-card">
-              <div class="field-title">页面内容</div>
+              <div class="field-title">学生看到的内容</div>
+              <input
+                :value="studentHeadline"
+                :disabled="working"
+                class="headline-input"
+                placeholder="这一页给学生看的核心标题"
+                @input="event => updateStudentHeadline(selectedPage!, event)"
+              />
+              <textarea
+                :value="studentPoints.join('\n')"
+                :disabled="working"
+                class="page-bullets"
+                placeholder="每行一条学生可见要点,避免写老师操作步骤"
+                @input="event => updateStudentPoints(selectedPage!, event)"
+              ></textarea>
+            </div>
+            <div class="field-card">
+              <div class="field-title">课堂活动</div>
+              <input
+                :value="selectedPage.activity?.task || ''"
+                :disabled="working"
+                class="headline-input"
+                placeholder="课堂任务,例如:听辨山谷和小鸟的声音"
+                @input="event => updateActivityTask(selectedPage!, event)"
+              />
               <textarea
-                :value="selectedPage.bullets.join('\n')"
+                :value="(selectedPage.activity?.steps || []).join('\n')"
                 :disabled="working"
                 class="page-bullets"
-                @input="event => updateBullets(selectedPage!, event)"
+                placeholder="每行一步课堂操作,供生成活动页或备注使用"
+                @input="event => updateActivitySteps(selectedPage!, event)"
               ></textarea>
             </div>
-            <div class="field-card visual">
+          </div>
+
+          <div class="field-card visual full-field">
               <div class="field-title">设计要求</div>
               <textarea
                 v-model="selectedPage.designRequirement"
@@ -72,7 +99,6 @@
                 class="page-design"
                 placeholder="例如:左文右图,右侧放卡通山谷和音符,整体暖色活泼,文字保持大字号。"
               ></textarea>
-            </div>
           </div>
 
           <div class="keyword-card">
@@ -171,6 +197,11 @@ const selectedPage = computed(() => {
   const pages = outline.value?.pages || []
   return pages.find(page => page.pageNo === selectedPageNo.value) || pages[0]
 })
+const studentHeadline = computed(() => selectedPage.value?.studentContent?.headline || selectedPage.value?.subtitle || "")
+const studentPoints = computed(() => {
+  if (!selectedPage.value) return []
+  return selectedPage.value.studentContent?.points?.length ? selectedPage.value.studentContent.points : selectedPage.value.bullets || []
+})
 
 watch(
   () => props.visible,
@@ -269,8 +300,29 @@ async function applyResult(taskId: string) {
   emit("update:visible", false)
 }
 
-function updateBullets(page: AiCoursewarePage, event: Event) {
-  page.bullets = (event.target as HTMLTextAreaElement).value
+function updateStudentHeadline(page: AiCoursewarePage, event: Event) {
+  if (!page.studentContent) page.studentContent = { headline: "", points: [] }
+  page.studentContent.headline = (event.target as HTMLInputElement).value.trim()
+}
+
+function updateStudentPoints(page: AiCoursewarePage, event: Event) {
+  if (!page.studentContent) page.studentContent = { headline: "", points: [] }
+  const points = (event.target as HTMLTextAreaElement).value
+    .split("\n")
+    .map(item => item.trim())
+    .filter(Boolean)
+  page.studentContent.points = points
+  page.bullets = points
+}
+
+function updateActivityTask(page: AiCoursewarePage, event: Event) {
+  if (!page.activity) page.activity = { task: "", steps: [] }
+  page.activity.task = (event.target as HTMLInputElement).value.trim()
+}
+
+function updateActivitySteps(page: AiCoursewarePage, event: Event) {
+  if (!page.activity) page.activity = { task: "", steps: [] }
+  page.activity.steps = (event.target as HTMLTextAreaElement).value
     .split("\n")
     .map(item => item.trim())
     .filter(Boolean)
@@ -481,6 +533,7 @@ function shortProgressLabel(message?: string) {
   font-weight: 600;
 }
 .page-title,
+.headline-input,
 .page-bullets,
 .page-design,
 .keyword-input,
@@ -497,13 +550,25 @@ function shortProgressLabel(message?: string) {
   font-size: 16px;
   font-weight: 600;
 }
+.headline-input {
+  height: 36px;
+  padding: 0 10px;
+  margin-bottom: 10px;
+  font-size: 14px;
+}
 .page-bullets,
 .page-design {
-  height: 180px;
+  height: 150px;
   padding: 10px;
   resize: vertical;
   line-height: 1.5;
 }
+.full-field {
+  margin-top: 12px;
+}
+.page-design {
+  height: 90px;
+}
 .keyword-card {
   margin-top: 12px;
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов