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

实例:药箱管理(Medicine Cabinet)|技术:全量代码解读、操作剧本、库存+有效期模式抽象

一、本篇定位

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

  1. 全量源码逐段解读——MedicineDao 与 MedicinePage 从头到尾过一遍;
  2. 运行效果演示——从新增药品到临期筛选的完整操作剧本;
  3. 模式抽象——"库存 + 有效期"通用模板(会过期的库存品),附两个改写场景。

二、完整源码:MedicineDao.ets

/**
 * 药箱管理数据访问对象(DAO)
 * 对应文章:29_药箱管理/29-1 建表与数据层设计、29-2 页面UI与操作实现、29-3 完整代码与运行效果
 * 核心能力:药品档案 + 数量增减事务、批次有效期管理、临期筛选(日期函数)、用药记录
 */
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

/** 药品实体,对应 medicine 表结构 */
export interface Medicine {
  id: number;
  name: string;
  spec: string;
  category: string;
  batch: string;
  count: number;
  unit: string;
  expireDate: number;   // 有效期至(0 = 不限)
  storage: string;
  note: string;
  createdAt: number;
}

/** 用药记录实体,对应 dose_log 表结构 */
export interface DoseLog {
  id: number;
  medicineId: number;
  change: number;       // 正=入库 负=取出
  reason: string;
  createdAt: number;
}

const DOMAIN = 0x0001;
const TAG = 'MedicineDao';

/** 临期阈值(天) */
export const EXPIRE_WARN_DAYS = 90;

export class MedicineDao {
  private static readonly TABLE = 'medicine';
  private static readonly LOG_TABLE = 'dose_log';
  private static store?: relationalStore.RdbStore;
  // getStore / 行映射 / 增删改查 / 统计 / 种子数据(见上篇逐段解读)
}

逐段解读总表

代码段 职责 关键决策
实体接口 Medicine / DoseLog change 有符号编码方向
EXPIRE_WARN_DAYS 临期阈值常量 页面与 DAO 口径一致
getStore 单例 + 建 2 表 + 3 索引 expire 索引服务临期查询
insertMedicine 新增 + 初始日志 总量来源可追溯
changeCount 事务增减 + 负库存校验 数量与日志原子,布尔返回
deleteMedicine 先删日志再删药品 杜绝孤儿日志
queryExpiring / queryExpired 日期范围筛选 边界严谨不重叠
queryLogs 变动日志倒序 审计追溯
summary 种类/总数/临期/过期 动态时间快照
categoryStats GROUP BY 分类 分布一屏
initSeedData 10 种药四态效期 演示全覆盖

三、完整源码:MedicinePage.ets

/**
 * 药箱管理页面
 * 对应文章:29_药箱管理/29-1 ~ 29-3
 * 功能:临期/过期筛选 Tab + 药品卡片列表 + 新增药品 + 入库/取出 + 数量日志抽屉 + 分类统计
 */
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { MedicineDao, Medicine, DoseLog, EXPIRE_WARN_DAYS } from '../../database/MedicineDao';

@Entry
@Component
struct MedicinePage {
  @State meds: Medicine[] = [];
  @State filter: number = 0;
  @State summaryText: string = '';
  @State categoryText: string = '';
  @State formVisible: boolean = false;
  @State fName: string = '';
  @State fSpec: string = '';
  @State fCategory: string = '';
  @State fCount: string = '';
  @State fExpire: string = '';
  @State detailVisible: boolean = false;
  @State current: Medicine | null = null;
  @State logs: DoseLog[] = [];
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

  aboutToAppear(): void {
    this.refresh();
  }
  // refresh/loadList/onAdd/onCountChange/openDetail/onDelete/statusInfo/fmtDate/fmtDateTime 见中篇
}

页面组织纪律回顾

  1. 状态量三组:列表态(meds/filter/summaryText/categoryText)、表单态(f*)、抽屉态(detailVisible/current/logs);
  2. 交互方法齐全:增(onAdd)、改数量(onCountChange)、查(openDetail)、删(onDelete)——CRUD 全覆盖;
  3. UI 零 SQL:效期徽标由 statusInfo 纯函数计算,数据来自 DAO。

四、接入工程三步

Step 1:放文件

  • MedicineDao.etsentry/src/main/ets/database/
  • MedicinePage.etsentry/src/main/ets/pages/samples/

Step 2:注册路由(main_pages.json)

"pages/samples/MedicinePage",

Step 3:首页入口(Index.ets)

Button('🧴 29 药箱管理')
  .fontSize(14)
  .width('80%')
  .onClick(() => {
    this.getUIContext().getRouter().pushUrl({ url: 'pages/samples/MedicinePage' });
  })

五、运行效果演示(操作剧本)

场景一:首次打开

步骤 界面表现 数据来源
1 标题栏:“🧴 药箱管理”,统计:“共 10 种 27 件 · 临期 1 · 已过期 1” summary
2 分类行:“感冒 9 · 肠胃 7 · 外伤 7 · 止痛退烧 2 · 保健 1 · 器材 1” categoryStats
3 列表按效期升序:健胃消食片(红"已过期 20 天")排最前,感冒灵颗粒(橙"临期 60 天")第二,其后正常绿徽标,温度计灰"不限有效期"最后 queryAll(orderByAsc expire_date)
4 每张卡片第三行:库存数 + 红-绿+ + 📋

场景二:临期筛选

点"临期"Tab → 只显示感冒灵颗粒(橙徽标"临期 60 天")。点"已过期"→ 只显示健胃消食片(红徽标"已过期 20 天")。点"全部"恢复。

