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

实例:家庭物品清单(Household)|收官文章

一、文件清单

文件 职责 行数
database/HouseholdDao.ets 数据层:双表、事务借还、LEFT JOIN、16 件物品 + 5 条借用种子 约 385 行
pages/samples/HouseholdPage.ets UI 层:收纳格卡片 + 分类/状态筛选 + 借用记录 + 借还登记 约 229 行
resources/base/profile/main_pages.json 路由注册:pages/samples/HouseholdPage 追加一行
pages/Index.ets 首页入口按钮 追加一个按钮

本篇文章完整展示可编译运行的 HouseholdPage 代码,最后描述运行效果。

二、HouseholdPage 完整代码

import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { HouseholdDao, HouseholdItem, BorrowRecord, HouseholdOverview } from '../../database/HouseholdDao';

@Entry
@Component
struct HouseholdPage {
  @State items: HouseholdItem[] = [];
  @State borrows: BorrowRecord[] = [];
  @State overview: HouseholdOverview = { total: 0, borrowed: 0, atHome: 0 };
  @State categoryFilter: string = '全部';
  @State statusFilter: number = -1;
  @State borrowVisible: boolean = false;
  @State borrowItem: HouseholdItem | null = null;
  @State borrower: string = '';
  @State note: string = '';
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  private readonly categories: string[] = ['厨房', '家用', '工具', '数码', '书籍', '运动'];

  aboutToAppear(): void {
    this.refresh();
  }

  async refresh(): Promise<void> {
    try {
      await HouseholdDao.initSeedData(this.context);
      this.overview = await HouseholdDao.overview(this.context);
      this.borrows = await HouseholdDao.queryBorrows(this.context);
      await this.applyFilter();
    } catch (e) {
      promptAction.showToast({ message: `加载失败: ${e}` });
    }
  }

  async applyFilter(): Promise<void> {
    this.items = await HouseholdDao.queryFilter(this.context, this.categoryFilter, '全部', this.statusFilter);
  }

  async switchCategory(cat: string): Promise<void> {
    this.categoryFilter = cat;
    await this.applyFilter();
  }

  async switchStatus(s: number): Promise<void> {
    this.statusFilter = this.statusFilter === s ? -1 : s;
    await this.applyFilter();
  }

  openBorrow(item: HouseholdItem): void {
    this.borrowItem = item;
    this.borrower = '';
    this.note = '';
    this.borrowVisible = true;
  }

  async doBorrow(): Promise<void> {
    if (!this.borrower.trim() || !this.borrowItem) {
      promptAction.showToast({ message: '请输入借用人' });
      return;
    }
    const ok = await HouseholdDao.borrowItem(this.context, this.borrowItem.id, this.borrower.trim(), this.note.trim());
    if (ok) {
      this.borrowVisible = false;
      await this.refresh();
      promptAction.showToast({ message: '📤 借用已登记' });
    } else {
      promptAction.showToast({ message: '登记失败' });
    }
  }

  /** 归还最近一条未还记录 */
  returnBorrow(b: BorrowRecord): void {
    if (b.returnTime !== 0) {
      return;
    }
    HouseholdDao.returnItem(this.context, b.id, b.itemId).then(async () => {
      await this.refresh();
      promptAction.showToast({ message: '📥 已归还' });
    });
  }

  deleteItem(item: HouseholdItem): void {
    promptAction.showDialog({
      title: '删除物品',
      message: `删除「${item.name}」及借用记录?`,
      buttons: [
        { text: '取消', color: '#808080' },
        { text: '删除', color: '#EF4444' },
      ],
    }).then((res: promptAction.ShowDialogSuccessResponse) => {
      if (res.index === 1) {
        HouseholdDao.deleteItem(this.context, item.id).then(async () => {
          await this.refresh();
          promptAction.showToast({ message: '🗑 已删除' });
        });
      }
    });
  }

  private fmtDate(ts: number): string {
    const d = new Date(ts);
    return `${d.getMonth() + 1}${d.getDate()}`;
  }

