#我的鸿蒙开发手记#鸿蒙打卡场景实现 原创 精华

豆龙YJJ
发布于 2025-5-6 09:18
浏览
0收藏

一、介绍与准备
1.整体介绍
本练习主要围绕基于华为手机直板机及 HarmonyOS 5.0.0 以上版本、DevEco Studio 5.0.0 以上版本等环境进行打卡场景开发展开。
先是介绍了应用的整体构建布局设计,涵盖登录模块与打卡模块,其中登录模块会在进入应用时检测账号登录状态,未登录则进入登录界面,已登录则直接进入打卡页面。接着详细阐述了打卡 UI 布局的开发,包括创建 constants 常量文件来定义底部导航栏配置数组,以及主入口页面的构建,该页面包含主打卡页面、打卡记录页面和快捷设置页面等。
最后深入讲解了打卡功能的实现,如在根目录 OfficeAttendance 下创建 scenes 场景文件目录,实现打卡记录获取、打卡操作、动画效果、快捷设置等功能,涵盖了从页面构建到功能实现的各个环节,基本呈现了一个完整的打卡场景开发案例。
2.基础准备
(1)设备类型:华为手机(直板机)
(2)HarmonyOS版本:HarmonyOS 5.0.0 Release及以上
(3)DevEco Studio版本:DevEco Studio 5.0.0 Release及以上
(4)HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上
二、应用整体构建布局设计
1.模块设计
(1)登录模块:Login.ets
(2)打卡模块:CheckInView.ets
2.登录UI布局开发与功能实现
进入应用时,检测是否有账号已登陆,未登录进入进入登录界面,若以登录则直接进入应用打卡页。
Login.ets关键代码如下:

// 导入登录页面组件、路由管理及ArkUI框架模块
import { LoginView } from '@ohos_agcit/office_attendance_account'
import { MainEntryVM, RouterMap, RouterModule } from '@ohos_agcit/office_attendance_common_lib';
import { router, window } from '@kit.ArkUI';

@Entry // 标记为入口组件
@ComponentV2 // 声明为ArkUI V2版本组件
struct Login { // 定义登录页面组件
  // 使用单例模式获取路由管理对象
  vm: MainEntryVM = MainEntryVM.instance;

  // --------------------------
  // 生命周期方法:页面显示时触发
  // --------------------------
  onPageShow(): void {
    // 获取当前窗口实例并开启隐私模式(隐藏敏感内容)
    window.getLastWindow(getContext(this)).then((windowStage: window.Window) => {
      windowStage.setWindowPrivacyMode(true); 
    });
  }

  // --------------------------
  // 生命周期方法:页面隐藏时触发
  // --------------------------
  onPageHide(): void {
    // 获取当前窗口实例并关闭隐私模式
    window.getLastWindow(getContext(this)).then((windowStage: window.Window) => {
      windowStage.setWindowPrivacyMode(false);
    });
  }
-------------------------
  build() {
    // 创建导航容器(堆栈模式)
    Navigation(this.vm.navStack) {
      Row() { // 行布局容器
        // 加载登录组件,设置登录成功回调
        LoginView({
          'callback': () => {
            // 登录成功后跳转至主页面(替换当前路由)
            router.replaceUrl({url: 'pages/MainEntry'})
          }
        });
      }
    }
    // 导航栏配置:隐藏标题栏/工具栏/返回按钮
    .hideTitleBar(true)
    .hideToolBar(true)
    .hideBackButton(true)
    // 设置导航模式为堆栈(先进后出)
    .mode(NavigationMode.Stack);
  }
}

3.打卡UI布局开发
(1)在ets下创建contants常量文件。
#我的鸿蒙开发手记#鸿蒙打卡场景实现-鸿蒙开发者社区
(2)Constants.ets代码实现:

// 导入底部导航栏Tab项类型定义
import { TabListItem } from '@ohos_agcit/office_attendance_agency'

