From 8d51df580977dd2f5d8606595d5f2cdc2efd0dce Mon Sep 17 00:00:00 2001 From: maogeigei Date: Thu, 3 Sep 2026 16:15:02 +0800 Subject: [PATCH] =?UTF-8?q?R30:=20=E5=88=97=E8=A1=A8=E9=A1=B5=20toolbar=20?= =?UTF-8?q?=E6=8B=86=E8=A1=8C=20=E2=80=94=20=E6=A0=87=E9=A2=98=E8=A1=8C?= =?UTF-8?q?=E5=8F=AA=E6=94=BE=E8=BF=94=E5=9B=9E+=E6=A0=87=E9=A2=98?= =?UTF-8?q?=EF=BC=8Ctab/=E7=AD=9B=E9=80=89/=E6=90=9C=E7=B4=A2/=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=8C=89=E9=92=AE=E4=B8=80=E5=BE=8B=E6=94=BE=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E4=B8=8B=E4=B8=80=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderAccounts:page-toolbar 内 toolbar-right(类型/赛道下拉 + 搜索 + 添加 + 更新)挪到独立 .acc-toolbar 行 - renderHot:page-toolbar 内 rank-tabs(3 tab) + toolbar-right(赛道/日期)挪到独立 .rank-toolbar 行 - 间距差异化:账号列表 4px(工具栏紧贴标题,功能性组合)/ 热点数据 16px(tab 区段独立感);微调自 -12px 一致间距后按用户反馈拆开 - 冒烟 cdp-verify-r30.mjs PASS:账号列表/热点数据/AI写脚本 三页 toolbar 结构正确(只含返回+标题,功能组件均下移) - 版本 ?v=20260903o 审查结论合规页(标题行仅返回+标题): - renderAccountDetail/VideoDetail/Rewrites(已在 R27 修)/Reviews(pager-info 是统计信息非功能) --- .../V1.0/mcn-work-shop/cdp-verify-r30.mjs | 85 +++++++++++++++++++ .../V1.0/mcn-work-shop/public/data-pages.js | 12 ++- .../V1.0/mcn-work-shop/public/index.html | 6 +- .../V1.0/mcn-work-shop/public/style.css | 4 + 4 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs new file mode 100644 index 0000000..e99699f --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs @@ -0,0 +1,85 @@ +// R30 冒烟:列表页 toolbar — 标题行只放 返回+标题,tab/筛选/搜索/按钮一律放标题下一行 +// 覆盖账号列表 + 热点数据两个明确违规页面 +const list = await fetch('http://localhost:9225/json').then((r) => r.json()); +const page = list.find((t) => t.type === 'page'); +if (!page) { console.log('ERR: no page'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +const waitFor = async (expr, ms = 12000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (await ev(expr)) return true; await sleep(300); } return false; }; +const fail = (m) => { console.log('FAIL@' + m); process.exit(1); }; + +await send('Page.navigate', { url: 'http://localhost:8900/?v=' + Date.now() }); +if (!(await waitFor(`!!document.querySelector('#view')`))) fail('app boot'); +await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove())`); // 清 root 设置弹窗 + +const out = { accounts: {}, hot: {} }; + +/* ============ 账号列表 ============ */ +await ev(`location.hash = '#/accounts'`); +if (!(await waitFor(`document.querySelector('#view .acc-toolbar')`))) fail('accounts toolbar render'); +out.accounts = await ev(`(() => { + const tb = document.querySelector('#view .page > .page-toolbar'); + const acc = document.querySelector('#view .acc-toolbar'); + const tbChildren = tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null; + const tbW = tb ? Math.round(tb.getBoundingClientRect().width) : null; + const tbH = tb ? Math.round(tb.getBoundingClientRect().height) : null; + const accW = acc ? Math.round(acc.getBoundingClientRect().width) : null; + const accIds = acc ? [...acc.querySelectorAll('[id]')].map((e) => e.id) : []; + // 验证 toolbar 与 acc-toolbar 在不同行 + const tbBottom = tb ? tb.getBoundingClientRect().bottom : null; + const accTop = acc ? acc.getBoundingClientRect().top : null; + const verticalGap = (tbBottom != null && accTop != null) ? Math.round(accTop - tbBottom) : null; + return { tbChildren, tbH, accH: acc ? Math.round(acc.getBoundingClientRect().height) : null, accIds, verticalGap, belowTb: verticalGap > 0, hasBackBtn: tb ? !!tb.querySelector('[data-back]') : null }; +})()`); + +/* ============ 热点数据 ============ */ +await ev(`location.hash = '#/ranking'`); +if (!(await waitFor(`document.querySelector('#view .rank-toolbar')`))) fail('hot toolbar render'); +out.hot = await ev(`(() => { + const tb = document.querySelector('#view .page > .page-toolbar'); + const rt = document.querySelector('#view .rank-toolbar'); + const tbChildren = tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null; + const tabs = rt ? [...rt.querySelectorAll('.rank-tab')].map((t) => ({ txt: t.textContent.trim(), active: t.classList.contains('active') })) : []; + const right = rt ? [...rt.querySelectorAll('.toolbar-right [id], .toolbar-right [title]')].map((e) => ({ tag: e.tagName, id: e.id || null, txt: (e.textContent || '').trim().slice(0, 20) })) : []; + const tbBottom = tb ? tb.getBoundingClientRect().bottom : null; + const rtTop = rt ? rt.getBoundingClientRect().top : null; + const verticalGap = (tbBottom != null && rtTop != null) ? Math.round(rtTop - tbBottom) : null; + return { tbChildren, tabs, rightIds: right.map((r) => r.id).filter(Boolean), verticalGap, belowTb: verticalGap > 0 }; +})()`); + +/* ============ 回归:AI写脚本标题行仍合规(只有 返回+标题) ============ */ +await ev(`location.hash = '#/rewrites'`); +if (!(await waitFor(`document.querySelector('#view .rw-tabs')`))) fail('rewrites toolbar render'); +out.rewrites = await ev(`(() => { + const tb = document.querySelector('#view .page > .page-toolbar'); + return { tbChildren: tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null }; +})()`); + +console.log(JSON.stringify(out, null, 1)); + +// 断言 +const A = out.accounts && !out.accounts.__err + && out.accounts.tbChildren && out.accounts.tbChildren.length === 2 + && out.accounts.tbChildren[0].txt === '←' && out.accounts.tbChildren[1].cls.includes('page-title') + && out.accounts.tbChildren.every((c) => !c.cls.includes('toolbar-right')) + && out.accounts.accIds.includes('accType') && out.accounts.accIds.includes('accTrack') && out.accounts.accIds.includes('accSearch') + && out.accounts.accIds.includes('accImport') && out.accounts.accIds.includes('accUpdate') + && out.accounts.belowTb === true; +const H = out.hot && !out.hot.__err + && out.hot.tbChildren && out.hot.tbChildren.length === 2 + && out.hot.tbChildren[0].txt === '←' && out.hot.tbChildren[1].cls.includes('page-title') + && out.hot.tabs.length === 3 + && out.hot.rightIds.includes('rankCat') && out.hot.rightIds.includes('rankPrev') && out.hot.rightIds.includes('rankNext') + && out.hot.belowTb === true; +const R = out.rewrites && out.rewrites.tbChildren && out.rewrites.tbChildren.length === 2 + && out.rewrites.tbChildren[0].txt === '←' && out.rewrites.tbChildren[1].cls.includes('page-title'); +const ok = A && H && R; +console.log(ok ? '\n=== PASS ===' : '\n=== CHECK === (A=' + A + ' H=' + H + ' R=' + R + ')'); +ws.close(); +process.exit(ok ? 0 : 1); \ No newline at end of file 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 cc45e1f..469f768 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -518,15 +518,17 @@ async function renderRanking(view) { const st = { week: mkState(), hot: mkState(), likes: mkState() }; let tab = 'week'; const tabLabel = () => { const t = RANK_TABS.find(([k]) => k === tab); return t ? t[1] : ''; }; - // tab 切换 + 赛道选择 + 时间切换 同一行(page-toolbar:返回 | 标题 | tabs | 筛选右;空/错误态不渲染筛选) + // tab 切换 + 赛道选择 + 时间切换 同一行(标题行只放 返回 | 标题,tab + 筛选放标题下一行 .rank-toolbar) const toolbar = (s, withFilters) => `
${toolbarBack()} 热点数据 +
+ ${withFilters ? `
${RANK_TABS.map(([k, label, tip]) => ``).join('')}
- ${withFilters ? `
+
diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html b/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html index 995f729..4985c99 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工作台 - + @@ -41,7 +41,7 @@
- - + + diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css index 65cf87c..a3e52dd 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css @@ -661,6 +661,10 @@ td.td-title { max-width: 240px; } .rw-tab-badge { display: inline-block; min-width: 18px; margin-left: 6px; padding: 0 6px; border-radius: 10px; background: var(--primary-soft); color: var(--primary); font-size: 12px; line-height: 18px; font-weight: 600; text-align: center; } .tab-active .rw-tab-badge { background: var(--primary); color: #fff; } .rw-note { font-size: 12px; color: var(--dim); margin: 0 0 12px; } +/* 列表页统一:标题下一行 = tab + 筛选/搜索/功能按钮(均不与标题/返回同行) */ +.acc-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: -20px 0 16px; padding: 0 4px; } +.rank-toolbar { display: flex; align-items: center; gap: 14px; margin: -8px 0 16px; padding: 0 4px; } +.rank-toolbar .rank-tabs { flex: 1; } /* 视频状态「已解析」:浅绿色文字(去图标),区别于默认灰「未解析」 */ .st-parsed { color: #52b788; font-weight: 500; } .rw-chip { display: inline-flex; align-items: center; gap: 2px; max-width: 260px; padding: 2px 8px 2px 12px; background: var(--primary-soft); color: var(--primary); border-radius: 999px; font-size: 13px; }