工作台「账号周榜」入口改为「热点数据」多榜 tab 页:首页入口卡片/面包屑/路由标题同步改名;renderRanking 重构——RANK_TABS 四 tab(账号周榜/视频热榜/点赞榜/涨粉榜,与首页 rank-preview 一致),账号周榜 tab 保留完整列表(筛选/排序/分页/关注),其余榜暂占位(后端仅周榜 json,数据落盘后接入)
This commit is contained in:
1 parent
b346c37b10
commit
a1227600f7
3 files changed
+86
-43
No files matched your search
@@ -115,3 +115,9 @@
|
||||
- **缓存目录数据清理(用户指令「去掉的赛道 现有数据也要删除」)**:删除全部已剔除赛道历史文件 12 个(颜值造型 3 + 情感 3 + 二次元 3 + 化妆美容 3),目录现仅保留 5 赛道×3 期 = 15 文件
|
||||
- 验证:py_compile OK + 断言(保留=5 赛道,颜值造型/情感不在)+ ranking 接口实测 categories=5(美食/生活vlog/旅行/小剧场/亲子)dates=3 total=250 读取正常
|
||||
- 教训:用户「情感 去掉」紧接「颜值造型 去掉」=连续精简指令,需完整执行不可只当上一条确认
|
||||
|
||||
## 工作台「账号周榜」入口 →「热点数据」多榜 tab 页
|
||||
- 用户指令:「账号周榜入口改为热点数据,里面用 tab 区分数据列表,和首页一样」
|
||||
- 改动:app.js 首页入口卡片 title 账号周榜→热点数据(desc 多榜切换)、CRUMB_TITLES.ranking + router titles.ranking→热点数据;data-pages.js renderRanking 重构为多 tab 页——RANK_TABS 四 tab(week 账号周榜/hot 视频热榜/likes 点赞榜/fans 涨粉榜,与首页 rank-preview 一致),week 显示完整周榜列表(筛选/排序/分页/关注,原逻辑保留),其余 tab 显示「建设中」占位(与首页行为一致,后端 RANKING_DIR 目前仅周榜 json,后续数据落盘即可接入)
|
||||
- 验证:node --check 两文件通过;服务 8900 存活、新 js 已生效(grep 热点数据=3/1);带 ?v= 打开 #/ranking 页面
|
||||
- 纯前端静态改动,server.js 未变无需重启服务
|
||||
@@ -696,7 +696,7 @@ async function router() {
|
||||
}
|
||||
// 数据功能页
|
||||
if (typeof window.DataPages !== 'undefined') {
|
||||
const titles = { ranking: '账号周榜', accounts: '账号列表', account: '账号详情', video: '视频详情', rewrites: 'AI写脚本', reviews: 'AI脚本诊断' };
|
||||
const titles = { ranking: '热点数据', accounts: '账号列表', account: '账号详情', video: '视频详情', rewrites: 'AI写脚本', reviews: 'AI脚本诊断' };
|
||||
const pages = {
|
||||
'ranking': () => window.DataPages.renderRanking(view),
|
||||
'accounts': () => window.DataPages.renderAccounts(view),
|
||||
|
||||
@@ -502,18 +502,64 @@ async function confirmAiScript(accountName, btn) {
|
||||
}
|
||||
|
||||
/* ================= 周榜 ================= */
|
||||
/* ================= 热点数据(#/ranking:账号周榜为主,多榜 tab 与首页 rank-preview 一致) ================= */
|
||||
async function renderRanking(view) {
|
||||
let state = { date: '', category: '', sortBy: 'comprehensiveScore', order: 'desc', page: 1, pageSize: 20, dates: [], categories: [] };
|
||||
const data = await api('/api/dsh/ranking?pageSize=20').catch((e) => ({ ok: false, error: e.message }));
|
||||
if (!data.ok) { view.innerHTML = `<div class="page"><div class="page-toolbar">${toolbarBack()}<span class="page-title">账号周榜</span></div><div class="empty">${esc(data.error || '加载失败')}</div></div>`; bindBack(view); return; }
|
||||
state.dates = data.dates || []; state.categories = data.categories || []; state.date = data.date; state.category = data.category;
|
||||
// tab 与首页 rank-tabs 一致:账号周榜有本地数据文件;视频热榜/点赞榜/涨粉榜暂占位(后端 RANKING_DIR 目前仅周榜 json)
|
||||
const RANK_TABS = [
|
||||
['week', '账号周榜'],
|
||||
['hot', '视频热榜'],
|
||||
['likes', '点赞榜'],
|
||||
['fans', '涨粉榜'],
|
||||
];
|
||||
let state = { tab: 'week', date: '', category: '', sortBy: 'comprehensiveScore', order: 'desc', page: 1, pageSize: 20, dates: [], categories: [] };
|
||||
const meta = await api('/api/dsh/ranking?pageSize=20').catch((e) => ({ ok: false, error: e.message }));
|
||||
if (!meta.ok) {
|
||||
view.innerHTML = `<div class="page"><div class="page-toolbar">${toolbarBack()}<span class="page-title">热点数据</span></div><div class="empty">${esc(meta.error || '加载失败')}</div></div>`;
|
||||
bindBack(view); return;
|
||||
}
|
||||
state.dates = meta.dates || []; state.categories = meta.categories || []; state.date = meta.date || (state.dates[0] || ''); state.category = meta.category || '';
|
||||
|
||||
async function load() {
|
||||
const d = await api('/api/dsh/ranking?date=' + encodeURIComponent(state.date) + '&category=' + encodeURIComponent(state.category) + '&sortBy=' + state.sortBy + '&order=' + state.order + '&page=' + state.page + '&pageSize=' + state.pageSize);
|
||||
const di = state.dates.indexOf(state.date);
|
||||
const WIDE = new Set(['comprehensiveScore', 'followers', 'newFans', 'newLikes', 'newComments', 'newShares']); // 数值列(窄+右对齐)
|
||||
const sortable = (key, label) => `<th class="sortable${WIDE.has(key) ? ' th-wide' : ''}" data-sort="${key}">${label}${state.sortBy === key ? (state.order === 'asc' ? ' ↑' : ' ↓') : ''}</th>`;
|
||||
const rows = (d.items || []).map((r) => `<tr>
|
||||
const tabLabel = () => { const t = RANK_TABS.find(([k]) => k === state.tab); return t ? t[1] : ''; };
|
||||
const tabs = () => `
|
||||
<div class="rank-tabs" role="tablist">
|
||||
${RANK_TABS.map(([k, label]) => `<button class="rank-tab${k === state.tab ? ' active' : ''}" data-rank-tab="${k}" role="tab">${label}</button>`).join('')}
|
||||
</div>`;
|
||||
const toolbar = (withFilters) => `
|
||||
<div class="page-toolbar">
|
||||
${toolbarBack()}
|
||||
<span class="page-title">热点数据</span>
|
||||
${withFilters ? `<div class="toolbar-right">
|
||||
<select id="rankCat" class="input-select" title="赛道筛选">
|
||||
<option value="">全部赛道</option>
|
||||
${state.categories.map((c) => `<option value="${esc(c)}" ${c === state.category ? 'selected' : ''}>${esc(c)}</option>`).join('')}
|
||||
</select>
|
||||
<button class="btn" id="rankPrev" title="上一期">◀</button>
|
||||
<span class="pager-info" title="选择日期">${esc(state.date)}</span>
|
||||
<button class="btn" id="rankNext" title="下一期">▶</button>
|
||||
</div>` : ''}
|
||||
</div>`;
|
||||
|
||||
function bindShell() {
|
||||
bindBack(view);
|
||||
view.querySelectorAll('.rank-tab').forEach((t) => t.addEventListener('click', () => {
|
||||
const k = t.dataset.rankTab;
|
||||
if (k === state.tab) return;
|
||||
state.tab = k; state.page = 1; render();
|
||||
}));
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (state.tab !== 'week') {
|
||||
// 其余榜单与首页一致:建设中占位(无本地数据文件,数据获取技能落盘后即可接入)
|
||||
view.innerHTML = `<div class="page">${toolbar(false)}${tabs()}<div class="rank-placeholder">「${esc(tabLabel())}」建设中,敬请期待</div></div>`;
|
||||
bindShell(); return;
|
||||
}
|
||||
(async () => {
|
||||
const d = await api('/api/dsh/ranking?date=' + encodeURIComponent(state.date) + '&category=' + encodeURIComponent(state.category) + '&sortBy=' + state.sortBy + '&order=' + state.order + '&page=' + state.page + '&pageSize=' + state.pageSize);
|
||||
const di = state.dates.indexOf(state.date);
|
||||
const WIDE = new Set(['comprehensiveScore', 'followers', 'newFans', 'newLikes', 'newComments', 'newShares']); // 数值列(窄+右对齐)
|
||||
const sortable = (key, label) => `<th class="sortable${WIDE.has(key) ? ' th-wide' : ''}" data-sort="${key}">${label}${state.sortBy === key ? (state.order === 'asc' ? ' ↑' : ' ↓') : ''}</th>`;
|
||||
const rows = (d.items || []).map((r) => `<tr>
|
||||
<td>${r.rank ?? '-'}</td>
|
||||
<td>${esc(r.category || '-')}</td>
|
||||
<td>${r.profileUrl ? `<a class="link" data-ext="${esc(r.profileUrl)}" title="在新标签打开账号主页">${esc(r.accountName)}</a>` : esc(r.accountName)}</td>
|
||||
@@ -525,21 +571,9 @@ async function renderRanking(view) {
|
||||
<td class="num">${fmt(r.newShares)}</td>
|
||||
<td class="cell-act">${r.inDb ? `<a class="btn btn-sm btn-view" href="#/account/${r.dbId}">查看</a>` : `<button class="btn btn-sm btn-import" data-import='${esc(JSON.stringify(r))}'>关注</button>`}</td>
|
||||
</tr>`).join('');
|
||||
view.innerHTML = `
|
||||
view.innerHTML = `
|
||||
<div class="page">
|
||||
<div class="page-toolbar">
|
||||
${toolbarBack()}
|
||||
<span class="page-title">账号周榜</span>
|
||||
<div class="toolbar-right">
|
||||
<select id="rankCat" class="input-select" title="赛道筛选">
|
||||
<option value="">全部赛道</option>
|
||||
${state.categories.map((c) => `<option value="${esc(c)}" ${c === state.category ? 'selected' : ''}>${esc(c)}</option>`).join('')}
|
||||
</select>
|
||||
<button class="btn" id="rankPrev" ${di <= 0 ? 'disabled' : ''}>◀</button>
|
||||
<span class="pager-info" title="选择日期">${esc(state.date)}</span>
|
||||
<button class="btn" id="rankNext" ${di < 0 || di >= state.dates.length - 1 ? 'disabled' : ''}>▶</button>
|
||||
</div>
|
||||
</div>
|
||||
${toolbar(true)}${tabs()}
|
||||
<div class="table-wrap">
|
||||
<table class="table table-rank">
|
||||
<thead><tr>
|
||||
@@ -548,26 +582,29 @@ async function renderRanking(view) {
|
||||
<tbody>${rows || '<tr><td colspan="10" class="empty">暂无周榜数据</td></tr>'}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${pagerHtml(d.total, d.page, state.pageSize, (p) => { state.page = p; load(); })}
|
||||
${pagerHtml(d.total, d.page, state.pageSize, (p) => { state.page = p; render(); })}
|
||||
</div>`;
|
||||
bindBack(view);
|
||||
view.querySelectorAll('.sortable').forEach((th) => th.addEventListener('click', () => {
|
||||
const k = th.dataset.sort;
|
||||
if (state.sortBy === k) state.order = state.order === 'asc' ? 'desc' : 'asc';
|
||||
else { state.sortBy = k; state.order = k === 'rank' ? 'asc' : 'desc'; }
|
||||
state.page = 1; load();
|
||||
}));
|
||||
view.querySelector('#rankCat').addEventListener('change', (e) => { state.category = e.target.value; state.page = 1; load(); });
|
||||
view.querySelector('#rankPrev').addEventListener('click', () => { const i = state.dates.indexOf(state.date) - 1; if (i >= 0) { state.date = state.dates[i]; state.page = 1; load(); } });
|
||||
view.querySelector('#rankNext').addEventListener('click', () => { const i = state.dates.indexOf(state.date) + 1; if (i < state.dates.length) { state.date = state.dates[i]; state.page = 1; load(); } });
|
||||
bindPager(view, (p) => { state.page = p; load(); });
|
||||
view.querySelectorAll('[data-import]').forEach((b) => b.addEventListener('click', () => {
|
||||
const r = JSON.parse(b.dataset.import);
|
||||
confirmSaveAccount(r.accountName, r.profileUrl || '', b);
|
||||
}));
|
||||
view._reload = load; // 静默自动刷新(保留筛选/翻页状态)
|
||||
bindShell();
|
||||
view.querySelectorAll('.sortable').forEach((th) => th.addEventListener('click', () => {
|
||||
const k = th.dataset.sort;
|
||||
if (state.sortBy === k) state.order = state.order === 'asc' ? 'desc' : 'asc';
|
||||
else { state.sortBy = k; state.order = k === 'rank' ? 'asc' : 'desc'; }
|
||||
state.page = 1; render();
|
||||
}));
|
||||
const rankCat = view.querySelector('#rankCat');
|
||||
if (rankCat) rankCat.addEventListener('change', (e) => { state.category = e.target.value; state.page = 1; render(); });
|
||||
const prev = view.querySelector('#rankPrev'), next = view.querySelector('#rankNext');
|
||||
if (prev) { prev.disabled = di <= 0; prev.addEventListener('click', () => { const i = state.dates.indexOf(state.date) - 1; if (i >= 0) { state.date = state.dates[i]; state.page = 1; render(); } }); }
|
||||
if (next) { next.disabled = di < 0 || di >= state.dates.length - 1; next.addEventListener('click', () => { const i = state.dates.indexOf(state.date) + 1; if (i < state.dates.length) { state.date = state.dates[i]; state.page = 1; render(); } }); }
|
||||
bindPager(view, (p) => { state.page = p; render(); });
|
||||
view.querySelectorAll('[data-import]').forEach((b) => b.addEventListener('click', () => {
|
||||
const r = JSON.parse(b.dataset.import);
|
||||
confirmSaveAccount(r.accountName, r.profileUrl || '', b);
|
||||
}));
|
||||
view._reload = render; // 静默自动刷新(保留 tab/筛选/翻页状态)
|
||||
})();
|
||||
}
|
||||
await load();
|
||||
render();
|
||||
}
|
||||
|
||||
/* ================= 账号列表 ================= */
|
||||
|
||||
Reference in new issue
Block a user