// 定义底部导航栏配置数组
export const TAB_LIST: TabListItem[] = [
  // 第一个Tab:考勤打卡模块
  {
    label: $r('app.string.attendance'), // 文字标签
    icon: $r('app.media.attendance'), // 默认状态图标(未选中时显示)
    iconChecked: $r('app.media.attendance_checked'), // 选中状态图标
    titleBackgroundColor: Color.White // 标题栏背景色(白色)
  },
  // 第二个Tab:待办任务模块
  {
    label: $r('app.string.agency_task'), 
    icon: $r('app.media.agency_task'),
    iconChecked: $r('app.media.agency_task_checked'),
    titleBackgroundColor: Color.White
  },
  // 第三个Tab:日程管理模块
  {
    label: $r('app.string.schedule'),
    icon: $r('app.media.schedule'),
    iconChecked: $r('app.media.schedule_checked'),
    titleBackgroundColor: Color.White
  },
  // 第四个Tab:个人中心模块
  {
    label: $r('app.string.mine'),
    icon: $r('app.media.mine_unchecked'),
    iconChecked: $r('app.media.mine_checked'),
    titleBackgroundColor: $r('app.color.title_background_color') // 特殊背景色
  }
];

(3)products产品层下的pages>MainEntry.ets关键代码实现:

// 导入模块:底部导航配置、各功能模块视图、类型定义等
import { TAB_LIST } from '../constants/Constants';
import { AgencyTaskView } from '@ohos_agcit/office_attendance_agency';
import { ScheduleView } from '@ohos_agcit/office_attendance_schedule';
import { TabListItem } from '@ohos_agcit/office_attendance_agency';
import { CheckInView } from '@ohos_agcit/office_attendance_checkin';
import { CommonConstants, MainEntryVM } from '@ohos_agcit/office_attendance_common_lib';
import { MineView } from '@ohos_agcit/office_attendance_account';
import { router } from '@kit.ArkUI';

// --------------------------
// 主入口页面构建器(供外部调用)
// --------------------------
@Builder
export function mainEntryBuilder() {
  MainEntry();
}
@Entry // 标记为应用入口组件
@ComponentV2 // 声明为ArkUI V2版本组件
struct MainEntry {
  // 路由管理单例
  vm: MainEntryVM = MainEntryVM.instance;
  
  // 页面状态控制
  @Provider() isPageShow: boolean = false; // 页面是否显示
  @Provider() currentTabIndex: number = this.vm.curIndex; // 当前选中Tab索引
  onPageShow(): void {
    this.isPageShow = true; // 页面显示时更新状态
  }

  onPageHide(): void {
    this.isPageShow = false; // 页面隐藏时更新状态
  }
  build() {
    Navigation(this.vm.navStack) { // 导航容器(管理页面堆栈)
      // 底部导航栏配置
      Tabs({ 
        barPosition: BarPosition.End, // 导航栏位置(底部)
        index: this.vm.curIndex // 当前选中索引
      }) {
        // Tab1: 考勤打卡模块
        TabContent() {
          CheckInView(); // 加载打卡组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[0], 0)); // 绑定导航栏样式

        // Tab2: 待办任务模块
        TabContent() {
          //AgencyTaskView(); // 加载待办组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[1], 1));

        // Tab3: 日程管理模块
        TabContent() {
          //ScheduleView(); // 加载日程组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[2], 2));

        // Tab4: 个人中心模块
        TabContent() {
          //MineView({ // 加载个人中心组件
          //  callback: () => { // 退出登录回调
          //    router.replaceUrl({ url: 'pages/Login' }) // 跳转登录页
          //  }
          });
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[3], 3));
      }
      // 导航栏样式配置
      .scrollable(false) // 禁止滑动切换
      .barHeight(80) // 导航栏高度
      .height(CommonConstants.FULL_HEIGHT) // 全屏高度
      .animationDuration(0) // 禁用切换动画
      .barMode(BarMode.Fixed) // 固定模式(不随内容滚动)
      .onChange((index: number) => { // Tab切换事件
        this.vm.curIndex = index; // 更新路由管理状态
        this.currentTabIndex = index; // 更新当前索引
      });
    }
    // 导航栏标题配置
    .title({ 
      builder: this.titleBuilder(TAB_LIST[this.vm.curIndex]), // 动态标题
      height: 92 // 标题栏高度
    })
    .mode(NavigationMode.Stack) // 堆栈导航模式
  }
  @Builder
  titleBuilder(title: TabListItem) {
    Row() {
      Text(title.label) // 显示当前模块名称
        .fontWeight(FontWeight.Bold) // 加粗
        .fontColor('rgba(0,0,0,0.90)') // 字体颜色
        .fontSize($r('app.float.navigation_title_font_size')) // 字体大小(资源引用)
        .margin({ left: 16, top: 36 }) // 边距
        .height(56) // 高度
    }
    .justifyContent(FlexAlign.Start) // 左对齐
    .backgroundColor(title.titleBackgroundColor) // 背景色(动态配置)
    .width('100%') // 全宽
    .height('100%') // 全高
    .alignItems(VerticalAlign.Bottom) // 垂直底部对齐
  }

