鸿蒙应用开发实战【54】— 应用详情页AppDetailPage开发(下)

鸿蒙应用开发实战【54】--- 应用详情页AppDetailPage开发(下)

本文是「号码助手全栈开发系列」第 54 篇,持续更新中...

开源社区:https://openharmonycrossplatform.csdn.net


前言

上篇完成了 AppDetailPage 的参数获取与数据加载。本篇聚焦编辑交互与保存------包括绑定卡号切换、分类选择、状态分段控件、备注编辑、保存修改、二次确认删除。

本篇涵盖:FormRow @Builder 复用、状态分段控件(Segmented)、ActionMenu 选择卡号/分类、保存逻辑、deleteById 删除、AlertDialog 二次确认。


一、页面布局

复制代码
┌──────────────────────┐
│  ‹     应用详情     保存 │
├──────────────────────┤
│                      │
│      ┌────┐          │
│      │ W  │ 微信      │  ← 大 Avatar
│      └────┘          │
│                      │
│  ┌──────────────────┐│
│  │ 绑定卡号  卡1·138›││  ← 可编辑
│  │──────────────────││
│  │ 分类       APP  ›││
│  └──────────────────┘│
│                      │
│  当前状态              │
│  [使用中][待换绑][待注销]│ ← 分段控件
│                      │
│  ┌──────────────────┐│
│  │ [备注文字]        ││
│  │──────────────────││
│  │ 创建时间 2026-01-15││
│  │ 更新时间 2026-03-20││
│  └──────────────────┘│
│                      │
│  [    保存修改      ]│
│                      │
│    删除该应用          │
└──────────────────────┘

二、FormRow 通用 Builder

2.1 组件定义

typescript 复制代码
@Builder
private FormRow(label: string, value: string, onTap: () => void) {
  Row() {
    Text(label).fontSize(AppFonts.SIZE_BODY).fontColor(AppColors.TEXT)
    Blank()
    Text(value).fontSize(AppFonts.SIZE_BODY).fontWeight(AppFonts.WEIGHT_MEDIUM)
      .fontColor(AppColors.TEXT_2)
    Text('›').fontSize(Size.font14).fontColor(AppColors.TEXT_3).margin({ left: 4 })
  }
  .width('100%').height(Size.s44)
  .onClick(() => onTap())
}

2.2 使用

typescript 复制代码
this.FormRow(
  '绑定卡号',
  `${this.cards[this.selectedCardIndex]?.label ?? ''} · ${this.formatPhone(...)}`,
  () => this.pickCard()
)
this.FormRow('分类', CATEGORIES[this.selectedCategoryIndex], () => this.pickCategory())

三、ActionMenu 选择器

3.1 选择卡号

typescript 复制代码
private async pickCard(): Promise<void> {
  if (this.cards.length === 0) return
  const buttons = this.cards.map(c => ({
    text: `${c.label} · ${c.phone_number}`,
    color: AppColors.TEXT
  }))
  const result = await promptAction.showActionMenu({
    title: '选择绑定卡号',
    buttons: buttons as [MenuBtn, MenuBtn?, MenuBtn?, MenuBtn?, MenuBtn?, MenuBtn?]
  })
  this.selectedCardIndex = result.index
}

3.2 选择分类

typescript 复制代码
private async pickCategory(): Promise<void> {
  const buttons = CATEGORIES.map(c => ({ text: c, color: AppColors.TEXT }))
  const result = await promptAction.showActionMenu({
    title: '选择分类',
    buttons: buttons as [MenuBtn, MenuBtn?, MenuBtn?, MenuBtn?, MenuBtn?, MenuBtn?]
  })
  this.selectedCategoryIndex = result.index
}

四、状态分段控件

4.1 UI 实现

typescript 复制代码
Column() {
  Text('当前状态')
    .fontSize(AppFonts.SIZE_SUB).fontColor(AppColors.TEXT_2)
    .alignSelf(ItemAlign.Start).margin({ bottom: 8 })

  Row() {
    ForEach(STATUS_OPTIONS, (s: string, index: number) => {
      Text(s)
        .fontSize(AppFonts.SIZE_SUB).fontWeight(AppFonts.WEIGHT_MEDIUM)
        .fontColor(this.selectedStatusIndex === index ? '#FFFFFF' : AppColors.TEXT_2)
        .backgroundColor(this.selectedStatusIndex === index ? AppColors.PRIMARY : Color.Transparent)
        .layoutWeight(1).textAlign(TextAlign.Center)
        .padding({ top: 8, bottom: 8 }).borderRadius(Size.s10)
        .onClick(() => this.selectedStatusIndex = index)
    })
  }
  .width('100%')
  .backgroundColor(AppColors.MUTED_BG)
  .borderRadius(Size.s12)
  .padding(Size.s3)
}

4.2 状态枚举

typescript 复制代码
const STATUS_OPTIONS: BindingStatus[] = ['使用中', '待换绑', '待注销', '已停用']

4 个状态横向均分,容器使用 MUTED_BG 灰色背景,选中项使用 PRIMARY 蓝色填充。


五、时间信息

