Files
bookshelf/templates/index.html
T
kai 0801f7c7df 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 交叉校验),形成完美的防呆与自动化壁垒。
2026-03-26 11:42:43 +08:00

163 lines
5.5 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的书架 — Ecliptic Pathfinder</title>
<meta name="description" content="个人图书阅读管理 — 阅读进度与书库展示">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>{{CSS}}</style>
</head>
<body>
<header class="hero">
<div class="hero-inner">
<h1>📚 我的书架</h1>
<p class="hero-sub">阅读是最好的投资</p>
</div>
</header>
<!-- 统计面板 -->
<section class="stats-bar">
<div class="stat">
<span class="stat-num">{{TOTAL}}</span>
<span class="stat-label">总计</span>
</div>
<div class="stat stat--reading">
<span class="stat-num">{{READING}}</span>
<span class="stat-label">在读</span>
</div>
<div class="stat stat--read">
<span class="stat-num">{{READ}}</span>
<span class="stat-label">已读</span>
</div>
<div class="stat stat--to-read">
<span class="stat-num">{{TO_READ}}</span>
<span class="stat-label">待读</span>
</div>
<div class="stat stat--paper">
<span class="stat-num">{{PAPER}}</span>
<span class="stat-label">纸质</span>
</div>
<div class="stat stat--ebook">
<span class="stat-num">{{EBOOK}}</span>
<span class="stat-label">电子</span>
</div>
</section>
<!-- 视图切换 -->
<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="groups">🗂️ 主题概览</button>
</nav>
<!-- 进度视图的子筛选 -->
<nav class="filter-nav" id="filter-progress">
<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>
<!-- 书库视图的子筛选 -->
<nav class="filter-nav hidden" id="filter-library">
<button class="filter-btn active" data-filter="all">全部</button>
<button class="filter-btn" data-filter="paper">📕 纸质书</button>
<button class="filter-btn" data-filter="ebook">📱 电子书</button>
</nav>
<!-- 书目卡片列表 -->
<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>
<script>
document.getElementById('gen-time').textContent = new Date().toLocaleDateString('zh-CN');
const cards = document.querySelectorAll('.book-card');
const viewBtns = document.querySelectorAll('.view-btn');
const filterNavs = {
progress: document.getElementById('filter-progress'),
library: document.getElementById('filter-library'),
};
let currentView = 'progress';
let currentFilter = 'reading';
function applyFilters() {
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 => {
btn.addEventListener('click', () => {
viewBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentView = btn.dataset.view;
currentFilter = 'all';
Object.values(filterNavs).forEach(n => {
if (n) n.classList.add('hidden');
});
if (filterNavs[currentView]) filterNavs[currentView].classList.remove('hidden');
// reset sub-filter active state
if (filterNavs[currentView]) {
filterNavs[currentView].querySelectorAll('.filter-btn').forEach((b, i) => {
b.classList.toggle('active', i === 0);
});
}
applyFilters();
});
});
document.querySelectorAll('.filter-nav').forEach(nav => {
nav.addEventListener('click', e => {
const btn = e.target.closest('.filter-btn');
if (!btn) return;
nav.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
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();
});
});
</script>
</body>
</html>