  // --------------------------
  // 底部导航栏Item构建器
  // --------------------------
  @Builder
  tabBarBuilder(item: TabListItem, index: number) {
    Column() { // 垂直布局
      // 动态显示选中/未选中图标
      Image(this.vm.curIndex === index ? item.iconChecked : item.icon)
        .width($r('app.float.navigation_image_size')) // 图标宽度(资源引用)
        .height($r('app.float.navigation_image_size')) // 图标高度
        .margin({ top: $r('app.string.margin_xs') }); // 上边距
      
      // Tab文字标签
      Text(item.label)
        .fontColor(this.vm.curIndex === index ? 
          $r('app.color.icon_color_highlight') : // 选中高亮色
          $r('app.color.icon_color_level2')) // 未选中默认色
        .fontSize($r('app.float.navigation_navi_size')) // 字体大小
        .height(14) // 固定高度
        .margin({ 
          top: $r('app.string.margin_xs'), 
          bottom: $r('app.string.margin_xs') 
        });
    }
    .height(80) // 固定高度
    .width('100%') // 全宽
    .justifyContent(FlexAlign.Start); // 顶部对齐
  }
}

三、打卡功能模块与实现流程
1.打卡模块UI结构设计
打卡由以下核心组件构成:
·主打卡页面:显示当前定位、打卡按钮、快捷设置入口;
·打卡记录页面:以列表形式展示历史打卡数据;
·快捷设置页面:配置常用打卡地点与时间。
2.功能实现步骤
(1)CheckInView.ets文件创建与实现代码:
①在根目录OfficeAttendance下创建scenes场景文件目录
②在scenes目录下创建checkin模块(HSP动态共享)
#我的鸿蒙开发手记#鸿蒙打卡场景实现-鸿蒙开发者社区
③CheckInView.ets代码实现:

// 导入ArkUI动画曲线、路由管理模块、网络请求代理和类型定义
import { curves } from '@kit.ArkUI';
import { DialogMap, RouterModule } from '@ohos_agcit/office_attendance_common_lib';
import { RequestProxy } from '../api/RequestProxy';
import { CheckInHistoryDetailDataItem } from '../types/Types';
import { CheckInComponent } from './components/CheckInComponent';

// --------------------------
// 考勤打卡主页面组件
// --------------------------
@ComponentV2
export struct CheckInView {
  // 本地状态管理
  @Local checkinFirstTime: string = ''; // 当天首次打卡时间
  @Local checkinLastTime: string = '';  // 当天最后打卡时间
  @Local imageOpacity: number = 1;     // 箭头图标透明度(用于呼吸动画)
  @Local timerId: number = 0;          // 定时器ID

  // --------------------------
  // 组件即将显示时触发
  // --------------------------
  aboutToAppear() {
    this.refreshCheckDetail(); // 初始化打卡数据
    // 启动箭头呼吸动画定时器(800ms间隔切换透明度)
    this.timerId = setInterval(() => {
      this.imageOpacity = this.imageOpacity === 1 ? 0 : 1; 
    }, 800);
  }

