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

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


+ 1 - 1
dist/assets/index-Buyn7FlE.js → dist/assets/index-BeKROJiY.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-C9woczL1.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-BZP7uwjz.js";/**
 * vue v3.4.34
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT

+ 1 - 1
dist/assets/index-DKlXn9Ut.js → dist/assets/index-BhRZlZ0y.js

@@ -1 +1 @@
-import{u as s,af as r}from"./index-Buyn7FlE.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-C9woczL1.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-BeKROJiY.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-BZP7uwjz.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};

+ 1 - 1
dist/assets/index-XNucre4b.js → dist/assets/index-ChhapN08.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-C9woczL1.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-BZP7uwjz.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-Ctm9Yv6G.css


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


+ 1 - 1
dist/assets/index-BxpAusfI.js → dist/assets/index-CvSP0MM_.js

@@ -1 +1 @@
-import{u as c,af as l}from"./index-Buyn7FlE.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-C9woczL1.js";import"./shapes-73wbLX8W.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-BeKROJiY.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-BZP7uwjz.js";import"./shapes-DiLDK5TY.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};

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


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


+ 1 - 1
dist/assets/index-Ca0rgJ1b.js → dist/assets/index-hdCrI7uE.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-HnNlB0lC.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-C9woczL1.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-Dara6Nad.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-BZP7uwjz.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};

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


+ 1 - 1
dist/assets/shapes-73wbLX8W.js → dist/assets/shapes-DiLDK5TY.js

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

+ 186 - 0
src/config/aiCoursewareTemplates.ts

@@ -0,0 +1,186 @@
+import type { AiCoursewareOutline } from "@/api/aiCourseware"
+
+export type AiCoursewareTemplateId =
+  | "playful-handdrawn"
+  | "fresh-education"
+  | "academic-blue"
+  | "oriental-culture"
+  | "digital-tech"
+  | "active-classroom"
+
+export interface AiCoursewareThemePalette {
+  primary: string
+  secondary: string
+  accent: string
+  warm: string
+  background: string
+  surface: string
+  soft: string
+  text: string
+  muted: string
+  dark: string
+}
+
+export interface AiCoursewareTemplate {
+  id: AiCoursewareTemplateId
+  name: string
+  sourceName: string
+  scene: string
+  description: string
+  baseStyle: "cartoon" | "clean"
+  visualPrompt: string
+  palettes: [AiCoursewareThemePalette, AiCoursewareThemePalette, AiCoursewareThemePalette]
+}
+
+const playfulPalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#E64A78", "#168F86", "#F28C28", "#FFD66B", "#FFF9F5", "#EAF7F4", "#263238", "#6B7C8F", "#283B4A"),
+  palette("#3478C8", "#32A07C", "#F08A3C", "#F7D66D", "#F7FBF8", "#E6F5EE", "#24333B", "#69808A", "#24495C"),
+  palette("#7A5AC8", "#20A39E", "#EE6C78", "#FFD166", "#FBF8FF", "#F0EAFB", "#312D3D", "#756E84", "#3C3154")
+]
+
+const freshPalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#2878C8", "#23967F", "#F19A3E", "#F6D365", "#F5FAFC", "#E5F4F1", "#20313A", "#6B7E88", "#244B5A"),
+  palette("#347D73", "#4B8BBE", "#E07A5F", "#F2CC8F", "#F7FBFA", "#E4F1EE", "#21343A", "#687C82", "#284C52"),
+  palette("#526FA8", "#5C9EAD", "#E98B73", "#F3C677", "#F7F9FC", "#E8EEF7", "#243040", "#6E7887", "#2C405F")
+]
+
+const academicPalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#1E4F86", "#2F6F66", "#C77D22", "#D8B45B", "#F4F7FA", "#E6EDF4", "#172534", "#647383", "#102A43"),
+  palette("#243B64", "#356A72", "#B56B45", "#D1AF68", "#F5F6F8", "#E8EBF0", "#1D2733", "#69727E", "#17243A"),
+  palette("#3E4C78", "#39736B", "#A75D72", "#D2B978", "#F6F6F9", "#EAEBF2", "#242636", "#707181", "#252B49")
+]
+
+const orientalPalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#9D2B22", "#36594B", "#C58A35", "#D9B66F", "#F7F1E7", "#EFE5D4", "#332A24", "#746A5F", "#4A2722"),
+  palette("#7D3530", "#446052", "#B07942", "#D6BC7C", "#F5F0E8", "#E9E1D4", "#302C28", "#706A62", "#3E302B"),
+  palette("#6E3F63", "#496554", "#B4864C", "#DCC68C", "#F8F3EA", "#EEE5D8", "#342D33", "#766D74", "#45333F")
+]
+
+const techPalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#2563EB", "#06B6D4", "#8B5CF6", "#FBBF24", "#071426", "#102744", "#EAF6FF", "#91AFC9", "#040B17", "#102744"),
+  palette("#3B82F6", "#14B8A6", "#EC4899", "#F59E0B", "#081A24", "#12333B", "#ECFEFF", "#91B5B9", "#041013", "#12333B"),
+  palette("#6366F1", "#22D3EE", "#A855F7", "#FACC15", "#0E1024", "#1C2142", "#F0F2FF", "#A1A7D0", "#080914", "#1C2142")
+]
+
+const activePalettes: AiCoursewareTemplate["palettes"] = [
+  palette("#F05A47", "#13A89E", "#6C63D9", "#F6C945", "#FFF8F2", "#FDEBDD", "#2D2B36", "#756F7C", "#3A3550"),
+  palette("#EC4899", "#0EA5A4", "#F97316", "#FACC15", "#FFF7FB", "#FCE7F3", "#302937", "#7A6D7D", "#46354D"),
+  palette("#7C5CE7", "#18A999", "#F06449", "#FFD166", "#FAF8FF", "#ECE8FB", "#302D3C", "#746E82", "#403755")
+]
+
+export const AI_COURSEWARE_TEMPLATES: AiCoursewareTemplate[] = [
+  {
+    id: "playful-handdrawn",
+    name: "趣味手绘课堂",
+    sourceName: "手绘风通用模板",
+    scene: "小学低年级",
+    description: "故事导入、情境欣赏和童趣互动",
+    baseStyle: "cartoon",
+    visualPrompt: "趣味手绘课堂风格,画面亲切活泼,使用统一插画语言和明确场景,避免商务卡片堆叠。",
+    palettes: playfulPalettes
+  },
+  {
+    id: "fresh-education",
+    name: "清新教育课件",
+    sourceName: "教育课件PPT模板",
+    scene: "常规音乐课",
+    description: "知识讲解、听赏练习和课堂总结",
+    baseStyle: "clean",
+    visualPrompt: "清新教育课件风格,留白充足、结构清楚、色彩温和,兼顾教师讲解和学生远距离阅读。",
+    palettes: freshPalettes
+  },
+  {
+    id: "academic-blue",
+    name: "蓝色学术课堂",
+    sourceName: "严肃蓝色学术模板",
+    scene: "初高中课堂",
+    description: "乐理、作品结构和知识密集型内容",
+    baseStyle: "clean",
+    visualPrompt: "蓝色学术课堂风格,严谨克制、层级明确,强化标题、数据和谱例焦点,减少无关装饰。",
+    palettes: academicPalettes
+  },
+  {
+    id: "oriental-culture",
+    name: "东方文化课堂",
+    sourceName: "中国风传统文化通用PPT模板",
+    scene: "传统文化主题",
+    description: "民歌、戏曲、民族音乐和传统节日",
+    baseStyle: "clean",
+    visualPrompt: "现代东方文化课堂风格,使用米白、黛色和朱砂色,装饰克制,突出传统文化意境与课堂可读性。",
+    palettes: orientalPalettes
+  },
+  {
+    id: "digital-tech",
+    name: "数字科技课堂",
+    sourceName: "互联网科技风模板",
+    scene: "音乐科技主题",
+    description: "电子音乐、数字工具和声音科技",
+    baseStyle: "clean",
+    visualPrompt: "数字科技课堂风格,深色背景配高对比蓝青光感,使用克制的网格和节点元素,保证教学文字清晰。",
+    palettes: techPalettes
+  },
+  {
+    id: "active-classroom",
+    name: "活力互动课堂",
+    sourceName: "活动型模板",
+    scene: "课堂活动",
+    description: "分组练习、节奏游戏、表演与展示",
+    baseStyle: "cartoon",
+    visualPrompt: "活力互动课堂风格,节奏感强、色彩明快,用清晰编号和动作路径组织任务,避免信息拥挤。",
+    palettes: activePalettes
+  }
+]
+
+export function getAiCoursewareTemplate(id?: string) {
+  return AI_COURSEWARE_TEMPLATES.find(template => template.id === id) || AI_COURSEWARE_TEMPLATES[1]
+}
+
+export function getAiCoursewarePalette(id: string | undefined, variant = 0) {
+  const template = getAiCoursewareTemplate(id)
+  return template.palettes[Math.abs(variant) % template.palettes.length]
+}
+
+export function recommendAiCoursewareTemplate(outline: AiCoursewareOutline): { id: AiCoursewareTemplateId; reason: string } {
+  const text = [
+    outline.title,
+    outline.gradeName,
+    outline.unitName,
+    outline.chapterName,
+    outline.courseType,
+    ...(outline.pages || []).flatMap(page => [page.title, page.teachingStage, page.teachingPurpose])
+  ]
+    .filter(Boolean)
+    .join(" ")
+
+  if (/民歌|民族|戏曲|京剧|昆曲|古琴|国乐|传统|节日|非遗|中国风/.test(text)) {
+    return { id: "oriental-culture", reason: "课程包含传统文化或民族音乐内容" }
+  }
+  if (/电子音乐|数字音乐|音乐科技|人工智能|AI|编曲软件|录音|声学|科技/.test(text)) {
+    return { id: "digital-tech", reason: "课程包含数字音乐或声音科技内容" }
+  }
+  if (/分组|小组|节奏游戏|律动|表演|展示|闯关|任务|活动/.test(text)) {
+    return { id: "active-classroom", reason: "课程以课堂活动和学生参与为主" }
+  }
+  if (/[初高]中|七年级|八年级|九年级|乐理|曲式|和声|结构|分析|鉴赏/.test(text)) {
+    return { id: "academic-blue", reason: "课程年级或内容需要更严谨的知识呈现" }
+  }
+  if (/一年级|二年级|三年级|低年级|故事|童话|动物|情境/.test(text)) {
+    return { id: "playful-handdrawn", reason: "课程面向低龄学生或采用故事化导入" }
+  }
+  return { id: "fresh-education", reason: "适合常规音乐课堂的讲解与练习结构" }
+}
+
+function palette(
+  primary: string,
+  secondary: string,
+  accent: string,
+  warm: string,
+  background: string,
+  soft: string,
+  text: string,
+  muted: string,
+  dark: string,
+  surface = "#FFFFFF"
+): AiCoursewareThemePalette {
+  return { primary, secondary, accent, warm, background, surface, soft, text, muted, dark }
+}

+ 2 - 0
src/utils/aiCoursewareToSlides.ts

@@ -1,11 +1,13 @@
 import type { Slide, SlideTheme } from "@/types/slides"
 import type { AiCoursewareOutline } from "@/api/aiCourseware"
+import type { AiCoursewareTemplateId } from "@/config/aiCoursewareTemplates"
 import { buildAiPptSlides } from "@/utils/aiPptTemplateEngine"
 
 interface AiCoursewareVisualOptions {
   backgroundImage?: string
   enableAnimations?: boolean
   paletteVariant?: number
+  templateId?: AiCoursewareTemplateId
 }
 
 export function aiCoursewareToSlides(

+ 53 - 94
src/utils/aiPptTemplateEngine.ts

@@ -16,11 +16,13 @@ import type {
   SlideTheme
 } from "@/types/slides"
 import type { AiCoursewareOutline, AiCoursewarePage } from "@/api/aiCourseware"
+import { getAiCoursewarePalette, type AiCoursewareTemplateId, type AiCoursewareThemePalette } from "@/config/aiCoursewareTemplates"
 
 interface AiPptOptions {
   backgroundImage?: string
   enableAnimations?: boolean
   paletteVariant?: number
+  templateId?: AiCoursewareTemplateId
 }
 
 type PageRole =
@@ -43,18 +45,7 @@ type PageRole =
   | "homework"
   | "content"
 
-interface Palette {
-  primary: string
-  secondary: string
-  accent: string
-  warm: string
-  background: string
-  surface: string
-  soft: string
-  text: string
-  muted: string
-  dark: string
-}
+type Palette = AiCoursewareThemePalette
 
 interface ImageRect {
   left: number
@@ -77,7 +68,7 @@ export function buildAiPptSlides(
   options: AiPptOptions = {}
 ): { title: string; theme: Partial<SlideTheme>; slides: Slide[] } {
   const normalized = normalizeOutline(outline)
-  const palette = buildPalette(normalized, options.paletteVariant || 0)
+  const palette = buildPalette(normalized, options.paletteVariant || 0, options.templateId)
   const slides = normalized.pages.map((page, index) => buildSlide(page, normalized, palette, options, index))
   runPreflight(slides)
   return {
@@ -96,21 +87,65 @@ export function buildAiPptSlides(
 function buildSlide(page: AiCoursewarePage, outline: AiCoursewareOutline, palette: Palette, options: AiPptOptions, index: number): Slide {
   const role = pageRole(page, index)
   const dedicatedResource = dedicatedResourceElement(page)
-  const elements = dedicatedResource
+  const contentElements = dedicatedResource
     ? [dedicatedResource]
     : videoBinding(page)
       ? videoResourceTemplate(page, outline, palette)
       : [...templateForPage(page, role)(page, outline, palette, index), ...inlineResourceElements(page)]
+  const elements = dedicatedResource ? contentElements : [...templateDecorationElements(options.templateId, palette, role), ...contentElements]
   return {
     id: nanoid(10),
     elements: scaleElementsToViewport(keepInCanvas(elements)),
     background: backgroundConfig(role, palette, options),
     remark: slideRemark(page),
-    animations: options.enableAnimations ? entranceAnimations(elements) : undefined,
+    animations: options.enableAnimations ? entranceAnimations(contentElements) : undefined,
     turningMode: options.enableAnimations ? "fade" : "no"
   }
 }
 
+function templateDecorationElements(templateId: AiCoursewareTemplateId | undefined, palette: Palette, role: PageRole): PPTElement[] {
+  if (!templateId || role === "cover") return []
+  switch (templateId) {
+    case "playful-handdrawn":
+      return [
+        shapeElement({ left: 1450, top: 36, width: 74, height: 74, fill: palette.warm, opacity: 0.5, radius: 37 }),
+        shapeElement({ left: 1518, top: 92, width: 42, height: 42, fill: palette.accent, opacity: 0.45, radius: 21 }),
+        shapeElement({ left: 26, top: 792, width: 58, height: 58, fill: palette.secondary, opacity: 0.26, radius: 29 })
+      ]
+    case "fresh-education":
+      return [
+        shapeElement({ left: 0, top: 0, width: 1600, height: 12, fill: palette.secondary, opacity: 0.85, radius: 0 }),
+        shapeElement({ left: 1530, top: 742, width: 70, height: 158, fill: palette.soft, opacity: 0.8, radius: 0 })
+      ]
+    case "academic-blue":
+      return [
+        shapeElement({ left: 0, top: 0, width: 1600, height: 18, fill: palette.primary, radius: 0 }),
+        shapeElement({ left: 1548, top: 38, width: 18, height: 132, fill: palette.accent, radius: 0 }),
+        shapeElement({ left: 1574, top: 38, width: 8, height: 84, fill: palette.secondary, radius: 0 })
+      ]
+    case "oriental-culture":
+      return [
+        shapeElement({ left: 1460, top: 42, width: 88, height: 88, fill: palette.primary, opacity: 0.14, radius: 44 }),
+        shapeElement({ left: 1499, top: 52, width: 10, height: 68, fill: palette.primary, opacity: 0.62, radius: 2 }),
+        shapeElement({ left: 1434, top: 148, width: 114, height: 4, fill: palette.accent, opacity: 0.55, radius: 2 })
+      ]
+    case "digital-tech":
+      return [
+        shapeElement({ left: 80, top: 120, width: 1440, height: 2, fill: palette.secondary, opacity: 0.16, radius: 0 }),
+        shapeElement({ left: 80, top: 780, width: 1440, height: 2, fill: palette.primary, opacity: 0.16, radius: 0 }),
+        shapeElement({ left: 118, top: 80, width: 2, height: 740, fill: palette.secondary, opacity: 0.12, radius: 0 }),
+        shapeElement({ left: 1480, top: 80, width: 2, height: 740, fill: palette.primary, opacity: 0.12, radius: 0 }),
+        shapeElement({ left: 1468, top: 108, width: 26, height: 26, fill: palette.secondary, opacity: 0.75, radius: 4 })
+      ]
+    case "active-classroom":
+      return [
+        shapeElement({ left: 1390, top: 30, width: 58, height: 18, fill: palette.accent, radius: 9 }),
+        shapeElement({ left: 1458, top: 30, width: 92, height: 18, fill: palette.secondary, radius: 9 }),
+        shapeElement({ left: 44, top: 816, width: 116, height: 20, fill: palette.warm, opacity: 0.8, radius: 10 })
+      ]
+  }
+}
+
 function entranceAnimations(elements: PPTElement[]): PPTAnimation[] {
   return elements
     .filter(
@@ -1415,85 +1450,9 @@ function slideRemark(page: AiCoursewarePage) {
     .join("\n")
 }
 
-function buildPalette(outline: AiCoursewareOutline, variant: number): Palette {
-  const clean = outline.style === "clean" || /[初高]中|七年级|八年级|九年级/.test(outline.gradeName || "")
-  if (clean && variant % 3 === 1)
-    return {
-      primary: "#1E3A5F",
-      secondary: "#2F6F66",
-      accent: "#D97706",
-      warm: "#F4C95D",
-      background: "#F6F7F9",
-      surface: "#FFFFFF",
-      soft: "#E9EEF3",
-      text: "#18212B",
-      muted: "#667085",
-      dark: "#102A43"
-    }
-  if (clean && variant % 3 === 2)
-    return {
-      primary: "#4F46A5",
-      secondary: "#287A62",
-      accent: "#C2415B",
-      warm: "#E5C07B",
-      background: "#F7F7FB",
-      surface: "#FFFFFF",
-      soft: "#ECECF7",
-      text: "#202033",
-      muted: "#6B6B80",
-      dark: "#27244F"
-    }
-  if (clean)
-    return {
-      primary: "#2563EB",
-      secondary: "#0F766E",
-      accent: "#E9553D",
-      warm: "#F5D06F",
-      background: "#F4F7FB",
-      surface: "#FFFFFF",
-      soft: "#EAF1F8",
-      text: "#17222E",
-      muted: "#64748B",
-      dark: "#132A3A"
-    }
-  if (variant % 3 === 1)
-    return {
-      primary: "#3478C8",
-      secondary: "#32A07C",
-      accent: "#F08A3C",
-      warm: "#F7D66D",
-      background: "#F7FBF8",
-      surface: "#FFFFFF",
-      soft: "#E6F5EE",
-      text: "#24333B",
-      muted: "#69808A",
-      dark: "#24495C"
-    }
-  if (variant % 3 === 2)
-    return {
-      primary: "#7A5AC8",
-      secondary: "#20A39E",
-      accent: "#EE6C78",
-      warm: "#FFD166",
-      background: "#FBF8FF",
-      surface: "#FFFFFF",
-      soft: "#F0EAFB",
-      text: "#312D3D",
-      muted: "#756E84",
-      dark: "#3C3154"
-    }
-  return {
-    primary: "#E64A78",
-    secondary: "#168F86",
-    accent: "#F28C28",
-    warm: "#FFD66B",
-    background: "#FFF9F5",
-    surface: "#FFFFFF",
-    soft: "#EAF7F4",
-    text: "#263238",
-    muted: "#6B7C8F",
-    dark: "#283B4A"
-  }
+function buildPalette(outline: AiCoursewareOutline, variant: number, templateId?: AiCoursewareTemplateId): Palette {
+  const fallbackTemplate = outline.style === "cartoon" ? "playful-handdrawn" : "fresh-education"
+  return { ...getAiCoursewarePalette(templateId || fallbackTemplate, variant) }
 }
 
 function backgroundConfig(_role: PageRole, palette: Palette, options: AiPptOptions): SlideBackground {

+ 337 - 27
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -53,25 +53,48 @@
         </div>
 
         <div class="style-selector">
-          <div class="label">视觉方向</div>
-          <div class="style-options">
+          <div class="template-selector-head">
+            <div>
+              <div class="label">场景模板</div>
+              <div class="template-help">生成大纲后系统会按年级和课程内容推荐,你也可以直接指定。</div>
+            </div>
             <button
+              v-if="templateSelectionMode === 'manual'"
               type="button"
-              :class="['style-option', { active: selectedStyle === 'cartoon' }]"
+              class="auto-template-button"
               :disabled="working"
-              @click="selectedStyle = 'cartoon'"
+              @click="useRecommendedTemplate"
             >
-              <strong>卡通课堂</strong>
-              <span>适合小学和互动课堂</span>
+              恢复智能推荐
             </button>
+          </div>
+          <div v-if="outline" class="template-recommendation">
+            <span>系统推荐</span>
+            <strong>{{ recommendedTemplate.name }}</strong>
+            <span>{{ templateRecommendation.reason }}</span>
+          </div>
+          <div class="style-options">
             <button
+              v-for="template in coursewareTemplates"
+              :key="template.id"
               type="button"
-              :class="['style-option', { active: selectedStyle === 'clean' }]"
+              :class="['style-option', { active: selectedTemplateId === template.id }]"
               :disabled="working"
-              @click="selectedStyle = 'clean'"
+              @click="selectTemplate(template.id)"
             >
-              <strong>简洁教学</strong>
-              <span>适合高年级和知识讲解</span>
+              <span class="template-mini" :style="templateCardStyle(template)">
+                <i class="mini-band"></i>
+                <i class="mini-title"></i>
+                <i class="mini-content first"></i>
+                <i class="mini-content second"></i>
+                <i class="mini-accent"></i>
+              </span>
+              <span class="template-card-title">
+                <strong>{{ template.name }}</strong>
+                <em v-if="outline && templateRecommendation.id === template.id">推荐</em>
+              </span>
+              <span>{{ template.description }}</span>
+              <small>{{ template.scene }}</small>
             </button>
           </div>
         </div>
@@ -172,6 +195,35 @@
             <div class="sample-title">第{{ samplePageNo }}页 · {{ samplePageTitle }}</div>
             <div class="sample-hint">{{ samplePageReason }}。你可以切换任意页面查看,也可以只重新生成当前页样张。</div>
 
+            <div class="current-template-card">
+              <div>
+                <span>当前模板</span>
+                <strong>{{ selectedTemplate.name }}</strong>
+                <small>切换模板只重绘视觉,不改变大纲和教学内容</small>
+              </div>
+              <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
+                {{ templatePickerExpanded ? "收起模板" : "更换模板" }}
+              </button>
+            </div>
+            <div v-if="templatePickerExpanded" class="sample-template-picker">
+              <button
+                v-for="template in coursewareTemplates"
+                :key="template.id"
+                type="button"
+                :class="['sample-template-option', { active: selectedTemplateId === template.id }]"
+                :disabled="working"
+                @click="selectTemplate(template.id, true)"
+              >
+                <span class="template-swatches">
+                  <i v-for="color in templateSwatches(template)" :key="color" :style="{ background: color }"></i>
+                </span>
+                <span
+                  ><strong>{{ template.name }}</strong
+                  ><small>{{ template.scene }}</small></span
+                >
+              </button>
+            </div>
+
             <div class="sample-page-control">
               <button type="button" :disabled="working || samplePageIndex <= 0" @click="previewPreviousPage">上一页</button>
               <select v-model.number="selectedPageNo" :disabled="working">
@@ -371,6 +423,13 @@ import {
 } from "@/api/aiCourseware"
 import { aiCoursewareToSlides } from "@/utils/aiCoursewareToSlides"
 import { inspectAiPptQuality, type AiPptQualityReport } from "@/utils/aiPptQuality"
+import {
+  AI_COURSEWARE_TEMPLATES,
+  getAiCoursewareTemplate,
+  recommendAiCoursewareTemplate,
+  type AiCoursewareTemplate,
+  type AiCoursewareTemplateId
+} from "@/config/aiCoursewareTemplates"
 import type { Slide, SlideTheme } from "@/types/slides"
 
 const props = defineProps<{
@@ -395,7 +454,10 @@ const workflowSteps = [
   { value: 3, label: "全稿预览" }
 ] as const
 const taskMode = ref<"full" | "sample">("full")
-const selectedStyle = ref<"cartoon" | "clean">("cartoon")
+const coursewareTemplates = AI_COURSEWARE_TEMPLATES
+const selectedTemplateId = ref<AiCoursewareTemplateId>("fresh-education")
+const templateSelectionMode = ref<"auto" | "manual">("auto")
+const templatePickerExpanded = ref(false)
 const paletteVariant = ref(0)
 const pageRange = ref("9-12")
 const preferences = reactive<AiCoursewarePreferences>({
@@ -419,6 +481,14 @@ const pollTimer = ref<number>()
 const selectedPageNo = ref(1)
 const outlineLoading = ref(false)
 const working = computed(() => outlineLoading.value || taskInfo.value?.status === "PENDING" || taskInfo.value?.status === "RUNNING")
+const selectedTemplate = computed(() => getAiCoursewareTemplate(selectedTemplateId.value))
+const selectedStyle = computed(() => selectedTemplate.value.baseStyle)
+const templateRecommendation = computed(() =>
+  outline.value
+    ? recommendAiCoursewareTemplate(outline.value)
+    : { id: "fresh-education" as AiCoursewareTemplateId, reason: "生成大纲后将根据课程内容自动推荐" }
+)
+const recommendedTemplate = computed(() => getAiCoursewareTemplate(templateRecommendation.value.id))
 const progressValue = computed(() => taskInfo.value?.progress || 0)
 const progressSteps = [
   { threshold: 0, label: "准备中" },
@@ -464,9 +534,11 @@ const samplePageReason = computed(() => {
 const sampleSlide = computed(() => {
   if (!outline.value?.pages.length) return undefined
   const previewOutline = { ...outline.value, style: selectedStyle.value }
-  return aiCoursewareToSlides(previewOutline, { enableAnimations: preferences.enableAnimations, paletteVariant: paletteVariant.value }).slides[
-    samplePageIndex.value
-  ]
+  return aiCoursewareToSlides(previewOutline, {
+    enableAnimations: preferences.enableAnimations,
+    paletteVariant: paletteVariant.value,
+    templateId: selectedTemplateId.value
+  }).slides[samplePageIndex.value]
 })
 
 const layoutOptions = [
@@ -495,9 +567,9 @@ const layoutOptions = [
   { value: "homework_list", label: "课后任务" }
 ]
 
-watch(selectedStyle, style => {
+watch(selectedTemplateId, () => {
   paletteVariant.value = 0
-  if (outline.value) outline.value.style = style
+  if (outline.value) outline.value.style = selectedStyle.value
   invalidateVisualApproval()
 })
 
@@ -541,6 +613,7 @@ async function handleOutline() {
   })
   if (res.code === 200) {
     outline.value = res.data as AiCoursewareOutline
+    if (templateSelectionMode.value === "auto") selectedTemplateId.value = recommendAiCoursewareTemplate(outline.value).id
     outline.value.style = selectedStyle.value
     selectedPageNo.value = outline.value.pages[0]?.pageNo || 1
     sampleFeedbackTags.value = []
@@ -675,7 +748,8 @@ function rebuildDraftPreview() {
   draftOutline.value.style = selectedStyle.value
   draftConverted.value = aiCoursewareToSlides(draftOutline.value, {
     enableAnimations: preferences.enableAnimations,
-    paletteVariant: paletteVariant.value
+    paletteVariant: paletteVariant.value,
+    templateId: selectedTemplateId.value
   })
   draftQuality.value = inspectAiPptQuality(draftOutline.value, draftConverted.value.slides, preferences)
 }
@@ -798,6 +872,37 @@ function previewNextPage() {
   if (page) selectedPageNo.value = page.pageNo
 }
 
+function selectTemplate(templateId: AiCoursewareTemplateId, fromSample = false) {
+  templateSelectionMode.value = "manual"
+  selectedTemplateId.value = templateId
+  if (fromSample) {
+    templatePickerExpanded.value = false
+    ElMessage.success(`已切换为${getAiCoursewareTemplate(templateId).name},教学内容未改变`)
+  }
+}
+
+function useRecommendedTemplate() {
+  templateSelectionMode.value = "auto"
+  selectedTemplateId.value = templateRecommendation.value.id
+  ElMessage.success(`已使用系统推荐的${recommendedTemplate.value.name}`)
+}
+
+function templateCardStyle(template: AiCoursewareTemplate): Record<string, string> {
+  const palette = template.palettes[0]
+  return {
+    "--template-primary": palette.primary,
+    "--template-secondary": palette.secondary,
+    "--template-accent": palette.accent,
+    "--template-background": palette.background,
+    "--template-surface": palette.surface
+  }
+}
+
+function templateSwatches(template: AiCoursewareTemplate) {
+  const palette = template.palettes[0]
+  return [palette.primary, palette.secondary, palette.accent]
+}
+
 function updateStudentHeadline(page: AiCoursewarePage, event: Event) {
   if (!page.studentContent) page.studentContent = { headline: "", points: [] }
   page.studentContent.headline = (event.target as HTMLInputElement).value.trim()
@@ -842,10 +947,7 @@ function buildExtraPrompt() {
 }
 
 function buildVisualPrompt() {
-  const base =
-    selectedStyle.value === "clean"
-      ? "简洁教学风格,层级清晰,避免装饰性卡片堆叠,保持大字号和明确视觉焦点。"
-      : "卡通课堂风格,适合低龄学生,使用统一画风的具体人物、动作和场景,避免成人商务卡片风格。"
+  const base = selectedTemplate.value.visualPrompt
   const asset =
     preferences.assetStyle === "illustration"
       ? "主视觉使用统一画风插画。"
@@ -864,7 +966,7 @@ function buildVisualPrompt() {
       : paletteVariant.value === 2
         ? "配色使用柔和紫色与珊瑚色体系。"
         : "配色保持明快且有清晰对比。"
-  return `${base}${asset}${source}${palette}`
+  return `${base}模板场景:${selectedTemplate.value.scene}。${asset}${source}${palette}`
 }
 
 function formatPageNo(pageNo?: number) {
@@ -1103,13 +1205,56 @@ function resourceTypeName(type: string) {
 .style-selector {
   margin-top: 12px;
 }
-.style-options {
+.template-selector-head {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 16px;
+}
+.template-help {
+  margin-top: -3px;
+  color: #7b8794;
+  font-size: 12px;
+}
+.auto-template-button {
+  flex-shrink: 0;
+  padding: 5px 10px;
+  border: 1px solid #cfe0f6;
+  border-radius: 6px;
+  background: #f5f9ff;
+  color: $themeColor;
+  font-size: 12px;
+  cursor: pointer;
+}
+.template-recommendation {
   display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 8px;
+  padding: 8px 10px;
+  border-radius: 6px;
+  background: #eef6ff;
+  color: #52616b;
+  font-size: 12px;
+  span:first-child {
+    padding: 2px 6px;
+    border-radius: 999px;
+    background: $themeColor;
+    color: #fff;
+  }
+  strong {
+    color: #1f2933;
+  }
+}
+.style-options {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
   gap: 10px;
+  margin-top: 9px;
 }
 .style-option {
-  width: 210px;
-  padding: 10px 12px;
+  min-width: 0;
+  padding: 9px;
   border: 1px solid #dfe5ec;
   border-radius: 8px;
   background: #fff;
@@ -1117,18 +1262,91 @@ function resourceTypeName(type: string) {
   cursor: pointer;
 }
 .style-option strong,
-.style-option span {
+.style-option span,
+.style-option small {
   display: block;
 }
-.style-option span {
+.style-option > span:not(.template-mini):not(.template-card-title) {
   margin-top: 4px;
   color: #7b8794;
   font-size: 12px;
 }
+.style-option small {
+  margin-top: 6px;
+  color: #52616b;
+  font-size: 11px;
+  font-weight: 600;
+}
 .style-option.active {
   border-color: $themeColor;
   box-shadow: 0 0 0 2px rgba($themeColor, 0.12);
 }
+.template-mini {
+  position: relative;
+  height: 66px;
+  overflow: hidden;
+  margin-bottom: 8px;
+  border-radius: 6px;
+  background: var(--template-background);
+  box-shadow: inset 0 0 0 1px rgba(31, 41, 51, 0.06);
+  i {
+    position: absolute;
+    display: block;
+  }
+  .mini-band {
+    left: 0;
+    top: 0;
+    width: 32%;
+    height: 100%;
+    background: var(--template-primary);
+  }
+  .mini-title {
+    left: 38%;
+    top: 13px;
+    width: 42%;
+    height: 7px;
+    border-radius: 4px;
+    background: var(--template-primary);
+  }
+  .mini-content {
+    left: 38%;
+    width: 52%;
+    height: 14px;
+    border-radius: 3px;
+    background: var(--template-surface);
+    box-shadow: 0 2px 5px rgba(31, 41, 51, 0.08);
+  }
+  .mini-content.first {
+    top: 29px;
+  }
+  .mini-content.second {
+    top: 47px;
+    width: 37%;
+  }
+  .mini-accent {
+    right: 7%;
+    bottom: 6px;
+    width: 11px;
+    height: 11px;
+    border-radius: 50%;
+    background: var(--template-accent);
+  }
+}
+.template-card-title {
+  display: flex !important;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  em {
+    padding: 2px 5px;
+    border-radius: 999px;
+    background: #eaf7f4;
+    color: #168f86;
+    font-size: 10px;
+    font-style: normal;
+    font-weight: 600;
+  }
+}
 .label {
   margin-bottom: 8px;
   font-size: 14px;
@@ -1169,6 +1387,98 @@ function resourceTypeName(type: string) {
   font-size: 12px;
   line-height: 1.6;
 }
+.current-template-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 10px;
+  padding: 9px 10px;
+  border: 1px solid #dbe7f5;
+  border-radius: 7px;
+  background: #f5f9ff;
+  span,
+  small {
+    display: block;
+  }
+  span {
+    color: #7b8794;
+    font-size: 10px;
+  }
+  strong {
+    display: block;
+    margin: 2px 0;
+    color: #1f2933;
+    font-size: 13px;
+  }
+  small {
+    color: #7b8794;
+    font-size: 10px;
+  }
+  button {
+    flex-shrink: 0;
+    height: 30px;
+    padding: 0 10px;
+    border: 1px solid $themeColor;
+    border-radius: 6px;
+    background: #fff;
+    color: $themeColor;
+    font-size: 12px;
+    cursor: pointer;
+  }
+}
+.sample-template-picker {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 6px;
+  margin: -2px 0 10px;
+}
+.sample-template-option {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  min-width: 0;
+  padding: 7px 8px;
+  border: 1px solid #dfe5ec;
+  border-radius: 6px;
+  background: #fff;
+  text-align: left;
+  cursor: pointer;
+  &.active {
+    border-color: $themeColor;
+    background: #f5f9ff;
+  }
+  > span:last-child {
+    min-width: 0;
+  }
+  strong,
+  small {
+    display: block;
+  }
+  strong {
+    overflow: hidden;
+    color: #33404d;
+    font-size: 11px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  small {
+    margin-top: 2px;
+    color: #8a96a3;
+    font-size: 9px;
+  }
+}
+.template-swatches {
+  display: flex;
+  flex-shrink: 0;
+  overflow: hidden;
+  width: 34px;
+  height: 28px;
+  border-radius: 4px;
+  i {
+    flex: 1;
+  }
+}
 .sample-page-control {
   display: grid;
   grid-template-columns: auto minmax(0, 1fr) auto;

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