'update'
This commit is contained in:
519
detaildesign.md
519
detaildesign.md
@@ -1,72 +1,13 @@
|
||||
# 2025年阅读统计功能设计补充
|
||||
# iBooks 笔记专家 详细设计文档
|
||||
|
||||
## 书籍阅读时长统计
|
||||
> 版本: 1.1 (2025-09 重构整理)
|
||||
> 维护者: 项目开发组
|
||||
> 说明: 本文档统一重新编排章节,增加架构与 UML 部分,便于后续扩展与维护。
|
||||
|
||||
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小时的部分),更真实反映实际阅读行为。
|
||||
|
||||
## 全局统计函数
|
||||
|
||||
1. `get_total_readtime_year()`:返回全年所有书的累计阅读时间(分钟)。
|
||||
2. `get_total_readtime12m()`:返回全年所有书的月度累计阅读时间(长度12的列表,单位:分钟)。
|
||||
3. `get_total_readtime(days=30)`:返回最近days天每天所有书籍的总阅读时间(分钟),索引0为今天。
|
||||
|
||||
## 设计说明
|
||||
|
||||
- 所有统计均以“分钟”为单位,便于可视化和分析。
|
||||
- 年度统计遍历今年每一天,保证月度和年度数据完整。
|
||||
- 统计逻辑与实际阅读行为高度贴合,支持无笔记但有打开书籍的场景。
|
||||
|
||||
## 可视化设计(统计标签页)
|
||||
|
||||
布局:统计页使用 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)。
|
||||
|
||||
气泡图:
|
||||
- 使用 4 个气泡分别表示上述四项指标。
|
||||
- 半径 r ~ sqrt(value_normalized) 以减弱大值差异;对“小时数”统一换算为分钟后再归一。
|
||||
- 颜色建议:全年(蓝)、月均(橙)、近7天(绿)、日均(紫)。
|
||||
- 文本格式:`标签\n数值+单位`,例如:`全年\n120h`,`日均\n45min`。
|
||||
|
||||
渲染技术(已更新):
|
||||
- 使用原生 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 退出全屏逻辑。
|
||||
|
||||
# iBooks 笔记导出工具 详细设计文档
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| 1.0 | 2025-08 | 初版文档 |
|
||||
| 1.1 | 2025-09 | 重组目录;新增模块拆分、UML、AI 简评与可视化章节整理 |
|
||||
|
||||
## 1. 概述
|
||||
|
||||
@@ -181,7 +122,6 @@ name = info.get('displayname') or info.get('itemname') or assetid
|
||||
if '-' in name: name = name.split('-', 1)[0].strip()
|
||||
```
|
||||
|
||||
|
||||
### 7.2 交互式选择与排序
|
||||
|
||||
```python
|
||||
@@ -203,7 +143,6 @@ answer = inquirer.fuzzy(
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 8. 依赖说明
|
||||
|
||||
- Python 3
|
||||
@@ -212,8 +151,6 @@ answer = inquirer.fuzzy(
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 9. 目录结构
|
||||
|
||||
- `data/`:存放同步下来的数据库和 plist 文件(含 AEAnnotation.sqlite、Books.plist、BKLibrary.sqlite 等)
|
||||
@@ -222,54 +159,57 @@ answer = inquirer.fuzzy(
|
||||
|
||||
---
|
||||
|
||||
## 10. 主要代码文件说明(细化)
|
||||
|
||||
### 10.1 exportbooknotes.py
|
||||
|
||||
## 9.1 主要代码文件说明(细化)
|
||||
- 采用 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
|
||||
|
||||
- `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。
|
||||
- 依赖核心解析模块,负责主流程调度。
|
||||
- OOP 设计,核心类为 `AnnotationManager`:
|
||||
- `get_annotations(bookid=None)`:返回所有或指定 assetid 的笔记。
|
||||
- `parse_location(location)`:静态方法,解析定位信息。
|
||||
- 解析 AEAnnotation.sqlite,提取所有或指定 assetid 的笔记。
|
||||
- 支持苹果时间戳转换,结构化输出。
|
||||
|
||||
- `annotationdata.py`
|
||||
- OOP 设计,核心类为 `AnnotationManager`:
|
||||
- `get_annotations(bookid=None)`:返回所有或指定 assetid 的笔记。
|
||||
- `parse_location(location)`:静态方法,解析定位信息。
|
||||
- 解析 AEAnnotation.sqlite,提取所有或指定 assetid 的笔记。
|
||||
- 支持苹果时间戳转换,结构化输出。
|
||||
### 10.3 booklist_parse.py
|
||||
|
||||
- `booklist_parse.py`
|
||||
- OOP 设计,核心类为 `BookListManager`:
|
||||
- `get_books_info()`:获取书籍元数据。
|
||||
- `get_books_last_open()`:获取每本书的最近打开时间。
|
||||
- 解析 Books.plist,获取书籍元数据(书名、作者、路径、时间等)。
|
||||
- 解析 BKLibrary.sqlite,获取每本书的最近打开时间。
|
||||
- OOP 设计,核心类为 `BookListManager`:
|
||||
- `get_books_info()`:获取书籍元数据。
|
||||
- `get_books_last_open()`:获取每本书的最近打开时间。
|
||||
- 解析 Books.plist,获取书籍元数据(书名、作者、路径、时间等)。
|
||||
- 解析 BKLibrary.sqlite,获取每本书的最近打开时间。
|
||||
|
||||
- `opf_parse.py`
|
||||
- OOP 设计,核心类为 `OPFParser`:
|
||||
- `parse_opf(filepath)`:静态方法,返回 id->href 映射。
|
||||
- 解析 epub 的 OPF 文件,获取章节与文件映射关系(idref -> href)。
|
||||
### 10.4 opf_parse.py
|
||||
|
||||
- `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 目录文件,递归构建章节树结构。
|
||||
- OOP 设计,核心类为 `OPFParser`:
|
||||
- `parse_opf(filepath)`:静态方法,返回 id->href 映射。
|
||||
- 解析 epub 的 OPF 文件,获取章节与文件映射关系(idref -> href)。
|
||||
|
||||
- `backup/booksnote.py`
|
||||
- 历史/备份脚本,辅助数据迁移或格式转换。
|
||||
### 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
|
||||
|
||||
- 历史/备份脚本,辅助数据迁移或格式转换。
|
||||
|
||||
---
|
||||
|
||||
## 10. 扩展与维护建议
|
||||
## 11. 扩展与维护建议
|
||||
|
||||
- 可扩展支持多本书批量导出
|
||||
- 可增加导出格式(如 HTML、PDF)
|
||||
@@ -279,3 +219,368 @@ answer = inquirer.fuzzy(
|
||||
---
|
||||
|
||||
如需进一步细化某一部分设计,请告知!
|
||||
|
||||
---
|
||||
|
||||
## 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 退出全屏逻辑。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user