  // --------------------------
  // 获取当天打卡记录详情
  // --------------------------
  private refreshCheckDetail() {
    RequestProxy.getTodayCheckInDetail().then((value: CheckInHistoryDetailDataItem[]) => {
      if (value.length > 0) {
        this.checkinFirstTime = value[0].time;          // 取第一条作为首次打卡
        this.checkinLastTime = value[value.length - 1].time; // 取最后一条作为末次打卡
      }
    });
  }

  // --------------------------
  // 组件即将消失时触发
  // --------------------------
  aboutToDisappear(): void {
    clearInterval(this.timerId); // 清除动画定时器防止内存泄漏
  }

  // --------------------------
  // 页面构建
  // --------------------------
  build() {
    // 纵向弹性布局(内容区与底部信息栏上下分布)
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
      // 1. 主打卡组件(占满剩余空间)
      CheckInComponent({
        onCheckIn: () => { // 打卡成功回调
          this.refreshCheckDetail() // 刷新打卡记录
        }
      }).flexGrow(1)

      // 2. 底部信息栏
      Column() {
        // 2.1 动态箭头指示器
        Image($r('app.media.double_arrow_up')) // 双箭头图标
          .height(16)
          .width(80)
          .opacity(this.imageOpacity) // 动态透明度
          .animation({ 
            duration: 800, 
            curve: curves.springMotion() // 弹性动画曲线
          })
          .margin({ top: 4 })
          .onClick(() => { // 点击跳转打卡历史弹窗
            RouterModule.openDialog(DialogMap.CHECK_IN_HISTORY)
          })

        // 2.2 打卡时间展示区
        Row() {
          // 首次打卡时间
          Column() {
            Text($r('app.string.checkin_first_time')) // 国际化文案
              .fontSize(14)
              .fontColor($r('app.string.white_60')) // 60%透明度白色
              .height(20)
            Text(this.checkinFirstTime) // 动态时间数据
              .fontSize(18)
              .fontColor(Color.White)
              .height(24)
          }.alignItems(HorizontalAlign.Start)

          // 末次打卡时间
          Column() {
            Text($r('app.string.checkin_last_time'))
              .fontSize(14)
              .fontColor($r('app.string.white_60'))
              .height(20)
            Text(this.checkinLastTime)
              .fontSize(18)
              .fontColor(Color.White)
              .height(24)
          }.alignItems(HorizontalAlign.Start)
        }
        .margin({ top: 24 })
        .justifyContent(FlexAlign.SpaceAround) // 两端对齐
        .width('100%')
      }
      .flexShrink(0) // 禁止压缩高度
      .height(113)  // 固定高度
      .width('100%')
      .borderRadius({ topLeft: 36, topRight: 36 }) // 顶部圆角
      .backgroundColor($r('app.color.checkin_background')) // 背景色
      .align(Alignment.Top)
      // 添加上滑手势(打开历史记录)
      .gesture(
        PanGesture({ direction: PanDirection.Up })
          .onActionEnd(() => {
            RouterModule.openDialog(DialogMap.CHECK_IN_HISTORY)
          })
    }.height('100%')
  }
}

(2)步骤一:打卡记录页面
描述:签到组件,以列表形式展示历史打卡数据
CheckInComponent.ets代码实现:

// 导入基础模块:动画、路由、工具类等
import {
  DialogMap,
  FormatUtil,
  LocationUtil,
  MainEntryVM,
  RouterModule,
  TabIndex,
  VibratorUtil,
  WindowUtil
} from '@ohos_agcit/office_attendance_common_lib'
import { CheckInStatus } from '@ohos_agcit/office_attendance_common_lib/src/main/ets/constants/CheckInEnums';
import { Animator as animator, AnimatorResult } from '@kit.ArkUI';
import { RequestProxy } from '../../api/RequestProxy';
import { CheckInChannelUtil } from '../util/CheckInChannelUtil';
import { ServiceCardNotificationUtil } from '../util/ServiceCardNotificationUtil';

// --------------------------
// 考勤打卡核心组件
// --------------------------
@ComponentV2
export struct CheckInComponent {
  // 组件状态管理
  @Local checkInComponentHeight: number | string = '100%' // 滚动区域高度
  private circleAnimator: AnimatorResult | undefined = this.create() // 打卡按钮动画控制器

