feat(shelf): 新增书架分页展示与排序选择

This commit is contained in:
admin committed 2026-09-21 22:25:11 +08:00
1 parent bbed5a5b98
commit 40b00a8b40
7 files changed
+92 -8

No files matched your search

+63 -6
View File
@@ -24,7 +24,7 @@ let searchComposing = false, discoverSearchTimer = null;
let recommendOffset = 0;
let noteBook = 'all', noteType = 'all', goalFilter = 'all';
let detailBook = null, bookTab = 'info', reviewFilter = 0, selectedChapter = 0;
let shelfLimit = SHELF_PAGE, chapterLimit = 30, searchScope = 'store', notesLimit = 30;
let shelfPage = 1, shelfSort = 'recent', chapterLimit = 30, searchScope = 'store', notesLimit = 30;
let editingGoalId = null, draftGoalId = null, draftIds = new Set(), draftSources = [], pendingBooks = [], pendingSources = [];
let toastTimer, storageWarning = '', goalBusy = false;
@@ -332,6 +332,50 @@ function renderLists(lists) {
return `<div class="list-grid">${lists.map(listCard).join('') || '<div class="empty">没有匹配的书单。</div>'}</div>`;
}
function compareShelfBooks(a, b) {
const pinOrder = Number(b.is_top || 0) - Number(a.is_top || 0);
if (pinOrder) return pinOrder;
const titleOrder = String(a.title || '').localeCompare(String(b.title || ''), 'zh-CN');
const authorOrder = String(a.author || '').localeCompare(String(b.author || ''), 'zh-CN');
const idOrder = String(a.id || '').localeCompare(String(b.id || ''));
if (shelfSort === 'title_asc') return titleOrder || idOrder;
if (shelfSort === 'title_desc') return -titleOrder || idOrder;
if (shelfSort === 'author_asc') return authorOrder || titleOrder || idOrder;
const dateA = String(a.read_update_time || '');
const dateB = String(b.read_update_time || '');
if (dateA !== dateB) {
if (!dateA) return 1;
if (!dateB) return -1;
return dateB.localeCompare(dateA);
}
return titleOrder || idOrder;
}
function renderShelfPagination(current, total) {
if (total <= 1) return '';
const maxButtons = 7;
let start = Math.max(1, current - 3);
let end = Math.min(total, start + maxButtons - 1);
start = Math.max(1, end - maxButtons + 1);
let html = `<nav class="shelf-pagination" aria-label="书架分页">`;
html += `<button type="button" class="page-btn" ${current <= 1 ? 'disabled' : ''} data-action="shelf-page" data-id="${current - 1}">上一页</button>`;
if (start > 1) {
html += `<button type="button" class="page-btn" data-action="shelf-page" data-id="1">1</button>`;
if (start > 2) html += '<span class="page-info" aria-hidden="true">…</span>';
}
for (let i = start; i <= end; i++) {
html += `<button type="button" class="page-btn ${i === current ? 'active' : ''}" ${i === current ? 'aria-current="page"' : ''} data-action="shelf-page" data-id="${i}">${i}</button>`;
}
if (end < total) {
if (end < total - 1) html += '<span class="page-info" aria-hidden="true">…</span>';
html += `<button type="button" class="page-btn" data-action="shelf-page" data-id="${total}">${total}</button>`;
}
html += `<button type="button" class="page-btn" ${current >= total ? 'disabled' : ''} data-action="shelf-page" data-id="${current + 1}">下一页</button></nav>`;
return html;
}
function renderShelf() {
if (loadError.shelf) return statusBox('error', '暂时无法读取书架', loadError.shelf, 'retry-shelf');
if (!shelfData) { ensureShelf(); return statusBox('loading', '正在同步书架…', '读取微信读书书架与阅读进度。'); }
@@ -348,8 +392,20 @@ function renderShelf() {
} else if (shelfTab === 'articles') {
content = `<article class="card media-card"><div class="media-icon">▤</div><div><h3>${esc((shelfData.mp && shelfData.mp.title) || '文章收藏')}</h3><p>这是书架中的收藏入口,共 ${counts.mp ? 1 : 0} 个。</p><p class="hint">书架接口不提供入口内文章列表;可在发现页按「文章 / 公众号」检索。</p><button class="text-button" data-action="article-search">去发现页搜索 →</button></div></article>`;
} else {
const shown = filtered.slice(0, shelfLimit);
content = `<div class="card panel"><div class="books full-books">${shown.map(bookCard).join('') || '<div class="empty">这里还没有对应的书。</div>'}</div>${filtered.length > shown.length ? `<button class="text-button section-space" data-action="shelf-more">+ 继续加载(还有 ${filtered.length - shown.length} 本)</button>` : ''}</div>`;
const sorted = [...filtered].sort(compareShelfBooks);
const totalPages = Math.ceil(sorted.length / SHELF_PAGE);
const currentPage = totalPages ? Math.min(shelfPage, totalPages) : 1;
shelfPage = currentPage;
const shown = sorted.slice((currentPage - 1) * SHELF_PAGE, currentPage * SHELF_PAGE);
const pageSummary = totalPages ? `共 ${sorted.length} 本 · 第 ${currentPage}/${totalPages} 页` : `共 ${sorted.length} 本`;
const sortOptions = [
['recent', '最近阅读'],
['title_asc', '书名升序'],
['title_desc', '书名降序'],
['author_asc', '作者升序'],
];
const sortControl = sortOptions.map(([value, label]) => `<option value="${value}" ${shelfSort === value ? 'selected' : ''}>${label}</option>`).join('');
content = `<div class="shelf-toolbar"><span class="hint">${pageSummary}</span><label class="shelf-sort-control" for="shelf-sort">排序 <select id="shelf-sort" aria-label="书架排序">${sortControl}</select></label></div><div class="card panel"><div class="books full-books">${shown.map(bookCard).join('') || '<div class="empty">这里还没有对应的书。</div>'}</div></div>${renderShelfPagination(currentPage, totalPages)}`;
}
const sub = `${counts.ebooks || 0} 本电子书 · ${counts.albums || 0} 个有声专辑 · ${counts.mp ? 1 : 0} 个文章收藏入口,共 ${counts.total || 0} 个书架条目。`;
@@ -600,7 +656,7 @@ function renderDiscover() {
} else {
const list = payload.books || [];
const local = list.filter(book => !searchQuery || `${book.title}${book.author}${book.category}`.toLowerCase().includes(searchQuery.toLowerCase()));
content = `<div class="card panel"><div class="books full-books">${(payload.local ? local.slice(0, shelfLimit) : list).map(searchCard).join('') || '<div class="empty">没有找到相关书籍,试试其他书名或作者。</div>'}</div></div>`;
content = `<div class="card panel"><div class="books full-books">${(payload.local ? local.slice(0, SHELF_PAGE) : list).map(searchCard).join('') || '<div class="empty">没有找到相关书籍,试试其他书名或作者。</div>'}</div></div>`;
}
return welcome('下一本,遇见什么?', '寻找一本书,也发现一份恰好适合你的书单。') +
@@ -709,8 +765,8 @@ function handleAction(button) {
case 'add-list': { const list = booklists.find(l => l.id === id); if (list) chooseTarget(list.ids, [list.name]); break; }
case 'append-goal': appendGoal(id); break;
case 'new-with-selection': openGoalEditor(null, pendingBooks, pendingSources); break;
case 'shelf-tab': shelfTab = id; shelfLimit = SHELF_PAGE; renderPage(); break;
case 'shelf-more': shelfLimit += SHELF_PAGE; renderPage(); break;
case 'shelf-tab': shelfTab = id; shelfPage = 1; renderPage(); break;
case 'shelf-page': shelfPage = Number(id); renderPage(); break;
case 'period': period = id; renderPage(); break;
case 'discover-tab': discoverTab = id; renderPage(); break;
case 'refresh-recommend': recommendOffset += 12; ensureDiscover(true, true); break;
@@ -758,6 +814,7 @@ document.addEventListener('change', event => {
selectedChapter = event.target.value; bookTab = 'popular'; renderBookDetail();
if (!numberData[`hl:${detailBook}:${selectedChapter}`]) WeApi.highlights(detailBook, selectedChapter).then(res => { numberData[`hl:${detailBook}:${selectedChapter}`] = res; renderBookDetail(); }).catch(error => toast(error.message));
}
if (event.target.id === 'shelf-sort') { shelfSort = event.target.value; shelfPage = 1; renderPage(); }
if (event.target.id === 'search-scope') { searchScope = event.target.value; renderPage(); }
});
$('#goal-start').onchange = () => { $('#goal-end').min = $('#goal-start').value; };