yonge 4 days ago
parent
commit
91bc8d773f

+ 1 - 1
dist/assets/index-FBueO5vW.js → dist/assets/index-B9rUcuyJ.js

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

+ 1 - 1
dist/assets/index-CHO5jbS4.js → dist/assets/index-BKvw1dmz.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-DdEUP37F.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-BRBTDjW-.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};

File diff suppressed because it is too large
+ 1 - 1
dist/assets/index-BRBTDjW-.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-BuAzbJUn.js


+ 1 - 1
dist/assets/index-BSJJckK4.js → dist/assets/index-CcHKVOcP.js

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

+ 1 - 1
dist/assets/index-C5h2r6OB.js → dist/assets/index-CxnU4J8j.js

@@ -1 +1 @@
-import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Db4WI5MQ.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-DdEUP37F.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-cMmEwGzU.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-BRBTDjW-.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-BdJBwSaZ.js → dist/assets/index-DxaHGDJo.js

@@ -1 +1 @@
-import{u as c,ah as l}from"./index-FBueO5vW.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-DdEUP37F.js";import"./shapes-DBHhoFfw.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,ah as l}from"./index-B9rUcuyJ.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-BRBTDjW-.js";import"./shapes-OCYpAg2n.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};

File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-Dz-XTmzu.css


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-KSgEQf0S.css


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-cMmEwGzU.js


+ 1 - 1
dist/assets/shapes-DBHhoFfw.js → dist/assets/shapes-OCYpAg2n.js

@@ -1,4 +1,4 @@
-import{cG as k,bB as $1}from"./index-DdEUP37F.js";import{ai as Z,aj as H1,ak as U,al as n0,am as c0,an as i0,ao as j1,ap as K1,aq as Y1,ar as R}from"./index-FBueO5vW.js";var z1={exports:{}};/*!
+import{cG as k,bB as $1}from"./index-BRBTDjW-.js";import{ai as Z,aj as H1,ak as U,al as n0,am as c0,an as i0,ao as j1,ap as K1,aq as Y1,ar as R}from"./index-B9rUcuyJ.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-DdEUP37F.js"></script>
+    <script type="module" crossorigin src="./assets/index-BRBTDjW-.js"></script>
     <link rel="stylesheet" crossorigin href="./assets/index-AavUEaYy.css">
   </head>
   <body>

+ 285 - 247
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -2,11 +2,13 @@
   <Modal
     :visible="visible"
     :width="1120"
-    :close-button="true"
+    :content-style="{ maxWidth: 'calc(100vw - 32px)', boxSizing: 'border-box' }"
+    :close-button="!working"
     :close-on-click-mask="!working"
+    :close-on-esc="!working"
     @update:visible="value => emit('update:visible', value)"
   >
-    <div class="ai-dialog">
+    <div :class="['ai-dialog', workflowStep === 1 ? 'setup-mode' : 'review-mode']">
       <div class="header">
         <div>
           <div class="title">{{ pageTitle }}</div>
@@ -69,60 +71,7 @@
 
         <div class="extra">
           <div class="label">还有什么特别要求?<span>选填</span></div>
-          <TextArea v-model:value="extraPrompt" :rows="2" placeholder="例如:多安排节奏游戏,语言适合一年级学生" :disabled="working" />
-        </div>
-
-        <div class="template-summary-card">
-          <div class="template-real-cover" :style="templateCoverStyle(selectedTemplate.id)"></div>
-          <div class="template-summary-copy">
-            <span>{{ templateSelectionMode === "auto" ? "系统将自动匹配" : "当前选择" }}</span>
-            <strong>{{ selectedTemplate.name }}</strong>
-            <small>{{ selectedTemplate.scene }} · {{ selectedTemplate.description }}</small>
-          </div>
-          <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
-            {{ templatePickerExpanded ? "收起" : "更换模板" }}
-          </button>
-        </div>
-
-        <div v-if="templatePickerExpanded" class="style-selector compact-template-selector">
-          <div class="template-selector-head">
-            <div>
-              <div class="label">选择模板</div>
-              <div class="template-help">优先展示适合当前课程的模板。</div>
-            </div>
-            <button
-              v-if="templateSelectionMode === 'manual'"
-              type="button"
-              class="auto-template-button"
-              :disabled="working"
-              @click="useRecommendedTemplate"
-            >
-              恢复智能推荐
-            </button>
-          </div>
-          <div class="style-options">
-            <button
-              v-for="template in visibleTemplates"
-              :key="template.id"
-              type="button"
-              :class="['style-option', { active: selectedTemplateId === template.id }]"
-              :disabled="working"
-              @click="selectTemplate(template.id)"
-            >
-              <span class="template-real-cover option" :style="templateCoverStyle(template.id)"></span>
-              <span class="template-card-title">
-                <strong>{{ template.name }}</strong>
-                <em v-if="outline && template.id === recommendedTemplate.id">系统推荐</em>
-              </span>
-              <span>{{ template.description }}</span>
-              <small>{{ template.scene }}</small>
-            </button>
-          </div>
-          <button v-if="!showAllTemplates" type="button" class="show-all-templates" @click="showAllTemplates = true">查看全部模板</button>
-          <div v-if="outline && selectedTemplateFit.level === 'low'" class="template-fit-warning">
-            <span>当前模板不太适合这节课:{{ selectedTemplateFit.reason }}</span>
-            <button type="button" :disabled="working" @click="useRecommendedTemplate">切换为系统推荐</button>
-          </div>
+          <TextArea v-model:value="extraPrompt" :rows="3" placeholder="例如:多安排节奏游戏,语言适合一年级学生" :disabled="working" />
         </div>
 
         <button type="button" class="advanced-toggle" @click="advancedSettingsExpanded = !advancedSettingsExpanded">
@@ -177,7 +126,19 @@
         </div>
         <div v-if="draftQuality && !draftQuality.passed" class="quality-actions">
           <button type="button" :disabled="working" @click="autoOptimizeDraft">自动优化全部页面</button>
-          <button v-if="selectedTemplateFit.level === 'low'" type="button" :disabled="working" @click="useRecommendedTemplateForDraft">切换推荐模板</button>
+          <button v-if="selectedTemplateFit.level === 'low'" type="button" :disabled="working" @click="useRecommendedTemplateForDraft">
+            切换推荐模板
+          </button>
+        </div>
+        <div v-if="selectedDraftPage" class="selected-draft-actions">
+          <div>
+            <span>当前页面</span><strong>第{{ selectedDraftPageIndex + 1 }}页 · {{ selectedDraftPage.title }}</strong>
+          </div>
+          <div>
+            <button type="button" :disabled="working" @click="compactDraftPage(selectedDraftPageIndex)">精简文字</button>
+            <button type="button" :disabled="working || !selectedDraftPage.image" @click="removeDraftImage(selectedDraftPageIndex)">移除图片</button>
+            <button type="button" :disabled="working" @click="regenerateDraftPage(selectedDraftPageIndex)">重新生成本页</button>
+          </div>
         </div>
         <div class="preview-grid">
           <button
@@ -191,20 +152,17 @@
             <div class="preview-page-no">{{ formatPageNo(index + 1) }}</div>
           </button>
         </div>
-        <div v-if="selectedDraftPage" class="selected-draft-actions">
-          <div><span>当前页面</span><strong>第{{ selectedDraftPageIndex + 1 }}页 · {{ selectedDraftPage.title }}</strong></div>
-          <div>
-            <button type="button" :disabled="working" @click="compactDraftPage(selectedDraftPageIndex)">精简文字</button>
-            <button type="button" :disabled="working || !selectedDraftPage.image" @click="removeDraftImage(selectedDraftPageIndex)">移除图片</button>
-            <button type="button" :disabled="working" @click="regenerateDraftPage(selectedDraftPageIndex)">重新生成本页</button>
-          </div>
-        </div>
       </div>
 
       <div v-else-if="workflowStep === 2 && outline" class="outline">
         <div class="sample-stage-head">
-          <div><strong>确认课件的整体样式</strong><span>查看封面、正文和课堂活动三类页面,一次确认即可。</span></div>
-          <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">更换模板</button>
+          <div>
+            <strong>确认课件的整体样式</strong>
+            <span>当前模板:{{ selectedTemplate.name }}。查看三类代表页面,一次确认即可。</span>
+          </div>
+          <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
+            {{ templatePickerExpanded ? "收起模板" : "更换模板" }}
+          </button>
         </div>
 
         <div class="sample-thumbnail-grid">
@@ -232,10 +190,17 @@
               @click="selectTemplate(template.id, true)"
             >
               <span class="template-real-cover option" :style="templateCoverStyle(template.id)"></span>
-              <span><strong>{{ template.name }}</strong><small>{{ template.id === recommendedTemplate.id ? "系统推荐 · " : "" }}{{ template.scene }}</small></span>
+              <span
+                ><strong>{{ template.name }}</strong
+                ><small>{{ template.id === recommendedTemplate.id ? "系统推荐 · " : "" }}{{ template.scene }}</small></span
+              >
             </button>
           </div>
           <button v-if="!showAllTemplates" type="button" class="show-all-templates" @click="showAllTemplates = true">查看全部模板</button>
+          <div v-if="selectedTemplateFit.level === 'low'" class="template-fit-warning">
+            <span>当前模板不太适合这节课:{{ selectedTemplateFit.reason }}</span>
+            <button type="button" :disabled="working" @click="useRecommendedTemplate">切换为系统推荐</button>
+          </div>
         </div>
 
         <div v-if="sampleSlide" class="sample-review simplified-sample-review">
@@ -243,32 +208,32 @@
             <div class="sample-eyebrow">当前查看</div>
             <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>
-                <small v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</small>
-              </div>
-            </div>
+            <div v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</div>
             <div class="sample-feedback">
               <div class="feedback-label">这一页哪里需要调整?</div>
               <div class="feedback-chips">
-                <button v-for="item in sampleFeedbackOptions" :key="item" type="button" :class="{ active: sampleFeedbackTags.includes(item) }" :disabled="working" @click="toggleSampleFeedback(item)">
+                <button
+                  v-for="item in sampleFeedbackOptions"
+                  :key="item"
+                  type="button"
+                  :class="{ active: sampleFeedbackTags.includes(item) }"
+                  :disabled="working"
+                  @click="toggleSampleFeedback(item)"
+                >
                   {{ item }}
                 </button>
               </div>
-              <textarea v-model="sampleFeedbackText" :disabled="working" placeholder="也可以直接说明你希望怎么调整" @input="invalidateVisualApproval"></textarea>
+              <textarea
+                v-model="sampleFeedbackText"
+                :disabled="working"
+                placeholder="也可以直接说明你希望怎么调整"
+                @input="invalidateVisualApproval"
+              ></textarea>
             </div>
             <Button @click="regenerateSamplePage" :disabled="working">调整当前页面</Button>
           </div>
           <div class="sample-canvas">
-            <ThumbnailSlide
-              :slide="sampleSlide"
-              :size="420"
-              :show-background-error="true"
-              @background-load-state="handleSampleBackgroundLoadState"
-            />
+            <ThumbnailSlide :slide="sampleSlide" :size="420" :show-background-error="true" @background-load-state="handleSampleBackgroundLoadState" />
           </div>
         </div>
 
@@ -277,25 +242,25 @@
         </button>
         <div v-if="outlineExpanded" class="outline-editor-panel">
           <div class="page-nav">
-          <button
-            v-for="page in outline.pages"
-            :key="page.pageNo"
-            class="nav-item"
-            :class="{ active: selectedPageNo === page.pageNo }"
-            type="button"
-            @click="selectedPageNo = page.pageNo"
-          >
-            <span class="nav-no">{{ formatPageNo(page.pageNo) }}</span>
-            <span class="nav-title">{{ page.title || "未命名页面" }}</span>
-          </button>
+            <button
+              v-for="page in outline.pages"
+              :key="page.pageNo"
+              class="nav-item"
+              :class="{ active: selectedPageNo === page.pageNo }"
+              type="button"
+              @click="selectedPageNo = page.pageNo"
+            >
+              <span class="nav-no">{{ formatPageNo(page.pageNo) }}</span>
+              <span class="nav-title">{{ page.title || "未命名页面" }}</span>
+            </button>
           </div>
 
           <div v-if="selectedPage" class="page-editor simplified-page-editor">
-          <div class="editor-head">
-            <div class="title-editor">
-              <input v-model="selectedPage.title" class="page-title" :disabled="working" />
+            <div class="editor-head">
+              <div class="title-editor">
+                <input v-model="selectedPage.title" class="page-title" :disabled="working" />
+              </div>
             </div>
-          </div>
 
             <div class="field-card">
               <div class="field-title">学生看到的内容</div>
@@ -318,21 +283,21 @@
         </div>
       </div>
 
-      <div v-else-if="workflowStep === 1 && !outline" class="empty">完成生成设置后,系统会先生成大纲和可调整的视觉样张。</div>
-
       <div class="footer">
         <Button @click="emit('update:visible', false)" :disabled="working">取消</Button>
         <template v-if="workflowStep === 1">
           <Button v-if="outline" @click="workflowStep = 2" :disabled="working">继续选择样式</Button>
-          <Button type="primary" @click="handleOutline" :disabled="working">{{ outline ? "按当前设置重新生成" : "生成大纲和视觉样稿" }}</Button>
+          <Button type="primary" @click="handleOutline" :disabled="working">{{ outline ? "按当前设置重新生成" : "生成大纲并选择样式" }}</Button>
         </template>
         <template v-else-if="workflowStep === 2">
           <Button @click="workflowStep = 1" :disabled="working">返回修改需求</Button>
-          <Button type="primary" @click="confirmSamplesAndGenerate" :disabled="working || sampleBackgroundState.status === 'error'">样式满意,生成完整课件</Button>
+          <Button type="primary" @click="confirmSamplesAndGenerate" :disabled="working || sampleBackgroundState.status === 'error'"
+            >确认样式并生成课件</Button
+          >
         </template>
         <template v-else>
           <Button @click="backToOutline" :disabled="working">返回调整</Button>
-          <Button type="primary" @click="applyDraft" :disabled="working || !draftQuality?.passed">应用到当前课件</Button>
+          <Button type="primary" @click="applyDraft" :disabled="working || !draftQuality?.passed">确认并应用到当前课件</Button>
         </template>
       </div>
     </div>
@@ -392,9 +357,9 @@ const outline = ref<AiCoursewareOutline>()
 const taskInfo = ref<AiCoursewareTaskInfo>()
 const workflowStep = ref<1 | 2 | 3>(1)
 const workflowSteps = [
-  { value: 1, label: "说明需求" },
-  { value: 2, label: "选择样式" },
-  { value: 3, label: "预览课件" }
+  { value: 1, label: "设置课程" },
+  { value: 2, label: "确认样式" },
+  { value: 3, label: "检查课件" }
 ] as const
 const taskMode = ref<"full" | "sample">("full")
 const coursewareTemplates = AI_COURSEWARE_TEMPLATES
@@ -433,9 +398,7 @@ const working = computed(() => outlineLoading.value || taskInfo.value?.status ==
 const selectedTemplate = computed(() => getAiCoursewareTemplate(selectedTemplateId.value))
 const selectedStyle = computed(() => selectedTemplate.value.baseStyle)
 const templateRecommendation = computed(() =>
-  outline.value
-    ? recommendAiCoursewareTemplate(outline.value)
-    : { id: defaultTemplateId, reason: "生成大纲后将根据课程内容自动推荐" }
+  outline.value ? recommendAiCoursewareTemplate(outline.value) : { id: defaultTemplateId, reason: "生成大纲后将根据课程内容自动推荐" }
 )
 const recommendedTemplate = computed(() => getAiCoursewareTemplate(templateRecommendation.value.id))
 const visibleTemplates = computed(() => {
@@ -449,9 +412,10 @@ const visibleTemplates = computed(() => {
     })
     .slice(0, 4)
 })
-const selectedTemplateFit = computed(() => outline.value
-  ? scoreAiCoursewareTemplate(outline.value, selectedTemplateId.value)
-  : { templateId: selectedTemplateId.value, score: 80, level: "usable" as const, label: "可以使用" as const, reason: "生成大纲后评估" }
+const selectedTemplateFit = computed(() =>
+  outline.value
+    ? scoreAiCoursewareTemplate(outline.value, selectedTemplateId.value)
+    : { templateId: selectedTemplateId.value, score: 80, level: "usable" as const, label: "可以使用" as const, reason: "生成大纲后评估" }
 )
 const progressValue = computed(() => taskInfo.value?.progress || 0)
 const progressSteps = [
@@ -465,9 +429,9 @@ const currentProgressLabel = computed(() => {
   const matched = [...progressSteps].reverse().find(step => progressValue.value >= step.threshold)
   return shortProgressLabel(taskInfo.value?.message) || matched?.label || "处理中"
 })
-const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "课件大纲")
+const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "AI生成课件")
 const pageMeta = computed(() => {
-  if (!outline.value) return "根据当前教材、单元和章节生成9至15页音乐课件"
+  if (!outline.value) return `根据当前教材生成${preferences.minPages}至${preferences.maxPages}页音乐课件`
   return [...[outline.value.gradeName, outline.value.unitName].filter(Boolean), `共${outline.value.pages.length}页`].join(" · ")
 })
 const selectedPage = computed(() => {
@@ -495,11 +459,27 @@ const representativeSamples = computed(() => {
   }
   add(pages[0], "封面主视觉")
   add(
-    pages.find(page => ["hero_focus", "media_focus", "split_content", "diagram_focus", "story_stage", "full_image_question", "split_image", "observe_question"].includes(page.visualPlan?.layout || "") && page.pageNo !== pages[0].pageNo),
+    pages.find(
+      page =>
+        [
+          "hero_focus",
+          "media_focus",
+          "split_content",
+          "diagram_focus",
+          "story_stage",
+          "full_image_question",
+          "split_image",
+          "observe_question"
+        ].includes(page.visualPlan?.layout || "") && page.pageNo !== pages[0].pageNo
+    ),
     "内容主视觉"
   )
   add(
-    pages.find(page => ["activity", "summary", "action_demo", "group_mission", "performance_stage", "activity_steps", "practice_check"].includes(page.visualPlan?.layout || "")),
+    pages.find(page =>
+      ["activity", "summary", "action_demo", "group_mission", "performance_stage", "activity_steps", "practice_check"].includes(
+        page.visualPlan?.layout || ""
+      )
+    ),
     "课堂活动"
   )
   pages.forEach(page => {
@@ -530,10 +510,12 @@ const previewSlides = computed(() => {
   }).slides
 })
 const sampleSlide = computed(() => previewSlides.value?.[samplePageIndex.value])
-const representativeSampleSlides = computed(() => representativeSamples.value.map(sample => {
-  const pageIndex = outline.value?.pages.findIndex(page => page.pageNo === sample.pageNo) ?? -1
-  return { ...sample, slide: pageIndex >= 0 ? previewSlides.value?.[pageIndex] : undefined }
-}))
+const representativeSampleSlides = computed(() =>
+  representativeSamples.value.map(sample => {
+    const pageIndex = outline.value?.pages.findIndex(page => page.pageNo === sample.pageNo) ?? -1
+    return { ...sample, slide: pageIndex >= 0 ? previewSlides.value?.[pageIndex] : undefined }
+  })
+)
 
 watch(selectedTemplateId, () => {
   paletteVariant.value = 0
@@ -660,7 +642,9 @@ async function fetchTask(taskId: string) {
     else await loadDraft(taskId)
   } else if (taskInfo.value.status === "FAILED") {
     clearPollTimer()
-    ElMessage.error(taskInfo.value.message || "AI课件生成失败")
+    const message = taskInfo.value.message || "AI课件生成失败"
+    clearTask()
+    ElMessage.error(message)
   }
 }
 
@@ -808,11 +792,13 @@ async function confirmSamplesAndGenerate() {
     return
   }
 
-  const backgroundUrls = Array.from(new Set(
-    representativeSampleSlides.value
-      .map(sample => sample.slide?.background?.type === "image" ? resolveTemplateAssetUrl(sample.slide.background.image) : "")
-      .filter(Boolean)
-  ))
+  const backgroundUrls = Array.from(
+    new Set(
+      representativeSampleSlides.value
+        .map(sample => (sample.slide?.background?.type === "image" ? resolveTemplateAssetUrl(sample.slide.background.image) : ""))
+        .filter(Boolean)
+    )
+  )
 
   try {
     await Promise.all(backgroundUrls.map(preloadImage))
@@ -898,7 +884,12 @@ function autoOptimizeDraft() {
   draftOutline.value.pages.forEach((page, index) => {
     if (!page.visualPlan) page.visualPlan = {}
     const points = [...new Set(page.studentContent?.points || page.bullets || [])]
-      .map(item => String(item || "").replace(/\s+/g, " ").trim().slice(0, 24))
+      .map(item =>
+        String(item || "")
+          .replace(/\s+/g, " ")
+          .trim()
+          .slice(0, 24)
+      )
       .filter(Boolean)
       .slice(0, 4)
     if (!page.studentContent) page.studentContent = { headline: "", points: [] }
@@ -911,7 +902,8 @@ function autoOptimizeDraft() {
       page.visualPlan.mainVisual ||= page.imageKeywords?.[0] || `${page.title}的课堂主视觉`
     }
     if (index > 0 && !page.classroomAction?.teacherInstruction && !page.teacherTips && !page.speakerNotes) {
-      if (!page.classroomAction) page.classroomAction = { teacherInstruction: "", studentTask: page.studentContent.headline || page.title, interactionType: "" }
+      if (!page.classroomAction)
+        page.classroomAction = { teacherInstruction: "", studentTask: page.studentContent.headline || page.title, interactionType: "" }
       page.classroomAction.teacherInstruction = `引导学生完成“${page.classroomAction.studentTask || page.title}”,根据学生的语言、动作或声音表现及时反馈。`
     }
   })
@@ -978,22 +970,29 @@ function shortProgressLabel(message?: string) {
   if (message.includes("失败")) return "生成失败"
   return message.length > 4 ? message.slice(0, 4) : message
 }
-
 </script>
 
 <style lang="scss" scoped>
 .ai-dialog {
-  height: 82vh;
+  max-height: calc(100vh - 64px);
   display: flex;
   flex-direction: column;
   color: #1f2933;
 }
+.ai-dialog.review-mode {
+  height: 82vh;
+}
 .header,
 .footer {
   display: flex;
   align-items: center;
   justify-content: space-between;
 }
+.header,
+.workflow-steps,
+.footer {
+  flex-shrink: 0;
+}
 .title {
   font-size: 24px;
   font-weight: 700;
@@ -1052,7 +1051,7 @@ function shortProgressLabel(message?: string) {
 }
 .setup-stage {
   min-height: 0;
-  flex: 1;
+  flex: 0 1 auto;
   overflow: auto;
   padding-right: 4px;
 }
@@ -1162,43 +1161,6 @@ function shortProgressLabel(message?: string) {
     color: #33404d;
   }
 }
-.template-summary-card {
-  display: grid;
-  grid-template-columns: 116px minmax(0, 1fr) auto;
-  align-items: center;
-  gap: 14px;
-  margin-top: 14px;
-  padding: 12px;
-  border: 1px solid #dbe7f5;
-  border-radius: 9px;
-  background: #f7faff;
-  button {
-    height: 34px;
-    padding: 0 12px;
-    border: 1px solid $themeColor;
-    border-radius: 6px;
-    background: #fff;
-    color: $themeColor;
-    cursor: pointer;
-  }
-}
-.template-summary-copy {
-  min-width: 0;
-  span,
-  strong,
-  small {
-    display: block;
-  }
-  span,
-  small {
-    color: #7b8794;
-    font-size: 12px;
-  }
-  strong {
-    margin: 3px 0;
-    font-size: 16px;
-  }
-}
 .template-real-cover {
   display: block;
   width: 116px;
@@ -1593,34 +1555,106 @@ function shortProgressLabel(message?: string) {
     font-size: 9px;
   }
   &.story {
-    .mini-band { width: 100%; height: 100%; opacity: 0.22; }
-    .mini-title { left: 10%; top: 12px; width: 46%; }
-    .mini-content { left: 10%; background: rgba(255, 255, 255, 0.9); }
-    .mini-accent { right: 12%; bottom: 13px; width: 30px; height: 30px; opacity: 0.85; }
+    .mini-band {
+      width: 100%;
+      height: 100%;
+      opacity: 0.22;
+    }
+    .mini-title {
+      left: 10%;
+      top: 12px;
+      width: 46%;
+    }
+    .mini-content {
+      left: 10%;
+      background: rgba(255, 255, 255, 0.9);
+    }
+    .mini-accent {
+      right: 12%;
+      bottom: 13px;
+      width: 30px;
+      height: 30px;
+      opacity: 0.85;
+    }
   }
   &.activity {
-    .mini-band { left: 7%; top: 12px; width: 18%; height: 42px; border-radius: 50%; }
-    .mini-title { left: 32%; width: 48%; }
-    .mini-content.first { left: 32%; top: 29px; width: 58%; }
-    .mini-content.second { left: 32%; top: 47px; width: 45%; }
+    .mini-band {
+      left: 7%;
+      top: 12px;
+      width: 18%;
+      height: 42px;
+      border-radius: 50%;
+    }
+    .mini-title {
+      left: 32%;
+      width: 48%;
+    }
+    .mini-content.first {
+      left: 32%;
+      top: 29px;
+      width: 58%;
+    }
+    .mini-content.second {
+      left: 32%;
+      top: 47px;
+      width: 45%;
+    }
   }
   &.technology {
     background: var(--template-dark, #102744);
-    .mini-band { width: 100%; height: 3px; opacity: 0.8; }
-    .mini-title { left: 10%; background: var(--template-secondary); }
-    .mini-content { left: 10%; width: 76%; background: rgba(255, 255, 255, 0.15); }
-    .mini-accent { border-radius: 3px; }
+    .mini-band {
+      width: 100%;
+      height: 3px;
+      opacity: 0.8;
+    }
+    .mini-title {
+      left: 10%;
+      background: var(--template-secondary);
+    }
+    .mini-content {
+      left: 10%;
+      width: 76%;
+      background: rgba(255, 255, 255, 0.15);
+    }
+    .mini-accent {
+      border-radius: 3px;
+    }
   }
   &.culture {
-    .mini-band { left: 78%; width: 14%; height: 100%; opacity: 0.18; }
-    .mini-title { left: 10%; width: 44%; }
-    .mini-content { left: 10%; width: 58%; background: var(--template-soft); box-shadow: none; }
+    .mini-band {
+      left: 78%;
+      width: 14%;
+      height: 100%;
+      opacity: 0.18;
+    }
+    .mini-title {
+      left: 10%;
+      width: 44%;
+    }
+    .mini-content {
+      left: 10%;
+      width: 58%;
+      background: var(--template-soft);
+      box-shadow: none;
+    }
   }
   &.academic {
-    .mini-band { width: 4%; }
-    .mini-title { left: 12%; width: 54%; }
-    .mini-content { left: 12%; width: 76%; border-radius: 0; box-shadow: none; }
-    .mini-accent { border-radius: 1px; }
+    .mini-band {
+      width: 4%;
+    }
+    .mini-title {
+      left: 12%;
+      width: 54%;
+    }
+    .mini-content {
+      left: 12%;
+      width: 76%;
+      border-radius: 0;
+      box-shadow: none;
+    }
+    .mini-accent {
+      border-radius: 1px;
+    }
   }
   em.fit-usable {
     background: #fff7e6;
@@ -1655,55 +1689,11 @@ function shortProgressLabel(message?: string) {
 .template-fit-warning.compact {
   margin: 0 0 10px;
 }
-.current-template-card {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 12px;
+.template-load-error {
   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;
-  }
-  .template-debug-info {
-    margin-top: 3px;
-    color: #50647a;
-  }
-  .template-load-error {
-    max-width: 620px;
-    margin-top: 4px;
-    color: #b42318;
-    overflow-wrap: anywhere;
-  }
-  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;
-  }
+  color: #b42318;
+  font-size: 12px;
+  overflow-wrap: anywhere;
 }
 .sample-template-picker {
   display: grid;
@@ -1888,8 +1878,6 @@ function shortProgressLabel(message?: string) {
 }
 .quality-list {
   margin-top: 12px;
-  max-height: 150px;
-  overflow: auto;
   border: 1px solid #edf1f5;
   border-radius: 8px;
 }
@@ -1978,6 +1966,9 @@ function shortProgressLabel(message?: string) {
   }
 }
 .selected-draft-actions {
+  position: sticky;
+  top: 0;
+  z-index: 2;
   display: flex;
   align-items: center;
   justify-content: space-between;
@@ -1987,6 +1978,7 @@ function shortProgressLabel(message?: string) {
   border: 1px solid #dbe7f5;
   border-radius: 8px;
   background: #f7faff;
+  box-shadow: 0 4px 12px rgba(31, 41, 51, 0.06);
   span,
   strong {
     display: block;
@@ -2279,21 +2271,67 @@ function shortProgressLabel(message?: string) {
   line-height: 1.5;
   background: #fff;
 }
-.empty {
-  flex: 1;
-  margin-top: 14px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: 1px dashed #d9d9d9;
-  color: #7b8794;
-}
 .task {
   min-height: 20px;
 }
 .footer {
   margin-top: 14px;
+  padding-top: 12px;
+  border-top: 1px solid #edf1f5;
+  background: #fff;
   justify-content: flex-end;
   gap: 10px;
 }
+
+@media (max-width: 900px) {
+  .ai-dialog.review-mode {
+    height: calc(100vh - 64px);
+  }
+  .setup-primary-grid,
+  .outline-editor-panel {
+    grid-template-columns: 1fr;
+  }
+  .sample-review {
+    grid-template-columns: 1fr;
+  }
+  .sample-canvas {
+    justify-self: center;
+    max-width: 100%;
+  }
+  .sample-thumbnail-grid {
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+  }
+  .compact-template-panel .sample-template-picker {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+  .selected-draft-actions {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+}
+
+@media (max-width: 640px) {
+  .workflow-step {
+    gap: 4px;
+    font-size: 12px;
+  }
+  .step-no {
+    width: 20px;
+    height: 20px;
+  }
+  .sample-thumbnail-grid {
+    grid-template-columns: 1fr;
+  }
+  .compact-template-panel .sample-template-picker {
+    grid-template-columns: 1fr;
+  }
+  .preview-head,
+  .sample-stage-head {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+  .footer {
+    flex-wrap: wrap;
+  }
+}
 </style>

Some files were not shown because too many files changed in this diff