yonge 4 dni temu
rodzic
commit
0e3583851d

Plik diff jest za duży
+ 0 - 0
dist/assets/index-BdyAxVy4.js


Plik diff jest za duży
+ 0 - 0
dist/assets/index-BuAzbJUn.js


Plik diff jest za duży
+ 1 - 1
dist/assets/index-CTPkOFAg.js


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

Plik diff jest za duży
+ 0 - 0
dist/assets/index-Cq_5irci.css


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

@@ -1 +1 @@
-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};
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-BdyAxVy4.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-CTPkOFAg.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-BKvw1dmz.js → dist/assets/index-DMxwym03.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-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};
+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-CTPkOFAg.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};

+ 1 - 1
dist/assets/index-DxaHGDJo.js → dist/assets/index-DuAGIZys.js

@@ -1 +1 @@
-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};
+import{u as c,ah as l}from"./index-CcCGo14p.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-CTPkOFAg.js";import"./shapes-CiFnAZ1O.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/index-CcHKVOcP.js → dist/assets/index-Dux_OJAL.js

@@ -1 +1 @@
-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};
+import{u as s,ah as r}from"./index-CcCGo14p.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-CTPkOFAg.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};

Plik diff jest za duży
+ 0 - 0
dist/assets/index-KSgEQf0S.css


Plik diff jest za duży
+ 0 - 0
dist/assets/index-SuyKuPio.js


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

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

+ 4 - 1
scripts/aiPptSelfTest.mjs

@@ -35,7 +35,10 @@ try {
       page(4, "work_context", "走进凤鸣山谷", "split_image", ["清晨的山谷十分安静", "鸟鸣由远处慢慢靠近", "溪水一直轻轻流淌"]),
       page(5, "first_listen", "第一次完整聆听", "listen_wave", ["安静听完音乐", "用手势表示声音的远近", "记住最喜欢的声音"], { resourceBindings: [{ resourceId: "song-demo", resourceType: "SONG", name: "凤鸣山谷", contentUrl: "https://example.com/fengming.mp3", verified: true }] }),
       page(6, "lyric_read", "读一读山谷歌谣", "lyric_focus", ["小鸟唱得清脆", "溪水流得轻柔", "山风吹得悠长"]),
-      page(7, "rhythm_practice", "跟着节奏拍一拍", "rhythm_grid", ["小鸟:短短拍", "溪水:连续拍", "山风:长长拍"], { activity: { task: "三组同学接力表现", steps: ["选择一种声音", "先慢速练习", "听指挥加入合奏"] } }),
+      page(7, "rhythm_practice", "跟着节奏拍一拍", "rhythm_grid", ["小鸟:短短拍", "溪水:连续拍", "山风:长长拍"], {
+        studentContent: { headline: "跟着节奏拍一拍", points: ["小鸟:短短拍", "溪水:连续拍", "山风:长长拍"] },
+        activity: { task: "教师示范不同节奏,引导学生模仿", steps: ["选择一种声音", "先慢速练习", "听指挥加入合奏"] }
+      }),
       page(8, "melody_score", "旋律是怎样行走的?", "score_focus", ["向上表示声音变高", "向下表示声音变低", "平直表示保持稳定"]),
       page(9, "music_knowledge", "认识声音的强与弱", "knowledge_cards", ["强:有力量但不喊叫", "弱:轻柔但保持清楚", "强弱变化让音乐更生动"]),
       page(10, "compare_listen", "两段声音有什么不同?", "compare_listen", ["比较速度", "比较力度"], { interaction: { type: "compare", question: "哪一段更像远处的鸟鸣?", options: ["第一段更轻", "第二段更强", "第一段更慢", "第二段更快"] } }),

+ 4 - 5
src/utils/aiPptQuality.ts

@@ -110,13 +110,15 @@ function inspectOutline(outline: AiCoursewareOutline, issues: AiPptQualityIssue[
     if (consecutive > 2) push(issues, "layout_consecutive", "error", pageNo, `连续${consecutive}页使用相同布局“${layout}”`)
     previousLayout = layout
 
+    // 只检查真正会进入学生投屏画面的字段。activity 属于教师组织信息,
+    // 最终只会写入 PPT 备注,不能把它当成学生可见内容制造误报。
     const visibleTexts = [
       page.title,
       page.studentContent?.headline,
-      page.activity?.task,
+      page.classroomAction?.studentTask,
       page.interaction?.question,
       ...(page.studentContent?.points || []),
-      ...(page.activity?.steps || [])
+      ...((page.interaction?.options || []).map(clean).filter(Boolean))
     ]
       .map(clean)
       .filter(Boolean)
@@ -137,9 +139,6 @@ function inspectOutline(outline: AiCoursewareOutline, issues: AiPptQualityIssue[
       }
     })
 
-    const duplicate = firstDuplicate(visibleTexts)
-    if (duplicate) push(issues, "duplicate_text", "warning", pageNo, `结构化字段存在重复文本:“${short(duplicate)}”`)
-
     const imageUrl = clean(page.image?.url)
     if (imageUrl) imagePages.set(imageUrl, [...(imagePages.get(imageUrl) || []), pageNo])
 

+ 354 - 240
src/views/Editor/EditorHeader/AiCoursewareDialog.vue

@@ -43,8 +43,8 @@
       <div v-if="workflowStep === 1" class="setup-stage simplified-setup">
         <div class="stage-intro">
           <div>
-            <div class="stage-title">告诉系统这节课怎么上</div>
-            <div class="stage-desc">填写必要信息即可,模板和素材由系统自动匹配。</div>
+            <div class="stage-title">设置这节课的基本需求</div>
+            <div class="stage-desc">只需确认课时和篇幅,样式、素材与质量检查由系统完成。</div>
           </div>
           <span v-if="outline" class="saved-state">已有大纲,可继续确认</span>
         </div>
@@ -105,60 +105,48 @@
       </div>
 
       <div v-if="workflowStep === 3 && draftConverted" class="result-preview">
-        <div class="preview-head">
+        <div :class="['draft-status', draftPreparationState]">
+          <span class="draft-status-icon">{{ draftPreparationState === "ready" ? "✓" : draftPreparationState === "failed" ? "!" : "" }}</span>
           <div>
-            <div class="preview-title">全稿预览</div>
-            <div class="preview-sub">共{{ draftConverted.slides.length }}页,确认后才会替换当前课件</div>
-          </div>
-          <div :class="['quality-badge', { passed: draftQuality?.passed }]">
-            {{ draftQuality?.passed ? "可以直接使用" : `还有${draftQuality?.errors.length || 0}页需要优化` }}
-          </div>
-        </div>
-        <div v-if="draftQuality?.issues.length" class="quality-list">
-          <div
-            v-for="issue in draftQuality.issues"
-            :key="`${issue.code}-${issue.pageNo || 0}-${issue.message}`"
-            :class="['quality-item', issue.severity]"
-          >
-            <span>{{ issue.severity === "error" ? "阻止应用" : "建议检查" }}</span>
-            <span>{{ issue.pageNo ? `第${issue.pageNo}页:` : "" }}{{ issue.message }}</span>
+            <strong>{{ draftStatusTitle }}</strong>
+            <span>{{ draftStatusDescription }}</span>
           </div>
         </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>
-        </div>
-        <div v-if="selectedDraftPage" class="selected-draft-actions">
-          <div>
-            <span>当前页面</span><strong>第{{ selectedDraftPageIndex + 1 }}页 · {{ selectedDraftPage.title }}</strong>
+
+        <div class="draft-workspace">
+          <div class="draft-page-list" aria-label="课件页面">
+            <button
+              v-for="(slide, index) in draftConverted.slides"
+              :key="slide.id"
+              type="button"
+              :class="['draft-page-item', { active: selectedDraftPageIndex === index }]"
+              @click="selectedDraftPageIndex = index"
+            >
+              <ThumbnailSlide :slide="slide" :size="148" />
+              <span>{{ formatPageNo(index + 1) }} · {{ draftOutline?.pages[index]?.title || "未命名页面" }}</span>
+            </button>
           </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 v-if="selectedDraftSlide" class="draft-preview-pane">
+            <div class="draft-preview-toolbar">
+              <div>
+                <span>第{{ selectedDraftPageIndex + 1 }} / {{ draftConverted.slides.length }}页</span>
+                <strong>{{ selectedDraftPage?.title || "课件页面" }}</strong>
+              </div>
+              <button type="button" :disabled="working" @click="regenerateDraftPage(selectedDraftPageIndex)">重新生成本页</button>
+            </div>
+            <div class="draft-canvas-shell">
+              <ThumbnailSlide :slide="selectedDraftSlide" :size="680" />
+            </div>
           </div>
         </div>
-        <div class="preview-grid">
-          <button
-            v-for="(slide, index) in draftConverted.slides"
-            :key="slide.id"
-            type="button"
-            :class="['preview-card', { active: selectedDraftPageIndex === index }]"
-            @click="selectedDraftPageIndex = index"
-          >
-            <ThumbnailSlide :slide="slide" :size="220" />
-            <div class="preview-page-no">{{ formatPageNo(index + 1) }}</div>
-          </button>
-        </div>
       </div>
 
       <div v-else-if="workflowStep === 2 && outline" class="outline">
         <div class="sample-stage-head">
           <div>
-            <strong>确认课件的整体样式</strong>
-            <span>当前模板:{{ selectedTemplate.name }}。查看三类代表页面,一次确认即可。</span>
+            <strong>选择喜欢的课件样式</strong>
+            <span>已推荐“{{ selectedTemplate.name }}”,查看代表页面后可直接生成完整课件。</span>
           </div>
           <button type="button" :disabled="working" @click="templatePickerExpanded = !templatePickerExpanded">
             {{ templatePickerExpanded ? "收起模板" : "更换模板" }}
@@ -210,7 +198,7 @@
             <div class="sample-hint">{{ samplePageReason }}</div>
             <div v-if="sampleBackgroundState.status === 'error'" class="template-load-error">模板背景加载失败,请更换模板或稍后重试</div>
             <div class="sample-feedback">
-              <div class="feedback-label">这一页哪里需要调整?</div>
+              <div class="feedback-label">不满意?告诉系统怎么调整</div>
               <div class="feedback-chips">
                 <button
                   v-for="item in sampleFeedbackOptions"
@@ -287,17 +275,19 @@
         <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
           >
         </template>
         <template v-else>
-          <Button @click="backToOutline" :disabled="working">返回调整</Button>
-          <Button type="primary" @click="applyDraft" :disabled="working || !draftQuality?.passed">确认并应用到当前课件</Button>
+          <Button v-if="draftPreparationState !== 'optimizing'" @click="backToOutline">返回调整</Button>
+          <span v-if="draftPreparationState === 'optimizing'" class="footer-progress">正在准备可用课件…</span>
+          <Button v-else-if="draftPreparationState === 'failed'" type="primary" @click="retryAutomaticOptimization">重新自动优化</Button>
+          <Button v-else-if="draftPreparationState === 'ready'" type="primary" @click="applyDraft">应用到当前课件</Button>
         </template>
       </div>
     </div>
@@ -357,9 +347,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
@@ -390,11 +380,22 @@ const sampleFeedbackText = ref("")
 const draftOutline = ref<AiCoursewareOutline>()
 const draftConverted = ref<{ title: string; theme: Partial<SlideTheme>; slides: Slide[] }>()
 const draftQuality = ref<AiPptQualityReport>()
+const draftPreparationState = ref<"idle" | "optimizing" | "ready" | "failed">("idle")
+const automaticRepairAttempts = ref(0)
+const MAX_AUTOMATIC_REPAIR_ATTEMPTS = 1
+const TEACHER_VISIBLE_PATTERN = /(?:教师|老师)(?:讲述|提问|引导|指导|组织|巡视|示范)|(?:引导|指导|组织)学生/
+const PLACEHOLDER_PATTERN = /(?:待补充|待完善|请输入|可参考教材|placeholder|lorem ipsum|\{\{.+?\}\}|\[.+?占位.+?\])/i
 const pollTimer = ref<number>()
 const selectedPageNo = ref(1)
 const selectedDraftPageIndex = ref(0)
 const outlineLoading = ref(false)
-const working = computed(() => outlineLoading.value || taskInfo.value?.status === "PENDING" || taskInfo.value?.status === "RUNNING")
+const working = computed(
+  () =>
+    outlineLoading.value ||
+    draftPreparationState.value === "optimizing" ||
+    taskInfo.value?.status === "PENDING" ||
+    taskInfo.value?.status === "RUNNING"
+)
 const selectedTemplate = computed(() => getAiCoursewareTemplate(selectedTemplateId.value))
 const selectedStyle = computed(() => selectedTemplate.value.baseStyle)
 const templateRecommendation = computed(() =>
@@ -488,6 +489,17 @@ const representativeSamples = computed(() => {
   return selected.slice(0, 3).map((item, index) => ({ ...item, order: index + 1 }))
 })
 const selectedDraftPage = computed(() => draftOutline.value?.pages[selectedDraftPageIndex.value])
+const selectedDraftSlide = computed(() => draftConverted.value?.slides[selectedDraftPageIndex.value])
+const draftStatusTitle = computed(() => {
+  if (draftPreparationState.value === "ready") return "课件已准备完成"
+  if (draftPreparationState.value === "failed") return "部分页面还未处理完成"
+  return "正在自动检查和优化课件"
+})
+const draftStatusDescription = computed(() => {
+  if (draftPreparationState.value === "ready") return "系统已完成内容、排版和教学可用性检查,可以直接预览并应用。"
+  if (draftPreparationState.value === "failed") return "无需逐项修改,点击重新自动优化即可继续。"
+  return "正在清理重复内容、调整版式并修复问题页面,请稍候。"
+})
 const requiredSamplePages = computed(() => representativeSamples.value.map(item => item.pageNo))
 const allSamplesApproved = computed(
   () => requiredSamplePages.value.length === 3 && requiredSamplePages.value.every(pageNo => approvedSamplePages.value.includes(pageNo))
@@ -584,6 +596,8 @@ async function handleGenerate() {
 
   draftConverted.value = undefined
   draftQuality.value = undefined
+  draftPreparationState.value = "idle"
+  automaticRepairAttempts.value = 0
   sourceOutline.style = selectedStyle.value
   sourceOutline.templateId = selectedTemplateId.value
   taskMode.value = "full"
@@ -644,7 +658,13 @@ async function fetchTask(taskId: string) {
     clearPollTimer()
     const message = taskInfo.value.message || "AI课件生成失败"
     clearTask()
-    ElMessage.error(message)
+    if (workflowStep.value === 3) {
+      draftPreparationState.value = "failed"
+      console.error("AI课件自动优化失败", message)
+      ElMessage.error("自动优化未完成,请重新尝试")
+    } else {
+      ElMessage.error(message)
+    }
   }
 }
 
@@ -670,12 +690,10 @@ async function loadDraft(taskId: string) {
   const result = res.data as AiCoursewareGenerateResult
   draftOutline.value = result.outline
   draftOutline.value.templateId = selectedTemplateId.value
-  rebuildDraftPreview()
   selectedDraftPageIndex.value = 0
   workflowStep.value = 3
   clearTask()
-  if (draftQuality.value?.passed) ElMessage.success("课件草稿已生成,请预览后确认应用")
-  else ElMessage.error(`课件草稿存在${draftQuality.value?.errors.length || 0}项阻止应用的问题`)
+  await prepareDraftForUse()
 }
 
 async function loadSampleResult(taskId: string) {
@@ -711,53 +729,24 @@ function rebuildDraftPreview() {
   draftQuality.value = inspectAiPptQuality(draftOutline.value, draftConverted.value.slides, preferences)
 }
 
-function compactDraftPage(index: number) {
-  const page = draftOutline.value?.pages[index]
-  if (!page) return
-  const compact = (value: string | undefined, limit: number) =>
-    String(value || "")
-      .replace(/\s+/g, " ")
-      .trim()
-      .slice(0, limit)
-  if (page.studentContent) {
-    page.studentContent.headline = compact(page.studentContent.headline, 18)
-    page.studentContent.points = [...new Set(page.studentContent.points || [])]
-      .slice(0, 3)
-      .map(item => compact(item, 20))
-      .filter(Boolean)
-    page.bullets = [...page.studentContent.points]
-  }
-  if (page.activity) {
-    page.activity.task = compact(page.activity.task, 22)
-    page.activity.steps = [...new Set(page.activity.steps || [])]
-      .slice(0, 3)
-      .map(item => compact(item, 20))
-      .filter(Boolean)
-  }
-  rebuildDraftPreview()
-}
-
-function removeDraftImage(index: number) {
-  const page = draftOutline.value?.pages[index]
-  if (!page) return
-  page.image = undefined
-  page.resourceBindings = (page.resourceBindings || []).filter(binding => binding.resourceType !== "IMG")
-  rebuildDraftPreview()
-}
-
 async function regenerateDraftPage(index: number) {
   if (!draftOutline.value || !pptWork.id) return
+  draftPreparationState.value = "optimizing"
+  automaticRepairAttempts.value = 0
   taskMode.value = "full"
   const res = await httpAjaxErrMsg(aiCoursewareGenerateApi, {
     id: pptWork.id,
     fromType: queryParams.pptResourcesType || queryParams.fromType,
     outline: draftOutline.value,
     templateId: selectedTemplateId.value,
-    visualPrompt: buildVisualPrompt(),
+    visualPrompt: `${buildVisualPrompt()}请重新生成本页并主动修正学生可见内容、文字密度和版式问题。`,
     preferences: { ...preferences },
     regeneratePageNo: index + 1
   })
-  if (res.code !== 200) return
+  if (res.code !== 200) {
+    draftPreparationState.value = "failed"
+    return
+  }
   taskInfo.value = res.data as AiCoursewareTaskInfo
   await pollTask(taskInfo.value.taskId)
 }
@@ -779,6 +768,8 @@ function backToOutline() {
   draftOutline.value = undefined
   draftConverted.value = undefined
   draftQuality.value = undefined
+  draftPreparationState.value = "idle"
+  automaticRepairAttempts.value = 0
   workflowStep.value = 2
 }
 
@@ -871,44 +862,149 @@ function templateFit(templateId: AiCoursewareTemplateId) {
     : { templateId, score: 80, level: "usable" as const, label: "可以使用" as const, reason: "生成大纲后评估" }
 }
 
-function useRecommendedTemplateForDraft() {
-  useRecommendedTemplate()
-  if (draftOutline.value) {
-    draftOutline.value.templateId = selectedTemplateId.value
-    rebuildDraftPreview()
+async function prepareDraftForUse() {
+  if (!draftOutline.value) return
+  draftPreparationState.value = "optimizing"
+  normalizeDraftLocally()
+  rebuildDraftPreview()
+
+  if (draftQuality.value?.passed) {
+    draftPreparationState.value = "ready"
+    ElMessage.success("课件已完成自动检查,可以直接应用")
+    return
   }
+
+  console.warn("AI课件自动检查发现待修复项", draftQuality.value?.errors)
+  if (automaticRepairAttempts.value >= MAX_AUTOMATIC_REPAIR_ATTEMPTS) {
+    draftPreparationState.value = "failed"
+    return
+  }
+
+  automaticRepairAttempts.value += 1
+  await requestAutomaticRepair()
 }
 
-function autoOptimizeDraft() {
+async function requestAutomaticRepair() {
+  if (!draftOutline.value || !pptWork.id || !draftQuality.value) {
+    draftPreparationState.value = "failed"
+    return
+  }
+  const repairSummary = draftQuality.value.errors
+    .slice(0, 12)
+    .map(issue => `${issue.pageNo ? `第${issue.pageNo}页` : "整套课件"}:${issue.message}`)
+    .join(";")
+  taskMode.value = "full"
+  const res = await httpAjaxErrMsg(aiCoursewareGenerateApi, {
+    id: pptWork.id,
+    fromType: queryParams.pptResourcesType || queryParams.fromType,
+    outline: draftOutline.value,
+    templateId: selectedTemplateId.value,
+    visualPrompt: `${buildVisualPrompt()}系统检查发现以下问题,请在保持教学目标和页数不变的前提下全部修正:${repairSummary}。`,
+    preferences: { ...preferences }
+  })
+  if (res.code !== 200) {
+    draftPreparationState.value = "failed"
+    return
+  }
+  taskInfo.value = res.data as AiCoursewareTaskInfo
+  await pollTask(taskInfo.value.taskId)
+}
+
+async function retryAutomaticOptimization() {
+  automaticRepairAttempts.value = 0
+  await prepareDraftForUse()
+}
+
+function normalizeDraftLocally() {
   if (!draftOutline.value) return
+  const imageUseCounts = new Map<string, number>()
   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)
-      )
-      .filter(Boolean)
-      .slice(0, 4)
     if (!page.studentContent) page.studentContent = { headline: "", points: [] }
+
+    const movedTeacherNotes: string[] = []
+    const headline = cleanDraftText(page.studentContent.headline, 24)
+    if (isTeacherOnlyText(headline)) movedTeacherNotes.push(headline)
+    page.studentContent.headline = isTeacherOnlyText(headline) ? "" : headline
+
+    const excluded = new Set([page.title, page.studentContent.headline].map(normalizeDraftText).filter(Boolean))
+    const pointKeys = new Set<string>()
+    const points = (page.studentContent.points?.length ? page.studentContent.points : page.bullets || [])
+      .map(item => cleanDraftText(item, 22))
+      .filter(item => {
+        if (!item) return false
+        if (isTeacherOnlyText(item)) {
+          movedTeacherNotes.push(item)
+          return false
+        }
+        const key = normalizeDraftText(item)
+        if (!key || excluded.has(key) || pointKeys.has(key)) return false
+        pointKeys.add(key)
+        return true
+      })
+      .slice(0, 4)
     page.studentContent.points = points
     page.bullets = [...points]
     page.visualPlan.visualDensity = points.length <= 2 ? "low" : points.length >= 4 ? "high" : "medium"
+
+    if (movedTeacherNotes.length) {
+      if (!page.activity) page.activity = { task: "", steps: [] }
+      page.activity.steps = uniqueDraftTexts([...(page.activity.steps || []), ...movedTeacherNotes])
+    }
+
+    if (!page.classroomAction) {
+      page.classroomAction = { teacherInstruction: "", studentTask: page.studentContent.headline || page.title, interactionType: "" }
+    }
+    const studentTask = cleanDraftText(page.classroomAction.studentTask, 24)
+    page.classroomAction.studentTask = !studentTask || isTeacherOnlyText(studentTask) ? page.studentContent.headline || page.title : studentTask
+
     const layout = page.visualPlan.layout || ""
     if (["hero_focus", "media_focus", "split_content", "diagram_focus"].includes(layout)) {
       page.visualPlan.focalContent ||= page.title
       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: "" }
-      page.classroomAction.teacherInstruction = `引导学生完成“${page.classroomAction.studentTask || page.title}”,根据学生的语言、动作或声音表现及时反馈。`
+
+    if (page.image?.url) {
+      const useCount = (imageUseCounts.get(page.image.url) || 0) + 1
+      imageUseCounts.set(page.image.url, useCount)
+      if (useCount > 2) {
+        page.image = undefined
+        page.resourceBindings = (page.resourceBindings || []).filter(binding => binding.resourceType !== "IMG")
+      }
+    }
+
+    if (index > 0) {
+      const fallbackInstruction = `引导学生完成“${page.classroomAction.studentTask || page.title}”,根据学生的语言、动作或声音表现及时反馈。`
+      const instruction = cleanDraftText(page.classroomAction.teacherInstruction || page.teacherTips || page.speakerNotes) || fallbackInstruction
+      page.classroomAction.teacherInstruction = instruction
+      page.teacherTips ||= instruction
+      page.speakerNotes ||= instruction
     }
   })
-  rebuildDraftPreview()
-  ElMessage.success("已按投屏字号、内容密度、主视觉和教师备注重新优化全稿")
+}
+
+function cleanDraftText(value?: string, limit?: number) {
+  const cleaned = String(value || "").replace(/\s+/g, " ").trim()
+  return limit ? cleaned.slice(0, limit) : cleaned
+}
+
+function normalizeDraftText(value?: string) {
+  return cleanDraftText(value).replace(/[,。!?、;:,.!?;:\s]+/g, "").toLowerCase()
+}
+
+function uniqueDraftTexts(values: string[]) {
+  const keys = new Set<string>()
+  return values.filter(value => {
+    const key = normalizeDraftText(value)
+    if (!key || keys.has(key)) return false
+    keys.add(key)
+    return true
+  })
+}
+
+function isTeacherOnlyText(value?: string) {
+  const text = cleanDraftText(value)
+  return Boolean(text && (TEACHER_VISIBLE_PATTERN.test(text) || PLACEHOLDER_PATTERN.test(text)))
 }
 
 function updateStudentHeadline(page: AiCoursewarePage, event: Event) {
@@ -1848,137 +1944,123 @@ function shortProgressLabel(message?: string) {
   min-height: 0;
   flex: 1;
   margin-top: 14px;
-  overflow: auto;
-}
-.preview-head {
   display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-.preview-title {
-  font-size: 18px;
-  font-weight: 700;
-}
-.preview-sub {
-  margin-top: 4px;
-  color: #7b8794;
-  font-size: 12px;
-}
-.quality-badge {
-  padding: 7px 12px;
-  border-radius: 999px;
-  background: #fff1f0;
-  color: #cf1322;
-  font-size: 12px;
-  font-weight: 700;
-}
-.quality-badge.passed {
-  background: #eaf7f4;
-  color: #168f86;
-}
-.quality-list {
-  margin-top: 12px;
-  border: 1px solid #edf1f5;
-  border-radius: 8px;
+  flex-direction: column;
+  overflow: hidden;
 }
-.quality-actions {
+.draft-status {
   display: flex;
-  gap: 8px;
-  margin-top: 10px;
-  button {
-    padding: 7px 11px;
-    border: 1px solid #d9e2ec;
-    border-radius: 6px;
-    background: #fff;
-    color: $themeColor;
-    cursor: pointer;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 14px;
+  border: 1px solid #dce8f7;
+  border-radius: 10px;
+  background: #f6f9fe;
+  color: #35516f;
+  &.ready {
+    border-color: #cce8df;
+    background: #f0faf7;
+    color: #16776f;
+  }
+  &.failed {
+    border-color: #f3d7b2;
+    background: #fff8ee;
+    color: #9a5b08;
   }
-}
-.quality-scores {
-  display: grid;
-  grid-template-columns: repeat(4, minmax(0, 1fr)) auto auto;
-  gap: 8px;
-  margin-top: 12px;
   > div {
-    padding: 9px 10px;
-    border: 1px solid #e5eaf0;
-    border-radius: 7px;
-    background: #fafcff;
+    min-width: 0;
+    flex: 1;
   }
+  strong,
   span {
     display: block;
-    color: #7b8794;
-    font-size: 11px;
   }
   strong {
-    display: block;
-    margin-top: 2px;
     color: #1f2933;
-    font-size: 18px;
+    font-size: 14px;
   }
-  button {
-    padding: 0 12px;
-    border: 1px solid #d9e2ec;
-    border-radius: 7px;
-    background: #fff;
-    color: #2563eb;
-    cursor: pointer;
+  > div > span {
+    margin-top: 3px;
+    font-size: 12px;
   }
 }
-.quality-item {
-  display: grid;
-  grid-template-columns: 70px 1fr;
-  gap: 10px;
-  padding: 8px 10px;
-  font-size: 12px;
-}
-.quality-item + .quality-item {
-  border-top: 1px solid #edf1f5;
-}
-.quality-item.error > span:first-child {
-  color: #cf1322;
-  font-weight: 700;
-}
-.quality-item.warning > span:first-child {
-  color: #d97706;
-  font-weight: 700;
+.draft-status-icon {
+  width: 28px;
+  height: 28px;
+  flex-shrink: 0;
+  display: inline-flex !important;
+  align-items: center;
+  justify-content: center;
+  border: 2px solid currentColor;
+  border-radius: 50%;
+  font-size: 15px !important;
+  font-weight: 800;
+  .optimizing & {
+    border-color: #c6d7ee;
+    border-top-color: $themeColor;
+    animation: loading-rotate 0.8s linear infinite;
+  }
 }
-.preview-grid {
+.draft-workspace {
+  min-height: 0;
+  flex: 1;
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
-  gap: 14px;
-  margin-top: 14px;
+  grid-template-columns: 170px minmax(0, 1fr);
+  gap: 16px;
+  margin-top: 12px;
 }
-.preview-card {
-  position: relative;
-  width: 220px;
-  padding: 0;
-  overflow: hidden;
-  border: 1px solid #e5eaf0;
-  border-radius: 6px;
-  background: #fff;
+.draft-page-list {
+  min-height: 0;
+  overflow-y: auto;
+  padding: 3px 7px 3px 3px;
+}
+.draft-page-item {
+  width: 100%;
+  padding: 6px;
+  border: 1px solid transparent;
+  border-radius: 8px;
+  background: transparent;
+  color: #52616b;
   text-align: left;
   cursor: pointer;
-  box-shadow: 0 4px 12px rgba(31, 41, 51, 0.08);
+  transition: border-color 0.16s ease, background 0.16s ease;
+  + .draft-page-item {
+    margin-top: 7px;
+  }
+  &:hover {
+    background: #f5f8fc;
+  }
   &.active {
     border-color: $themeColor;
-    box-shadow: 0 0 0 2px rgba($themeColor, 0.12);
+    background: rgba($themeColor, 0.06);
+    box-shadow: 0 0 0 2px rgba($themeColor, 0.08);
+  }
+  :deep(.thumbnail-slide) {
+    border-radius: 5px;
+    box-shadow: 0 3px 10px rgba(31, 41, 51, 0.1);
+  }
+  > span {
+    display: block;
+    margin-top: 6px;
+    overflow: hidden;
+    font-size: 11px;
+    line-height: 1.4;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
 }
-.selected-draft-actions {
-  position: sticky;
-  top: 0;
-  z-index: 2;
+.draft-preview-pane {
+  min-width: 0;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+.draft-preview-toolbar {
   display: flex;
   align-items: center;
   justify-content: space-between;
   gap: 14px;
-  margin-top: 12px;
-  padding: 10px 12px;
-  border: 1px solid #dbe7f5;
-  border-radius: 8px;
-  background: #f7faff;
-  box-shadow: 0 4px 12px rgba(31, 41, 51, 0.06);
+  margin-bottom: 8px;
   span,
   strong {
     display: block;
@@ -1989,15 +2071,11 @@ function shortProgressLabel(message?: string) {
   }
   strong {
     margin-top: 2px;
-    font-size: 13px;
-  }
-  > div:last-child {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 6px;
+    color: #1f2933;
+    font-size: 14px;
   }
   button {
-    padding: 6px 9px;
+    padding: 7px 11px;
     border: 1px solid #d9e2ec;
     border-radius: 6px;
     background: #fff;
@@ -2005,6 +2083,24 @@ function shortProgressLabel(message?: string) {
     cursor: pointer;
   }
 }
+.draft-canvas-shell {
+  min-width: 0;
+  min-height: 0;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: auto;
+  padding: 18px;
+  border: 1px solid #e2e8f0;
+  border-radius: 10px;
+  background: #eef2f7;
+  :deep(.thumbnail-slide) {
+    flex-shrink: 0;
+    border-radius: 7px;
+    box-shadow: 0 14px 34px rgba(31, 41, 51, 0.16);
+  }
+}
 .page-actions {
   display: grid;
   gap: 7px;
@@ -2037,16 +2133,6 @@ function shortProgressLabel(message?: string) {
     opacity: 0.5;
   }
 }
-.preview-page-no {
-  position: absolute;
-  right: 6px;
-  bottom: 6px;
-  padding: 2px 5px;
-  border-radius: 4px;
-  background: rgba(31, 41, 51, 0.72);
-  color: #fff;
-  font-size: 11px;
-}
 .page-nav {
   overflow: auto;
   border: 1px solid #edf1f5;
@@ -2281,6 +2367,20 @@ function shortProgressLabel(message?: string) {
   background: #fff;
   justify-content: flex-end;
   gap: 10px;
+  :deep(.button.primary) {
+    min-width: 154px;
+    height: 40px;
+    line-height: 40px;
+    font-size: 14px;
+    font-weight: 700;
+  }
+}
+.footer-progress {
+  min-width: 154px;
+  color: $themeColor;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
 }
 
 @media (max-width: 900px) {
@@ -2304,9 +2404,22 @@ function shortProgressLabel(message?: string) {
   .compact-template-panel .sample-template-picker {
     grid-template-columns: repeat(2, minmax(0, 1fr));
   }
-  .selected-draft-actions {
-    align-items: flex-start;
-    flex-direction: column;
+  .draft-workspace {
+    grid-template-columns: 1fr;
+  }
+  .draft-page-list {
+    display: flex;
+    gap: 8px;
+    overflow-x: auto;
+    overflow-y: hidden;
+    padding: 3px 3px 8px;
+  }
+  .draft-page-item {
+    width: 160px;
+    flex-shrink: 0;
+    + .draft-page-item {
+      margin-top: 0;
+    }
   }
 }
 
@@ -2325,7 +2438,8 @@ function shortProgressLabel(message?: string) {
   .compact-template-panel .sample-template-picker {
     grid-template-columns: 1fr;
   }
-  .preview-head,
+  .draft-status,
+  .draft-preview-toolbar,
   .sample-stage-head {
     align-items: flex-start;
     flex-direction: column;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików