yonge há 7 horas atrás
pai
commit
4d7936484a

+ 1 - 1
dist/assets/index-BojStd_o.js → dist/assets/index-2JQ7pu3X.js

@@ -1 +1 @@
-import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,en as t,ae as r,af as c,bO as _,bP as d,ak as s,bH as p}from"./index-Cl8r2IqW.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,bO as _,bP as d,ak as s,bH as p}from"./index-Qxv2V3to.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};

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
dist/assets/index-B_R5r3G0.js


+ 1 - 1
dist/assets/index-DAf27j6U.js → dist/assets/index-DJYV1XR6.js

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

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
dist/assets/index-DZ98mI-K.js


+ 1 - 1
dist/assets/index-CXImoIxQ.js → dist/assets/index-Dj15YXuC.js

@@ -1 +1 @@
-import{u as s,af as r}from"./index-DAf27j6U.js";import{a5 as c,cD as n,R as p,ae as t,af as _,al as u,ag as i,bH as f}from"./index-Cl8r2IqW.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}))]))}}),h=f(m,[["__scopeId","data-v-678905e9"]]);export{h as default};
+import{u as s,af as r}from"./index-DJYV1XR6.js";import{a5 as c,cD as n,R as p,ae as t,af as _,al as u,ag as i,bH as f}from"./index-Qxv2V3to.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}))]))}}),h=f(m,[["__scopeId","data-v-678905e9"]]);export{h as default};

Diff do ficheiro suprimidas por serem muito extensas
+ 1 - 1
dist/assets/index-Qxv2V3to.js


+ 1 - 1
dist/assets/index-Bb4im07Y.js → dist/assets/index-SW7j3WVK.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Czb4c4TQ.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,bO as m,bP as f,bH as u}from"./index-Cl8r2IqW.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})])]))}}),C=u(v,[["__scopeId","data-v-340bbdaa"]]);export{C as default};
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-DZ98mI-K.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,bO as m,bP as f,bH as u}from"./index-Qxv2V3to.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})])]))}}),C=u(v,[["__scopeId","data-v-340bbdaa"]]);export{C as default};

+ 1 - 1
dist/assets/index-QgeN-F-_.js → dist/assets/index-a1O_7-2N.js

@@ -1 +1 @@
-import{u as c,af as l}from"./index-DAf27j6U.js";import{w as p,bA as d,a5 as g,cD as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-Cl8r2IqW.js";import"./shapes-CgitaJ8K.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}))]))}}),P=k(_,[["__scopeId","data-v-90b2692a"]]);export{P as default};
+import{u as c,af as l}from"./index-DJYV1XR6.js";import{w as p,bA as d,a5 as g,cD as m,R as t,ae as r,af as u,al as f,ag as w,bH as k}from"./index-Qxv2V3to.js";import"./shapes-H9Hj85K_.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}))]))}}),P=k(_,[["__scopeId","data-v-90b2692a"]]);export{P as default};

+ 1 - 1
dist/assets/shapes-CgitaJ8K.js → dist/assets/shapes-H9Hj85K_.js

@@ -1,4 +1,4 @@
-import{cG as k,bB as $1}from"./index-Cl8r2IqW.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-DAf27j6U.js";var z1={exports:{}};/*!
+import{cG as k,bB as $1}from"./index-Qxv2V3to.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-DJYV1XR6.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-Cl8r2IqW.js"></script>
+    <script type="module" crossorigin src="./assets/index-Qxv2V3to.js"></script>
     <link rel="stylesheet" crossorigin href="./assets/index-AavUEaYy.css">
   </head>
   <body>

+ 80 - 0
src/config/aiCoursewareTemplateMasters.ts

@@ -0,0 +1,80 @@
+import type { AiCoursewareTemplateId } from "@/config/aiCoursewareTemplates"
+
+export interface TemplateMasterElement {
+  type: "shape" | "text"
+  left: number
+  top: number
+  width: number
+  height: number
+  fill?: string
+  color?: string
+  content?: string
+  fontSize?: number
+  opacity?: number
+  radius?: number
+  bold?: boolean
+}
+
+export interface TemplateMasterPage {
+  background: string
+  base: TemplateMasterElement[]
+  chrome: TemplateMasterElement[]
+}
+
+export interface TemplateMaster {
+  cover: TemplateMasterPage & {
+    label: string
+    titleColor: string
+    subtitleColor: string
+    imageFrame: { left: number; top: number; width: number; height: number; radius: number }
+  }
+  content: TemplateMasterPage
+}
+
+const shape = (left: number, top: number, width: number, height: number, fill: string, opacity = 1, radius = 0): TemplateMasterElement => ({ type: "shape", left, top, width, height, fill, opacity, radius })
+const text = (left: number, top: number, width: number, content: string, color: string): TemplateMasterElement => ({ type: "text", left, top, width, height: 30, content, fontSize: 14, color, bold: true })
+
+const master = (
+  cover: TemplateMaster["cover"],
+  content: TemplateMaster["content"]
+): TemplateMaster => ({ cover, content })
+
+const DEFAULT_MASTER: TemplateMaster = master(
+  { background: "#F7FBFA", label: "MUSIC CLASS", titleColor: "#20313A", subtitleColor: "#6B7E88", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 24 }, base: [], chrome: [] },
+  { background: "#F7FBFA", base: [], chrome: [text(82, 28, 260, "MUSIC CLASS", "#2878C8")] }
+)
+
+/**
+ * 新增模板时,只需在这里注册封面与内页母版,并在 aiCoursewareTemplates.ts 增加选择器信息和 PPTX 文件。
+ * base 位于内容下方,chrome 位于内容上方,避免全屏资源页跳过模板视觉。
+ */
+export const AI_COURSEWARE_TEMPLATE_MASTERS: Record<string, TemplateMaster> = {
+  "fairytale-forest": master(
+    { background: "#F7FBF8", label: "MUSIC FOREST", titleColor: "#24495C", subtitleColor: "#69808A", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 28 }, base: [shape(1150, -170, 470, 470, "#F7D66D", 0.85, 235), shape(-100, 710, 550, 290, "#32A07C", 0.74, 200), shape(1140, 630, 550, 320, "#3478C8", 0.16, 180)], chrome: [] },
+    { background: "#F7FBF8", base: [shape(1370, -90, 300, 300, "#F7D66D", 0.42, 150), shape(-80, 760, 300, 220, "#32A07C", 0.22, 150)], chrome: [text(82, 28, 240, "MUSIC FOREST", "#3478C8")] }
+  ),
+  "warm-handdrawn": master(
+    { background: "#F7FBFA", label: "MUSIC FOR TODAY", titleColor: "#284C52", subtitleColor: "#687C82", imageFrame: { left: 1060, top: 105, width: 430, height: 470, radius: 36 }, base: [shape(0, 0, 1600, 22, "#8FC6B6"), shape(1060, 105, 430, 470, "#FFFFFF", 1, 36), shape(1210, 38, 180, 180, "#F2CC8F", 0.55, 90)], chrome: [] },
+    { background: "#F7FBFA", base: [shape(0, 0, 1600, 16, "#8FC6B6", 0.9)], chrome: [shape(1405, 730, 230, 230, "#E4F1EE", 0.82, 115), text(82, 28, 280, "MUSIC FOR TODAY", "#347D73")] }
+  ),
+  "music-doodle": master(
+    { background: "#17233D", label: "MUSIC DOODLE", titleColor: "#FFFFFF", subtitleColor: "#D5E0F1", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 28 }, base: [shape(0, 0, 1600, 16, "#F6C945"), shape(1040, 135, 290, 290, "#F6C945", 1, 145), shape(1260, 390, 180, 180, "#80B9D7", 1, 90), { ...text(1150, 205, 110, "♪", "#17233D"), height: 90, fontSize: 76 }], chrome: [] },
+    { background: "#FFF8F2", base: [], chrome: [shape(0, 0, 1600, 84, "#17233D"), shape(0, 84, 1600, 8, "#F6C945"), { ...text(76, 26, 100, "♪", "#F6C945"), height: 38, fontSize: 28 }, { ...text(1420, 24, 100, "♫", "#80B9D7"), height: 38, fontSize: 28 }] }
+  ),
+  "ink-wash": master(
+    { background: "#F7F1E7", label: "东方音乐", titleColor: "#332A24", subtitleColor: "#746A5F", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 12 }, base: [shape(1110, 590, 580, 270, "#36594B", 0.16, 130), shape(1250, 665, 380, 190, "#9D2B22", 0.17, 95), shape(92, 142, 6, 560, "#9D2B22", 0.75, 2)], chrome: [] },
+    { background: "#F7F1E7", base: [shape(1250, 690, 420, 250, "#36594B", 0.12, 125)], chrome: [shape(80, 116, 5, 680, "#9D2B22", 0.45, 2), text(116, 79, 220, "东方音乐", "#9D2B22")] }
+  ),
+  "stage-documentary": master(
+    { background: "#1C2940", label: "MUSIC ON STAGE", titleColor: "#FFFFFF", subtitleColor: "#DDE6F4", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 0 }, base: [shape(1040, -170, 610, 610, "#D8B45B", 0.74, 305), shape(1180, 170, 430, 430, "#8AA1C2", 0.42, 215), shape(0, 830, 1600, 70, "#D8B45B", 0.72)], chrome: [] },
+    { background: "#F7F8FA", base: [], chrome: [shape(0, 0, 1600, 96, "#1C2940"), shape(0, 76, 1600, 20, "#D8B45B", 0.84), shape(1450, 0, 150, 18, "#E7C469"), text(82, 29, 260, "MUSIC ON STAGE", "#FFFFFF")] }
+  ),
+  "modern-geometry": master(
+    { background: "#F8FBFA", label: "MUSIC STRUCTURE", titleColor: "#20342F", subtitleColor: "#6B817A", imageFrame: { left: 935, top: 175, width: 490, height: 470, radius: 14 }, base: [shape(1120, 0, 480, 900, "#E7F3EF"), shape(1220, 160, 270, 120, "#4FAD93", 0.25, 14), shape(1345, 610, 180, 180, "#E07A35", 0.3, 16)], chrome: [] },
+    { background: "#F8FBFA", base: [shape(1325, 0, 275, 900, "#E7F3EF", 0.78)], chrome: [shape(1415, 88, 145, 68, "#4FAD93", 0.24, 8), shape(1470, 690, 110, 110, "#E07A35", 0.26, 10), shape(80, 118, 1160, 2, "#287B72", 0.18), text(82, 28, 280, "MUSIC STRUCTURE", "#287B72")] }
+  )
+}
+
+export function getAiCoursewareTemplateMaster(templateId?: AiCoursewareTemplateId): TemplateMaster {
+  return AI_COURSEWARE_TEMPLATE_MASTERS[templateId || ""] || DEFAULT_MASTER
+}