场景三:快捷增减

  1. 布洛芬库存 2 → 点 + → toast"✅ 入库 1 盒",库存变 3;
  2. 点 - → toast"📤 取出 1 盒",库存回 2;
  3. 温度计库存 1 → 点 - → toast"⚠️ 库存不足"(0 不能负)。

场景四:日志追溯

点布洛芬的 📋 → 抽屉显示日志(倒序):

  • “手动入库 · +1 盒 · 2026-06-15 14:30”(刚才的操作)
  • “初始入库 · +2 盒 · 2026-06-15 14:28”(种子)

每次增减都被记录,来源可溯。

场景五:新增药品

点 + → 填"开瑞坦 / 10mg*6片/盒 / 过敏 / 1 / 2027-03-01" → 保存 → toast"✅ 药品已添加",列表出现(效期 2027-03-01 绿色徽标),统计"共 11 种"。

场景六:删除

打开任意药 📋 → 点"🗑 删除"→ 确认(提示日志一并删除)→ toast"🗑 已删除",统计同步。

场景七:数据持久性

杀应用重开 → 数据仍在。

六、模式抽象:"库存 + 有效期"通用模板

6.1 模型

┌───────────────────────────────────────────────┐
│  库存品档案表(如 medicine)                     │
│  id / 名称规格 / 分类 / 数量(count) / 单位      │
│  / 有效期至(expire_date, 0=不限) / 存放位置     │
│  ── 有什么、有多少、到何时                      │
└──────────────────────┬────────────────────────┘
                       │ 1:N
┌──────────────────────┴────────────────────────┐
│  数量变动日志表(如 dose_log)                   │
│  id / 物品_id / change(±数量) / reason / 时间   │
│  ── 每次增减的可追溯记录(只追加不修改)          │
└───────────────────────────────────────────────┘

6.2 七个约定

  1. 数量 + 有效期双维度:count 管"有多少",expire_date 管"到何时";
  2. 0 编码"无":expire_date=0 不限效期;count 从不负(负库存被校验拦截);
  3. 变更即日志:任何数量变化都写 dose_log(含初始入库),可追溯;
  4. 有符号变动:change 正=入、负=出,SUM 即净变化;
  5. 事务原子:数量与日志同写,缺一不可;
  6. 日期范围筛选:临期/过期用 expire_date BETWEEN / < now,边界严谨;
  7. 阈值集中:EXPIRE_WARN_DAYS 一处定义,徽标与筛选同步。

6.3 两个改写场景

场景 A:食品保质期管理(冰箱/储物柜)

药箱实例 食品改写
medicine food(食品:名称/品牌/保质期至)
dose_log consume_log(消费:吃掉/购入)
效期徽标 临期食品提醒(先吃临期的)
分类统计 冷藏/冷冻/干货分类分布

场景 B:化妆品/日用品效期管理

药箱实例 化妆品改写
medicine cosmetic(品名/规格/开封日期/保质期)
临期阈值 开封后 6 个月(PAO 标识)
数量日志 用空/补货记录

场景 C:仓库耗材/办公用品——数量事务 + 到期批次 + 领用日志,模板完全复用。

三个场景的共同点:"库存品 + 数量变动 + 有效期"三元结构。掌握 29,任何"会过期、有数量"的管理系统都能落地。

七、三篇内容总对照

篇章 核心知识点 一句话总结
29-1 建表与数据层设计 库存事务、0 编码、日期筛选 数量与效期双维度,变更即日志
29-2 页面UI与操作实现 效期四态徽标、快捷增减、日志抽屉 效期颜色化,增减一步到位
29-3 完整代码与运行效果 全量源码、操作剧本、库存模板 会过期的库存品,改表名即复用

八、FAQ(三篇汇总补遗)

Q1:29 和 06 库存进销存有什么区别?
A:06 进销存是"商品 + 出入库流水"的完整商贸模型(含单价、供应商、多仓库);29 药箱是"个人库存品 + 数量增减"的轻量模型,但新增了有效期维度。06 管"账",29 管"物品的时效"。若药箱要管单价进货,可合并两模型。

Q2:为什么日志的 reason 不是枚举?
A:家庭场景原因多样(新购入/服用/过期处理/送人),自由文本更灵活。若做强制规范(统计原因分布),可用枚举。自由文本优先,枚举在"需要聚合统计"时引入

Q3:效期排序为什么升序?
A:orderByAsc('expire_date') 让临期/过期排最前——用户打开列表先看到"最紧急的"。这是按紧迫度排序,与 26/27 的按时间倒序(最新优先)不同。排序方向由业务关注点决定。

Q4:多种药品同分类为什么数量相加?
A:categoryStats 的 SUM(count) 是"该分类总件数",跨单位(盒+袋+支)只是粗略示意。精确统计按单位拆分。页面文案已弱化单位(“感冒 9”),接受近似。

Q5:能加服药提醒吗?
A:可以,但提醒属于"定时任务"范畴(@kit.BackgroundTasksKit 的提醒代理或闹钟),超出本系列 SQLite 主题。建议单独实例(30 美食菜谱之后的选题)或扩展 05 习惯打卡的提醒机制。

Q6:temperature 药品(如胰岛素)需要冷藏记录吗?
A:可加 storage 字段存"冷藏 2-8℃"等条件,本实例 storage 已存在。温度监控属 IoT 范畴,非 SQLite 主题。

九、结语

三篇文章完整走完了药箱管理的"设计 → 实现 → 复用"闭环:

  • -1 教你库存建模:数量 + 有效期双维度、变更即日志、日期范围筛选;
  • -2 教你交互落地:效期四态徽标、快捷增减、日志抽屉追溯;
  • -3 教你模式复用:三元结构模板,改表名即覆盖食品、化妆品、耗材。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