typescript 复制代码
Row() {
  Text('创建时间').fontSize(AppFonts.SIZE_BODY).fontColor(AppColors.TEXT)
  Blank()
  Text(formatDate(this.binding.created_at)).fontSize(AppFonts.SIZE_BODY).fontColor(AppColors.TEXT_2)
}
Row() {
  Text('更新时间').fontSize(AppFonts.SIZE_BODY).fontColor(AppColors.TEXT)
  Blank()
  Text(formatDate(this.binding.updated_at)).fontSize(AppFonts.SIZE_BODY).fontColor(AppColors.TEXT_2)
}

六、保存修改

typescript 复制代码
private async onSave(): Promise<void> {
  if (!this.binding || this.saving) return
  this.saving = true
  try {
    const now = Date.now()
    const card = this.cards[this.selectedCardIndex]
    await AppBindingDao.update({
      id: this.binding.id,
      app_name: this.binding.app_name,
      icon_key: this.binding.icon_key,
      category: CATEGORIES[this.selectedCategoryIndex],
      card_id: card?.id ?? this.binding.card_id,
      status: STATUS_OPTIONS[this.selectedStatusIndex],
      remark: this.remark.trim(),
      source: this.binding.source,
      created_at: this.binding.created_at,
      updated_at: now
    })
    promptAction.showToast({ message: '已保存' })
  } catch (e) {
    promptAction.showToast({ message: '保存失败,请重试' })
  } finally {
    this.saving = false
  }
}

注意 card_id 的兜底:card?.id ?? this.binding.card_id,防止 cards 数组为空时出错。


七、删除应用

7.1 二次确认

typescript 复制代码
private onDeleteTap(): void {
  this.getUIContext().showAlertDialog({
    title: '删除该应用',
    message: '删除后无法恢复,确定要删除该应用绑定吗?',
    buttons: [
      { value: '取消', action: () => {} },
      { value: '确定删除', action: () => this.doDelete() }
    ]
  })
}

7.2 执行删除

typescript 复制代码
private async doDelete(): Promise<void> {
  if (!this.binding || this.binding.id === undefined) return
  try {
    await AppBindingDao.deleteById(this.binding.id)
    promptAction.showToast({ message: '已删除' })
    this.getUIContext().getRouter().back()
  } catch (e) {
    promptAction.showToast({ message: '删除失败,请重试' })
  }
}

删除后自动 back() 返回上一页,首页的 onPageShow 会自动刷新。


操作 交互组件 数据方法 反馈方式
切换绑定卡号 ActionMenu selectedCardIndex 更新 选择后自动关闭
切换分类 ActionMenu selectedCategoryIndex 更新 选择后自动关闭
切换状态 分段控件 selectedStatusIndex 更新 即时切换
编辑备注 TextArea remark 绑定 onChange 即时更新
保存修改 Button AppBindingDao.update Toast「已保存」
删除应用 Button + AlertDialog AppBindingDao.deleteById Toast + router.back()

八、完整操作流程

复制代码
AppDetailPage
    │
    ├── 加载数据(loadData)
    │
    ├── 编辑绑定卡号 → ActionMenu
    ├── 编辑分类 → ActionMenu
    ├── 切换状态 → 分段控件 onClick
    ├── 编辑备注 → TextArea
    │
    ├── 保存修改
    │   ├── onSave → AppBindingDao.update
    │   └── Toast "已保存"
    │
    └── 删除应用
        ├── AlertDialog 确认
        └── AppBindingDao.deleteById → Toast → back

小结

要点 说明
FormRow 可复用 Builder:label + value + right arrow
ActionMenu 选择 pickCard / pickCategory 原生弹窗
状态分段控件 MUTED_BG 容器 + Primary 选中
保存逻辑 update 全部字段 + updated_at 时间戳
删除 AlertDialog 二次确认 + deleteById + back
数据刷新 子页面返回后首页 onPageShow 自动刷新

下一篇文章,我们开发搜索页 SearchPage------实时搜索、历史记录、无结果快速添加。


如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
aqi006 小时前
鸿蒙版本的电子书阅读APP开放源码啦
android·华为·ai编程·harmonyos·鸿蒙
xq952710 小时前
HDC Assistant 插件横空出世
harmonyos
垆边人似月.11 小时前
华为机试题 :两数之和
c++·算法·华为
垆边人似月.11 小时前
华为机试题 :有效的括号
c++·算法·华为
高德开放平台14 小时前
高德开放平台×华为:一句话,开启腕上打车
华为
垆边人似月.14 小时前
华为机试题 :字符串压缩
算法·华为
OH_TPC17 小时前
【鸿蒙优选三方库】@ohos/aki:一行代码让 ArkTS 调 C++
harmonyos
垆边人似月.18 小时前
华为机试题 :最长递增子序列题目
数据结构·c++·算法·华为
wenyiyiyiyiyi18 小时前
我用鸿蒙 ArkTS 写了个自己天天用的备忘录:待办、课表和长期目标,全塞进一块桌面卡片
harmonyos
承渊政道20 小时前
鸿蒙设备远控电脑实测:ToDesk蓝牙键鼠、双模式鼠标、跨端剪贴板,远控电脑更顺手了
计算机外设·电脑·harmonyos·远程工作·todesk