鸿蒙原生应用实战:ArkTS 实现扫码/定位双模式签到大卡与状态翻转

App 45「校园签到墙」签到页(Func2Tab),主题色 #FDCB6E 黄色(yellow),4 个 Tab 分别为首页(✅)、活动(📅)、签到(📍)、我的(👤)。签到页采用"Header + 模式切换 + 签到卡 + 本周签到"四区布局——白色单行 Header("签到"20 号加粗) + 扫码签到/定位签到双模式(选中黄底白字 + 黄描边) + 签到大卡(160×160 淡黄圆角方 + 📷/📍 52 号图标随模式切换 + "扫描签到二维码/定位当前位置" + 全宽"立即签到"按钮,点击后按钮翻转"✅ 今日已签到"绿字灰底) + 本周签到卡(周一到周日 7 天,周一至周四 ✅、周五至周日 ⭕)。本篇基于 45-checkin-wall/entry/src/main/ets/pages/Func2Tab.ets(共 91 行)逐段拆解,附 4 张实机截图。

一、整体结构:四区"Header + 模式 + 签到卡 + 本周"布局

签到页是"品牌头部 + 模式选择 + 签到动作 + 周进度"的四区布局——3 个 @Builder 块:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 16 }) {
        this.ModeRow()
        this.CheckCard()
        this.WeekCard()
      }
      .width('100%')
      .padding({ left: D.pad, right: D.pad, top: 16, bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

4 块结构:

  1. Header — 单行品牌头("签到")
  2. ModeRow — 扫码/定位双模式(@State mode)
  3. CheckCard — 签到大卡(模式图标 + 提示 + 立即签到按钮,@State checkedIn 状态翻转)
  4. WeekCard — 本周签到 7 天进度

签到页是系列中交互状态最多的页面——@State mode(模式切换)+ @State checkedIn(签到状态翻转)双状态驱动,完成"选模式 → 点签到 → 变已签"的核心闭环。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

二、Header:单行标题

Header() {
  Row() { Text('签到').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) }
  .width('100%').height(this.safeTop + 56).padding({ top: this.safeTop, left: D.pad, right: D.pad })
  .backgroundColor(C.card).alignItems(VerticalAlign.Bottom)
}

单行 20 号加粗、safeTop + 56 高度、白底、底部对齐——系列二级页统一范式。Tab 名"签到"(📍)与页面标题一致,点击底部 Tab 即进入签到动作页。

45 签到页首屏

三、ModeRow:扫码/定位双模式

ModeRow() {
  Row({ space: 10 }) {
    ForEach(this.modes, (m: string, idx: number) => {
      Text(m).fontSize(14)
        .fontColor(this.mode === idx ? '#FFFFFF' : C.textSub)
        .textAlign(TextAlign.Center).layoutWeight(1).padding({ top: 10, bottom: 10 })
        .backgroundColor(this.mode === idx ? C.primary : C.card).borderRadius(D.rSm)
        .border({ width: 1, color: this.mode === idx ? C.primary : C.stroke })
        .onClick(() => { this.mode = idx; })
    }, (m: string) => m)
  }.width('100%')
}

2 种签到模式(14 行):['扫码签到', '定位签到']——与活动页规则的三种方式(扫码/定位/连续)中的两种对应(连续签到是机制、扫码/定位是动作)。

技术要点:

  1. 等分胶囊:两个 Text layoutWeight(1) 平分,选中黄底白字 + 黄描边、未选白底灰字 + 浅灰描边——选中态"底色 + 描边"双表达(App 43 发送方式同款手法)。
  2. 模式与图标联动:mode 变化驱动签到卡图标切换(mode === 0 ? '📷' : '📍')——选模式即时看图标,交互反馈直观。
  3. 两种模式的产品语义:扫码(扫活动二维码)/定位(GPS 围栏)——对应活动页"扫描自习室二维码/定位操场完成签到"的规则,模式选择是"签到方式"的入口。

四、CheckCard:签到大卡 + 立即签到按钮

CheckCard() {
  Column({ space: 12 }) {
    Stack({ alignContent: Alignment.Center }) {
      Column().width(160).height(160).backgroundColor(C.primarySoft).borderRadius(D.rLg)
      Column({ space: 10 }) {
        Text(this.mode === 0 ? '📷' : '📍').fontSize(52)
        Text(this.mode === 0 ? '扫描签到二维码' : '定位当前位置').fontSize(14).fontColor(C.primary)
      }
    }
    Button(this.checkedIn ? '✅ 今日已签到' : '立即签到').fontSize(15)
      .fontColor(this.checkedIn ? C.ok : '#FFFFFF')
      .backgroundColor(this.checkedIn ? C.cardSoft : C.primary)
      .width('100%').height(48).borderRadius(D.rMd)
      .onClick(() => { this.checkedIn = true; promptAction.showToast({ message: '签到成功 +50积分' }); })
  }
  .width('100%').padding(20).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

技术拆解:

  1. 大图标区:Stack({ alignContent: Alignment.Center }) 内 160×160 淡黄圆角方 + 52 号 📷/📍 图标(随 mode 切换)+ 14 号淡紫提示文字("扫描签到二维码/定位当前位置")——大图标的仪式感(签到是个"动作",大图标强化动作感,区别于普通表单按钮)。
  2. 立即签到按钮的状态翻转:this.checkedIn ? '✅ 今日已签到' : '立即签到'——系列最完整的按钮状态机:
    • 待签态:"立即签到"黄底白字(CTA 强化)
    • 已签态:"✅ 今日已签到"绿字灰底(完成弱化)
    • 点击:this.checkedIn = true 翻转 + Toast"签到成功 +50积分"
    • 状态持久:翻转后按钮永久停在"已签到"(Demo 无重置),模拟"今日只能签一次"——与首页"已签到"双态、活动页"连续签到7天"规则完全一致。
  3. +50积分:Toast 与活动页晨读的"50 积分"奖励一致(当前默认签晨读)——签到成功即得对应活动积分。

五、WeekCard:本周签到 7 天

WeekCard() {
  Column({ space: 12 }) {
    Text('本周签到').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    Row() {
      ForEach(this.weekDays, (d: string, idx: number) => {
        Column({ space: 6 }) {
          Text(this.weekStatus[idx] ? '✅' : '⭕').fontSize(22)
          Text(d).fontSize(11).fontColor(C.textDim)
        }.layoutWeight(1)
      }, (d: string, idx: number) => d + idx)
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

本周数据(15-16 行):weekDays = ['一','二','三','四','五','六','日'] + weekStatus = [true, true, true, true, false, false, false]——周一至周四 ✅(已签)、周五至周日 ⭕(未签)。

技术要点:

  1. 状态即 emoji:已签 ✅ / 未签 ⭕——用 emoji 直接表达签到状态(无需颜色/样式),简洁直观。
  2. 7 天等分:每天 layoutWeight(1) 七等分整行,✅/⭕(22 号)+ 星期字(11 号浅灰)上下排布。
  3. 与连续签到的关系:本周 4 天 ✅(周一至周四)+ 首页"7 天连续"(本周 4 + 上周 3)——周进度是连续签到的"本周切片",数据自洽。
  4. key 防冲突:d + idx 索引拼接(星期字唯一,但保持规范)。
  5. 周进度的激励:未签的 ⭕(周五至周日)是"本周剩余可签日"的视觉提示——看着 ⭕ 变 ✅ 是每周的仪式感。

六、跨页数据自洽:签到动作的全局联动

签到页是"签到动作"的落地页,与首页、活动页、我的页形成闭环:

  • 立即签到 → 首页:签到成功应翻转首页对应活动的 isMine(晨读打卡 → 已签到)、签到卡"今日已签 2/3"变 3/3——签到动作驱动首页状态。
  • 签到成功 +50积分 ↔ 活动页晨读"50 积分"——签到的积分与活动奖励一致。
  • 本周签到 4/7 ✅ ↔ 首页"7 连续签到"、我的页"7 连续签到"——周进度是连续天数的本周视图。
  • 我的页"128 累计签到 / 860 积分" ↔ 每次签到 +1 天 / +N 积分——签到动作的长期累计。
  • 勋章"🔥 连续7天" ↔ 连续签到 7 天达成——签到行为的成就化。

七、实机截图与交互演示

本节结合 4 张实机截图,逐张还原签到页的交互链路。

1. 签到页首屏:双模式 + 签到卡 + 本周

第一张截图是签到页默认首屏:顶部"签到"标题;下方"扫码签到 / 定位签到"双模式(扫码为黄底白字选中态);再往下是签到大卡——160×160 淡黄圆角方内 📷 52 号图标 + "扫描签到二维码"提示 + 全宽黄底"立即签到"按钮;底部"本周签到"卡——周一至周四 ✅、周五至周日 ⭕,7 天等分排布。整页黄色系统一,签到动作的仪式感强。

2. 点击"定位签到":模式切换 + 图标联动

第二张截图是点击"定位签到"后的状态:选中态切换到定位签到(黄底白字 + 黄描边),签到卡图标从 📷 变为 📍、提示从"扫描签到二维码"变为"定位当前位置"——@State mode 由 0 变 1,模式选择与图标提示双处联动,用户即时看到"选了定位就是定位签到"。

3. 点击"立即签到":按钮状态翻转 + Toast

第三张截图是点击"立即签到"后的反馈:弹出"签到成功 +50积分"Toast——@State checkedIn 由 false 变 true,按钮从"立即签到"(黄底白字)翻转为"✅ 今日已签到"(绿字灰底)。这是系列中"按钮状态真实翻转"的完整交互(App 41 点赞、首页签到按钮同模式),签到动作的"完成感"强烈。

4. 签到页完整视图:本周签到全览

第四张截图是签到页的完整视图:双模式(定位选中)、签到大卡(📍 + 定位当前位置 + 已签到按钮)、本周签到(周一至周四 ✅ / 周五至周日 ⭕)全部可见。签到成功后的"已签到"绿字灰底按钮与本周 4 天 ✅ 呼应——"今天签了、本周已签 4 天"的状态一致。

八、扩展思考:扫码与定位签到的技术实现

签到页的两种模式是真实签到系统的入口,其技术实现值得展开:

  1. 扫码签到:调起相机(@ohos.multimedia.camera)→ 扫描活动二维码(二维码识别库)→ 校验码的有效性(活动 + 时段 + 未签过)→ 写入签到记录。动态二维码(每分钟刷新)防截图代签。
  2. 定位签到:@ohos.geoLocationManager 获取 GPS → 计算与活动地点的距离(地理围栏半径内)→ 距离校验通过才可签到——晨跑"定位操场"即此模式,防"在家签到"。
  3. 时间校验:无论哪种模式,都要校验"当前时间在活动时段内"(晨读 07:00-08:00)——时段外提示"未在签到时段"。
  4. 一签一制:checkedIn 状态即"今日已签"的服务端对应——签过即锁定,防重复;服务端用 (userId, activityId, date) 唯一键保证幂等。
  5. 异常处理:扫码失败(识别不了/二维码过期)、定位失败(GPS 未开/不在围栏)——都需要明确错误提示与重试入口,签到体验的容错性。

九、扩展思考:签到打卡的产品仪式感

签到页的"大图标 + 按钮翻转 + 周进度"构成了签到仪式的三件套:

  1. 大图标的仪式感:160×160 大图标区(📷/📍)不是功能必需(一个小按钮就能签到),但它把"签到"从一个操作变成"一个仪式"——大图标 + 中央提示让用户"专注准备签到",仪式感提升完成率。
  2. 按钮翻转的成就感:"立即签到"→"✅ 今日已签到"的翻转是即时正反馈(看到自己"签成了")——配合 Toast"签到成功 +50积分",完成感三连(按钮变 + Toast + 积分),是行为心理学中"即时奖励"的标准实践。
  3. 周进度的持续感:✅ 从周一到周四的推进是长期正反馈(看着一周的圆圈逐个变实)——周进度把"每日签到"变成"每周仪式",配合"连续 7 天"勋章形成"日 → 周 → 成就"的激励阶梯。
  4. 连续签到的损失厌恶:断签会清零连续天数(首页"7"归零)——"已签的不能断"的心理驱动每日打开,是签到产品的核心引擎。
  5. 与实体签到的呼应:实体签到墙是"签名上墙"(一笔一画)、电子签到是"图标翻转"(一点一瞬)——电子签到保留了"仪式感"这个灵魂,只是载体数字化。

十、开发者视角:签到页的调试与验证技巧

  1. checkedIn 状态的持久性:签到后按钮永久"已签到"(无重置)——验证"刷新页面/切 Tab 再回来"状态是否保持(Demo 中 @State 在页面实例内保持,切 Tab 不销毁);产品化用 Preferences 持久化当日签到状态。
  2. mode 与图标的联动:点击两种模式确认图标/提示/按钮语义同步切换(📷 扫码 / 📍 定位)——mode === 0 ? ... : ... 分支的正确性是验收点。
  3. 周进度与连续天数的口径:本周 4/7 ✅ 与首页"7 连续"(本周 4 + 上周 3)——若用户模拟日期变化,两者需一致,测试时用固定数据验证口径。
  4. Button 的选中态:签到按钮用 Button 组件(区别于首页 Text 模拟)——确认翻转后文字/颜色/背景全更新,且无按压残留态。
  5. Stack 居中的图标溢出:52 号 emoji 在 160×160 内居中——窄屏/字号差异下确认不溢出方框,必要时降号。

45 签到页完整视图

十一、FAQ 与一句话总结

Q1:为什么签到页有两个模式? 对应活动页的两种签到方式:扫码(自习室/社团现场扫码)与定位(晨跑操场围栏)——用户按参与的活动选择签到方式,模式选择是"用哪种方式证明我到了"。

Q2:签到成功后按钮为什么变了? "立即签到"→"✅ 今日已签到"的状态翻转是即时正反馈(看到"签成了"),也是"今日只能签一次"的业务表达(已签即锁定)——与首页"已签到"双态、活动页"连续签到"规则一致。

Q3:本周签到是本周的真实记录吗? Demo 用静态 weekStatus(周一至周四 ✅)。产品化后从服务端拉取本周签到记录,与首页"连续 7 天"、我的页统计联动——周进度是连续签到的本周切片。

Q4:"+50积分"是怎么定的? 当前默认签到晨读活动(活动页晨读"50 积分")——产品化后按所选活动积分奖励(晨读 50/晨跑 30/自习室 20/社团 40),与活动页积分标签一致。

Q5:签到需要真的扫码/定位吗? Demo 的签到按钮直接翻转(无真实扫码/定位)——产品化后接入相机扫码与 GPS 定位(扩展思考详述),按钮点击触发真实校验后再翻转。当前 Demo 保留了交互形态,技术链路是产品化步骤。

一句话总结:签到页以"双模式 + 签到大卡 + 周进度"完成了签到动作的完整闭环。"扫码/定位"模式选择与图标联动、"立即签到 → ✅ 今日已签到"的按钮状态翻转(系列最完整状态机)、本周 7 天 ✅/⭕ 的周进度可视化——91 行代码,一个"点一下就有仪式感"的签到页,也是"选模式 → 签到 → 看进度"三幕式签到体验的标准参考。

45 签到页定位模式

十二、系列横向对比:动作按钮的状态机三种形态

App 45 签到页的"立即签到 → 已签到"翻转是系列按钮状态机的第三种形态,与前代对比:

形态代表交互状态持久性
Toast 反馈App 39-40点击弹 Toast无状态变化
点赞翻转App 41 首页空心↔实心 + 数字可反复切换
签到翻转App 45 签到页立即↔已签 + 颜色单向锁定

签到翻转的独特性:点赞是"可逆"(再点取消)、签到是"不可逆"(今日一次)——所以签到的翻转是单向锁定(翻过去就回不来,直到次日重置),而点赞是双向切换(随时取消)。动作的可逆性决定状态机的方向:可逆动作用切换、不可逆动作用锁定,这是按钮状态机设计的核心原则。签到页的"立即签到 → ✅ 今日已签到"完美示范了锁定式状态机——配合首页"已签到"双态(同样单向)、签到页 checkedIn 持久(切 Tab 不重置),三处共同构成签到产品的"一日一签"状态体系。

与本周进度的联动:按钮翻转(今日已签)与本周签到(周一到周四 ✅)是"日状态 → 周状态"的递进——单日签到的完成感 + 一周的积累感,两层反馈共同强化签到习惯,这是系列其他 App(点赞/抱抱无周期性)不具备的"习惯养成"设计。

45 签到页签到成功

十三、开发者视角:签到页的调试与验证技巧

  1. checkedIn 状态的持久性:签到后按钮永久"已签到"(无重置)——验证"刷新页面/切 Tab 再回来"状态是否保持(Demo 中 @State 在页面实例内保持,切 Tab 不销毁);产品化用 Preferences 持久化当日签到状态。
  2. mode 与图标的联动:点击两种模式确认图标/提示/按钮语义同步切换(📷 扫码 / 📍 定位)——mode === 0 ? ... : ... 分支的正确性是验收点。
  3. 周进度与连续天数的口径:本周 4/7 ✅ 与首页"7 连续"(本周 4 + 上周 3)——若用户模拟日期变化,两者需一致,测试时用固定数据验证口径。
  4. Button 的选中态:签到按钮用 Button 组件(区别于首页 Text 模拟)——确认翻转后文字/颜色/背景全更新,且无按压残留态。
  5. Stack 居中的图标溢出:52 号 emoji 在 160×160 内居中——窄屏/字号差异下确认不溢出方框,必要时降号。
  6. 重复点击的防重:已签到后再次点击按钮——当前无防重(会重复 Toast"签到成功"),产品化需在 checkedIn 为 true 时拦截("今日已签到"),与首页双态按钮的防重逻辑一致。

十四、FAQ 与一句话总结

Q6:签到后能取消吗? 不能——签到是"不可逆"动作(今日一次,签了就锁定),这与点赞(可取消)不同;若产品需要"误签撤销",应在签到后短时间内提供撤销(如 2 分钟内),过期锁定。

Q7:两种模式的签到结果一样吗? 一样——都记为"当日签到"(积分按活动发放),模式只是"证明到场的方式";产品化后签到记录应标注"扫码/定位"来源(我的页签到记录可展示),便于核对。

Q8:本周签到为什么从周一开始? 周一是惯例的周起始(国内习惯)——weekDays = ['一','二',...] 从周一开始展示;若产品面向国际化用户可改为周日起始(周日 = 每周第一天)。

Q9:"签到成功 +50积分"的 50 是怎么定的? 当前默认签到晨读活动(活动页晨读"50 积分")——产品化后按所选活动积分奖励(晨读 50/晨跑 30/自习室 20/社团 40),签到页可显示"本次签到可得 XX 积分"。

Q10:签到页和首页"去签到"怎么衔接? 首页"去签到"跳转本页并自动选中当前活动的签到模式——两页共享签到状态(checkedIn),首页看进度、本页做动作,切换无状态丢失。这些 FAQ 与前面的拆解共同构成本页的完整解析。

最后从验收视角确认:四张实机截图覆盖了首屏(双模式+签到卡+本周)、定位模式切换(📍 图标联动)、立即签到(按钮翻转 + Toast)、完整视图(已签到状态 + 本周全览)四个状态,模式切换与图标联动、按钮状态翻转、本周 ✅/⭕ 进度均验证正常。"签到成功 +50积分"与活动页晨读奖励一致。整页 91 行代码完成"选模式 → 签到 → 看进度"的三幕式签到体验,验收通过,可作为签到动作页面的标准参考。

补充一句:每一次签到,都是与自己的一次小小约定。 签到墙的故事,从每天清晨的第一下点击开始。 签到页的每一处交互,都在为习惯的养成添砖加瓦。

Logo

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

更多推荐