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

实例:家庭物品清单(Household)|风格:收纳格卡片

一、设计理念:物品清单要「像给收纳盒贴标签」

家庭物品清单的 UI 目标是收纳盒/库存管理——每件物品一张「收纳格卡片」:emoji 图标 + 名称 + 分类·位置 + 状态(在家/借出)。实例 20 的风格「收纳格卡片」:分类筛选条(橙色主题)+ 物品卡(暖黄描边)+ 借用记录 + 借还登记

页面信息架构:

  1. 标题栏:家庭物品清单 + 总数/借出数 + 刷新;
  2. 分类筛选:全部/厨房/家用/工具/数码/书籍/运动;
  3. 状态切换:在家/借出中(toggle);
  4. 物品列表:收纳格卡片(emoji + 名称 + 分类·位置 + 状态标签 + 借出/删除);
  5. 借用记录:最近 5 条借还流水(物品/借用人/时间/归还);
  6. 借出登记弹窗:借用人 + 备注。

二、页面骨架与核心状态

@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 readonly categories: string[] = ['厨房', '家用', '工具', '数码', '书籍', '运动'];

核心状态:items(物品列表)、borrows(借用记录)、overview(统计)、categoryFilter/statusFilter(双筛选)、borrowVisible/borrowItem/borrower/note(借出弹窗)。

build 骨架(无 Scroll 包裹——List 占主区 + 底部借用记录固定):

Column() {
  // 标题栏
  // 分类筛选
  // 状态切换
  // 物品列表(List)
  // 借用记录(最近 5 条)
  // 借出登记弹窗
}

三、分类筛选 + 状态切换:橙色主题

分类筛选(选中橙色——本实例主色):

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%')

状态切换(在家/借出中 toggle):

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 })

toggle 语义:点「在家」→ statusFilter=0(筛选在家)→ 再点 → -1(全部状态)——toggle 取消(与影音状态筛选同模式);「借出中」选中红底(借出是警示色)。

switchStatus

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

「再点取消」的 toggle 逻辑statusFilter === s ? -1 : s

四、物品收纳格卡片:核心视觉

每件物品一张「收纳格卡」:

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)

收纳格卡五要素

  1. emoji 图标:30fp 放 52×52 暖黄圆角块(#FFF7E6)——收纳格图标位;
  2. 名称 + 状态标签:在家绿 / 借出中红——状态可扫读;
  3. 分类 · 位置厨房 · 厨房橱柜 灰字——「分类 + 收纳位置」双元信息(找物品的关键);
  4. 备注📌 借给王阿姨 棕色字(有备注才显示);
  5. 操作:在家 → 「借出」橙色按钮(借出中不显示)+ ✕ 删除。

暖黄描边border({ width: 1, color: '#FDE68A' })——「收纳格贴标」的暖黄色调贯穿(背景 #FFFBEB 米黄 + 图标块 #FFF7E6 + 描边 #FDE68A)。

五、借用记录:最近 5 条流水

列表下方「借用记录」(最近 5 条 + 归还按钮):

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)

流水行三要素:物品名(来自 LEFT JOIN 的 itemName)+ 借用人·日期 + 归还按钮/已还状态——returnTime === 0 显示绿色「归还」按钮(点击 returnBorrow)、否则灰色「已还」。

slice(0, 5) 截取:只显示最近 5 条——「列表限制条数」(完整历史在真实应用分页查看)。

returnBorrow

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: '📥 已归还' });
  });
}

归还守卫returnTime !== 0 直接 return(已还的记录不处理)——「状态守卫」防御重复点击

六、借出登记弹窗

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%' })
}

弹窗标题带物品名借出「破壁机」——用户确认借的是哪件doBorrow 校验!borrower.trim() || !borrowItem → Toast「请输入借用人」——借用人必填校验

七、UI 风格要素一览

风格项 取值 说明
页面背景 #FFFBEB 米黄 收纳暖色
主色 #F59E0B 分类选中/借出
卡片描边 #FDE68A 暖黄 贴标感
图标块 #FFF7E6 52×52 emoji 收纳格
状态标签 在家绿 / 借出红 状态可辨
借用记录 最近 5 条 + 归还按钮 借还闭环

八、文章小结

本篇文章完成了实例 20 的 UI 层:分类筛选(橙色主题)+ 状态 toggle + 物品收纳格卡(emoji 图标 + 分类·位置 + 状态标签 + 借出/删除)+ 借用记录(最近 5 条 + 归还按钮)+ 借出登记弹窗。设计核心是「收纳贴标感」——暖黄主题贯穿(米黄背景 + 暖黄描边 + 橙主色);技术亮点是状态驱动 UI(在家显示借出按钮/借出中不显示)与归还守卫(returnTime 判断)。物品清单像给收纳盒贴标签。

下一篇(20-3)深入数据层,讲解事务借还与 LEFT JOIN 联表——borrowItem/returnItem 是本实例的技术核心。

九、补充:暖黄主题与借还交互

9.1 暖黄色系统一配色规则

角色 色值 使用位置
页面背景 #FFFBEB 米黄 整页底色
卡片描边 #FDE68A 暖黄 物品卡 border
主色 #F59E0B 分类选中/借出按钮
图标块 #FFF7E6 浅暖黄 emoji 收纳格
警示红 #DC2626 借出中标签/筛选

配色规则:背景最浅(米黄)→ 图标块次之 → 描边加深 → 主色最深——「同一色系、三级递进」:暖黄色调贯穿不刺眼,橙主色在所有暖黄元素中自然突出,红色仅用于「借出」警示,语义不混淆。

9.2 状态驱动的借出按钮显隐

if (item.status === 0) {
  Button('借出').height(30).fontSize(12).backgroundColor('#F59E0B')
    .onClick(() => this.openBorrow(item))
}

状态驱动status === 0(在家)才渲染「借出」按钮,借出中的物品按钮直接消失——UI 由数据状态单向驱动,用户不会对已借出的物品重复操作,也无需二次判断。

9.3 借用记录的 slice 截取

ForEach(this.borrows.slice(0, 5), (b: BorrowRecord) => { ... })

slice(0, 5):渲染前截取前 5 条——「渲染层限流」,借用记录只会增长、不会缩短,若全量渲染列表会无限变长;完整历史由真实应用的分页或「查看全部」承载。

9.4 FAQ

Q1:为什么借出按钮用 if 而不是 disabled?
借出中的物品连按钮都不渲染(if 条件渲染),比 disabled 置灰更彻底——列表更干净,避免「可点但报错」的歧义;同时按钮消失本身就是「已借出」的强视觉暗示。

Q2:slice(0, 5) 取的是最近 5 条吗?
是——borrows 由 SQL ORDER BY borrow_time DESC 排序返回,最新记录在前,slice(0, 5) 截取的正是最近 5 条;若查询漏了排序,slice 取到的就不是「最近」了,这是排序与截取配合的关键。

Logo

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

更多推荐