diff --git a/.workbuddy/memory/2026-09-03.md b/.workbuddy/memory/2026-09-03.md index 5a8bfb3..79d6f68 100644 --- a/.workbuddy/memory/2026-09-03.md +++ b/.workbuddy/memory/2026-09-03.md @@ -207,3 +207,17 @@ - data-pages.js:.rw-tabs div 从 toolbar 下独立行移入 .page-toolbar(返回|标题|tabs|toolbar-right 筛选右),加 rw-toolbar-tabs 类;rw-note 留在 tab 行下方表上方 - style.css:.page-toolbar .rw-tabs 行内化(border-bottom none/margin 0/padding 0/center),tab 压缩 13px;tab-active 保留下划线条 - 纯静态改动无需重启;git 待查;版本 ?v=20260903h + +## R27a AI写脚本页布局修正(09-03,R26 返工) +- 用户澄清:R26「tab 与筛选同行」≠ 并入标题行;且追加要求筛选搜索也移下标题行 +- 终局结构:page-toolbar 只留 返回|标题「AI写脚本」;其下 .rw-tabs 单行 = 左三个 tab + 右 toolbar-right(搜索/时间/状态,margin-left:auto),rw-tabs 加 align-items:center +- 还原教训:R26 把 tabs 并入 toolbar 标题行属误读「放一行」,用户两次纠正(先 tab 放下、再筛选也放下) +- 状态列:ref tab「✅已解析」去图标改浅绿文字(.st-parsed #52b788,font-weight 500);「未解析」保持默认 + +## R27 账号列表 重置按钮 → 更新按钮(09-03,口径已确认) +- 口径(AskUserQuestion 确认):范围=⭐ 已关注(favorite=1 共 3 个:做夢/平凡的胡德禄/旧梦留声机);交互=确认弹窗后执行 +- data-pages.js:L700 accReset(纯 refresh 图标)→ accUpdate「refresh 图标 + 更新」;L733 重置绑定 → updateFollowedAccounts(btn) +- 新函数 updateFollowedAccounts(renderAccounts 后,文件级):GET /api/dsh/accounts pageSize=100 → filter favorite → 自建 modal 确认(confirm-target 列名单)→ 逐账号串行 POST /api/run(prompt=SKILL_HINT_ACCOUNT +「更新已登记账号」语义:浏览器重抓账号信息+视频列表覆盖更新、身份标识禁库中旧值回填;name='更新账号·X';skills mcn-dou-analysis)→ 轮询 /api/run/status(每 5s,单任务 20min 上限;done/error/review/notfound/超时 五终态)→ 按钮「更新中 i/N…」→ 汇总 toast + refreshCurrentPage +- 串行理由:多会话并发会抢浏览器 9223 调试实例;executeTask 单按钮封装不适配 N 任务,自写轮询 +- 未验证:首次实点后需核对任务侧能否覆盖更新 hot_accounts 主表(server.js importAccountIntoDb 不写 hot_accounts 粉丝/获赞/作品数——只 upsert 视频解析/人设/视频列表/拆解;技能侧登记链路待实测) +- 纯静态改动无需重启;版本 ?v=20260903j diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r27.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r27.mjs new file mode 100644 index 0000000..9b8ec31 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r27.mjs @@ -0,0 +1,87 @@ +// R27 冒烟 v3:AI写脚本页布局(标题行干净/tab+筛选下行)+ 状态列浅绿 + 账号列表「更新」按钮弹窗 +// 注意:hash 不能带 ?query(会被路由当作账号名 scope 过滤)→ 切页用 location.hash +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); }; +const goto = async (hash) => { await ev(`location.hash = ${JSON.stringify(hash)}`); await sleep(600); }; + +/* ============ 0) 先加载应用 ============ */ +await send('Page.navigate', { url: 'http://localhost:8900/#/' }); +if (!(await waitFor(`!!document.querySelector('#view')`))) fail('app boot'); + +/* ============ 1) AI写脚本页布局 + 状态列 ============ */ +await goto('#/rewrites'); +if (!(await waitFor(`!!document.querySelector('#view .rw-tabs')`))) fail('rw-tabs render'); +if (!(await waitFor(`!!document.querySelector('#view table tbody tr')`))) fail('table rows: ' + JSON.stringify(await ev(`document.querySelector('#view .table-wrap') ? document.querySelector('#view .table-wrap').textContent.slice(0,80) : 'no wrap'`))); +const rw = await ev(`(() => { + const tb = document.querySelector('#view .page-toolbar'); + const tabs = document.querySelector('#view .rw-tabs'); + const tr = tabs.querySelector('.toolbar-right'); + return { + toolbarChildren: [...tb.children].map((c) => (c.className && String(c.className)) || c.tagName), + toolbarHasFilter: !!tb.querySelector('.toolbar-right'), + toolbarHasTabs: !!tb.querySelector('.rw-tabs'), + tabsIsNextSibling: tb.nextElementSibling === tabs, + tabsCount: tabs.querySelectorAll('button[data-tab]').length, + tabsRowRight: !!tr, + rightInputs: tr ? [...tr.querySelectorAll('input,select')].map((x) => x.id) : [], + parsedCells: [...document.querySelectorAll('#view tbody td')].filter((td) => td.textContent.trim() === '已解析').slice(0, 3).map((td) => { + const sp = td.querySelector('span.st-parsed'); + return { hasSpan: !!sp, color: sp ? getComputedStyle(sp).color : null }; + }), + hasEmojiParsed: document.querySelector('#view tbody').textContent.includes('✅已解析'), + }; +})()`); + +/* ============ 2) 账号列表「更新」按钮 + 弹窗 ============ */ +await goto('#/accounts'); +// 全新测试 profile 无 localStorage:应用可能自动弹出「设置产出根目录」弹窗 → 先清掉杂项弹窗,避免误匹配 +await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove())`); +if (!(await waitFor(`!!document.querySelector('#accUpdate')`))) fail('accUpdate render'); +const acc = await ev(`(() => { + const b = document.querySelector('#accUpdate'); + const r = { txt: b.textContent.trim(), hasIcon: !!b.querySelector('svg'), title: b.title }; + b.click(); + return r; +})()`); +await sleep(300); +if (!(await waitFor(`[...document.querySelectorAll('.modal-mask .modal')].some((m) => m.textContent.includes('更新关注账号'))`))) fail('update modal open'); +const md = await ev(`(() => { + const m = [...document.querySelectorAll('.modal-mask .modal')].find((x) => x.textContent.includes('更新关注账号')); + const r = { + title: m.querySelector('h3').textContent.trim(), + target: m.querySelector('.confirm-target') ? m.querySelector('.confirm-target').textContent.trim() : null, + desc: m.querySelector('p.dim').textContent.trim(), + hasCancel: !!m.querySelector('[data-cancel]'), + hasOk: !!m.querySelector('[data-ok]'), + maskCount: document.querySelectorAll('.modal-mask').length, + }; + m.querySelector('[data-cancel]').click(); + return r; +})()`); +await sleep(400); +const closed = await ev(`![...document.querySelectorAll('.modal-mask')].some((m) => m.textContent.includes('更新关注账号'))`); + +console.log(JSON.stringify({ rw, acc, md, closed }, null, 1)); +const ok = rw && !rw.__err + && rw.toolbarHasFilter === false && rw.toolbarHasTabs === false + && rw.tabsIsNextSibling === true && rw.tabsCount === 3 + && rw.tabsRowRight === true && rw.rightInputs.length >= 2 + && rw.hasEmojiParsed === false + && rw.parsedCells.length > 0 && rw.parsedCells.every((c) => c.hasSpan && c.color === 'rgb(82, 183, 136)') + && acc && !acc.__err && acc.txt === '更新' && acc.hasIcon + && md && !md.__err && md.title.includes('更新关注账号') && md.target.includes('做夢') && md.target.includes('胡德禄') && md.target.includes('旧梦') + && md.hasCancel && md.hasOk && closed === true; +console.log(ok ? '\n=== PASS ===' : '\n=== CHECK ==='); +ws.close(); +process.exit(ok ? 0 : 1); 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 f9e4aad..2fbeacb 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -697,7 +697,7 @@ async function renderAccounts(view) { - +
将重新抓取 ${names.length} 个已关注账号的最新账号数据与视频列表(后台任务逐个执行,每个需数分钟,请耐心等待,可到左侧会话栏查看进度)。确认更新?
+