yonge 10 часов назад
Родитель
Сommit
a9aaefc91f

BIN
dist/ai-courseware-templates/fairytale-forest.pptx


BIN
dist/ai-courseware-templates/ink-wash.pptx


BIN
dist/ai-courseware-templates/modern-geometry.pptx


BIN
dist/ai-courseware-templates/music-doodle.pptx


BIN
dist/ai-courseware-templates/stage-documentary.pptx


BIN
dist/ai-courseware-templates/warm-handdrawn.pptx


+ 1 - 1
dist/assets/index-CX_sSYUQ.js → dist/assets/index-3u7FQsBF.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-DFzcmnzu.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-RU3f-24x.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};

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


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


+ 1 - 1
dist/assets/index-DuxVnsxW.js → dist/assets/index-D9LdPdRD.js

@@ -1 +1 @@
-import{u as s,af as r}from"./index-z-hsNSCS.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-DFzcmnzu.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-jcQEKldN.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-RU3f-24x.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};

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


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


+ 1 - 1
dist/assets/index-D3jfJ_kE.js → dist/assets/index-cOqpW_Yu.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Bikyjl5G.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-DFzcmnzu.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-Dx5yLpXe.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-RU3f-24x.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-z-hsNSCS.js → dist/assets/index-jcQEKldN.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-DFzcmnzu.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-RU3f-24x.js";/**
 * vue v3.4.34
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT

+ 1 - 1
dist/assets/index-C6rSfLk-.js → dist/assets/index-lDM_MoI3.js

@@ -1 +1 @@
-import{u as c,af as l}from"./index-z-hsNSCS.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-DFzcmnzu.js";import"./shapes-DveQjeNx.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-jcQEKldN.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-RU3f-24x.js";import"./shapes-BHz5E06k.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-DveQjeNx.js → dist/assets/shapes-BHz5E06k.js

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

BIN
public/ai-courseware-templates/fairytale-forest.pptx


BIN
public/ai-courseware-templates/ink-wash.pptx


BIN
public/ai-courseware-templates/modern-geometry.pptx


BIN
public/ai-courseware-templates/music-doodle.pptx


BIN
public/ai-courseware-templates/stage-documentary.pptx


BIN
public/ai-courseware-templates/warm-handdrawn.pptx


+ 172 - 0
scripts/sanitize_music_templates.py

@@ -0,0 +1,172 @@
+from __future__ import annotations
+
+import os
+import sys
+import time
+import zipfile
+import xml.etree.ElementTree as ET
+from pathlib import Path
+
+from pptx import Presentation
+from pptx.enum.shapes import MSO_SHAPE_TYPE
+
+
+TARGET_DIR = Path("public/ai-courseware-templates")
+
+TEMPLATES = [
+    (
+        "fairytale-forest.pptx",
+        Path(r"H:\音乐教材PPT\人教版(2024)\1年级下册(新)\【2025新教材】人教版音乐一年级下册-《动画音乐会》-课件.pptx"),
+    ),
+    ("warm-handdrawn.pptx", Path(r"H:\音乐教材PPT\人音版(2024)\3年级上册(新)\音乐小屋.pptx")),
+    (
+        "music-doodle.pptx",
+        Path(r"H:\音乐教材PPT\人音版(2024)\7年级上册(新)\课件《溜冰圆舞曲》新版人音版七年级上册第二单元.pptx"),
+    ),
+    ("ink-wash.pptx", Path(r"H:\音乐教材PPT\人音版(2024)\8年级上册(新)\《姑苏行》《十面埋伏》.pptx")),
+    ("stage-documentary.pptx", Path(r"H:\音乐教材PPT\人音版(2024)\8年级上册(新)\《鼓乐》.pptx")),
+    (
+        "modern-geometry.pptx",
+        Path(r"H:\音乐教材PPT\人教版(2024)\8年级上册(新)\学习项目二 探索旋律结构的规律.pptx"),
+    ),
+]
+
+
+def select_slide_indexes(total: int) -> set[int]:
+    """保留正文版式,避开通常包含版权页的首尾页。"""
+    if total <= 4:
+        return set(range(total))
+    candidates = [1, round(total * 0.35), round(total * 0.6), total - 2]
+    return {min(total - 1, max(0, index)) for index in candidates}
+
+
+def delete_shape(shape) -> None:
+    element = shape._element
+    element.getparent().remove(element)
+
+
+def clear_shape_text(shape, ordinal: list[int]) -> None:
+    if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
+        for child in list(shape.shapes):
+            clear_shape_text(child, ordinal)
+        return
+    if shape.shape_type in {MSO_SHAPE_TYPE.MEDIA, MSO_SHAPE_TYPE.WEB_VIDEO}:
+        delete_shape(shape)
+        return
+    if not getattr(shape, "has_text_frame", False):
+        return
+
+    text_frame = shape.text_frame
+    if not text_frame.text.strip():
+        return
+    text_frame.clear()
+    placeholder = "章节标题" if ordinal[0] == 0 else "在此填写教学内容"
+    paragraph = text_frame.paragraphs[0]
+    paragraph.text = placeholder
+    ordinal[0] += 1
+
+
+def remove_media_relationships(slide) -> None:
+    for relation_id, relation in list(slide.part.rels.items()):
+        relation_type = relation.reltype.lower()
+        if any(token in relation_type for token in ("audio", "video", "media")):
+            slide.part.drop_rel(relation_id)
+
+
+def remove_notes_parts(path: Path) -> None:
+    """删除备注、课件标题等不可见信息,避免原课程信息随模板保留。"""
+    temp_path = path.with_suffix(".cleaning.pptx")
+    with zipfile.ZipFile(path, "r") as source, zipfile.ZipFile(temp_path, "w", zipfile.ZIP_DEFLATED) as target:
+        for entry in source.infolist():
+            name = entry.filename
+            if name.startswith(("ppt/notesSlides/", "ppt/notesMasters/")) or name == "docProps/app.xml":
+                continue
+            content = source.read(entry)
+            if name.endswith(".rels"):
+                root = ET.fromstring(content)
+                for relation in list(root):
+                    relation_type = relation.attrib.get("Type", "")
+                    if relation_type.endswith(("/notesSlide", "/notesMaster", "/extended-properties")):
+                        root.remove(relation)
+                content = ET.tostring(root, encoding="utf-8", xml_declaration=True)
+            elif name == "[Content_Types].xml":
+                root = ET.fromstring(content)
+                for override in list(root):
+                    part_name = override.attrib.get("PartName", "")
+                    if "notesSlides" in part_name or "notesMasters" in part_name or part_name == "/docProps/app.xml":
+                        root.remove(override)
+                content = ET.tostring(root, encoding="utf-8", xml_declaration=True)
+            elif name.startswith("ppt/slides/slide") and name.endswith(".xml"):
+                root = ET.fromstring(content)
+                presentation_ns = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
+                drawing_ns = "{http://schemas.openxmlformats.org/drawingml/2006/main}"
+                media_shape_tags = {
+                    f"{drawing_ns}audioFile",
+                    f"{drawing_ns}videoFile",
+                    "{http://schemas.microsoft.com/office/powerpoint/2010/main}media"
+                }
+                for parent in root.iter():
+                    for child in list(parent):
+                        if child.tag != f"{presentation_ns}pic":
+                            continue
+                        if any(node.tag in media_shape_tags for node in child.iter()):
+                            parent.remove(child)
+                for text_node in root.iter("{http://schemas.openxmlformats.org/drawingml/2006/main}t"):
+                    if text_node.text and text_node.text.strip():
+                        text_node.text = "在此填写教学内容"
+                for properties in root.iter(f"{presentation_ns}cNvPr"):
+                    properties.attrib.pop("descr", None)
+                    properties.attrib.pop("title", None)
+                content = ET.tostring(root, encoding="utf-8", xml_declaration=True)
+            target.writestr(entry, content)
+    for attempt in range(5):
+        try:
+            os.replace(temp_path, path)
+            return
+        except PermissionError:
+            if attempt == 4:
+                raise
+            time.sleep(1)
+
+
+def retain_selected_slides(presentation: Presentation, keep_indexes: set[int]) -> None:
+    for index in reversed(range(len(presentation.slides))):
+        if index in keep_indexes:
+            continue
+        relation_id = presentation.slides._sldIdLst[index].rId
+        presentation.part.drop_rel(relation_id)
+        del presentation.slides._sldIdLst[index]
+
+
+def sanitize(source: Path, output: Path) -> None:
+    presentation = Presentation(source)
+    retain_selected_slides(presentation, select_slide_indexes(len(presentation.slides)))
+    for slide in presentation.slides:
+        ordinal = [0]
+        for shape in list(slide.shapes):
+            clear_shape_text(shape, ordinal)
+        remove_media_relationships(slide)
+    presentation.core_properties.author = "系统模板"
+    presentation.core_properties.last_modified_by = "系统模板"
+    presentation.core_properties.subject = "音乐课件模板"
+    presentation.core_properties.keywords = ""
+    presentation.core_properties.comments = ""
+    presentation.save(output)
+    remove_notes_parts(output)
+
+
+def main() -> None:
+    TARGET_DIR.mkdir(parents=True, exist_ok=True)
+    if "--clean-output" in sys.argv:
+        for output in sorted(TARGET_DIR.glob("*.pptx")):
+            remove_notes_parts(output)
+            print(f"{output.name}\t{output.stat().st_size / 1024 / 1024:.1f} MB")
+        return
+    for filename, source in TEMPLATES:
+        output = TARGET_DIR / filename
+        sanitize(source, output)
+        print(f"{filename}\t{output.stat().st_size / 1024 / 1024:.1f} MB")
+
+
+if __name__ == "__main__":
+    main()

+ 14 - 0
src/config/aiCoursewareTemplates.ts

@@ -41,6 +41,8 @@ export interface AiCoursewareTemplate {
   sourceName: string
   scene: string
   description: string
+  templateFile: string
+  templateSlides: number
   baseStyle: "cartoon" | "clean"
   layoutFamily: "story" | "education" | "academic" | "culture" | "technology" | "activity"
   typography: string
@@ -94,6 +96,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《动画音乐会》的童话场景语言",
     scene: "低年级歌曲与律动",
     description: "绘本场景、故事导入和童趣互动",
+    templateFile: "/ai-courseware-templates/fairytale-forest.pptx",
+    templateSlides: 4,
     baseStyle: "cartoon",
     layoutFamily: "story",
     typography: "圆润醒目的低龄课堂字体层级,标题短而大,正文不低于30px",
@@ -120,6 +124,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《音乐小屋》的温暖手绘语言",
     scene: "小学音乐活动",
     description: "轻柔插画、情境聆听和课堂练习",
+    templateFile: "/ai-courseware-templates/warm-handdrawn.pptx",
+    templateSlides: 4,
     baseStyle: "clean",
     layoutFamily: "education",
     typography: "清晰现代的教学字体层级,标题明确,正文适合远距离投屏",
@@ -146,6 +152,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《鼓乐》的写实音乐现场氛围",
     scene: "世界音乐与演出欣赏",
     description: "大图舞台、演出现场和音乐文化体验",
+    templateFile: "/ai-courseware-templates/stage-documentary.pptx",
+    templateSlides: 4,
     baseStyle: "clean",
     layoutFamily: "academic",
     typography: "具有演出节目单感的标题、正文和注释三级层级",
@@ -171,6 +179,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《姑苏行》《十面埋伏》的东方意境",
     scene: "传统文化主题",
     description: "民歌、戏曲、民族音乐和传统节日",
+    templateFile: "/ai-courseware-templates/ink-wash.pptx",
+    templateSlides: 4,
     baseStyle: "clean",
     layoutFamily: "culture",
     typography: "现代中文标题与舒展正文层级",
@@ -196,6 +206,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《探索旋律结构的规律》的现代几何版式",
     scene: "项目学习与乐理分析",
     description: "结构讲解、旋律分析和知识项目展示",
+    templateFile: "/ai-courseware-templates/modern-geometry.pptx",
+    templateSlides: 4,
     baseStyle: "clean",
     layoutFamily: "technology",
     typography: "高对比现代标题与简洁正文层级",
@@ -221,6 +233,8 @@ export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
     sourceName: "参考《溜冰圆舞曲》的音符涂鸦语言",
     scene: "乐理、节奏与欣赏",
     description: "音符插画、节奏任务和轻松知识讲解",
+    templateFile: "/ai-courseware-templates/music-doodle.pptx",
+    templateSlides: 4,
     baseStyle: "cartoon",
     layoutFamily: "activity",
     typography: "大字号动作指令与清晰编号层级",

+ 2 - 2
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -94,7 +94,7 @@
                 <em v-if="outline && templateRecommendation.id === template.id">推荐</em>
               </span>
               <span>{{ template.description }}</span>
-              <small>{{ template.scene }}</small>
+              <small>{{ template.scene }} · {{ template.templateSlides }}页母版</small>
             </button>
           </div>
         </div>
@@ -233,7 +233,7 @@
                 </span>
                 <span
                   ><strong>{{ template.name }}</strong
-                  ><small>{{ template.scene }}</small></span
+                  ><small>{{ template.scene }} · {{ template.templateSlides }}页母版</small></span
                 >
               </button>
             </div>

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