  // --------------------------
  // 创建打卡按钮动画
  // --------------------------
  create(): AnimatorResult {
    const circleAnimator = animator.create({
      duration: 1000,       // 动画时长1秒
      easing: 'ease-in-out', // 缓动效果
      delay: 0,             // 无延迟
      fill: 'backwards',    // 动画结束后保持结束状态
      direction: 'alternate', // 往返播放
      iterations: -1,       // 无限循环
      begin: 1,             // 初始缩放值
      end: 1.05             // 结束缩放值
    })
    // 动画帧回调
    circleAnimator.onFrame = (value: number) => {
      this.scaleX = value;
      this.scaleY = value;
    }
    return circleAnimator;
  }

  // 参数与状态定义
  @Param onCheckIn: () => void = () => {}; // 打卡成功回调
  @Local checkInStatus: CheckInStatus = CheckInStatus.NOT_STARTED; // 打卡状态
  @Local max: number = 170;  // 外圈最大尺寸
  @Local min: number = 160;  // 内圈最小尺寸
  @Local scaleX: number = 1; // X轴缩放
  @Local scaleY: number = 1; // Y轴缩放
  @Local displayAddition: boolean = false; // 是否显示附加信息
  @Local timerId: number = 0; // 定时器ID
  @Local location: string | undefined = undefined; // 打卡位置
  @Local time: string = FormatUtil.formatTime(new Date()) // 当前时间
  @Consumer() isPageShow: boolean = false; // 页面显示状态
  @Consumer() currentTabIndex: number = MainEntryVM.instance.curIndex; // 当前Tab索引

  // --------------------------
  // 监听页面显示状态变化
  // --------------------------
  @Monitor('isPageShow')
  tryCheckInAutomatically() {
    if (this.isPageShow) { 
      // 服务卡片快捷打卡
      if (CheckInChannelUtil.fromServiceCard()) {
        this.checkin();
        CheckInChannelUtil.reset();
      } else {
        // 检查是否开启快捷打卡
        RequestProxy.isQuickCheckIn().then((value: boolean) => {
          if (value) this.checkin();
        })
      }
    }
  }

  // --------------------------
  // 监听Tab切换事件
  // --------------------------
  @Monitor('currentTabIndex')
  tryWhenTabChanged() {
    if (this.currentTabIndex === TabIndex.CHECKIN) {
      RequestProxy.isQuickCheckIn().then((value: boolean) => {
        if (value) this.checkin();
      })
    }
  }

  // --------------------------
  // 组件生命周期
  // --------------------------
  aboutToAppear() {
    this.calculateScrollAreaHeight(); // 计算滚动区域高度
    // 每秒更新时间显示
    this.timerId = setInterval(() => {
      this.time = FormatUtil.formatTime(new Date());
    }, 1000);
  }

  aboutToDisappear(): void {
    clearInterval(this.timerId); // 清除定时器
  }

  // --------------------------
  // 组件构建
  // --------------------------
  build() {
    Scroll() {
      Stack() {
        // 主内容区
        Column() {
          // 打卡按钮区域
          Stack() {
            // 背景圆圈动画
            Shape() {
              this.circleBuilder(this.max, 0.1) // 外圈(半透明)
              this.circleBuilder(this.min, 1.0)  // 内圈(实心)
            }.align(Alignment.Center).width(180).height(180)

            // 不同状态下的UI展示
            this.checkInBuilder($r('app.media.click'), $r('app.string.checkin_tip'), CheckInStatus.NOT_STARTED)
            this.checkInBuilder($r('app.media.ticking'), $r('app.string.checkin_is_finished'), CheckInStatus.FINISHED)
            this.checkInStatusTipBuilder($r('app.string.checkin_is_on_going'), CheckInStatus.ON_GOING)
            this.checkInStatusTipBuilder($r('app.string.checkin_is_failed'), CheckInStatus.FAILED)
          }
          .onClick(async () => { this.checkin(); })
          .margin({ top: 76 })

          // 附加信息(位置/时间)
          this.additionBuilder()
        }
        .alignItems(HorizontalAlign.Center)
        .width('100%')

        // 设置按钮
        Stack() {
          Circle().shadow({ radius: vp2px(8) }) // 白色圆形背景
          Image($r('app.media.settings')).height(24).width(24) // 设置图标
        }
        .onClick(() => {
          RouterModule.openDialog(DialogMap.CHECK_IN_SETTINGS) // 打开设置弹窗
        })
        .alignContent(Alignment.BottomEnd) // 右下角定位
      }
    }
    .onAreaChange(() => { this.calculateScrollAreaHeight() }) // 区域变化时重新计算高度
  }

