feat: 重构图书墙交互及多源高阶信息抓取注入系统
1. UI 层: - 抛弃网格卡片结构,改版为沉浸式的“图书墙”(1:1.43 原比例包含模式)。 - 新增悬浮深色遮罩系统(hover-details),解绑评分点击阻塞。 - 过滤系统默认进入“在读”状态页,彻底移除冗余无效的全选按钮。 2. 抓取与刮削层 (scrape.py): - 抛弃对 ISBN 查询 Google Books 的严重强依赖。 - 重构抓取链路,首选 Goodreads 与特供的 Books.com.tw(博客来) 高精爬取港台原版资料。 - 建立 Amazon/Goodreads 图片去码净化正则,告别低分辨率与畸形图片。 - 修复 bs4 (.select_first) 解析选择器在旧版环境的兼容性异常。 3. Agent AI 数据管线设计 (bookshelf.py): - 开放基于 --json 与 --cover 的强力直接注入功能。 - 升级 add 命令作为最优先级的录入手段,免去繁琐的 enrich 阶段,直接连带高清图和满载元数据强插。 - 建立入库反重名智能拦截 (title + author 交叉校验),形成完美的防呆与自动化壁垒。
This commit is contained in:
+42
-30
@@ -50,13 +50,13 @@
|
||||
<nav class="view-nav">
|
||||
<button class="view-btn active" data-view="progress">📖 阅读进度</button>
|
||||
<button class="view-btn" data-view="library">📚 书库</button>
|
||||
<button class="view-btn" data-view="tags">🏷️ 主题</button>
|
||||
<button class="view-btn" data-view="groups">🗂️ 主题概览</button>
|
||||
</nav>
|
||||
|
||||
<!-- 进度视图的子筛选 -->
|
||||
<nav class="filter-nav" id="filter-progress">
|
||||
<button class="filter-btn active" data-filter="all">全部</button>
|
||||
<button class="filter-btn" data-filter="reading">在读</button>
|
||||
<button class="filter-btn" data-filter="all">全部</button>
|
||||
<button class="filter-btn active" data-filter="reading">在读</button>
|
||||
<button class="filter-btn" data-filter="read">已读</button>
|
||||
<button class="filter-btn" data-filter="to-read">待读</button>
|
||||
</nav>
|
||||
@@ -68,17 +68,16 @@
|
||||
<button class="filter-btn" data-filter="ebook">📱 电子书</button>
|
||||
</nav>
|
||||
|
||||
<!-- 标签视图的按钮 -->
|
||||
<nav class="filter-nav hidden" id="filter-tags">
|
||||
<button class="filter-btn tag-btn active" data-tag="">全部</button>
|
||||
{{TAG_BUTTONS}}
|
||||
</nav>
|
||||
|
||||
<!-- 书目卡片列表 -->
|
||||
<main class="book-grid">
|
||||
<main class="book-grid" id="book-list">
|
||||
{{BOOK_CARDS}}
|
||||
</main>
|
||||
|
||||
<!-- 群组概况列表 -->
|
||||
<main id="group-overview" style="display: none;">
|
||||
{{GROUP_OVERVIEW_HTML}}
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Ecliptic Pathfinder · 生成于 <span id="gen-time"></span></p>
|
||||
</footer>
|
||||
@@ -91,26 +90,29 @@ const viewBtns = document.querySelectorAll('.view-btn');
|
||||
const filterNavs = {
|
||||
progress: document.getElementById('filter-progress'),
|
||||
library: document.getElementById('filter-library'),
|
||||
tags: document.getElementById('filter-tags'),
|
||||
};
|
||||
|
||||
let currentView = 'progress';
|
||||
let currentFilter = 'all';
|
||||
let currentTag = '';
|
||||
let currentFilter = 'reading';
|
||||
|
||||
function applyFilters() {
|
||||
cards.forEach(card => {
|
||||
let show = true;
|
||||
if (currentView === 'progress' && currentFilter !== 'all') {
|
||||
show = card.dataset.status === currentFilter;
|
||||
} else if (currentView === 'library' && currentFilter !== 'all') {
|
||||
show = card.dataset.format === currentFilter;
|
||||
} else if (currentView === 'tags' && currentTag) {
|
||||
const cardTags = card.dataset.tags.split(',').map(t => t.trim());
|
||||
show = cardTags.includes(currentTag);
|
||||
}
|
||||
card.style.display = show ? '' : 'none';
|
||||
});
|
||||
if (currentView === 'groups') {
|
||||
document.getElementById('book-list').style.display = 'none';
|
||||
document.getElementById('group-overview').style.display = 'flex';
|
||||
} else {
|
||||
document.getElementById('book-list').style.display = '';
|
||||
document.getElementById('group-overview').style.display = 'none';
|
||||
|
||||
cards.forEach(card => {
|
||||
let show = true;
|
||||
if (currentView === 'progress' && currentFilter !== 'all') {
|
||||
show = card.dataset.status === currentFilter;
|
||||
} else if (currentView === 'library' && currentFilter !== 'all') {
|
||||
show = card.dataset.format === currentFilter;
|
||||
}
|
||||
card.style.display = show ? '' : 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
viewBtns.forEach(btn => {
|
||||
@@ -119,13 +121,16 @@ viewBtns.forEach(btn => {
|
||||
btn.classList.add('active');
|
||||
currentView = btn.dataset.view;
|
||||
currentFilter = 'all';
|
||||
currentTag = '';
|
||||
Object.values(filterNavs).forEach(n => n.classList.add('hidden'));
|
||||
Object.values(filterNavs).forEach(n => {
|
||||
if (n) n.classList.add('hidden');
|
||||
});
|
||||
if (filterNavs[currentView]) filterNavs[currentView].classList.remove('hidden');
|
||||
// reset sub-filter active state
|
||||
filterNavs[currentView]?.querySelectorAll('.filter-btn').forEach((b, i) => {
|
||||
b.classList.toggle('active', i === 0);
|
||||
});
|
||||
if (filterNavs[currentView]) {
|
||||
filterNavs[currentView].querySelectorAll('.filter-btn').forEach((b, i) => {
|
||||
b.classList.toggle('active', i === 0);
|
||||
});
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
@@ -139,8 +144,15 @@ document.querySelectorAll('.filter-nav').forEach(nav => {
|
||||
if (btn.dataset.tag !== undefined) {
|
||||
currentTag = btn.dataset.tag;
|
||||
currentFilter = 'all';
|
||||
currentGroup = '';
|
||||
} else if (btn.dataset.group !== undefined) {
|
||||
currentGroup = btn.dataset.group;
|
||||
currentFilter = 'all';
|
||||
currentTag = '';
|
||||
} else {
|
||||
currentFilter = btn.dataset.filter;
|
||||
currentTag = '';
|
||||
currentGroup = '';
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user