账号列表重置按钮改更新按钮(⭐已关注批量重抓,确认弹窗+逐账号串行任务);AI写脚本页tab与搜索筛选移至标题下同一行(还原R26误并入标题行);状态列已解析去图标改浅绿
This commit is contained in:
1 parent
d874dcc386
commit
1a68637de8
5 files changed
+187
-17
No files matched your search
@@ -697,7 +697,7 @@ async function renderAccounts(view) {
|
||||
<select id="accTrack" class="input-select"><option value="">全部赛道</option>${tracks.map((t) => `<option value="${esc(t)}" ${t === state.track ? 'selected' : ''}>${esc(t)}</option>`).join('')}</select>
|
||||
<input id="accSearch" class="input" placeholder="搜索账号名 / 内容 / 赛道" value="${esc(state.search)}">
|
||||
<button class="btn" id="accImport" title="添加账号数据(抖音账号信息 / 账号地址)">${svgIcon('user-plus', 15, 'currentColor')} 添加</button>
|
||||
<button class="btn" id="accReset" title="重置筛选">${svgIcon('refresh', 15, 'currentColor')}</button>
|
||||
<button class="btn" id="accUpdate" title="重新抓取所有已关注账号(⭐)的最新账号数据与视频列表(后台任务逐个执行)">${svgIcon('refresh', 15, 'currentColor')} 更新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
@@ -730,7 +730,7 @@ async function renderAccounts(view) {
|
||||
view.querySelector('#accType').addEventListener('change', (e) => { state.type = e.target.value; state.page = 1; load(); });
|
||||
view.querySelector('#accSearch').addEventListener('keydown', (e) => { if (e.key === 'Enter') { state.search = e.target.value; state.page = 1; load(); } });
|
||||
view.querySelector('#accTrack').addEventListener('change', (e) => { state.track = e.target.value; state.page = 1; load(); });
|
||||
view.querySelector('#accReset').addEventListener('click', () => { state = { type: 'all', search: '', track: '', page: 1, pageSize: 20, batch: false }; load(); });
|
||||
view.querySelector('#accUpdate').addEventListener('click', () => updateFollowedAccounts(view.querySelector('#accUpdate')));
|
||||
view.querySelector('#accImport').addEventListener('click', (e) => openImportAccounts(e.currentTarget));
|
||||
bindPager(view, (p) => { state.page = p; load(); });
|
||||
bindBatchBar(view, {
|
||||
@@ -746,6 +746,76 @@ async function renderAccounts(view) {
|
||||
await load();
|
||||
}
|
||||
|
||||
/* ===== 账号列表「更新」按钮(09-03 R27):重新抓取所有已关注账号的最新账号数据与视频列表 =====
|
||||
范围 = ⭐ 已关注(favorite=1);后台任务逐账号串行执行(多会话并发会抢浏览器调试实例);
|
||||
每个任务复用 mcn-dou-analysis 技能流程(SKILL_HINT_ACCOUNT 同款技能加载+红线),任务语义改为「更新已登记账号」:
|
||||
身份标识(sec_uid/抖音号/地址)一律以本次浏览器抓取为准,禁止沿用库中旧值回填 */
|
||||
function updateFollowedAccounts(btn) {
|
||||
(async () => {
|
||||
try {
|
||||
const d = await api('/api/dsh/accounts?type=all&search=&track=&page=1&pageSize=100');
|
||||
const favs = ((d && d.items) || []).filter((a) => a.favorite);
|
||||
if (!favs.length) { showToast('⭐ 暂无已关注账号(先点击列表中的 ☆ 关注)'); return; }
|
||||
const names = favs.map((a) => String(a.account_name || '').trim()).filter(Boolean);
|
||||
if (!names.length) { showToast('已关注账号缺少账号名,无法更新'); return; }
|
||||
const mask = document.createElement('div'); mask.className = 'modal-mask';
|
||||
mask.innerHTML = `
|
||||
<div class="modal" style="max-width:540px">
|
||||
<h3>${svgIcon('refresh', 18)} 更新关注账号</h3>
|
||||
<p class="dim">将重新抓取 ${names.length} 个已关注账号的最新账号数据与视频列表(后台任务逐个执行,每个需数分钟,请耐心等待,可到左侧会话栏查看进度)。确认更新?</p>
|
||||
<div class="confirm-target">${names.map((n) => esc(n)).join('、')}</div>
|
||||
<div class="modal-actions">
|
||||
<button class="ghost" data-cancel>取消</button>
|
||||
<button class="primary" data-ok>确认更新</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(mask);
|
||||
const close = () => mask.remove();
|
||||
mask.addEventListener('click', (e) => { if (e.target === mask) close(); });
|
||||
mask.querySelector('[data-cancel]').addEventListener('click', close);
|
||||
mask.querySelector('[data-ok]').addEventListener('click', async () => {
|
||||
close();
|
||||
const oldHtml = btn ? btn.innerHTML : '';
|
||||
const restore = () => { if (btn) { btn.disabled = false; btn.innerHTML = oldHtml; btn.classList.remove('btn-busy'); } };
|
||||
if (btn) { btn.disabled = true; btn.classList.add('btn-busy'); }
|
||||
const done = [], failed = [];
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
if (btn) btn.innerHTML = `更新中 ${i + 1}/${names.length}…`;
|
||||
const prompt = `${SKILL_HINT_ACCOUNT}\n请更新抖音账号「${name}」的数据:该账号已在本工作台账号库中登记,属数据更新场景(非新增)。通过抖音网页重新获取该账号最新的账号信息(粉丝/获赞/作品数/简介等)与最新视频列表,并覆盖更新该账号的登记数据与视频列表记录。账号身份标识(sec_uid/抖音号/抖音地址)一律以本次浏览器抓取到的为准,禁止沿用库中旧值回填。`;
|
||||
try {
|
||||
const r = await api('/api/run', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt, name: '更新账号·' + name, skills: ['mcn-dou-analysis'] }),
|
||||
});
|
||||
if (r.error) throw new Error(r.error);
|
||||
const taskId = r.id;
|
||||
const deadline = Date.now() + 20 * 60 * 1000;
|
||||
let state = 'running';
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((res) => setTimeout(res, 5000));
|
||||
let s = null;
|
||||
try { s = await api('/api/run/status?id=' + encodeURIComponent(taskId)); } catch (e) { continue; }
|
||||
if (!s || s.state === 'notfound') { state = 'done'; break; }
|
||||
if (s.state === 'done') { state = 'done'; break; }
|
||||
if (s.state === 'error' || s.state === 'review') { state = s.state; break; }
|
||||
}
|
||||
if (state === 'done') done.push(name);
|
||||
else failed.push(name + (state === 'error' ? '(任务失败)' : (state === 'review' ? '(待确认)' : '(超时)')));
|
||||
} catch (e) { failed.push(name + '(' + e.message + ')'); }
|
||||
}
|
||||
restore();
|
||||
if (btn) btn.blur();
|
||||
showToast(done.length ? `✅ 已更新 ${done.length} 个关注账号${failed.length ? `;${failed.length} 个未完成:${failed.join('、')}` : ''}` : '❌ 更新未完成:' + failed.join('、'));
|
||||
refreshCurrentPage();
|
||||
});
|
||||
} catch (e) {
|
||||
showToast('❌ 获取账号列表失败: ' + e.message);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/* ================= 账号详情 ================= */
|
||||
async function renderAccountDetail(view, id) {
|
||||
const accountId = Number(id);
|
||||
@@ -1077,7 +1147,7 @@ async function renderRewrites(view, initAccount) { /* v22:initAccount=账号
|
||||
<td>${fmtWan(v.share_count)}</td>
|
||||
<td>${fmtDuration(v.duration)}</td>
|
||||
<td>${fmt(v.publish_time)}</td>
|
||||
<td>${(v.has_source || v.has_analysis) ? '✅已解析' : '未解析'}</td>
|
||||
<td>${(v.has_source || v.has_analysis) ? '<span class="st-parsed">已解析</span>' : '未解析'}</td>
|
||||
<td>${v.rewrite_count > 0 ? `<button class="btn btn-sm btn-view" data-detail="${v.id}" title="查看该视频下的 AI 脚本(源脚本对比/分镜)">${v.rewrite_count} 查看</button>` : '—'}</td>
|
||||
<td class="cell-act"><button class="btn btn-sm" data-rewrite='${esc(JSON.stringify({ id: v.id, name: v.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id || '', accountId: v.account_id, parsed: !!(v.has_source || v.has_analysis), url: v.video_url || '' }))}'>AI创作</button></td>
|
||||
</tr>`).join('');
|
||||
@@ -1129,12 +1199,12 @@ async function renderRewrites(view, initAccount) { /* v22:initAccount=账号
|
||||
<div class="page-toolbar">
|
||||
${toolbarBack()}
|
||||
<span class="page-title">AI写脚本</span>
|
||||
<div class="rw-tabs rw-toolbar-tabs">
|
||||
<button class="${tabCls('ref')}" data-tab="ref" title="有原视频:以视频行为选题来源创作,脚本保存后挂在该视频下">参考选题创作<span class="rw-tab-badge">${state.stats.videoScripts}</span></button>
|
||||
<button class="${tabCls('custom')}" data-tab="custom" title="无原视频:账号列表「AI创作」弹窗等创作,脚本保存后自动入库">自定义选题创作<span class="rw-tab-badge">${state.stats.customScripts}</span></button>
|
||||
<button class="${tabCls('abc')}" data-tab="abc" title="多视频爆款元素融合创作(开发中)">ABC融合创作</button>
|
||||
</div>
|
||||
<div class="toolbar-right" style="margin-right:10px">${toolbarCtrl}</div>
|
||||
</div>
|
||||
<div class="rw-tabs">
|
||||
<button class="${tabCls('ref')}" data-tab="ref" title="有原视频:以视频行为选题来源创作,脚本保存后挂在该视频下">参考选题创作<span class="rw-tab-badge">${state.stats.videoScripts}</span></button>
|
||||
<button class="${tabCls('custom')}" data-tab="custom" title="无原视频:账号列表「AI创作」弹窗等创作,脚本保存后自动入库">自定义选题创作<span class="rw-tab-badge">${state.stats.customScripts}</span></button>
|
||||
<button class="${tabCls('abc')}" data-tab="abc" title="多视频爆款元素融合创作(开发中)">ABC融合创作</button>
|
||||
<div class="toolbar-right" style="margin-left:auto">${toolbarCtrl}</div>
|
||||
</div>
|
||||
<div class="rw-note">${note}</div>
|
||||
${bodyHtml}
|
||||
|
||||
@@ -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=20260903h">
|
||||
<link rel="stylesheet" href="style.css?v=20260903j">
|
||||
<script src="vendor/echarts.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="app.js?v=20260903h"></script>
|
||||
<script src="data-pages.js?v=20260903h"></script>
|
||||
<script src="app.js?v=20260903j"></script>
|
||||
<script src="data-pages.js?v=20260903j"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -650,15 +650,14 @@ td.td-title { max-width: 240px; }
|
||||
.cb-cell input { width: 15px; height: 15px; cursor: pointer; vertical-align: middle; accent-color: var(--primary); }
|
||||
|
||||
/* ---- AI写脚本 三 tab(09-02 v25:参考选题创作 / 自定义选题创作 / ABC融合创作) ---- */
|
||||
.rw-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: -12px 0 12px; padding: 0 4px; }
|
||||
/* 标题行(返回|标题)下方独立一行:左=tab 切换,右=搜索/时间/状态筛选(tab 与筛选同一行,均不与标题/返回同行) */
|
||||
.rw-tabs { display: flex; align-items: center; gap: 2px; border-bottom: 1px solid var(--border); margin: -12px 0 12px; padding: 0 4px; }
|
||||
.rw-tabs .tab { font-size: 15px; padding: 9px 14px; }
|
||||
/* AI写脚本页:tab 并入 page-toolbar 同一行(与搜索/筛选同行),去整行分隔条 */
|
||||
.page-toolbar .rw-tabs { border-bottom: none; margin: 0; padding: 0; align-self: center; }
|
||||
.page-toolbar .rw-tabs .tab { padding: 4px 10px 3px; font-size: 13px; margin-bottom: 0; }
|
||||
.page-toolbar .rw-tabs .tab-active { border-bottom: 2px solid var(--primary); }
|
||||
.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; }
|
||||
/* 视频状态「已解析」:浅绿色文字(去图标),区别于默认灰「未解析」 */
|
||||
.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; }
|
||||
.rw-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.rw-chip button { border: none; background: none; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0 3px; border-radius: 50%; }
|
||||
|
||||
Reference in new issue
Block a user