  // --------------------------
  // 构建方法
  // --------------------------
  // 圆圈构建器
  @Builder
  circleBuilder(size: number, opacity: number) {
    Circle()
      .width(size).height(size)
      .scale({ x: this.scaleX, y: this.scaleY }) // 动态缩放
      .fillOpacity(opacity)
      .fill($r('app.color.checkin_background')) // 使用主题色
  }

  // 打卡按钮构建器
  @Builder
  checkInBuilder(image: Resource, text: Resource, checkInStatus: CheckInStatus) {
    Column() {
      Image(image).opacity(this.checkInStatus === checkInStatus ? 1 : 0) // 状态匹配时显示
      Text(text).fontColor(Color.White).opacity(this.checkInStatus === checkInStatus ? 1 : 0)
    }.height(160)
  }

  // 打卡状态提示构建器
  @Builder
  checkInStatusTipBuilder(text: Resource, checkInStatus: CheckInStatus) {
    Text(text)
      .fontColor(Color.White)
      .opacity(this.checkInStatus === checkInStatus ? 1 : 0) // 状态匹配时显示
      .scale({ x: this.scaleX, y: this.scaleY }) // 同步缩放动画
  }

  // 附加信息构建器
  @Builder
  additionBuilder() {
    if (this.displayAddition) {
      Column() {
        Text(this.location) // 位置信息
        Divider().color($r('app.color.checkin_background')) // 分隔线
        Text(this.time).fontSize(26) // 时间信息
      }
    }
  }

  // --------------------------
  // 业务逻辑
  // --------------------------
  // 添加打卡记录
  addCheckInDetail(address: string) {
    RequestProxy.addCheckInDetail({
      'day': FormatUtil.formatDate(new Date()),
      'location': address,
      'time': this.time
    })
  }

  // 执行打卡操作
  checkin() {
    if (this.checkInStatus !== CheckInStatus.NOT_STARTED) return;
    
    this.circleAnimator?.play(); // 启动动画
    this.checkInStatus = CheckInStatus.ON_GOING;
    VibratorUtil.notification(500, 0); // 震动反馈

    // 超时处理(8秒未获取位置则失败)
    const timeoutId = setTimeout(() => {
      this.checkInStatus = CheckInStatus.FAILED;
      this.finishAnimator();
      this.resetCheckInStatusLater();
    }, 8000);

    // 获取定位
    LocationUtil.getLocationName().then((address: string | undefined) => {
      clearInterval(timeoutId);
      if (this.checkInStatus === CheckInStatus.FAILED) return;

      if (address) {
        this.location = address;
        this.checkInStatus = CheckInStatus.FINISHED;
        this.displayAddition = true;
        this.addCheckInDetail(address); // 保存记录
        this.onCheckIn(); // 触发回调
      } else {
        this.checkInStatus = CheckInStatus.FAILED;
      }
      this.resetCheckInStatusLater();
      ServiceCardNotificationUtil.notify(this.checkInStatus); // 通知服务卡片
    });
  }

  // 辅助方法
  private finishAnimator() {
    this.circleAnimator?.finish();
    this.scaleX = this.scaleY = 1; // 重置缩放
  }

  private resetCheckInStatusLater() {
    setTimeout(() => {
      this.checkInStatus = CheckInStatus.NOT_STARTED; // 1.5秒后重置状态
      ServiceCardNotificationUtil.notify(this.checkInStatus);
    }, 1500);
  }