  build() {
    Column() {
      // ===== 标题栏 =====
      Row() {
        Column() {
          Text('🏠 家庭物品清单').fontSize(22).fontWeight(FontWeight.Bold)
          Text(`${this.overview.total} 件 · 借出 ${this.overview.borrowed}`)
            .fontSize(12).fontColor('#999999').margin({ top: 2 })
        }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        Text('↻').fontSize(22).onClick(() => this.refresh())
      }.width('100%').padding({ left: 16, right: 16, top: 12 })

      // ===== 分类筛选 =====
      Scroll() {
        Row({ space: 8 }) {
          ForEach(['全部'].concat(this.categories), (cat: string) => {
            Text(cat)
              .fontSize(13).padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .borderRadius(16)
              .backgroundColor(this.categoryFilter === cat ? '#F59E0B' : '#FFFFFF')
              .fontColor(this.categoryFilter === cat ? Color.White : '#4B5563')
              .onClick(() => this.switchCategory(cat))
          }, (cat: string) => cat)
        }.padding({ left: 16, right: 16, top: 10 })
      }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')

      Row({ space: 8 }) {
        Text(this.statusFilter === -1 ? '📍 全部状态' : '📍 在家')
          .fontSize(12).padding({ left: 10, right: 10, top: 4, bottom: 4 })
          .borderRadius(12).backgroundColor('#FFFFFF').fontColor('#6B7280')
          .onClick(() => this.switchStatus(0))
        Text('📤 借出中')
          .fontSize(12).padding({ left: 10, right: 10, top: 4, bottom: 4 })
          .borderRadius(12).backgroundColor(this.statusFilter === 1 ? '#FEE2E2' : '#FFFFFF')
          .fontColor(this.statusFilter === 1 ? '#DC2626' : '#6B7280')
          .onClick(() => this.switchStatus(1))
      }.width('94%').margin({ top: 8 })

      // ===== 物品收纳格 =====
      List({ space: 10 }) {
        ForEach(this.items, (item: HouseholdItem) => {
          ListItem() {
            Row({ space: 12 }) {
              Text(item.emoji).fontSize(30)
                .width(52).height(52).borderRadius(12)
                .backgroundColor('#FFF7E6').textAlign(TextAlign.Center)
              Column({ space: 3 }) {
                Row({ space: 6 }) {
                  Text(item.name).fontSize(15).fontWeight(FontWeight.Bold)
                  Text(item.status === 1 ? '借出中' : '在家')
                    .fontSize(10).padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .borderRadius(8)
                    .backgroundColor(item.status === 1 ? '#FEE2E2' : '#E5F6EC')
                    .fontColor(item.status === 1 ? '#DC2626' : '#059669')
                }
                Text(`${item.category} · ${item.location}`).fontSize(11).fontColor('#9CA3AF')
                if (item.note) {
                  Text(`📌 ${item.note}`).fontSize(11).fontColor('#B45309')
                }
              }.alignItems(HorizontalAlign.Start).layoutWeight(1)
              if (item.status === 0) {
                Button('借出').height(30).fontSize(12).backgroundColor('#F59E0B')
                  .onClick(() => this.openBorrow(item))
              }
              Text('✕').fontSize(14).fontColor('#CCCCCC').onClick(() => this.deleteItem(item))
            }
            .width('100%').padding(12)
            .backgroundColor(Color.White).borderRadius(10)
            .border({ width: 1, color: '#FDE68A' })
          }
        }, (item: HouseholdItem) => `${item.id}-${item.name}`)
      }
      .width('94%').layoutWeight(1).margin({ top: 10 })
      .scrollBar(BarState.Off)

      // ===== 借用记录 =====
      Column() {
        Text('借用记录').fontSize(15).fontWeight(FontWeight.Bold)
        ForEach(this.borrows.slice(0, 5), (b: BorrowRecord) => {
          Row({ space: 8 }) {
            Text(b.itemName).fontSize(13).fontWeight(FontWeight.Medium).layoutWeight(1)
            Text(`${b.borrower} ${this.fmtDate(b.borrowTime)}`)
              .fontSize(11).fontColor('#9CA3AF')
            if (b.returnTime === 0) {
              Text('归还').fontSize(11).padding({ left: 8, right: 8, top: 3, bottom: 3 })
                .borderRadius(10).backgroundColor('#E5F6EC').fontColor('#059669')
                .onClick(() => this.returnBorrow(b))
            } else {
              Text('已还').fontSize(11).fontColor('#9CA3AF')
            }
          }
          .width('100%').padding({ top: 6, bottom: 6 })
        }, (b: BorrowRecord) => `${b.id}-${b.itemId}`)
      }
      .width('94%').padding(12).backgroundColor(Color.White).borderRadius(12)
      .margin({ top: 4, bottom: 20 })
      .alignItems(HorizontalAlign.Start)

      // ===== 借出登记弹窗 =====
      if (this.borrowVisible) {
        Column() {
          Text(`📤 借出「${this.borrowItem?.name ?? ''}`).fontSize(18).fontWeight(FontWeight.Bold)
          TextInput({ placeholder: '借用人 *', text: this.borrower })
            .margin({ top: 12 }).onChange((v: string) => this.borrower = v)
          TextInput({ placeholder: '备注(可选)', text: this.note })
            .margin({ top: 8 }).onChange((v: string) => this.note = v)
          Row({ space: 8 }) {
            Button('取消').layoutWeight(1).backgroundColor('#EEF2F7').fontColor('#555555')
              .onClick(() => this.borrowVisible = false)
            Button('确认借出').layoutWeight(1).backgroundColor('#F59E0B')
              .onClick(() => this.doBorrow())
          }.margin({ top: 16 })
        }
        .padding(20).borderRadius(16).backgroundColor(Color.White).width('88%')
        .position({ x: '6%', y: '30%' })
      }
    }
    .width('100%').height('100%').backgroundColor('#FFFBEB')
  }
}

