R29: 账号详情视频列表「时长」前新增标签列(40px固定+悬停完整标签)

- data-pages.js: renderAccountDetail 表头 收藏后加 th-tag;数据行加 td-tag(title=完整标签, CSS省略); colspan 8→9/batch 9→10
- style.css: th-tag/td-tag(40px min/max + ellipsis)
- 冒烟 cdp-verify-r29.mjs 全 PASS; 版本 ?v=20260903l
- MEMORY.md 二次压缩 11148→7213B(注入截断修复)
This commit is contained in:
maogeigei committed 2026-09-03 16:01:50 +08:00
1 parent 2751a17889
commit fff261e6de
6 files changed
+95 -36

No files matched your search

@@ -0,0 +1,55 @@
// R29 冒烟:账号详情「视频列表」tab——「时长」前新增「标签」列(固定 40px,超长省略,悬停 title 显示完整)
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 = 15000) => { 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(800); };
await send('Page.navigate', { url: 'http://localhost:8900/#/' });
if (!(await waitFor(`!!document.querySelector('#view')`))) fail('app boot');
await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove())`); // 清 root 设置弹窗
/* ============ 账号 1629 视频列表 tab(persona) ============ */
await goto('#/account/1629/persona');
if (!(await waitFor(`document.querySelector('#view .table tbody tr')`))) fail('video table rows');
const out = await ev(`(() => {
const ths = [...document.querySelectorAll('#view .table thead th')].map((t) => t.textContent.trim());
const thTag = document.querySelector('#view .table thead th.th-tag');
const firstTr = document.querySelector('#view .table tbody tr');
const cells = [...firstTr.children];
const tagTd = firstTr.querySelector('td.td-tag');
return {
ths, // 完整表头文本序列
thTagIdx: ths.indexOf('标签'),
durIdx: ths.indexOf('时长'),
thTagW: thTag ? Math.round(thTag.getBoundingClientRect().width) : null,
tagBeforeDur: ths.indexOf('标签') === ths.indexOf('时长') - 1,
// 数据行
rowCells: cells.map((c) => ({ cls: c.className, txt: (c.textContent || '').trim().slice(0, 24) })),
tagTdText: tagTd ? (tagTd.textContent || '').trim() : null,
tagTdTitle: tagTd ? (tagTd.getAttribute('title') || '') : null,
tagTdClips: tagTd ? tagTd.scrollWidth > tagTd.clientWidth : null, // 是否发生省略截断
tagTdW: tagTd ? Math.round(tagTd.getBoundingClientRect().width) : null,
hasNumClass: tagTd ? tagTd.classList.contains('num') : null,
};
})()`);
console.log(JSON.stringify(out, null, 1));
const ok = out && !out.__err
&& out.ths.length === 9 && out.thTagIdx === 6 && out.durIdx === 7 && out.tagBeforeDur === true
&& out.thTagW === 40
&& out.rowCells.length === 9 && out.rowCells[6].cls.includes('td-tag')
&& !!out.tagTdText && out.tagTdText.length > 0
&& !!out.tagTdTitle && out.tagTdTitle.includes('#') && out.tagTdTitle.length >= out.tagTdText.length
&& out.tagTdClips === true && out.tagTdW === 40 && out.hasNumClass === false;
console.log(ok ? '\n=== PASS ===' : '\n=== CHECK ===');
ws.close();
process.exit(ok ? 0 : 1);
@@ -866,6 +866,7 @@ async function renderAccountDetail(view, id) {
<td class="num">${fmtWan(v.comment_count)}</td>
<td class="num">${fmtWan(v.share_count)}</td>
<td class="num">${fmtWan(v.collect_count)}</td>
<td class="td-tag"${v.tags ? ` title="${esc(v.tags)}"` : ''}>${v.tags ? esc(v.tags) : ''}</td>
<td class="num">${fmtDuration(v.duration)}</td>
<td class="cell-act"><button class="btn btn-sm ${(v.has_source || v.has_analysis) ? 'btn-view' : ''}" data-rewrite='${esc(JSON.stringify({ id: v.id, name: acc.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id, url: v.video_url || '', parsed: !!(v.has_source || v.has_analysis) }))}'>AI创作</button></td>
</tr>`).join('');
@@ -917,7 +918,7 @@ async function renderAccountDetail(view, id) {
${personaPanel()}`;
} else if (tab === 'persona') {
panel = `<div class="trend-chart"><div id="trendChart"></div></div>
<div class="table-wrap"><table class="table"><thead><tr>${bstate.batch ? '<th class="cb-cell"><input type="checkbox" class="batch-cb-all"></th>' : ''}<th class="th-pub">发布时间</th><th class="th-title">视频标题</th><th class="num">点赞</th><th class="num">评论</th><th class="num">分享</th><th class="num">收藏</th><th class="num">时长</th><th class="th-act">操作</th></tr></thead><tbody>${rows || `<tr><td colspan="${bstate.batch ? 9 : 8}" class="empty">暂无视频数据</td></tr>`}</tbody></table></div>
<div class="table-wrap"><table class="table"><thead><tr>${bstate.batch ? '<th class="cb-cell"><input type="checkbox" class="batch-cb-all"></th>' : ''}<th class="th-pub">发布时间</th><th class="th-title">视频标题</th><th class="num">点赞</th><th class="num">评论</th><th class="num">分享</th><th class="num">收藏</th><th class="th-tag">标签</th><th class="num">时长</th><th class="th-act">操作</th></tr></thead><tbody>${rows || `<tr><td colspan="${bstate.batch ? 10 : 9}" class="empty">暂无视频数据</td></tr>`}</tbody></table></div>
<div class="list-footer">
${batchBarHtml(bstate, 0)}
${pagerHtml(dv.total, dv.page, 20, (p) => { page = p; load(); })}
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCN工作台</title>
<link rel="stylesheet" href="style.css?v=20260903k">
<link rel="stylesheet" href="style.css?v=20260903l">
<script src="vendor/echarts.min.js"></script>
</head>
<body>
@@ -41,7 +41,7 @@
</div>
</div>
<script src="app.js?v=20260903k"></script>
<script src="data-pages.js?v=20260903k"></script>
<script src="app.js?v=20260903l"></script>
<script src="data-pages.js?v=20260903l"></script>
</body>
</html>
@@ -488,6 +488,9 @@ main#view { height: calc(100vh - 55px); overflow: auto; } /* 55px=topbar 实际
th.th-pub { width: 135px; }
td.td-pub { font-variant-numeric: tabular-nums; }
td.td-title { max-width: 240px; }
/* 账号详情视频列表标签列:固定 40px,超长省略,悬停 title 显示完整标签 */
.table th.th-tag { width: 40px; min-width: 40px; padding: 8px 2px; text-align: center; }
.table td.td-tag { max-width: 40px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 7px 2px; }
/* ================= 账号设定 卡片版/文字版 切换(右上角图标按钮组) ================= */
.persona-wrap { position: relative; } /* 账号设定内容左右无间隔(09-02 用户要求去掉 20px) */