  // 计算滚动区域高度
  private calculateScrollAreaHeight() {
    const windowHeight = WindowUtil.getInstance()?.getAppStorageData().windowHeightVp as number;
    const minHeight = 435; // 设计稿最小高度
    this.checkInComponentHeight = (windowHeight - 56 - 36 - 113 - 80 > minHeight) ? '100%' : minHeight;
  }
}

(3)步骤二:快捷设置页面
描述:配置常用打卡地点与时间,进入打卡界面时自动打卡一次
CheckInSettings.ets代码实现:

// 导入路由管理、基础组件和网络请求模块
import { RouterModule } from '@ohos_agcit/office_attendance_common_lib';
import { BlankBackground } from '@ohos_agcit/office_attendance_component_lib';
import { BaseResponse } from '@ohos_agcit/office_attendance_network';
import { RequestProxy } from '../../api/RequestProxy';

// --------------------------
// 快捷打卡设置弹窗构建器(供外部调用)
// --------------------------
@Builder
export function checkinSettingBuilder() {
  CheckInSettings();
}

// --------------------------
// 快捷打卡设置组件
// --------------------------
@ComponentV2
export struct CheckInSettings {
  @Local isQuickCheckIn: boolean = false; // 是否开启快捷打卡

  // --------------------------
  // 组件构建
  // --------------------------
  build() {
    NavDestination() { // 导航目标容器(弹窗模式)
      Stack() {
        // 1. 半透明背景
        BlankBackground();

        // 2. 主内容区
        Column() {
          // 2.1 顶部拖拽指示条
          Row() {
            Divider()
              .height(4)
              .width(48)
              .borderRadius(2)
              .backgroundColor($r('app.string.black_20')) // 20%透明度黑色
              .margin({ top: 8 })
          }.height(16).width(96).justifyContent(FlexAlign.Center)

          // 2.2 标题栏
          Row() {
            Text($r('app.string.settings')) // 国际化文案
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor($r('app.string.black_90')) // 90%黑色
            Blank() // 占位空间

            // 关闭按钮
            Stack() {
              Circle().fill('rgba(0,0,0,0.05)') // 圆形背景
              Image($r('app.media.xmark')).height(18).width(18) // 关闭图标
            }.onClick(() => RouterModule.pop()) // 点击关闭弹窗
          }.height(40).width('100%')

          // 2.3 快捷打卡开关
          Row() {
            Column() {
              Text($r('app.string.quick_checkin')) // 主标题
                .fontColor($r('app.string.black_90'))
                .fontSize(18)
                .height(24)
              Text($r('app.string.quick_checkin_desc')) // 副标题
                .fontColor($r('app.string.black_60'))
                .fontSize(12)
                .height(16)
            }

            // 开关组件
            Toggle({ 
              type: ToggleType.Switch, 
              isOn: this.isQuickCheckIn 
            }).onChange((isOn: boolean) => {
              // 保存设置到服务器
              RequestProxy.saveIsQuickCheckIn(isOn).then((value: BaseResponse<undefined>) => {})
            })
          }
          .height(56)
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween) 
        }
        // 内容区样式
        .height(200)
        .borderRadius({ topLeft: 32, topRight: 32 }) // 顶部圆角
        .padding({ left: 16, right: 16 })
        .backgroundColor($r('app.string.white_90')) // 90%透明度白色
        .backdropBlur(54.3656005859375) // 背景模糊效果
      }.alignContent(Alignment.Bottom) // 底部对齐
    }
    // 弹窗配置
    .mode(NavDestinationMode.DIALOG) // 设置为弹窗模式
    .onBackPressed(() => false) // 禁用返回键关闭
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) // 安全区域适配
    .hideTitleBar(true) // 隐藏默认标题栏
    .onShown(() => { // 弹窗显示时加载设置
      RequestProxy.isQuickCheckIn().then((value: boolean) => {
        this.isQuickCheckIn = value;
      })
    });
  }
}

四、最终成果展示
#我的鸿蒙开发手记#鸿蒙打卡场景实现-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2025-5-6 09:34:55修改
收藏
回复
举报
回复
    相关推荐