Initial commit
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""网页生成器 — 读取数据库,生成静态 HTML 展示页面。"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import db
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
TEMPLATE_PATH = os.path.join(BASE_DIR, "templates", "index.html")
|
||||
STYLE_PATH = os.path.join(BASE_DIR, "static", "style.css")
|
||||
OUTPUT_DIR = os.path.join(BASE_DIR, "output")
|
||||
OUTPUT_PATH = os.path.join(OUTPUT_DIR, "index.html")
|
||||
|
||||
|
||||
def _escape(text):
|
||||
"""简单的 HTML 转义。"""
|
||||
if not text:
|
||||
return ""
|
||||
return (str(text)
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """))
|
||||
|
||||
|
||||
def _render_book_card(book):
|
||||
"""渲染单本书的 HTML 卡片。"""
|
||||
cover = book.get("cover_url") or ""
|
||||
cover_html = (f'<img class="card-cover" src="{_escape(cover)}" alt="封面" loading="lazy">'
|
||||
if cover
|
||||
else '<div class="card-cover card-cover--placeholder">📖</div>')
|
||||
|
||||
rating_html = ""
|
||||
if book.get("rating"):
|
||||
stars = "★" * book["rating"] + "☆" * (5 - book["rating"])
|
||||
rating_html = f'<div class="card-rating personal">{stars}</div>'
|
||||
|
||||
scores = []
|
||||
if book.get("douban_score"):
|
||||
scores.append(f'<span class="score douban">豆瓣 {book["douban_score"]}</span>')
|
||||
if book.get("goodreads_score"):
|
||||
scores.append(f'<span class="score goodreads">GR {book["goodreads_score"]}</span>')
|
||||
scores_html = f'<div class="card-scores">{"".join(scores)}</div>' if scores else ""
|
||||
|
||||
tags_html = ""
|
||||
if book.get("tags"):
|
||||
tag_spans = "".join(
|
||||
f'<span class="tag" data-tag="{_escape(t.strip())}">{_escape(t.strip())}</span>'
|
||||
for t in book["tags"].split(",") if t.strip()
|
||||
)
|
||||
tags_html = f'<div class="card-tags">{tag_spans}</div>'
|
||||
|
||||
meta_parts = []
|
||||
if book.get("author"):
|
||||
meta_parts.append(f'{_escape(book["author"])}')
|
||||
if book.get("translator"):
|
||||
meta_parts.append(f'译: {_escape(book["translator"])}')
|
||||
if book.get("publisher"):
|
||||
meta_parts.append(f'{_escape(book["publisher"])}')
|
||||
if book.get("pub_date"):
|
||||
meta_parts.append(f'{_escape(book["pub_date"])}')
|
||||
meta_html = f'<div class="card-meta">{" · ".join(meta_parts)}</div>' if meta_parts else ""
|
||||
|
||||
notes_html = ""
|
||||
if book.get("notes"):
|
||||
notes_html = f'<div class="card-notes">{_escape(book["notes"])}</div>'
|
||||
|
||||
dates = []
|
||||
if book.get("start_date"):
|
||||
dates.append(f'开始: {_escape(book["start_date"])}')
|
||||
if book.get("finish_date"):
|
||||
dates.append(f'完成: {_escape(book["finish_date"])}')
|
||||
dates_html = f'<div class="card-dates">{" · ".join(dates)}</div>' if dates else ""
|
||||
|
||||
format_badge = "📱 电子书" if book.get("format") == "ebook" else "📕 纸质书"
|
||||
|
||||
return f"""
|
||||
<div class="book-card" data-status="{_escape(book.get('status', ''))}"
|
||||
data-format="{_escape(book.get('format', ''))}"
|
||||
data-tags="{_escape(book.get('tags', ''))}">
|
||||
{cover_html}
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">{_escape(book.get('title', ''))}</h3>
|
||||
{meta_html}
|
||||
{rating_html}
|
||||
{scores_html}
|
||||
{tags_html}
|
||||
{notes_html}
|
||||
{dates_html}
|
||||
<div class="card-format">{format_badge}</div>
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
|
||||
def build():
|
||||
"""生成静态 HTML 页面,返回输出路径。"""
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
books = db.list_books()
|
||||
stats = db.get_stats()
|
||||
all_tags = db.get_all_tags()
|
||||
|
||||
cards_html = "\n".join(_render_book_card(b) for b in books)
|
||||
|
||||
# 读取 CSS
|
||||
with open(STYLE_PATH, "r", encoding="utf-8") as f:
|
||||
css = f.read()
|
||||
|
||||
# 读取模板并填充
|
||||
with open(TEMPLATE_PATH, "r", encoding="utf-8") as f:
|
||||
template = f.read()
|
||||
|
||||
# 统计数据
|
||||
total = stats["total"]
|
||||
reading = stats["by_status"].get("reading", 0)
|
||||
read = stats["by_status"].get("read", 0)
|
||||
to_read = stats["by_status"].get("to-read", 0)
|
||||
paper = stats["by_format"].get("paper", 0)
|
||||
ebook = stats["by_format"].get("ebook", 0)
|
||||
|
||||
# 标签按钮
|
||||
tag_buttons = "".join(
|
||||
f'<button class="tag-btn" data-tag="{_escape(t)}">{_escape(t)}</button>'
|
||||
for t in all_tags
|
||||
)
|
||||
|
||||
html = (template
|
||||
.replace("{{CSS}}", css)
|
||||
.replace("{{TOTAL}}", str(total))
|
||||
.replace("{{READING}}", str(reading))
|
||||
.replace("{{READ}}", str(read))
|
||||
.replace("{{TO_READ}}", str(to_read))
|
||||
.replace("{{PAPER}}", str(paper))
|
||||
.replace("{{EBOOK}}", str(ebook))
|
||||
.replace("{{TAG_BUTTONS}}", tag_buttons)
|
||||
.replace("{{BOOK_CARDS}}", cards_html))
|
||||
|
||||
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
|
||||
# 复制封面图片不处理(直接使用 URL)
|
||||
return OUTPUT_PATH
|
||||
Reference in New Issue
Block a user