v67:读书模块补齐(书能点进去 / 加一本 / 读书笔记 / 统计)

用户 2026-10-20:「其他的功能也逐步完成……读书点不进去,也没法加书,
设置也没有任何的东西。」

## 先修三处「对着服务端抄错的口径」(都不报错,只是显示是错的)
1. 状态值:服务端 `Book::STATUS` 只有 wish/reading/done/paused,**没有 want**;
   `Book::status()` 遇到不认识的值会静默改成 `wish`,而端上分组键写的 `want`
   ⇒ 书架里「想读」那一组**整组不显示**(页头还数着它,看着像书丢了)。
   `paused`(搁置)同样没列 ⇒ 搁置的书也人间蒸发。
2. 评分:服务端 `rating` 是 0–10,端上当 0–5 用 ⇒ 8 分显示成 `8.0/5`。
   现在解析时折半、写回时 ×2。
3. 进度:服务端 `progress` 是「读到第几章」,端上当百分比 ⇒ 读到第 30 章 = 30%。
   现在按 `chapter_count` 换算,没解析过章节的书不画假进度。

## 补齐三个死掉的入口
- 书行整行可点 → `FbBookSheet`(状态 / 评分 / 章节进度 / 读书笔记 / 删除二次确认)
- 页头「+ 加一本」→ 同一浮层的新建态(书名 / 作者 / 状态)
- 设置「读书统计」→ 端上现算(藏书/在读/读完/想读/搁置/笔记条数);
  另两条(导入笔记 / 微信读书)要文件选择与授权,仍标「网页端」,不摆假入口

## 服务端能力边界(照实做)
- `PATCH /books/<id>` 只认 status / rating ⇒ 详情里不给改书名/作者的输入框
- 章节进度只读(`progress` 由阅读器翻页写,接口没有回写入口)
- 读书笔记走真实接口 `GET/POST /books/<id>/notes`

## 验证
- `hvigorw assembleHap` → BUILD SUCCESSFUL
- `_fb_arkts_guard.js` → 797 / 0(新增 groupV67 28 条,含「端上分组键 ==
  服务端枚举」的跨语言对照)
- `_fb_v59d_falsify.py` → 130 / 0(v67 新增 13 条注入全部变红)
- ⚠️ 设备仍断连(`hdc list targets` → `[Empty]`),真机未验收
This commit is contained in:
2026-10-20 17:14:40 +08:00
parent 58f5509c1c
commit ec2cb14c4c
7 changed files with 1013 additions and 31 deletions
+129 -8
View File
@@ -2,10 +2,17 @@
* 家书 FamilyBook · 读书模块(鸿蒙端)
*
* 对应 Web 端 `templates/books/index.php`(书架 + 上传解析 + 在线读 + 笔记 + 统计)。
* 端上先做**书架**这一层:按状态分组(在读 / 想读 / 读完),
* 每本书显示作者、评分、笔记数、阅读进度。
* 端上做**书架 + 一本书的详情**这两层:
* · 书架按状态分组(在读 / 想读 / 搁置 / 读完),显示作者、评分、笔记数、进度;
* · 点一本书 → `FbBookSheet`(状态 / 评分 / 章节进度 / 读书笔记 / 删除);
* · 页头 + → 同一浮层的新建态(书名 / 作者 / 状态);
* · 设置菜单 → 读书统计(端上现算)+ 两条网页端能力的入口说明。
*
* 「在线读」「上传 EPUB 解析」「笔记导入」这些属于交互+文件能力,留给第二轮。
* 🔴 v67(用户 2026-10-20「其他的功能也逐步完成……读书点不进去」):
* 原来这里**只有书架**:书行没有 `onClick`、页头"上传/统计"按钮没有 `onTap`、
* 设置三项全是空 `action`。三处现在都接上了。
*
* 「在线读」「上传 EPUB 解析」「笔记导入」属于交互+文件能力,仍在网页端。
*/
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK } from '../../common/Theme';
@@ -19,6 +26,7 @@ import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FbBookSheet, FbBookStatsSheet } from '../../components/FbBookSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_BOOKS } from '../../model/FbSettingsMenus';
@@ -45,6 +53,20 @@ export struct FbBooksPage {
@Local all: FbBookItem[] = [];
// ---- 一本书的浮层(v67:原来点了没反应)----
@Local bookOpen: boolean = false;
/** 0 = 加一本;>0 = 这本书的详情 */
@Local bookId: number = 0;
@Local bookTitle: string = '';
@Local bookAuthor: string = '';
@Local bookStatus: string = 'wish';
@Local bookRate: number = 0;
@Local bookCur: number = 0;
@Local bookChapters: number = 0;
/** 读书统计浮层 */
@Local statsOpen: boolean = false;
/**
* 页面出现:先读缓存、再拉接口(本地优先,见 FbModuleRepo 的说明)。
*
@@ -61,6 +83,14 @@ export struct FbBooksPage {
}
private backOnce(): boolean {
if (this.bookOpen) {
this.bookOpen = false;
return true;
}
if (this.statsOpen) {
this.statsOpen = false;
return true;
}
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
@@ -95,7 +125,13 @@ export struct FbBooksPage {
*/
@Monitor('settingsOpen')
onSettingsChanged(): void {
fbAppState().sheetCount = this.settingsOpen ? 1 : 0;
fbAppState().sheetCount = (this.bookOpen || this.statsOpen || this.settingsOpen) ? 1 : 0;
}
/** 三个浮层:任一个开着都要让底部悬浮栏让位(它们各自带全屏遮罩,会盖住悬浮栏) */
@Monitor('bookOpen', 'statsOpen')
onSheetsChanged(): void {
fbAppState().sheetCount = (this.bookOpen || this.statsOpen || this.settingsOpen) ? 1 : 0;
}
build() {
@@ -120,8 +156,22 @@ export struct FbBooksPage {
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 上传 / 统计(Web 端在设置菜单里)
FbIconButton({ icon: $r('app.media.fb_ic_chart'), tint: FbTint.muted })
/* 🔴 v67:原来这颗是「统计」图标且**没有 `onTap`**(纯摆设,点了没反应)。
现在拆成两颗:+ 加一本(主操作)、统计(设置里也有一条,这里给个快捷)。 */
FbIconButton({
icon: $r('app.media.fb_ic_plus'),
tint: this.accent,
onTap: (): void => {
this.openNewBook();
}
})
FbIconButton({
icon: $r('app.media.fb_ic_chart'),
tint: FbTint.muted,
onTap: (): void => {
this.statsOpen = true;
}
})
// 🔴 设置按钮 = 网页版那种**三条横线**(☰)—— 用户 2026-10-16。
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
FbIconButton({
@@ -196,8 +246,51 @@ export struct FbBooksPage {
this.settingsOpen = false;
},
onPick: (action: string): void => {
// 条目里 `action === ''` 的是「暂未开放」(组件里已画灰、点了不响应)
// ⇒ 这里只需处理已实现的;目前都还没有,保持浮层开着即可。
/* 🔴 v67:三条原来是空 `action`(画灰、点了没反应 = 用户说的"设置没有任何东西")。
现在 `bookstats` 真的能进去;另两条要文件选择/微信授权,
网页端才有 ⇒ 在组件里仍是「暂未开放」,这里的 hint 也写清楚了。 */
if (action === 'bookstats') {
this.settingsOpen = false;
this.statsOpen = true;
}
}
})
.transition(fbSheetTransition())
}
// ---- 一本书(新建 / 详情)----
if (this.bookOpen) {
FbBookSheet({
accent: this.accent,
editingId: this.bookId,
initTitle: this.bookTitle,
initAuthor: this.bookAuthor,
initStatus: this.bookStatus,
initRate: this.bookRate,
curChapter: this.bookCur,
chapters: this.bookChapters,
onClose: (): void => {
this.bookOpen = false;
},
onSaved: (): void => {
this.bookOpen = false;
this.load();
},
onDeleted: (): void => {
this.bookOpen = false;
this.load();
}
})
.transition(fbSheetTransition())
}
// ---- 读书统计 ----
if (this.statsOpen) {
FbBookStatsSheet({
accent: this.accent,
items: this.all,
onClose: (): void => {
this.statsOpen = false;
}
})
.transition(fbSheetTransition())
@@ -207,6 +300,30 @@ export struct FbBooksPage {
.height('100%')
}
/** 点一本书 → 详情 */
private openBook(b: FbBookItem): void {
this.bookId = b.id;
this.bookTitle = b.title;
this.bookAuthor = b.author;
this.bookStatus = b.status;
this.bookRate = b.rate;
this.bookCur = b.curChapter;
this.bookChapters = b.chapters;
this.bookOpen = true;
}
/** 页头 + → 走同一个浮层的新建态 */
private openNewBook(): void {
this.bookId = 0;
this.bookTitle = '';
this.bookAuthor = '';
this.bookStatus = 'wish';
this.bookRate = 0;
this.bookCur = 0;
this.bookChapters = 0;
this.bookOpen = true;
}
/** 一本书:书脊色块(代替封面)+ 书名/作者 + 评分/笔记/进度 */
@Builder
bookRow(b: FbBookItem) {
@@ -285,6 +402,10 @@ export struct FbBooksPage {
.borderRadius(FbMetric.radius)
.margin({ bottom: 8 })
.alignItems(VerticalAlign.Center)
/* 🔴 v67:整行可点 → 打开这本书(原来**没有 `onClick`** ⇒ "点不进去") */
.onClick((): void => {
this.openBook(b);
})
}
@Builder