diff --git a/.DS_Store b/.DS_Store index d771f3d..caae08f 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/detaildesign.md b/detaildesign.md new file mode 100644 index 0000000..6f68249 --- /dev/null +++ b/detaildesign.md @@ -0,0 +1,586 @@ +# iBooks 笔记专家 详细设计文档 + +> 版本: 1.1 (2025-09 重构整理) +> 维护者: 项目开发组 +> 说明: 本文档统一重新编排章节,增加架构与 UML 部分,便于后续扩展与维护。 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 1.0 | 2025-08 | 初版文档 | +| 1.1 | 2025-09 | 重组目录;新增模块拆分、UML、AI 简评与可视化章节整理 | + +## 1. 概述 + +本工具用于从 macOS iBooks(Apple Books)应用的数据文件中提取用户的书籍笔记,并以 Markdown 格式导出。支持从 iBooks 的数据库和 plist 文件自动同步数据,支持交互式选择书籍导出,导出内容结构清晰,便于后续整理和阅读。 +支持按最近打开时间排序书籍,菜单显示书名与时间戳,导出流程高效。 + +--- + +## 2. 主要功能 + +- 自动同步 iBooks 数据库和书籍信息文件到本地 `./data` 目录。 +- 解析 iBooks 笔记数据库,构建结构化的 `booksnote` 数据。 +- 解析书籍元数据(如书名、路径等)。 +- 支持交互式模糊搜索选择要导出的书籍。 +- 按章节导出所选书籍的所有笔记,格式为 Markdown。 +- 书名中如含有“-xxxx”后缀,仅保留“-”前的主书名。 +- 书籍选择菜单按最近打开时间(last_open)降序排序,显示格式为“书名 [时间戳]”。 + +--- + +## 3. 主要数据结构 + +### 3.1 booksnote + +```python +booksnote = { + assetid: { label_path: { uuid: { + 'creationdate': '2023/7/12', + 'filepos': None, + 'idref': '008.xhtml', + 'note': None, + 'selectedtext': '這就是宣傳的恐怖之處' + }}} +} +``` +- `assetid`:书籍唯一标识 +- `label_path`:章节名 +- `uuid`:笔记唯一标识 +- 其余字段为笔记内容及元数据 + +--- + +## 4. 主要流程 + +### 4.1 数据同步 + +- 自动将 iBooks 的数据库和 plist 文件复制到本地 `data/` 目录,便于后续处理。 + +### 4.2 构建 booksnote + +- 通过 `get_annotations` 解析 SQLite 笔记数据库,获取所有笔记。 +- 通过 `parse_books_plist` 解析书籍元数据,获取书名、路径等信息。 +- 遍历每本书的所有笔记,结合OPF、NCX文件和HTML 文件,定位章节名。 +- 若无法通过目录文件定位章节,则尝试通过笔记选中文本在 HTML 文件中查找章节,否则标记为“未找到章节”。 + +### 4.3 交互式选择书籍 + +- 读取 Books.plist 获取所有书籍元数据。 +- 读取 BKLibrary.sqlite,获取每本书的最近打开时间(last_open,苹果时间戳,基准2001-01-01)。 +- 生成书名列表(优先 `displayname`,其次 `itemname`,否则用 `assetid`),并去除“-xxxx”后缀。 +- 按 last_open 时间戳降序排列,菜单显示“书名 [时间戳]”,时间戳为 last_open 字段。 +- 使用 InquirerPy 提供模糊搜索交互界面,供用户选择要导出的书籍。 + +### 4.4 导出 Markdown + +- 仅导出用户选择的书籍。 +- Markdown 格式如下: + + ``` + # 笔记导出 2025-08-06 12:00 + ## 书名 + ### 章节名 + 选中文本 + > 笔记内容 + ``` + +- 每条笔记独立分行,章节分组。 + +--- + +## 5. 关键函数说明 + +### 5.1 build_booksnote + +- 输入:注释数据库路径、书籍 plist 路径 +- 输出:结构化的 booksnote 字典 +- 逻辑:遍历所有笔记,结合书籍元数据和目录信息,归类到章节下 + +### 5.2 export_booksnote_to_md + +- 输入:booksnote、booksinfo、导出路径 +- 输出:Markdown 字符串,并写入文件 +- 逻辑:遍历每本书、每个章节、每条笔记,按格式输出 + +--- + +## 6. 交互与用户体验 + +- 通过命令行交互,用户可模糊搜索并选择要导出的书籍。 +- 若无可导出的笔记,程序自动退出并提示。 +- 导出后,显示导出文件路径和书名。 + +--- + +## 7. 代码片段示例 + +### 7.1 书名处理逻辑 + +```python +name = info.get('displayname') or info.get('itemname') or assetid +# 如果书名中包含“-”,只取“-”前面的部分 +if '-' in name: name = name.split('-', 1)[0].strip() +``` + +### 7.2 交互式选择与排序 + +```python +from booklist_parse import get_books_last_open +last_open_times = get_books_last_open('data/BKLibrary.sqlite') +for assetid, info in booksinfo.items(): + ... + ts = last_open_times.get(assetid, {}).get('last_open', 0) + assetid2lastopen[assetid] = ts +sorted_assetids = sorted(assetid2name.keys(), key=lambda aid: assetid2lastopen[aid], reverse=True) +choices = [f"{assetid2name[aid]} [{assetid2lastopen[aid]}]" for aid in sorted_assetids] +answer = inquirer.fuzzy( + message="请选择要导出的书名(支持模糊搜索):", + choices=choices, + multiselect=False, + instruction="上下键选择,输入可模糊筛选,回车确定" +).execute() +``` + +--- + +## 8. 依赖说明 + +- Python 3 +- 主要依赖库:`InquirerPy`, `bs4`, `shutil`, `os`, `datetime`, `sqlite3` +- 需有 iBooks 数据库、plist 文件和 BKLibrary.sqlite 的本地访问权限 + +--- + +## 9. 目录结构 + +- `data/`:存放同步下来的数据库和 plist 文件(含 AEAnnotation.sqlite、Books.plist、BKLibrary.sqlite 等) +- `notes/`:导出的 Markdown 文件 +- `examples/`:epub 示例文件夹 + +--- + +## 10. 主要代码文件说明(细化) + +### 10.1 exportbooknotes.py + +- 采用 OOP 设计,核心类为 `BookNotesExporter`: + - `build_booksnote(bookid=None)`:构建结构化笔记数据。 + - `export_booksnote_to_md(booksnote, booksinfo, out_path=None)`:导出为 Markdown。 + - `find_file_by_ext`、`get_toc_tree` 等辅助方法。 +- 数据同步:自动复制 iBooks 数据库和元数据到本地。 +- 菜单交互:按最近打开时间戳排序,显示“书名 [时间戳]”,支持模糊搜索。 +- 只处理用户选中书籍的笔记,按章节分组导出 Markdown。 +- 依赖核心解析模块,负责主流程调度。 + +### 10.2 annotationdata.py + +- OOP 设计,核心类为 `AnnotationManager`: + - `get_annotations(bookid=None)`:返回所有或指定 assetid 的笔记。 + - `parse_location(location)`:静态方法,解析定位信息。 +- 解析 AEAnnotation.sqlite,提取所有或指定 assetid 的笔记。 +- 支持苹果时间戳转换,结构化输出。 + +### 10.3 booklist_parse.py + +- OOP 设计,核心类为 `BookListManager`: + - `get_books_info()`:获取书籍元数据。 + - `get_books_last_open()`:获取每本书的最近打开时间。 +- 解析 Books.plist,获取书籍元数据(书名、作者、路径、时间等)。 +- 解析 BKLibrary.sqlite,获取每本书的最近打开时间。 + +### 10.4 opf_parse.py + +- OOP 设计,核心类为 `OPFParser`: + - `parse_opf(filepath)`:静态方法,返回 id->href 映射。 +- 解析 epub 的 OPF 文件,获取章节与文件映射关系(idref -> href)。 + +### 10.5 toc_parse.py + +- OOP 设计,核心类为 `TOCParser`: + - `parse_navpoints(navpoints)`:递归解析 navPoint 节点。 + - `find_label_path(node, ref, filepos, path)`:查找章节路径。 + - `find_section_by_selectedtext(html_path, selectedtext)`:通过选中文本定位章节标题。 + - `parse_html_title(html_path)`:解析 html 文件标题。 +- 解析 NCX 目录文件,递归构建章节树结构。 + +### 10.6 backup/booksnote.py + +- 历史/备份脚本,辅助数据迁移或格式转换。 + +--- + +## 11. 扩展与维护建议 + +- 可扩展支持多本书批量导出 +- 可增加导出格式(如 HTML、PDF) +- 可优化章节定位算法,提升准确率 +- 可增加 GUI 交互界面 + +--- + +如需进一步细化某一部分设计,请告知! + +--- + +## 12. GUI 架构与模块调用关系(2025 拆分后更新) + +### 12.1 模块职责概览 + +| 模块 | 主要类/函数 | 职责 | 关键依赖 | +|------|-------------|------|----------| +| `ibook_export_app.py` | `IBookExportApp` | GUI 入口,组合各 Mixin,组织信号/槽,生命周期管理 | `CoverMixin`, `FinishedBooksMixin`, `BookReviewWorker`, `BookListManager`, `BookNotesExporter` | +| `cover_mixin.py` | `CoverMixin` | 解析、查找、缩放显示封面 | `config` (目录), Qt Widgets | +| `finished_books_mixin.py` | `FinishedBooksMixin` | 已读书籍网格数据装载与自适应布局、点击跳转 | `BookListManager.get_finished_books_this_year`, `CoverMixin.find_book_cover` | +| `review_worker.py` | `BookReviewWorker` | 后台线程获取书籍 AI 简评并写入 `bookintro.json` | `ai_interface.DashScopeChatClient` | +| `booklist_parse.py` | `BookListManager` | 书籍基础元数据、阅读统计、已读书籍列表 | `annotationdata.AnnotationManager`, `config` | +| `annotationdata.py` | `AnnotationManager` | 解析笔记 SQLite,返回结构化笔记 | SQLite DB | +| `exportbooknotes.py` | `BookNotesExporter` | 构建/导出 Markdown 笔记 | `annotationdata`, `toc_parse`, `opf_parse` | +| `toc_parse.py` | `TOCParser` | 解析 NCX/HTML 标题,定位章节路径 | 文件系统/HTML | +| `opf_parse.py` | `OPFParser` | 解析 OPF 获取 manifest 映射 | OPF XML | +| `charts.py` | 图表组件 | 周 / 月 / 年 / 气泡指标可视化 | `BookListManager` 汇总数据 | + +### 12.2 运行时对象关系(简化 UML 文本表示) +``` +IBookExportApp + ├── BookListManager (数据/统计) + ├── BookNotesExporter (导出) + ├── [Composition via Mixin] CoverMixin + ├── [Composition via Mixin] FinishedBooksMixin + ├── 0..n BookReviewWorker (异步 AI 简评线程池 _active_workers) + └── charts.* Widgets (按需创建) +``` + +`IBookExportApp` 通过多继承获得封面与已读网格功能: +1. 封面查找 -> `CoverMixin.find_book_cover` +2. 网格构建 -> `FinishedBooksMixin._populate_finished_books_grid` +3. AI 简评 -> `BookReviewWorker` 发起,完成后回调 `_on_review_finished` + +### 12.3 启动序列(Startup Sequence) +1. 用户运行 `ibook_export_app.py` → 创建 `QApplication` +2. `IBookExportApp.__init__`: + - 加载 `.ui` + - `sync_source_files(config)`(复制原始库到 `data/`) + - 构建 `BookListManager` → 加载 plist / 统计数据 + - 构建书籍列表(按 last_open 排序)填充 `QListWidget` + - 初始化封面标签 & `_load_initial()`(前三本封面 + 首本 AI 简评启动) + - `_populate_finished_books_grid()`(已读网格初填) + - 安装事件过滤器(视口 Resize + Tab 切换策略 C + A) + - 安排延迟 `_relayout_finished_grid()` 确保初次布局正确 +3. 主窗口显示;用户可交互。 + +### 12.4 书籍切换流程(Selecting a Book) +1. 用户在列表中选中条目 → `currentRowChanged` → `update_book_info(row)` +2. 刷新右侧三张封面(当前 + 后两本轮播预览) +3. 构建基础信息 `_base_info_cache` +4. 若 `bookintro.json` 已有简评 → 直接渲染;否则启动新 `BookReviewWorker` → 占位“简评获取中...” +5. 线程完成 → 通过信号调用 `_on_review_finished` → 更新 HTML。 + +### 12.5 AI 简评线程生命周期 +1. 实例化 `BookReviewWorker(bookname, prompt, json_path)` +2. 连接 `finished` 信号到: + - `_on_review_finished` + - `_remove_worker`(清理活动线程列表) +3. `worker.start()` → 线程内部:调用 `DashScopeChatClient.ask()`;写入/更新 `bookintro.json`;发送 `finished` 信号。 +4. 主线程根据 `_current_bookname` 校验是否仍是当前书,防止串写。 + +### 12.6 已读书籍网格刷新逻辑 +事件触发: +1. 程序启动初次调用 `_populate_finished_books_grid()` +2. Tab 切换到“已读书籍”标签 → `_on_main_tab_changed()` → 若命中,立即 `_relayout_finished_grid()` 并延迟一次;(后续可扩展为定期重新查询) +3. (可选待扩展)外部刷新按钮调用 `_populate_finished_books_grid()` + +数据来源:`BookListManager.get_finished_books_this_year()`: + - 查询本地 `BKLibrary.sqlite` 中 `ZISFINISHED=1 AND ZDATEFINISHED NOT NULL` + - 将 Apple epoch(2001) 秒转为 `datetime`,过滤 `year==当前年` + - 返回列表后排序(时间倒序) + +### 12.7 封面加载与缩放流程 +1. `_load_initial()` / `update_book_info()` 内调用 `find_book_cover()` 获取路径 +2. 原图 QPixmap 存入 `_cover_pixmaps_original` +3. `_apply_cover_scale()` 使用当前 `cover_ratio`(默认 1.2)和弹性策略计算目标高度 +4. 固定宽 180px,受硬上限 400px 与(可选)文本区 45% 限制 +5. 非弹性模式忽略文本高度,仅 ratio + 硬上限。 + +### 12.8 导出流程(Export Notes) +1. 用户点击“导出”按钮 → `export_notes()` +2. 取当前行 assetid → `BookNotesExporter.build_booksnote(bookid)` +3. 组装单书字典 → `export_booksnote_to_md()` 输出 Markdown 到 `notes/` 目录 +4. 弹窗提示路径。 + +### 12.9 统计图表流程 +1. 启动后调用 `_init_charts()`(懒加载) +2. 获取周 / 月 / 年聚合数据及总指标 +3. 构造原生自绘组件 `BarChartWidget` / `ScatterChartWidget` / `BubbleMetricsWidget` +4. 添加到对应 Layout。 + +### 12.10 关键调用关系(摘要) +``` +update_book_info -> find_book_cover (CoverMixin) +update_book_info -> BookReviewWorker.start -> _on_review_finished +_populate_finished_books_grid (FinishedBooksMixin) -> manager.get_finished_books_this_year +_on_finished_cover_clicked -> _switch_to_export_tab -> listwidget.setCurrentRow -> update_book_info +export_notes -> BookNotesExporter.build_booksnote -> export_booksnote_to_md +_init_charts -> manager.get_total_readtime* 系列函数 +``` + +### 12.11 数据流摘要 +``` +iBooks 原始文件 -> sync_source_files -> data/*.sqlite / Books.plist + └─ BookListManager 载入 -> booksinfo / open_times / 阅读统计 + ├─ IBookExportApp 构建主列表 + ├─ FinishedBooksMixin 查询已读 -> 网格 + └─ charts.py 生成可视化 + +注释数据库 -> AnnotationManager -> 笔记结构 -> BookNotesExporter.build_booksnote -> Markdown + +AI 请求 -> BookReviewWorker -> DashScopeChatClient.ask -> bookintro.json -> _on_review_finished 渲染 +``` + +### 12.12 扩展点与建议 +1. 已读书籍:增加“显示全部年份 / 仅今年”开关;提供手动“刷新”按钮。 +2. 封面缓存:引入 LRU (assetid -> QPixmap) 降低重复磁盘扫描。 +3. AI 简评:加速策略(本地缓存 TTL、批量预取前 N 本)。 +4. 异步任务:统一线程池/任务队列,避免过多 QThread 分散管理。 +5. 测试建议: + - 单元:`BookListManager.get_finished_books_this_year` 年份过滤、无行时返回。 + - 单元:封面查找:构造临时目录含多个候选文件。 + - 集成:启动后模拟选择书籍 → 断言 `_current_bookname` 及 HTML 含字段。 +6. 性能:大书量时(>1000)列表初始化可用分页或懒加载。 +7. 打包:后续可用 `PyInstaller`,将可执行与资源(icons、ui)整合。 + +### 12.13 风险与缓解 +| 风险 | 描述 | 缓解 | +|------|------|------| +| 数据不同步 | data/ 下 sqlite 未更新导致已读缺失 | 提供“重新同步”按钮;比对文件修改时间 | +| AI 接口失败 | 网络/配额问题 | 回退本地提示文本;重试按钮 | +| UI 首次网格错排 | 视口宽度未稳定 | 采用双阶段(立即+延迟)重排 & Resize 监听 | +| 线程未回收 | 多次切换书籍产生积压 | 维护 `_active_workers` 列表并在完成回收 | + +--- + +(本节为 2025-09 结构化重构新增) + +## 13. UML 图(Mermaid) + +> 注:使用 Mermaid 语法,支持在支持渲染的 Markdown 查看。类之间仅展示主要依赖/调用,非完整字段集合。 + +### 13.1 类图(核心模块) + +```mermaid +classDiagram + class IBookExportApp { + +_load_initial() + +update_book_info(row) + +export_notes() + +_on_finished_cover_clicked(asset_id) + +_on_main_tab_changed(index) + } + class CoverMixin { + +find_book_cover(assetid, info) + +_apply_cover_scale() + +set_cover_ratio(r, force) + } + class FinishedBooksMixin { + +_populate_finished_books_grid() + +_relayout_finished_grid() + } + class BookReviewWorker { + +run() + +finished(book, review) + } + class BookListManager { + +get_books_info() + +get_books_last_open() + +get_finished_books_this_year() + +get_total_readtime(days) + } + class BookNotesExporter { + +build_booksnote(bookid) + +export_booksnote_to_md(note, info, path) + } + class AnnotationManager { + +get_annotations(bookid) + } + class DashScopeChatClient { + +ask(prompt) + } + class BarChartWidget + class BubbleMetricsWidget + class ScatterChartWidget + + IBookExportApp --> CoverMixin : mixin + IBookExportApp --> FinishedBooksMixin : mixin + IBookExportApp --> BookListManager : uses + IBookExportApp --> BookNotesExporter : uses + IBookExportApp --> BookReviewWorker : creates + BookReviewWorker --> DashScopeChatClient : uses + BookListManager --> AnnotationManager : uses + IBookExportApp --> BarChartWidget : creates + IBookExportApp --> BubbleMetricsWidget : creates + IBookExportApp --> ScatterChartWidget : creates + FinishedBooksMixin --> BookListManager : query finished + CoverMixin --> config : paths +``` + +### 13.2 时序图:应用启动 + +```mermaid +sequenceDiagram + participant User + participant App as IBookExportApp + participant Sync as sync_source_files + participant BLM as BookListManager + participant Exporter as BookNotesExporter + User->>App: 启动应用 + App->>Sync: 同步原始数据 + App->>BLM: 构造 & 加载 booksinfo + App->>Exporter: 初始化导出器 + App->>App: _load_initial() (封面/首书) + App->>App: _populate_finished_books_grid() + App->>App: 延迟 _relayout_finished_grid() + App-->>User: 主界面显示 +``` + +### 13.3 时序图:选择书籍 + AI 简评 + +```mermaid +sequenceDiagram + participant User + participant App as IBookExportApp + participant Cover as CoverMixin + participant Worker as BookReviewWorker + participant Chat as DashScopeChatClient + User->>App: 列表选择书籍 + App->>Cover: find_book_cover() + Cover-->>App: 封面路径/None + App->>App: update_book_info() 刷新封面/HTML + alt 本地已有简评 + App-->>User: 显示简评 + else 无简评 + App->>Worker: 创建并 start() + Worker->>Chat: ask(prompt) + Chat-->>Worker: review 文本 + Worker-->>App: finished(book, review) + App-->>User: 渲染书评 + end +``` + +### 13.4 时序图:导出 Markdown + +```mermaid +sequenceDiagram + participant User + participant App as IBookExportApp + participant Exporter as BookNotesExporter + User->>App: 点击 导出 + App->>Exporter: build_booksnote(bookid) + Exporter-->>App: 笔记结构 + App->>Exporter: export_booksnote_to_md() + Exporter-->>App: 输出路径 + App-->>User: 弹窗提示成功 +``` + +### 13.5 时序图:已读书籍网格刷新 + +```mermaid +sequenceDiagram + participant User + participant App as IBookExportApp + participant FB as FinishedBooksMixin + participant BLM as BookListManager + User->>App: 切换到 已读书籍 Tab + App->>FB: _populate_finished_books_grid() (必要时) + FB->>BLM: get_finished_books_this_year() + BLM-->>FB: 已读列表 + FB->>FB: _relayout_finished_grid() + FB-->>User: 网格显示 +``` + +### 13.6 时序图:点击已读封面跳转 + +```mermaid +sequenceDiagram + participant User + participant FB as FinishedBooksMixin + participant App as IBookExportApp + User->>FB: 点击封面 QLabel + FB->>App: _on_finished_cover_clicked(asset_id) + App->>App: _switch_to_export_tab() + App->>App: listwidget.setCurrentRow() + App->>App: update_book_info() + App-->>User: 显示书籍 & 简评/加载中 +``` + +--- + +## 14. 书籍阅读时长统计与可视化 + +### 14.1 阅读时长统计逻辑 + +1. `readtime30d`:每本书最近30天每天的阅读时长(分钟),索引0为今天,索引29为30天前。 +2. `readtime12m`:每本书今年每月的累计阅读时长(分钟),索引0为1月,索引11为12月。统计逻辑为遍历今年每一天,按月累计。 +3. `readtime_year`:每本书今年总阅读时长(分钟),为`readtime12m`各月之和。 +4. 支持无笔记但当天有打开书籍时,阅读时长设为`READ_TIME_OPEN_DAY`(config.py配置,默认30分钟)。 +5. 多条笔记时,统计相邻笔记时间差(仅累加小于3小时的部分),更真实反映实际阅读行为。 + +### 14.2 全局统计函数 + +- `get_total_readtime_year()`:返回全年所有书的累计阅读时间(分钟)。 +- `get_total_readtime12m()`:返回全年所有书的月度累计阅读时间(长度12的列表,单位:分钟)。 +- `get_total_readtime(days=30)`:返回最近days天每天所有书籍的总阅读时间(分钟),索引0为今天。 + +#### 设计说明 +- 所有统计均以“分钟”为单位,便于可视化和分析。 +- 年度统计遍历今年每一天,保证月度和年度数据完整。 +- 统计逻辑与实际阅读行为高度贴合,支持无笔记但有打开书籍的场景。 + +--- + +### 14.3 可视化设计(统计标签页) + +**布局**:统计页使用 2x2 宫格: +- 左上(frame_bubble):综合指标气泡图。 +- 右上(frame_year):全年 12 个月阅读时长柱状图。 +- 左下 (frame_week):最近 7 天阅读时长柱状图(索引0=今天)。 +- 右下 (frame_month):最近 30 天阅读时长柱状图(索引0=今天)。 + +**数据来源**: +- 周图:`get_total_readtime(days=7)` 结果列表(单位:分钟)。 +- 月图:`get_total_readtime(days=30)` 结果列表(单位:分钟)。 +- 年图:`get_total_readtime12m()` 返回长度 12 列表(分钟)。 +- 综合: + * 全年阅读小时数 = `get_total_readtime_year() / 60`(向下取整或保留1位小数)。 + * 月均阅读小时数 = `(sum(month_list) / 12) / 60`。 + * 近7天阅读小时数 = `sum(week_list) / 60`。 + * 日均阅读分钟数 = `sum(month_list[:30 或 recent30]) / 30`(使用最近30天合计除以30)。 + +**气泡图**: +- 使用 5 个气泡(新增“已读”)分别表示:全年累计、月均、近7天、日均、今年已读完书籍数量。 +- 半径 r ~ sqrt(value_normalized) 以减弱大值差异;先将不同单位映射到统一“分钟等价”尺度:`h -> *60`,`m -> 原值`,`book -> *60`(约定 1 本折算 1 小时,确保视觉上不至过小)。 +- 默认布局:4 指标布局在十字形;当包含“已读”且数量≥5 时采用菱形 + 中心分布(中间放“已读”)。 +- 颜色:全年(#5b6ee1)、月均(#c9b2d9)、近7天(#f4b2c2)、日均(#b9b542)、已读(#6aa84f)。 +- 文本格式:`值 + 单位\n标签`;单位规则:`h` 显示“x.x 小时 / x 小时”,`m` 显示“x 分钟”,`book` 显示“x 本书”。 +- 归一化约束:自动检测重叠,通过求最小非重叠缩放系数 S,使圆之间保留最小 6px 间距。 + +**渲染技术**: +- 使用原生 Qt 自绘组件(QWidget + QPainter)实现柱状图与气泡图,文件 `charts.py`。 +- 优势:减少第三方依赖(移除 matplotlib),启动更快、打包体积更小;自绘可精细掌控布局与样式。 +- 结构: + * `BarChartWidget`:通用柱状图组件,支持数值标签、自适应缩放、单位显示。 + * `BubbleMetricsWidget`:四指标气泡图,按归一化后的平方根缩放半径,支持动态指标扩展。 +- 刷新策略:当前初始化时构建;若后续增加刷新按钮,可对组件调用 setData/setMetrics 后 update()。 + +**更新策略**: +1. 启动时已调用 `sync_source_files`,再构建 `BookListManager`。 +2. 通过管理器获取三类聚合数据。 +3. 生成 numpy 数组(可选)并绘制。 +4. 若无数据(全 0),显示占位提示“暂无阅读数据”。 + +**异常处理**: +- 捕获绘图异常(ImportError/RuntimeError),在 frame 中放置 QLabel 显示错误信息而不是抛出。 + +**后续扩展**: +- 柱状图支持堆叠 / 渐变填充、鼠标 hover tooltip。 +- 气泡图支持动画过渡或改为雷达/仪表盘形式;“已读”气泡可按年份切换(未来提供年份选择器)。 +- 增加刷新按钮与 Esc 退出全屏逻辑。 + diff --git a/downimg/.DS_Store b/downimg/.DS_Store new file mode 100644 index 0000000..1bc36c9 Binary files /dev/null and b/downimg/.DS_Store differ diff --git a/readme.md b/readme.md index 6f68249..cbe9407 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,9 @@ --- -## 2. 主要功能 +## 2. 功能 + +## 2.1 主要功能 - 自动同步 iBooks 数据库和书籍信息文件到本地 `./data` 目录。 - 解析 iBooks 笔记数据库,构建结构化的 `booksnote` 数据。 @@ -26,6 +28,17 @@ - 书名中如含有“-xxxx”后缀,仅保留“-”前的主书名。 - 书籍选择菜单按最近打开时间(last_open)降序排序,显示格式为“书名 [时间戳]”。 +## 2.1 GUI + +![img](uml/iShot_2025-09-07_12.22.26.png) +![img](uml/iShot_2025-09-07_12.22.05.png) +![img](uml/iShot_2025-09-07_12.22.16.png) + +笔记导出markdown格式: +![img](uml/iShot_2025-09-07_12.31.49.png) +![img](uml/iShot_2025-09-07_12.32.41.png) + + --- ## 3. 主要数据结构 @@ -365,151 +378,24 @@ AI 请求 -> BookReviewWorker -> DashScopeChatClient.ask -> bookintro.json -> _o > 注:使用 Mermaid 语法,支持在支持渲染的 Markdown 查看。类之间仅展示主要依赖/调用,非完整字段集合。 ### 13.1 类图(核心模块) - -```mermaid -classDiagram - class IBookExportApp { - +_load_initial() - +update_book_info(row) - +export_notes() - +_on_finished_cover_clicked(asset_id) - +_on_main_tab_changed(index) - } - class CoverMixin { - +find_book_cover(assetid, info) - +_apply_cover_scale() - +set_cover_ratio(r, force) - } - class FinishedBooksMixin { - +_populate_finished_books_grid() - +_relayout_finished_grid() - } - class BookReviewWorker { - +run() - +finished(book, review) - } - class BookListManager { - +get_books_info() - +get_books_last_open() - +get_finished_books_this_year() - +get_total_readtime(days) - } - class BookNotesExporter { - +build_booksnote(bookid) - +export_booksnote_to_md(note, info, path) - } - class AnnotationManager { - +get_annotations(bookid) - } - class DashScopeChatClient { - +ask(prompt) - } - class BarChartWidget - class BubbleMetricsWidget - class ScatterChartWidget - - IBookExportApp --> CoverMixin : mixin - IBookExportApp --> FinishedBooksMixin : mixin - IBookExportApp --> BookListManager : uses - IBookExportApp --> BookNotesExporter : uses - IBookExportApp --> BookReviewWorker : creates - BookReviewWorker --> DashScopeChatClient : uses - BookListManager --> AnnotationManager : uses - IBookExportApp --> BarChartWidget : creates - IBookExportApp --> BubbleMetricsWidget : creates - IBookExportApp --> ScatterChartWidget : creates - FinishedBooksMixin --> BookListManager : query finished - CoverMixin --> config : paths -``` +![img](uml/iShot_2025-09-07_12.55.41.png) ### 13.2 时序图:应用启动 - -```mermaid -sequenceDiagram - participant User - participant App as IBookExportApp - participant Sync as sync_source_files - participant BLM as BookListManager - participant Exporter as BookNotesExporter - User->>App: 启动应用 - App->>Sync: 同步原始数据 - App->>BLM: 构造 & 加载 booksinfo - App->>Exporter: 初始化导出器 - App->>App: _load_initial() (封面/首书) - App->>App: _populate_finished_books_grid() - App->>App: 延迟 _relayout_finished_grid() - App-->>User: 主界面显示 -``` +![img](uml/iShot_2025-09-07_12.56.27.png) ### 13.3 时序图:选择书籍 + AI 简评 - -```mermaid -sequenceDiagram - participant User - participant App as IBookExportApp - participant Cover as CoverMixin - participant Worker as BookReviewWorker - participant Chat as DashScopeChatClient - User->>App: 列表选择书籍 - App->>Cover: find_book_cover() - Cover-->>App: 封面路径/None - App->>App: update_book_info() 刷新封面/HTML - alt 本地已有简评 - App-->>User: 显示简评 - else 无简评 - App->>Worker: 创建并 start() - Worker->>Chat: ask(prompt) - Chat-->>Worker: review 文本 - Worker-->>App: finished(book, review) - App-->>User: 渲染书评 - end -``` +![img](uml/iShot_2025-09-07_12.56.00.png) ### 13.4 时序图:导出 Markdown +![img](uml/iShot_2025-09-07_12.57.06.png) -```mermaid -sequenceDiagram - participant User - participant App as IBookExportApp - participant Exporter as BookNotesExporter - User->>App: 点击 导出 - App->>Exporter: build_booksnote(bookid) - Exporter-->>App: 笔记结构 - App->>Exporter: export_booksnote_to_md() - Exporter-->>App: 输出路径 - App-->>User: 弹窗提示成功 -``` ### 13.5 时序图:已读书籍网格刷新 +![img](uml/iShot_2025-09-07_12.57.19.png) -```mermaid -sequenceDiagram - participant User - participant App as IBookExportApp - participant FB as FinishedBooksMixin - participant BLM as BookListManager - User->>App: 切换到 已读书籍 Tab - App->>FB: _populate_finished_books_grid() (必要时) - FB->>BLM: get_finished_books_this_year() - BLM-->>FB: 已读列表 - FB->>FB: _relayout_finished_grid() - FB-->>User: 网格显示 -``` ### 13.6 时序图:点击已读封面跳转 - -```mermaid -sequenceDiagram - participant User - participant FB as FinishedBooksMixin - participant App as IBookExportApp - User->>FB: 点击封面 QLabel - FB->>App: _on_finished_cover_clicked(asset_id) - App->>App: _switch_to_export_tab() - App->>App: listwidget.setCurrentRow() - App->>App: update_book_info() - App-->>User: 显示书籍 & 简评/加载中 -``` +![img](uml/iShot_2025-09-07_12.57.28.png) --- diff --git a/uml/iShot_2025-09-06_22.36.33.png b/uml/iShot_2025-09-06_22.36.33.png new file mode 100644 index 0000000..8fa7dbd Binary files /dev/null and b/uml/iShot_2025-09-06_22.36.33.png differ diff --git a/uml/iShot_2025-09-06_22.44.01.png b/uml/iShot_2025-09-06_22.44.01.png new file mode 100644 index 0000000..c1af549 Binary files /dev/null and b/uml/iShot_2025-09-06_22.44.01.png differ diff --git a/uml/iShot_2025-09-07_12.22.05.png b/uml/iShot_2025-09-07_12.22.05.png new file mode 100644 index 0000000..30d113b Binary files /dev/null and b/uml/iShot_2025-09-07_12.22.05.png differ diff --git a/uml/iShot_2025-09-07_12.22.16.png b/uml/iShot_2025-09-07_12.22.16.png new file mode 100644 index 0000000..3e5ab53 Binary files /dev/null and b/uml/iShot_2025-09-07_12.22.16.png differ diff --git a/uml/iShot_2025-09-07_12.22.26.png b/uml/iShot_2025-09-07_12.22.26.png new file mode 100644 index 0000000..a1899df Binary files /dev/null and b/uml/iShot_2025-09-07_12.22.26.png differ diff --git a/uml/iShot_2025-09-07_12.31.49.png b/uml/iShot_2025-09-07_12.31.49.png new file mode 100644 index 0000000..bb921af Binary files /dev/null and b/uml/iShot_2025-09-07_12.31.49.png differ diff --git a/uml/iShot_2025-09-07_12.32.41.png b/uml/iShot_2025-09-07_12.32.41.png new file mode 100644 index 0000000..dd49c28 Binary files /dev/null and b/uml/iShot_2025-09-07_12.32.41.png differ diff --git a/uml/iShot_2025-09-07_12.55.41.png b/uml/iShot_2025-09-07_12.55.41.png new file mode 100644 index 0000000..ee44ea5 Binary files /dev/null and b/uml/iShot_2025-09-07_12.55.41.png differ diff --git a/uml/iShot_2025-09-07_12.56.00.png b/uml/iShot_2025-09-07_12.56.00.png new file mode 100644 index 0000000..9c3a663 Binary files /dev/null and b/uml/iShot_2025-09-07_12.56.00.png differ diff --git a/uml/iShot_2025-09-07_12.56.27.png b/uml/iShot_2025-09-07_12.56.27.png new file mode 100644 index 0000000..c3fb7f2 Binary files /dev/null and b/uml/iShot_2025-09-07_12.56.27.png differ diff --git a/uml/iShot_2025-09-07_12.57.06.png b/uml/iShot_2025-09-07_12.57.06.png new file mode 100644 index 0000000..ed0d7f2 Binary files /dev/null and b/uml/iShot_2025-09-07_12.57.06.png differ diff --git a/uml/iShot_2025-09-07_12.57.19.png b/uml/iShot_2025-09-07_12.57.19.png new file mode 100644 index 0000000..d38a326 Binary files /dev/null and b/uml/iShot_2025-09-07_12.57.19.png differ diff --git a/uml/iShot_2025-09-07_12.57.28.png b/uml/iShot_2025-09-07_12.57.28.png new file mode 100644 index 0000000..361f2be Binary files /dev/null and b/uml/iShot_2025-09-07_12.57.28.png differ