三、注册与运行

  1. main_pages.json 追加 "pages/samples/HouseholdPage"
  2. Index.ets 追加:
    Button('🏠 20 家庭物品清单').fontSize(15).width('70%')
      .onClick(() => this.getUIContext().getRouter().pushUrl({ url: 'pages/samples/HouseholdPage' }))
    
  3. 构建验证 → BUILD SUCCESSFUL

四、运行效果描述

进入「🏠 20 家庭物品清单」:

第一屏:标题栏「🏠 家庭物品清单 · 共 16 件 · 借出 3」;分类筛选(全部/厨房/家用/工具/数码/书籍/运动,选中橙色);状态切换(📍 全部状态 / 📤 借出中);物品列表——借出在前:破壁机 🥤「借出中」红标 + 📌 借给王阿姨 → 冲击钻 🛠️ → 《三体》📚,之后 13 件在家(绿标 + 「借出」橙钮);下方「借用记录」5 条:李哥冲击钻(最近)→ 王阿姨破壁机 → 小刘《三体》→ 表弟笔记本「已还」→ 赵叔电饭煲「已还」。

交互一(借出闭环):点羽毛球拍「借出」→ 弹窗「📤 借出「羽毛球拍」」→ 填「小明」→ 确认借出 → Toast「📤 借用已登记」→ 羽毛球拍变「借出中」红标、「借出」按钮消失、借用记录顶部多一条「小明 6月25日 归还」。

交互二(归还闭环):点刚登记的「归还」→ Toast「📥 已归还」→ 羽毛球拍回「在家」绿标、「借出」按钮回来、记录变「已还」。

交互三(筛选):点「借出中」→ 4 件红标(无借出按钮)→ 再点取消 → 16 件 → 点「工具」→ 3 件(电钻🔧/冲击钻🛠️/扳手🔩)。

交互四(删除级联):删除「冲击钻」→ 确认框「删除「冲击钻」及借用记录?」→ 确认 → 物品消失、借用记录里李哥那条也消失(级联删除)、借出 3→2。

五、代码质量要点回顾

关注点 本实例做法
事务 begin/commit/rollBack 借还原子
LEFT JOIN 联表取 item_name
多条件筛选 分类 + 位置 + 状态哨兵
借出在前 orderByDesc(status)
级联删除 先删借用再删物品
状态驱动 UI 在家显示借出按钮
归还守卫 returnTime 判断

六、文章小结(系列收官)

实例 「家庭物品清单」收官。五篇文章覆盖:双表借还建模(1)→ 收纳格卡片 UI(2)→ 事务与联表查询(3)→ 16 件物品种子(4)→ 全量代码(5)。核心技术是事务三件套(多表写原子性)、LEFT JOIN 联表取名、多条件哨兵筛选、级联删除。

Logo

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

更多推荐