鸿蒙实战:图书借阅管理——完整源码与借阅台账通用模板(下)



实例:图书借阅管理(Book Library)|技术:全量代码解读、操作剧本、借阅台账模式抽象
一、本篇定位
前两篇分别讲了数据层与 UI 层,本篇完成三件事:
- 全量源码逐段解读——BookDao 与 BookPage 从头到尾过一遍,标注与文章前两篇的对应;
- 运行效果演示——从入库到归还的完整操作剧本;
- 模式抽象——“借阅台账”(物品档案 + 流转记录)通用模板,附两个改写场景。
二、完整源码:BookDao.ets
/**
* 图书借阅管理数据访问对象(DAO)
* 对应文章:27_图书借阅管理/27-1 建表与数据层设计、27-2 页面UI与操作实现、27-3 完整代码与运行效果
* 核心能力:书籍主表 + 借阅记录从表、借出/归还流程、逾期天数计算、借阅历史查询
*/
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
/** 书籍实体,对应 book 表结构 */
export interface Book {
id: number;
title: string; // 书名
author: string; // 作者
isbn: string; // ISBN
location: string; // 摆放位置
status: number; // 0 在馆可借 / 1 借出中
createdAt: number; // 录入时间戳(毫秒)
}
/** 借阅记录实体,对应 borrow 表结构 */
export interface BorrowRecord {
id: number;
bookId: number; // 逻辑外键 → book.id
borrower: string; // 借阅人
borrowTime: number; // 借出时间戳(毫秒)
dueTime: number; // 应还时间戳(毫秒)
returnTime: number; // 归还时间戳(0 表示未归还)
}
/** 书籍 + 借阅状态视图(列表页用) */
export interface BookView {
book: Book;
borrower: string;
dueTime: number;
overDays: number;
}
/** 借阅记录 + 书名视图(借阅历史页用) */
export interface BorrowView {
record: BorrowRecord;
title: string;
author: string;
}
const DOMAIN = 0x0001;
const TAG = 'BookDao';
/** 默认借阅周期(毫秒):30 天 */
export const BORROW_DAYS = 30;
export class BookDao {
private static readonly TABLE = 'book';
private static readonly BORROW_TABLE = 'borrow';
private static store?: relationalStore.RdbStore;
static async getStore(context: common.Context): Promise<relationalStore.RdbStore> {
// 单例 + 建 2 表 + 3 索引(见上篇)
}
// 其余方法见上篇逐段解读
}
逐段解读总表
| 代码段 | 职责 | 关键决策 |
|---|---|---|
| 实体接口 | Book / BorrowRecord / BookView / BorrowView | 两个 View 封装跨表组装结果 |
| BORROW_DAYS | 借阅周期常量 | 可复用,页面统一引用 |
| getStore | 单例 + 建表 + 索引 | 幂等初始化 |
| insertBook / updateBook | 书籍档案增改 | 新增默认在馆 |
| deleteBook | 带规则删除 | 借出中返回 false |
| borrow | 事务借出 | 状态置 1 + 记录插入 + due_time 计算 |
| returnBook | 事务归还 | 状态置 0 + 最新记录回填 |
| latestBorrow | 查最新借阅 | 归还与视图组装共用 |
| queryBooks* | 书籍查询家族 | 状态/搜索/全量 |
| queryBookViews | 视图组装 + 逾期计算 | N+1 组装,语义清晰 |
| queryBorrowHistory | LEFT JOIN 历史 | 已删书籍不丢历史 |
| summary | 四维统计 | 条件聚合 + 即时逾期 |
| initSeedData | 种子数据 | 含逾期演示数据 |
三、完整源码:BookPage.ets
/**
* 图书借阅管理页面
* 对应文章:27_图书借阅管理/27-1 ~ 27-3
* 功能:状态 Tab + 书籍卡片 + 入库/借出/归还/删除 + 逾期红标 + 借阅历史
*/
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BookDao, Book, BookView, BorrowView } from '../../database/BookDao';
@Entry
@Component
struct BookPage {
@State views: BookView[] = [];
@State filter: number = -1;
@State summaryText: string = '';
@State formVisible: boolean = false;
@State fTitle: string = '';
@State fAuthor: string = '';
@State fIsbn: string = '';
@State fLocation: string = '';
@State borrowVisible: boolean = false;
@State borrowBook: BookView | null = null;
@State fBorrower: string = '';
@State historyVisible: boolean = false;
@State history: BorrowView[] = [];
private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
aboutToAppear(): void {
this.refresh();
}
// refresh/loadList/onAddBook/onBorrow/onReturn/onDelete/openHistory/fmtDate 见中篇
}
页面组织纪律回顾
- 状态量在前:12 个 @State 集中声明,一眼看清页面"有哪些会变的数据";
- 数据方法中间、build 最后:ArkTS 标准组织,与 26 及前 20 实例一致;
- UI 零 SQL:所有数据操作收敛 DAO,页面只消费 BookView/BorrowView。
四、接入工程三步
Step 1:放文件
BookDao.ets→entry/src/main/ets/database/BookPage.ets→entry/src/main/ets/pages/samples/
Step 2:注册路由(main_pages.json)
"pages/samples/BookPage",
Step 3:首页入口(Index.ets)
Button('📚 27 图书借阅管理')
.fontSize(14)
.width('80%')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({ url: 'pages/samples/BookPage' });
})
五、运行效果演示(操作剧本)
场景一:首次打开
| 步骤 | 界面表现 | 数据来源 |
|---|---|---|
| 1 | 标题栏:“📚 图书借阅管理”,统计:“共 10 本 · 在馆 6 · 借出 4 · 逾期 1” | summary |
| 2 | Tab:全部 / 在馆可借 / 借出中 | — |
| 3 | 列表 10 本书:在馆绿标、借出橙标;《解忧杂货店》显示"逾期 10 天"红标(种子故意制造) | queryBookViews |
| 4 | 借出中的书显示借阅人 + 应还日期 + 绿色"📖 归还"按钮 | latestBorrow 组装 |
场景二:新书入库
点 + → 弹窗填"三体(全集)/刘慈欣/9787536692930/科幻区 A-1" → 入库 → toast"✅ 已入库",列表底部(按书名排序)出现新书,统计"共 11 本"。
场景三:借出
- 找到《小王子》→ 点"📤 借出";
- 弹窗标题"借出「小王子」“,输入借阅人"小王”;
- 确认借出 → toast"✅ 借出成功(30 天)";
- 卡片变借出中(橙标),显示"借阅人:小王 应还:2026-09-XX";
- 统计变"借出 5";Tab"借出中"下多一本。
场景四:逾期提醒
切到"借出中"Tab,《解忧杂货店》红标"逾期 10 天",应还日期文字也变红。点"📖 归还"→ 确认 → toast"📚 已归还"→ 卡片变在馆绿标,逾期数归 0。
场景五:删除保护
- 找一本借出中的书(如《人类简史》)→ 点 🗑 → 确认删除;
- toast"⚠️ 该书借出中,不能删除"——删除被业务规则阻止;
- 对在馆的书(如《红楼梦》)执行删除 → 成功,统计"共 10 本"。
场景六:借阅历史
点标题栏"📜"→ 抽屉打开,按借出时间倒序显示全部记录:每条含书名、借阅人、借出 → 应还 → 归还三段时间;借出中记录带浅橙"借出中"徽标,已归还带浅绿"已归还"徽标。
场景七:数据持久性
杀应用重开 → 数据仍在(SQLite 落盘)。
六、模式抽象:借阅台账通用模板
6.1 模型
┌───────────────────────────────────────────┐
│ 物品档案表(如 book) │
│ id / 档案字段... / status(可用/占用) │
│ ── 物品是什么、现在能不能用 │
└─────────────────────┬─────────────────────┘
│ 1:N
┌─────────────────────┴─────────────────────┐
│ 流转记录表(如 borrow) │
│ id / 物品_id / 使用人 / 借出时间 / 应还时间 │
│ / 归还时间(0未还) │
│ ── 谁用过、何时用、何时还 │
└───────────────────────────────────────────┘
6.2 七个约定
- 档案与流转分离:物品信息(档案)与使用记录(流转)分开存,各自演进;
- 状态二值化:status 只存"可用/占用"(0/1),细节交给流转表;
- 应还日期预计算:借出时算好 due_time 落库,支持差异化周期;
- 归还回填:return_time 从 0 变时间戳,标识该记录"已完成";
- 事务双写:借出/归还都是"改状态 + 写记录",必须原子;
- 删除保护:占用中禁止删除档案,规则在 DAO;
- 历史不丢:查询用 LEFT JOIN,档案删除后记录仍可见。
6.3 两个改写场景
场景 A:设备借用登记(公司 IT 资产)
| 图书实例 | 设备借用改写 |
|---|---|
| book | device(设备:编号/型号/存放地/负责人) |
| borrow | device_log(借用:借用人/部门/借出/应还/归还) |
| status 0/1 | 在库 / 借用中 |
| 逾期红标 | 超期未还设备清单(行政催还) |
场景 B:资产领用台账(仓库/物料)
| 图书实例 | 资产领用改写 |
|---|---|
| book | asset(资产:名称/规格/类别/存放位) |
| borrow | claim_record(领用:领用人/部门/领用/归还) |
| 删除保护 | 在用资产不可报废 |
两个场景的共同点:"物品档案 + 借还流转"是任何台账类系统的骨架。掌握 27 的全部代码,改改表名和字段就能落地。
七、三篇内容总对照
| 篇章 | 核心知识点 | 一句话总结 |
|---|---|---|
| 27-1 建表与数据层设计 | 双表、事务借还、逾期计算、LEFT JOIN | 档案存现状,流转存经历,借还事务双写 |
| 27-2 页面UI与操作实现 | 三色徽标、条件操作区、确认流程、历史抽屉 | 状态可视化 + 操作确认 + 反馈闭环 |
| 27-3 完整代码与运行效果 | 全量源码、操作剧本、台账模板 | 物品档案 + 流转记录,改表名即复用 |
八、FAQ(三篇汇总补遗)
Q1:27 和 26 的主从表有什么区别?
A:26 的时间线是"状态事件"(记录状态怎么变),27 的借阅是"业务交易"(记录谁借的、何时还)。前者从表只存 (status, note, time),后者从表携带 borrower/dueTime 等业务字段。事件表 vs 交易表——前者重"轨迹",后者重"业务"。
Q2:逾期计算放 DAO 还是页面?
A:本实例放 DAO(queryBookViews 里计算 overDays),页面只消费。理由:逾期规则(如"满 24 小时算一天")是业务逻辑,集中一处便于修改;页面只负责展示。业务计算尽量下沉到数据层。
Q3:借出时为什么不校验"这本书已经在馆"?
A:页面只在"在馆"状态卡片上渲染借出按钮,UI 层已保证。若担心并发(双端同时操作同一本书),DAO 可在借出前 SELECT status WHERE id=? 校验——本实例单机单用户无需,但注释说明了扩展点。
Q4:历史抽屉数据量大怎么办?
A:queryBorrowHistory 目前全量返回。记录上千时加分页(limitAs + offset)或按月份筛选。参考 08 商品分页实例的分页实现。
Q5:为什么 summary 的逾期数单独一条 SQL?
A:逾期条件(return_time=0 AND due_time<now)跨表且在 borrow 表,而总数/在馆/借出在 book 表。合并成一条 SQL 需要 JOIN + 子查询,复杂度上升收益有限;两条简单 SQL 可读性更好。SQL 拆分以可读性为先,合并以性能为先。
Q6:这本书还能扩展哪些功能?
A:① 借阅人管理表(borrower 独立成表,支持黑名单);② 预约/续借(在 borrow 表加 renew_count);③ 图书封面(photoAccessHelper 存图);④ 条形码扫码借还(相机 + 解码)。都是很好的后续练习方向。
九、结语
三篇文章完整走完了图书借阅的"设计 → 实现 → 复用"闭环:
- -1 教你双表建模与事务借还:档案存现状、流转存经历、应还预计算、逾期即时算;
- -2 教你交互落地:三色徽标、条件操作区、确认流程、历史抽屉;
- -3 教你模式抽象:七条约定 + 两个改写场景,让"台账系统"可复制。
更多推荐




所有评论(0)