+ 1 - 7
src/config/aiCoursewareTemplates.ts

@@ -1,12 +1,6 @@
 import type { AiCoursewareOutline } from "@/api/aiCourseware"
 
-export type AiCoursewareTemplateId =
-  | "fairytale-forest"
-  | "warm-handdrawn"
-  | "music-doodle"
-  | "ink-wash"
-  | "stage-documentary"
-  | "modern-geometry"
+export type AiCoursewareTemplateId = string
 
 export type AiCoursewareTemplatePageRole =
   | "cover"

+ 53 - 5
src/utils/aiPptTemplateEngine.ts

@@ -23,6 +23,7 @@ import {
   type AiCoursewareTemplatePageRole,
   type AiCoursewareThemePalette
 } from "@/config/aiCoursewareTemplates"
+import { getAiCoursewareTemplateMaster, type TemplateMasterElement, type TemplateMasterPage } from "@/config/aiCoursewareTemplateMasters"
 
 interface AiPptOptions {
   backgroundImage?: string
@@ -92,17 +93,23 @@ export function buildAiPptSlides(
 
 function buildSlide(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette, options: AiPptOptions, index: number): Slide {
   const role = pageRole(page, index)
+  const master = getAiCoursewareTemplateMaster(options.templateId)
+  const masterPage = role === "cover" ? master.cover : master.content
   const dedicatedResource = dedicatedResourceElement(page)
   const contentElements = dedicatedResource
     ? [dedicatedResource]
     : videoBinding(page)
       ? videoResourceTemplate(page, outline, palette)
       : [...templateForPage(page, role, options.templateId)(page, outline, palette, index), ...inlineResourceElements(page)]
-  const elements = dedicatedResource ? contentElements : [...templateDecorationElements(options.templateId, palette, role), ...contentElements]
+  const elements = [
+    ...renderTemplateMasterElements(masterPage.base),
+    ...contentElements,
+    ...renderTemplateMasterElements(masterPage.chrome)
+  ]
   return {
     id: nanoid(10),
     elements: scaleElementsToViewport(keepInCanvas(elements)),
-    background: backgroundConfig(role, palette, options),
+    background: backgroundConfig(masterPage, options),
     remark: slideRemark(page),
     animations: options.enableAnimations ? entranceAnimations(contentElements) : undefined,
     turningMode: options.enableAnimations ? "fade" : "no"
@@ -155,6 +162,31 @@ function templateDecorationElements(templateId: AiCoursewareTemplateId | undefin
   }
 }
 
+function renderTemplateMasterElements(elements: TemplateMasterElement[]): PPTElement[] {
+  return elements.map(element =>
+    element.type === "text"
+      ? textElement({
+          left: element.left,
+          top: element.top,
+          width: element.width,
+          height: element.height,
+          content: element.content || "",
+          fontSize: element.fontSize || 14,
+          color: element.color || "#20313A",
+          bold: element.bold
+        })
+      : shapeElement({
+          left: element.left,
+          top: element.top,
+          width: element.width,
+          height: element.height,
+          fill: element.fill || "#FFFFFF",
+          opacity: element.opacity,
+          radius: element.radius
+        })
+  )
+}
+
 function entranceAnimations(elements: PPTElement[]): PPTAnimation[] {
   return elements
     .filter(
@@ -173,7 +205,7 @@ function entranceAnimations(elements: PPTElement[]): PPTAnimation[] {
 
 function templateForPage(page: AiCoursewarePage, role: PageRole, templateId?: AiCoursewareTemplateId) {
   if (role === "cover" && templateId) {
-    return (currentPage: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette) => templateCover(currentPage, outline, palette, templateId)
+    return (currentPage: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette) => templateCoverFromMaster(currentPage, outline, palette, templateId)
   }
   const requestedLayout = cleanText(page.visualPlan?.layout || page.visual?.layout).toLowerCase()
   const layout = resolveTemplateLayout(requestedLayout, role, templateId)
@@ -1372,6 +1404,22 @@ function templateCover(page: AiCoursewarePage, outline: AiCoursewareOutline, pal
   return elements
 }
 
+function templateCoverFromMaster(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette, templateId: AiCoursewareTemplateId): PPTElement[] {
+  const master = getAiCoursewareTemplateMaster(templateId).cover
+  const title = chapterTitle(outline, page)
+  const subtitle = [outline.gradeName, conciseTextbook(outline.textbookName), outline.unitName].filter(Boolean).join(" · ")
+  const points = studentPoints(page, 2)
+  const elements: PPTElement[] = []
+
+  if (hasImage(page)) elements.push(imageElement(page, { ...master.imageFrame }))
+  elements.push(textElement({ left: 122, top: 108, width: 420, height: 32, content: master.label, fontSize: 15, color: palette.primary, bold: true }))
+  elements.push(textElement({ left: 120, top: 195, width: 710, height: 170, content: title, fontSize: fitFont(title, 710, 170, 70, 48), color: master.titleColor, bold: true }))
+  if (subtitle) elements.push(textElement({ left: 124, top: 405, width: 650, height: 46, content: subtitle, fontSize: 24, color: master.subtitleColor }))
+  elements.push(shapeElement({ left: 124, top: 492, width: 120, height: 10, fill: palette.accent, radius: 5 }))
+  points.forEach((point, index) => elements.push(textElement({ left: 124, top: 552 + index * 62, width: 650, height: 44, content: point, fontSize: fitFont(point, 650, 44, 29, 22), color: master.titleColor, bold: index === 0 })))
+  return elements
+}
+
 function resolveTemplateLayout(requestedLayout: string, role: PageRole, templateId?: AiCoursewareTemplateId) {
   // 乐谱、歌词、节奏和乐器页面具有内容专属性,不能被模板包的通用版式覆盖。
   const contentSpecificLayouts = new Set(["lyric_focus", "score_focus", "rhythm_grid", "instrument_focus", "homework_list"])
@@ -1534,9 +1582,9 @@ function buildPalette(outline: AiCoursewareOutline, variant: number, templateId?
   return { ...getAiCoursewarePalette(templateId || fallbackTemplate, variant) }
 }
 
-function backgroundConfig(_role: PageRole, palette: Palette, options: AiPptOptions): SlideBackground {
+function backgroundConfig(masterPage: TemplateMasterPage, options: AiPptOptions): SlideBackground {
   if (options.backgroundImage) return { type: "image", image: options.backgroundImage, imageSize: "cover" }
-  return { type: "solid", color: palette.background }
+  return { type: "solid", color: masterPage.background }
 }
 
 function fitFont(content: string, width: number, height: number, preferred: number, minimum: number) {

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff