From dfef295196d774c754fa3352518b259640c7ddac Mon Sep 17 00:00:00 2001 From: maogeigei Date: Thu, 3 Sep 2026 18:41:35 +0800 Subject: [PATCH] =?UTF-8?q?R40-R41:=20=E5=BC=B9=E7=AA=97=20tab=20=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E9=97=AA=E7=83=81=E6=A0=B9=E6=B2=BB=EF=BC=88=E5=B0=B1?= =?UTF-8?q?=E5=9C=B0=E5=B1=80=E9=83=A8=E5=88=B7=E6=96=B0=EF=BC=89+=20?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=E8=AF=8A=E6=96=AD=E6=96=87=E6=A1=88=20+=20ta?= =?UTF-8?q?b=20=E9=97=B4=20|=20=E5=88=86=E9=9A=94=EF=BC=9B=E5=8F=B3?= =?UTF-8?q?=E4=BE=A7=E8=AF=8A=E6=96=AD/=E5=88=86=E9=95=9C=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E5=8F=AF=E6=94=B6=E8=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R40 弹窗闪烁根因:所有状态切换 mask.remove()+renderModal() 整窗销毁重建,重建前 await review-full 网络 → 空窗闪一下 - 重构 openScriptModal:mask 生命周期内不再移除,tab/分镜翻版=patchSide 就地刷右侧列、版本翻版=patchAll 刷 AI+右侧列、源折叠=纯 CSS - sideContent 抽离(stab=diag 才请求复盘接口);空态按钮开始复盘→开始诊断 - sideToken 防连点竞态 + mask.isConnected 防关闭后写入;bindAll/bindSide/bindScrollSync 分层绑定 - 脚本诊断 | 分镜提示词 两 tab 间加 .rw-side-sep 分隔 R41 右侧面板收起:data-side-toggle 收起钮放 AI 列 head 最右(右列收起头部不可见,入口常驻) - showSide 状态 + rw-sec-hidden 复用;收起后 AI 列占满 stack 100%;chevrons-right/left 图标 - 版本 style.css aa→ac / data-pages.js ab→ad / app.js ICON_PATHS +2 - CDP 冒烟:R40 16/16 + R41 17/17 全绿(mask 同一节点=不闪验证)+ R40 回归复跑 16/16 --- .workbuddy/memory/2026-09-03.md | 28 +++ .../V1.0/mcn-work-shop/public/app.js | 2 + .../V1.0/mcn-work-shop/public/data-pages.js | 207 +++++++++++------- .../V1.0/mcn-work-shop/public/index.html | 4 +- .../V1.0/mcn-work-shop/public/style.css | 5 + 5 files changed, 167 insertions(+), 79 deletions(-) diff --git a/.workbuddy/memory/2026-09-03.md b/.workbuddy/memory/2026-09-03.md index ab77412..743bcee 100644 --- a/.workbuddy/memory/2026-09-03.md +++ b/.workbuddy/memory/2026-09-03.md @@ -392,3 +392,31 @@ - **SIGTERM 间歇**:单脚本含 Page.reload+Page.navigate+hash 多次切换时偶发被沙箱 SIGTERM 杀死 node 进程——规避=**拆段独立连接**,单脚本只做一件事;首页跳转 hash 用 location.hash=赋值(Runtime.evaluate)而非 Page.navigate - **视觉目检** `tmp/shot-r39-brand.png`:顶部 brand `MCN创意雷达` ✓;首页榜单 tab 组(账号周榜/视频热榜/点赞榜)+ globe 按钮 + 筛选箭头完全水平对齐,globe 图标浅灰 ✓ - **R39 待推送**:本地 HEAD=28eba2a(R37-R38 已提交),working tree R39 6 文件改动 + 记忆日志 R39 节未提交;累计本地领先 origin/main **4 commit**(449d784 R35 / 1f147e8 R36 / 28eba2a R37-38 / R39 待提交) + +## R40 弹窗 tab 切换闪烁根治 + 开始诊断文案 + tab 间 | 分隔(09-03 18:10) +- **用户反馈**:「为什么点击 脚本诊断 和 分镜提示词 整个弹窗页面都会闪一下。开始复盘 改为 开始诊断。脚本诊断 和 分镜提示词 切换按钮 之间加个 |」 +- **闪烁根因**:R34 弹窗所有状态切换(tab/版本/源折叠/分镜上下版)都是 `mask.remove(); renderModal();` **整窗销毁重建**;而 renderModal async 在重建前必须 `await diagOf()`(网络请求 `/api/dsh/review-full`)→ 弹窗被删后要等接口返回才重新 append → 空窗几百 ms = 用户看到的「闪一下」 +- **修复(架构级,openScriptModal 内重构)**:弹窗 DOM(mask)生命周期内**不再移除**,仅就地替换受影响列: + - `curOf()` / `verCtrl()` / `sideHeadHtml()` / `srcToggleHtml()` / `srcSecHtml()` / `aiSecHtml()` 抽成独立 HTML 工厂 + - `sideContent(cur)`:右侧列内容计算(**stab=diag 才请求 review-full**——原实现无论什么 tab 都查,浪费);空态按钮文案「开始复盘」→「**开始诊断**」 + - `patchSide(mask)`:就地刷新右侧列(tab 切换/分镜上下版)——active 即时切换 + body「加载中…」→ await sideContent → 填充;`sideToken` 递增防连点竞态、`mask.isConnected` 防已关闭后写入 + - `patchAll(mask)`:就地刷新 AI 列+右侧列(版本上/下一版) + - `bindAll/bindSide/bindScrollSync`:动态节点绑定分层——mask 外壳 data-x/背景关闭只绑一次;stack 内节点每次重建后重绑(旧节点随 innerHTML 替换销毁,无重复监听) + - 源折叠 `[data-src-toggle]` 改**纯 CSS class toggle**(不再重建) +- **tab 间 `|` 分隔**:sideHeadHtml 两按钮间插 `|`;CSS `.rw-side-sep { color: var(--border); font-size: 14px; user-select:none }` +- **版本**:data-pages.js ab→ac、style.css aa→ab(app.js 不动) +- **CDP 冒烟** `tmp/cdp-r40-modal.mjs` **16/16 PASS**:| 分隔在两 tab 之间 ✓ / 默认 tab=diag ✓ / 按钮「开始诊断」且零「开始复盘」残留(源码级)✓ / **切分镜、切回诊断、版本下一版全程 mask 同一节点未重建(不闪)** ✓ / 分镜内容已填充 ✓ +- **弹窗行为定版**:tab 切换/分镜翻版/版本翻版/源折叠全部就地刷新 → 无空窗闪烁;仅初次打开 await 数据齐后一次 append + +## R41 弹窗右侧面板可收起(只展示 AI 脚本)(09-03 18:25) +- **用户反馈**:「添加功能 可以收起 脚本复盘 | 分镜提示词 内容板块,只展示AI脚本」 +- **设计**:右侧「脚本诊断|分镜提示词」列可整体收起 → display:none → AI 列 flex:1 自动占满内容区(源列默认收起时即「只展示 AI 脚本」) + - 状态 `showSide`(默认 true);右侧列 rw-sec 挂 `data-side-sec` + 按需 `rw-sec-hidden`(复用 R34 源列收起类) + - **收起钮位置关键决策**:放 **AI 列 head 最右**(`margin-left:auto`)而非右列自身 head——右列收起后自身头部不可见,展开入口必须常驻 AI 列;`.rw-side-toggle`(chevrons-right=收起/chevrons-left=展开,收起态 `side-off` 高亮提示可点开) + - app.js ICON_PATHS 新增 `chevrons-right`/`chevrons-left`(Lucide 双箭头);改 app.js 后版本也要 bump +- **代码**:data-pages.js(showSide 状态、sideToggleHtml、bindAll 内 data-side-toggle 绑定、renderModal 初渲染按 showSide 加 class);style.css `.rw-side-toggle`(margin-left:auto + hover 蓝 + side-off 高亮) +- **版本**:style.css ab→ac、data-pages.js ac→ad +- **CDP 冒烟** `tmp/cdp-r41-side.mjs` **17/17 PASS**(两轮):收起后右列 display:none、仅 AI 列可见、**AI 宽=stack 宽 100%**、收起钮切展开态高亮 ✓ / 展开恢复 ✓ / 收起过程与展开过程 mask 未重建 ✓ / **收起态下切分镜 tab 正常**、展开后停在分镜 ✓ +- **冒烟断言口径坑(3 FAIL 全为此)**:**源列默认收起 → 打开弹窗初始可见列=2(AI+右侧)不是 3**;AI 宽度占比基准应取 `.rw-stack` 内容宽而非 `.rw-modal` 宽(modal 有 padding 26px×2,占 modal 宽 95% 即已满) +- **回归**:R40 冒烟版本断言更新(ab/ac→ac/ad)后复跑 16/16 全绿——R41 未破坏 R40 就地刷新架构 +- **R40-R41 待提交**:data-pages.js/style.css/app.js/index.html + 记忆日志本节;本地 HEAD=03de8be(R39 已 push origin/main) diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js index e84d915..b4eb3e4 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js @@ -175,6 +175,8 @@ const ICON_PATHS = { 'user-plus': '', 'download': '', 'globe': '', + 'chevrons-right': '', + 'chevrons-left': '', 'copy': '', }; diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js index e5c6a72..af4aa79 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -1650,7 +1650,7 @@ async function openScriptModal({ rewriteId, videoId } = {}) { const title = metaTitle || 'AI 脚本'; const hasSource = vid > 0 && !!source; let stab = 'diag'; /* 右侧 tab:diag=脚本诊断(默认)/ sb=分镜提示词 */ - let showSrc = false, sbIdx = 0; + let showSrc = false, sbIdx = 0, showSide = true; /* R41:showSide=右侧「脚本诊断|分镜提示词」面板是否展开(收起后只展示 AI 脚本) */ const stb = vid > 0 ? await api('/api/dsh/storyboards?videoId=' + vid).catch(() => ({ items: [] })) : { items: [] }; const storyboards = ((stb && stb.items) || []); const visibleSbOf = (curId) => storyboards.filter((s) => s.rewrite_id == null || String(s.rewrite_id) === String(curId)); @@ -1679,7 +1679,7 @@ async function openScriptModal({ rewriteId, videoId } = {}) { btn, }); }; - /* 当前版本诊断(review-full 仅返回已完成复盘;无结果 → null → 空态 + 开始复盘) */ + /* 当前版本诊断(review-full 仅返回已完成复盘;无结果 → null → 空态 + 开始诊断) */ const diagOf = async (cur) => { const rf = await api('/api/dsh/review-full?rewriteId=' + cur.id).catch(() => null); const rv = (rf && (rf.ai || rf.compare)) || null; @@ -1702,30 +1702,43 @@ async function openScriptModal({ rewriteId, videoId } = {}) { return html; }; - async function renderModal() { - const cur = rewrites[Math.min(verIdx, rewrites.length - 1)] || rewrites[0]; - if (!cur) return; - const visibleSb = visibleSbOf(cur.id); + /* ===== R40:弹窗状态切换就地局部刷新(不再 mask.remove() 整窗销毁重建——原实现销毁后需 await 复盘接口才重建,空窗即用户看到的「闪一下」) ===== + 切换类型与刷新范围:tab 脚本诊断/分镜提示词 / 分镜上下版 → 仅右侧列(patchSide);版本上下 → AI列+右侧列(patchAll);源折叠 → 纯 CSS toggle + 弹窗 DOM(mask)生命周期内不被移除,仅替换受影响列内容 → 无整窗闪断 */ + const curOf = () => rewrites[Math.min(verIdx, rewrites.length - 1)] || rewrites[0]; + const verCtrl = (p, idx, len, label, genLabel) => { + const inner = len > 1 + ? `${label} ${idx + 1}/${len}` + : (len === 1 ? `${label} 1/1` : ''); + return `
${inner}${genLabel ? `` : ''}
`; + }; + const sideHeadHtml = () => `
+ | + +
`; + const srcToggleHtml = () => hasSource ? `` : ''; + /* R41:右侧面板收起/展开钮(放 AI 列头部最右——右列收起后自身头部不可见,展开入口必须常驻 AI 列) */ + const sideToggleHtml = () => ``; + const srcSecHtml = () => hasSource + ? `
+
源脚本
+
${renderMarkdown(source)}
+
+
` : ''; + const aiSecHtml = (c) => `
AI脚本${srcToggleHtml()}${sideToggleHtml()}
${renderMarkdown(c.script_text || '(暂无内容)')}
${rewrites.length > 1 ? verCtrl('v', verIdx, rewrites.length, '版本') : ''}`; + /* 右侧列内容计算(diag 空态按钮 R40 改「开始诊断」;stab=diag 才请求复盘接口——原实现无论什么 tab 都查,浪费) */ + async function sideContent(c) { + const visibleSb = visibleSbOf(c.id); const sb = visibleSb[Math.min(sbIdx, visibleSb.length - 1)]; - const dg = await diagOf(cur); - const verCtrl = (p, idx, len, label, genLabel) => { - const inner = len > 1 - ? `${label} ${idx + 1}/${len}` - : (len === 1 ? `${label} 1/1` : ''); - return `
${inner}${genLabel ? `` : ''}
`; - }; - const sideHead = `
- - -
`; let sideBody = '', sideFoot = ''; if (stab === 'diag') { + const dg = await diagOf(c); if (dg) { sideBody = diagHtml(dg); sideFoot = `
`; } else { sideBody = '
该版本 AI 脚本尚未诊断
'; - sideFoot = `
`; + sideFoot = `
`; } } else if (visibleSb.length) { sideBody = sb ? renderSbBlocks(sb.script_text) : '
(暂无分镜提示词)
'; @@ -1737,73 +1750,113 @@ async function openScriptModal({ rewriteId, videoId } = {}) { sideBody = '
该脚本为自定义选题(无关联视频),暂不支持分镜提示词
'; sideFoot = ''; } - const srcSec = hasSource - ? `
-
源脚本
-
${renderMarkdown(source)}
-
-
` : ''; - const srcToggle = hasSource ? `` : ''; + return { sideBody, sideFoot }; + } + const sideSecOf = (mask) => [...mask.querySelectorAll('.rw-stack > .rw-sec')].find((s) => s.querySelector('.rw-side-tabs')); + const aiSecOf = (mask) => [...mask.querySelectorAll('.rw-stack > .rw-sec')].find((s) => { const h = s.querySelector('.rw-sec-head'); return h && h.textContent.trim().startsWith('AI脚本'); }); + /* AI脚本列滚动 ↔ 分镜列场次对齐(仅右侧为「分镜提示词」且有分镜时生效;分镜列滚动不反向联动) */ + const bindScrollSync = (mask) => { + const sideSec = sideSecOf(mask); + const aiSec = aiSecOf(mask); + const aiBody = aiSec && aiSec.querySelector('.rw-sec-body'); + const sbBody = sideSec && sideSec.querySelector('.rw-sec-body'); + if (!(stab === 'sb' && aiBody && sbBody)) return; + const syncSb = () => { + const heads = [...aiBody.querySelectorAll('strong, h1, h2, h3, h4')].filter((el) => /场\s*\d/.test(el.textContent)); + if (!heads.length) return; + const aiTop = aiBody.getBoundingClientRect().top; + const limit = aiBody.scrollTop + 60; /* 视口上缘往下 60px 内最后一个场标题 = 当前场 */ + let curH = heads[0]; + for (const h of heads) { + const t = h.getBoundingClientRect().top - aiTop + aiBody.scrollTop; + if (t <= limit) curH = h; else break; + } + const m = /场\s*(\d+)/.exec(curH.textContent); + if (!m) return; + const target = [...sbBody.querySelectorAll('.sb-block')].find((b) => new RegExp('场' + m[1] + '(?![0-9])').test((b.querySelector('.sb-head') || {}).textContent || '')); + if (target) { + const t = target.getBoundingClientRect().top - sbBody.getBoundingClientRect().top + sbBody.scrollTop; + if (Math.abs(sbBody.scrollTop - t) > 1) sbBody.scrollTop = t; /* 位置相同不重复设置,避免事件环 */ + } + }; + aiBody.addEventListener('scroll', syncSb, { passive: true }); + syncSb(); /* 打开弹窗 / 切入分镜 tab 时先对齐一次 */ + }; + /* 右侧列按钮重绑(diag 更新/开始诊断/完整报告 + 分镜生成/重生成/上下版 + 场次同步)——body/foot 每次局部刷新都是新节点,直接绑定无重复 */ + const bindSide = (mask) => { + const sideSec = sideSecOf(mask); if (!sideSec) return; + const b = (sel, fn) => { const el = sideSec.querySelector(sel); if (el) el.addEventListener('click', fn); }; + b('[data-diag-review]', (e) => fireReview(e.currentTarget)); + b('[data-review]', (e) => reportFullReview(e.currentTarget.dataset.review)); + b('[data-gen-sb]', (e) => fireGenSb(e.currentTarget, false)); + b('[data-sgen]', (e) => fireGenSb(e.currentTarget, true)); + b('[data-sprev]', () => { if (sbIdx > 0) { sbIdx--; patchSide(mask); } }); + b('[data-snext]', () => { const vs = visibleSbOf(curOf().id); if (sbIdx < vs.length - 1) { sbIdx++; patchSide(mask); } }); + bindScrollSync(mask); + }; + /* 就地刷新右侧列(tab 切换 / 分镜上下版):不销毁弹窗,active 即时切换 + body 短暂加载后填充 */ + let sideToken = 0; + const patchSide = async (mask) => { + const sideSec = sideSecOf(mask); if (!sideSec) return; + const c = curOf(); + mask.querySelectorAll('[data-stab]').forEach((x) => x.classList.toggle('active', x.dataset.stab === stab)); + const bodyEl = sideSec.querySelector('.rw-sec-body'); + [...sideSec.children].forEach((ch) => { if (ch.classList && ch.classList.contains('rw-sec-foot')) ch.remove(); }); + if (bodyEl) bodyEl.innerHTML = '
加载中…
'; + const token = ++sideToken; + const { sideBody, sideFoot } = await sideContent(c); + if (!mask.isConnected || token !== sideToken) return; /* 弹窗已关闭 / 期间又切了 tab → 丢弃过期结果 */ + if (bodyEl) bodyEl.innerHTML = sideBody; + if (sideFoot) sideSec.insertAdjacentHTML('beforeend', sideFoot); + bindSide(mask); + }; + /* 就地刷新 AI列 + 右侧列(版本上/下一版):源列保持不动,其余两列整体替换后重绑 */ + const patchAll = async (mask) => { + const c = curOf(); if (!c) return; + const { sideBody, sideFoot } = await sideContent(c); + if (!mask.isConnected) return; + const aiSec = aiSecOf(mask), sideSec = sideSecOf(mask); + if (aiSec) aiSec.innerHTML = aiSecHtml(c); + if (sideSec) sideSec.innerHTML = `${sideHeadHtml()}
${sideBody}
${sideFoot}`; + bindAll(mask); + }; + /* 全部动态节点绑定(每次列内容重建后调用一次;mask 外壳 data-x/背景关闭只在首次绑,不在此重复) */ + const bindAll = (mask) => { + mask.querySelectorAll('[data-stab]').forEach((t) => t.addEventListener('click', async () => { if (stab !== t.dataset.stab) { stab = t.dataset.stab; await patchSide(mask); } })); + const srcT = mask.querySelector('[data-src-toggle]'); + if (srcT) srcT.addEventListener('click', () => { + showSrc = !showSrc; + const sec = mask.querySelector('[data-src-sec]'); if (sec) sec.classList.toggle('rw-sec-hidden', !showSrc); + srcT.classList.toggle('src-on', showSrc); srcT.title = showSrc ? '收起源脚本' : '展开源脚本'; + }); + const sideT = mask.querySelector('[data-side-toggle]'); + if (sideT) sideT.addEventListener('click', () => { + showSide = !showSide; + const sec = sideSecOf(mask); if (sec) sec.classList.toggle('rw-sec-hidden', !showSide); /* R41:右侧面板收起=只展示 AI 脚本(display:none,AI 列 flex:1 自动占满) */ + sideT.classList.toggle('side-off', !showSide); + sideT.title = showSide ? '收起脚本诊断/分镜提示词,只看 AI 脚本' : '展开脚本诊断/分镜提示词'; + sideT.innerHTML = svgIcon(showSide ? 'chevrons-right' : 'chevrons-left', 16, 'currentColor'); + }); + const vp = mask.querySelector('[data-vprev]'); if (vp) vp.addEventListener('click', async () => { if (verIdx > 0) { verIdx--; sbIdx = 0; await patchAll(mask); } }); + const vn = mask.querySelector('[data-vnext]'); if (vn) vn.addEventListener('click', async () => { if (verIdx < rewrites.length - 1) { verIdx++; sbIdx = 0; await patchAll(mask); } }); + bindSide(mask); + }; + async function renderModal() { + const c = curOf(); if (!c) return; + const { sideBody, sideFoot } = await sideContent(c); const mask = document.createElement('div'); mask.className = 'modal-mask'; mask.innerHTML = ``; document.body.appendChild(mask); mask.addEventListener('click', (e) => { if (e.target === mask) mask.remove(); }); - const q = (s) => mask.querySelector(s); - const xb = q('[data-x]'); if (xb) xb.addEventListener('click', () => mask.remove()); - const srcT = q('[data-src-toggle]'); if (srcT) srcT.addEventListener('click', () => { showSrc = !showSrc; mask.remove(); renderModal(); }); - const vp = q('[data-vprev]'); if (vp) vp.addEventListener('click', () => { if (verIdx > 0) { verIdx--; sbIdx = 0; mask.remove(); renderModal(); } }); - const vn = q('[data-vnext]'); if (vn) vn.addEventListener('click', () => { if (verIdx < rewrites.length - 1) { verIdx++; sbIdx = 0; mask.remove(); renderModal(); } }); - mask.querySelectorAll('[data-stab]').forEach((t) => t.addEventListener('click', () => { if (stab !== t.dataset.stab) { stab = t.dataset.stab; mask.remove(); renderModal(); } })); - const sp = q('[data-sprev]'); if (sp) sp.addEventListener('click', () => { if (sbIdx > 0) { sbIdx--; mask.remove(); renderModal(); } }); - const sn = q('[data-snext]'); if (sn) sn.addEventListener('click', () => { if (sbIdx < visibleSb.length - 1) { sbIdx++; mask.remove(); renderModal(); } }); - const genSb = q('[data-gen-sb]'); if (genSb) genSb.addEventListener('click', () => fireGenSb(genSb, false)); - const sgen = q('[data-sgen]'); if (sgen) sgen.addEventListener('click', () => fireGenSb(sgen, true)); - const dgBtn = q('[data-diag-review]'); if (dgBtn) dgBtn.addEventListener('click', () => fireReview(dgBtn)); - const full = q('[data-review]'); if (full) full.addEventListener('click', () => reportFullReview(full.dataset.review)); - /* AI脚本列滚动 ↔ 分镜列场次对齐(仅右侧为「分镜提示词」且有分镜时生效;分镜列滚动不反向联动) */ - if (stab === 'sb' && visibleSb.length) { - const secs = [...mask.querySelectorAll('.rw-stack > .rw-sec')]; - const sideSec = secs.find((s2) => s2.querySelector('.rw-side-tabs')); - const aiSec = secs.find((s2) => s2 !== sideSec && (s2.querySelector('.rw-sec-head') || {}).textContent && (s2.querySelector('.rw-sec-head') || {}).textContent.trim().startsWith('AI脚本')); - const aiBody = aiSec && aiSec.querySelector('.rw-sec-body'); - const sbBody = sideSec && sideSec.querySelector('.rw-sec-body'); - if (aiBody && sbBody) { - const syncSb = () => { - const heads = [...aiBody.querySelectorAll('strong, h1, h2, h3, h4')].filter((el) => /场\s*\d/.test(el.textContent)); - if (!heads.length) return; - const aiTop = aiBody.getBoundingClientRect().top; - const limit = aiBody.scrollTop + 60; /* 视口上缘往下 60px 内最后一个场标题 = 当前场 */ - let curH = heads[0]; - for (const h of heads) { - const t = h.getBoundingClientRect().top - aiTop + aiBody.scrollTop; - if (t <= limit) curH = h; else break; - } - const m = /场\s*(\d+)/.exec(curH.textContent); - if (!m) return; - const target = [...sbBody.querySelectorAll('.sb-block')].find((b) => new RegExp('场' + m[1] + '(?![0-9])').test((b.querySelector('.sb-head') || {}).textContent || '')); - if (target) { - const t = target.getBoundingClientRect().top - sbBody.getBoundingClientRect().top + sbBody.scrollTop; - if (Math.abs(sbBody.scrollTop - t) > 1) sbBody.scrollTop = t; /* 位置相同不重复设置,避免事件环 */ - } - }; - aiBody.addEventListener('scroll', syncSb, { passive: true }); - syncSb(); /* 打开弹窗时先对齐一次 */ - } - } + const xb = mask.querySelector('[data-x]'); if (xb) xb.addEventListener('click', () => mask.remove()); + bindAll(mask); } renderModal(); } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html b/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html index ca2506a..a141ef8 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html @@ -4,7 +4,7 @@ MCN创意雷达 - + @@ -42,6 +42,6 @@ - + diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css index f993017..728e333 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css @@ -567,6 +567,11 @@ td.td-title { max-width: 240px; } .rw-side-tab { border: none; background: transparent; color: var(--dim); font-size: 14px; font-weight: 500; padding: 5px 12px; border-radius: 6px; cursor: pointer; line-height: 1.4; transition: color .15s; } .rw-side-tab:hover { color: var(--primary); } .rw-side-tab.active { color: var(--primary); font-weight: 600; } +.rw-side-sep { color: var(--border); font-size: 14px; line-height: 1; user-select: none; } /* R40:脚本诊断 | 分镜提示词 两 tab 间分隔符 */ +/* R41:右侧「脚本诊断|分镜提示词」面板收起/展开钮(AI 列头部最右;右列收起后头部不可见,入口必须常驻 AI 列) */ +.rw-side-toggle { margin-left: auto; border: none; background: transparent; color: var(--dim); padding: 4px; border-radius: 6px; cursor: pointer; display: inline-flex; align-items: center; line-height: 1; transition: color .15s; } +.rw-side-toggle:hover { color: var(--primary); background: var(--primary-soft); } +.rw-side-toggle.side-off { color: var(--primary); background: var(--primary-soft); } /* 收起态高亮:提示当前右列已收起、可点此展开 */ .src-on { background: #e8f1fd; border-color: #4a90e2; color: #2f6fed; } /* 源脚本展开态高亮 */ .rw-scroll { padding: 4px 2px; max-height: 75vh; overflow: auto; } .sb-block { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; overflow: hidden; background: var(--panel); }