在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实例:图书借阅管理(Book Library)|技术:全量代码解读、操作剧本、借阅台账模式抽象

一、本篇定位

前两篇分别讲了数据层与 UI 层,本篇完成三件事:

  1. 全量源码逐段解读——BookDao 与 BookPage 从头到尾过一遍,标注与文章前两篇的对应;
  2. 运行效果演示——从入库到归还的完整操作剧本;
  3. 模式抽象——“借阅台账”(物品档案 + 流转记录)通用模板,附两个改写场景。

二、完整源码: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 见中篇
}

页面组织纪律回顾

  1. 状态量在前:12 个 @State 集中声明,一眼看清页面"有哪些会变的数据";
  2. 数据方法中间、build 最后:ArkTS 标准组织,与 26 及前 20 实例一致;
  3. UI 零 SQL:所有数据操作收敛 DAO,页面只消费 BookView/BorrowView。

四、接入工程三步

Step 1:放文件

  • BookDao.etsentry/src/main/ets/database/
  • BookPage.etsentry/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 本"。

场景三:借出

  1. 找到《小王子》→ 点"📤 借出";
  2. 弹窗标题"借出「小王子」“,输入借阅人"小王”;
  3. 确认借出 → toast"✅ 借出成功(30 天)";
  4. 卡片变借出中(橙标),显示"借阅人:小王 应还:2026-09-XX";
  5. 统计变"借出 5";Tab"借出中"下多一本。

场景四:逾期提醒

切到"借出中"Tab,《解忧杂货店》红标"逾期 10 天",应还日期文字也变红。点"📖 归还"→ 确认 → toast"📚 已归还"→ 卡片变在馆绿标,逾期数归 0。

场景五:删除保护

  1. 找一本借出中的书(如《人类简史》)→ 点 🗑 → 确认删除;
  2. toast"⚠️ 该书借出中,不能删除"——删除被业务规则阻止;
  3. 对在馆的书(如《红楼梦》)执行删除 → 成功,统计"共 10 本"。

场景六:借阅历史

点标题栏"📜"→ 抽屉打开,按借出时间倒序显示全部记录:每条含书名、借阅人、借出 → 应还 → 归还三段时间;借出中记录带浅橙"借出中"徽标,已归还带浅绿"已归还"徽标。

场景七:数据持久性

杀应用重开 → 数据仍在(SQLite 落盘)。

六、模式抽象:借阅台账通用模板

6.1 模型

┌───────────────────────────────────────────┐
│  物品档案表(如 book)                       │
│  id / 档案字段... / status(可用/占用)        │
│  ── 物品是什么、现在能不能用                 │
└─────────────────────┬─────────────────────┘
                      │ 1:N
┌─────────────────────┴─────────────────────┐
│  流转记录表(如 borrow)                     │
│  id / 物品_id / 使用人 / 借出时间 / 应还时间  │
│        / 归还时间(0未还)                    │
│  ── 谁用过、何时用、何时还                  │
└───────────────────────────────────────────┘

6.2 七个约定

  1. 档案与流转分离:物品信息(档案)与使用记录(流转)分开存,各自演进;
  2. 状态二值化:status 只存"可用/占用"(0/1),细节交给流转表;
  3. 应还日期预计算:借出时算好 due_time 落库,支持差异化周期;
  4. 归还回填:return_time 从 0 变时间戳,标识该记录"已完成";
  5. 事务双写:借出/归还都是"改状态 + 写记录",必须原子;
  6. 删除保护:占用中禁止删除档案,规则在 DAO;
  7. 历史不丢:查询用 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 教你模式抽象:七条约定 + 两个改写场景,让"台账系统"可复制。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