動態

詳情 返回 返回

【小而美】HarmonyOS官方模板優秀案例(第5期:工具行業 · 日曆應用) - 動態 詳情

💡 鴻蒙生態為開發者提供海量的HarmonyOS模板/組件,助力開發效率原地起飛 💡

★ 一鍵直達生態市場組件&模板市場 , 快速應用DevEco Studio插件市場集成組件&模板 ★

工具行業羣英薈萃,是小而美應用的主要聚集賽道

本期介紹的案例是其中一類:日常剛需的日曆應用

👉 覆蓋20+行業,本帖以彙總形式持續更新中,點擊收藏!一鍵三連!常看常新!

【第5期】工具行業 · 日曆應用

一、概述

1. 行業洞察

1)行業訴求:

  • 日曆類應用,未來競爭將聚焦於 AI 驅動的個性化體驗、場景化生態構建及文化適配能力正從單一工具進化為連接工作、生活、社交的 “時間操作系統”。
  • 商業模式是日曆類應用的重要場景訴求,目前免費增值為主,差異化變現破局,如何結合小藝做個性化推薦是差異化的根本。
  • 滿足用户的進階需求:社交協作,隱私保護。

2)行業常用三方SDK

分類

三方庫名稱

功能

SDK鏈接

登錄認證

中國移動一鍵登錄SDK/易盾一鍵登錄SDK/創藍閃驗/極光安全認證/阿里雲號碼認證SDK/中國電信一鍵登錄SDK

登錄

嶽鷹全景監控SDk

支付寶支付 SDK

穿山甲廣告SDK

快手聯盟廣告SDK

友盟SDK

騰訊微信SDK

騰訊優量匯

極光 SDK

高德地圖

百度地圖

騰訊地圖定位

高德地圖定位

分享

友盟/ShareSDK/微信分享/QQ分享/新浪微博SDK/MobTech ShareSDK

統計/推送/分享

支付

支付寶支付/微信支付/銀聯支付

支付

數據分析

友盟移動統計SD/神策數據SDK

數據收集、處理、分析、運用

性能監控

騰訊Bugly SDK/聽雲SDK/嶽鷹全景監控SDK

異常上報和運營統計

地圖

高德地圖SDK

地圖

推送

個推/華為推送/極光PUSH/阿里推送SDK

消息推送

媒體

阿里雲視頻播放器SDK

音視頻

説明:“以上三方庫及鏈接僅為示例,三方庫由三方開發者獨立提供,以其官方內容為準”

2. 案例概覽(下載模板

基於以上行業分析,本期將介紹鴻蒙生態市場生活服務類行業模板——日曆應用模板,為行業提供常用功能的開發案例,模板主要分為萬年曆、黃曆、和我的三大模塊。

  • Stage開發模型 + 聲明式UI開發範式。
  • 分層架構設計 + 組件化拆分,支持開發者在開發時既可以選擇完整使用模板,也可以根據需求單獨選用其中的業務組件。
  • 本模板已集成華為賬號等服務,只需做少量配置和定製即可快速實現華為賬號的登錄。

本模板主要頁面及核心功能如下所示:

​日曆模板
 |-- 萬年曆
 |    |-- 日曆選擇
 |    |-- 吉日查詢
 |    |-- 日期計算
 |    |-- 節日節氣
 |    └-- 宜忌展示
 |-- 黃曆
 |    |-- 日期切換
 |    |-- 宜忌展示
 |    |-- 五行、衝煞 
 |    |-- 彭祖百忌
 └-- 我的
 |     |-- 個人信息
 |     └-- 設置
 |       └-- 主題切換
 |       └-- 隱私協議
 |       └-- 用户協議  

二、應用架構設計

1. 分層模塊化設計

  • 產品定製層:專注於滿足不同設備或使用場景的個性化需求,作為應用的入口,是用户直接互動的界面。
    • 本實踐暫時只支持直板機,為單HAP包形式,包含路由根節點、底部導航欄等。
  • 基礎特性層:用於存放相對獨立的功能UI和業務邏輯實現。
    • 本實踐的基礎特性層將應用底部導航欄的每個選項拆分成一個獨立的業務功能模塊。
    • 每個功能模塊都具備高內聚、低耦合、可定製的特點,支持產品的靈活部署。
  • 公共能力層:存放公共能力,包括公共UI組件、數據管理、外部交互和工具庫等共享功能。
    • 本實踐的公共能力層分為公共基礎能力和可分可合組件,均打包為HAR包被上層業務組件引用。
    • 公共基礎能力包含日誌、文件處理等工具類,公共類型定義,網絡庫,以及彈窗、加載等公共組件。
    • 可分可合組件將包含行業特點、可完全自閉環的能力抽出獨立的組件模塊,支持開發者在開發中單獨集成使用,詳見業務組件設計章節。

http://image.huawei.com/tiny-lts/v1/images/hi3ms/edf690f19332784457ac85f9514db6d1_1226x503.png

2. 業務組件設計

為支持開發者單獨獲取特定場景的頁面和功能,本模板將功能完全自閉環的部分能力抽離出獨立的行業組件模塊,不依賴公共基礎能力包,開發者可以單獨集成,開箱即用,降低使用難度。

http://image.huawei.com/tiny-lts/v1/images/hi3ms/0b4b99b97dd7af53b8f480b111944486_1160x399.png

三、行業場景技術方案

1. 個人信息

1)場景説明

支持華為賬號一鍵登錄及其他方式(賬號密碼登錄)。

用户登錄後展示暱稱和頭像,點擊用户信息欄可進入用户主頁,查看並編輯個人信息和歷史動態。

支持添加重要提醒(日程、生日、紀念日、代辦),更新提醒,刪除提醒。

2)技術方案

  • 華為賬號一鍵登錄
    • 通過Account Kit實現華為賬號一鍵登錄,並獲取用户手機號,關聯應用已有用户。
  • 頭像修改
    • 通過Scenario Fusion Kit提供的選擇頭像Button快速拉起頭像選擇頁面,供用户完成華為賬號頭像或其他頭像的選擇與展示。
  • 重要提醒
    • 通過@kit.CalendarKit提供的提供日曆與日程管理能力將應用中的工作、生活中與時間相關的日程服務與系統日曆進行集成,從而實現日程管理、事件創建、查詢等功能。
  • 主題切換
    • 通過全局主題對象,控制全局的主題顏色切換,並使用持久化存儲當前主題選擇。

3)代碼參考

  • 部分核心代碼參見華為賬號一鍵登錄實現章節。

2. 黃曆

1)場景説明

  • 支持根據日期查看當日黃曆信息。
  • 切換日期查詢其他日期黃曆。
  • 根據選擇日期查看今日宜今日忌。
  • 支持根據選擇的黃曆查看白話文。

2)技術方案

根據萬年曆選擇日期進行對應日期黃曆的展示。

通過日曆選擇組件暴露的句柄,感知當前選擇的日期,並通過句柄同步修改萬年曆對應的日期。

3)代碼參考

  • 部分核心代碼參見黃曆實現章節。

3. 萬年曆

1)場景説明

支持日曆查看,日期切換,設置周首日。

支持查看今日宜,今日忌。

支持實用工具查詢(吉日查詢,日期計算,節日節氣)。

支持查看城市限行。

支持查看歷史上的今天。

2)技術方案

  • 日曆查看
    • 通過使用Swiper組件結合計算每月的日期實現日期輪播查看。
  • 頭像修改
    • 通過Scenario Fusion Kit提供的選擇頭像Button快速拉起頭像選擇頁面,供用户完成華為賬號頭像或其他頭像的選擇與展示。
  • 實用工具
    • 使用工具結合DatePicker日期選擇器,實現日期選擇並根據條件計算。
  • 城市限行
    • 通過申請位置權限,或者當前城市的限行車牌尾號,並進行展示。

3)代碼參考

  • 部分核心代碼參見萬年曆實現章節。

四、模板代碼

1. 工程結構(下載模板

詳細代碼結構如下所示:

Application
├──├──commons
│   ├──common                                // 公共能力層
│     ├──src/main/ets                        // 基礎能力
│     │  └──components                       // 公共組件
│     │  └──dividerTmp                       // 下劃線公共組件
│     │  └──https                            // 網絡請求庫
│     │  └──models                           // 公共接口常量
│     │  └──quickLogin                       // 華為賬號一鍵登錄
│     │  └──style                            // 公共樣式
│     │  └──utils                            // 工具類
│     │  └──viewmodels                       // 接口層
│     └──Index.ets                           // 對外接口類
│  ├──router_module                          // 全局路由組件
├──├──components                             // 公共組件
│   ├──base_apis                             // 通用組件(模態框,彈窗,選擇器等)
│   ├──base_calendar                         // 日曆組件
│   ├──calendar_almanac                      // 黃曆組件
│   ├──calendar_events                       // 重要提醒組件
│   ├──date_calculation                      // 日期計算組件
│   ├──festival_solar                        // 節日節氣組件
│   ├──login_info                            // 登錄組件組件
│   ├──vip_center                            // 開通會員組件
│   ├──traffic_restriction                   // 城市限行組件
│   ├──yiji_query                            // 宜忌查詢組件
├──features                                  // 基礎特性層
│  ├──almanac/src/main/ets                   // 黃曆
│  │  ├──pages                               // 首頁入口
│     │  ├──AlmanacView                      // 黃曆入口
│  ├──almanac/src/main/resources             // 資源文件目錄
│  ├──almanac/Index.ets                      // 對外接口類
│  ├──perpetual/src/main/ets                 // 萬年曆
│  │  ├──components                          // 萬年曆組件
│  │  ├──pages                               
│     │  ├──PerpetualCalendar                // 萬年曆組件入口
│  ├──perpetual/src/main/resources           // 資源文件目錄
│  ├──perpetual/Index.ets                    // 對外接口類
│  ├──mine/src/main/ets                      // 我的(包含一鍵登錄)
│  │  └──pages                               // 我的入口頁
│     │  ├──MinePage                         // 登錄
│  │  └──components                          // 我的頁面入口
│  └──mine/src/main/resources                // 資源文件目錄
└─product/entry/src/main   
   ├─ets
   │  ├─widget
   │  │  ├──pages            
   │  │      ├──WidgetCard.ets       // 服務卡片    
   │  ├─entryability
   │  │      ├──EntryAbility.ets             // 應用程序入口
   │  ├─page
   │  │  ├──Index.ets                        // 入口
   │  │  ├──PrivacyPage.ets                  // 隱私協議   
   │  │  ├──SafePage.ets                     // 隱私協議彈窗  
   │  │  ├──SplashPage.ets                   // 閃屏頁        
   │  │  ├──TabContainer.ets                 // tab頁入口
   └─resources

2. 關鍵代碼解讀

本篇代碼非應用的全量代碼,只包括應用的部分能力的關鍵代碼。

若需獲取全量代碼,請查看模板集成章節。

1)個人信息

  • 華為賬號一鍵登錄
typescript
    getQuickLoginAnonymousPhone() {
    // 創建授權請求,並設置參數
    const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
    // 獲取手機號需要傳如下scope,傳參數之前需要先申請對應scope權限,才能返回對應數據
    authRequest.scopes = ['quickLoginAnonymousPhone'];
    authRequest.permissions = ['serviceauthcode'];
    // 用户是否需要登錄授權,該值為true且用户未登錄或未授權時,會拉起用户登錄或授權頁面
    authRequest.forceAuthorization = false;
    // 用於防跨站點請求偽造
    authRequest.state = util.generateRandomUUID();
    try {
      const controller = new authentication.AuthenticationController(getContext(this));
      controller.executeRequest(authRequest).then((response) => {
        const authorizationWithHuaweiIDResponse = response as authentication.AuthorizationWithHuaweiIDResponse;
        const state = authorizationWithHuaweiIDResponse.state;
        if (state !== undefined && authRequest.state !== state) {
          hilog.error(0x0000, 'testTag', `Failed to authorize. The state is different, response state: ${state}`);
          return;
        }
        hilog.info(0x0000, 'testTag', 'Succeeded in authentication.');
        const authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse.data!;
        const code = authorizationWithHuaweiIDCredential.authorizationCode;
        const unionID = authorizationWithHuaweiIDCredential.unionID;
        const openID = authorizationWithHuaweiIDCredential.openID;
        const anonymousPhone = authorizationWithHuaweiIDCredential?.extraInfo?.quickLoginAnonymousPhone as string;
        if (anonymousPhone) {
          hilog.info(0x0000, 'testTag', 'Succeeded in authentication.');
          this.quickLoginAnonymousPhone = anonymousPhone;
          return;
        } else {
          this.quickLoginAnonymousPhone = '123xxxxxx456'
        }
        // 開發者處理code、unionID、openID
        this.authorizationCode = code
      }).catch((err: BusinessError) => {
        this.dealAllPhoneError(err);
      });
    } catch (error) {
      this.dealAllPhoneError(error);
    }
  }

 

  • 重要提醒
typescript
class CalendarManage {
  context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  private static _instance: CalendarManage

  static get instance() {
    if (!CalendarManage._instance) {
      CalendarManage._instance = new CalendarManage()
    }
    return CalendarManage._instance
  }

  public getCalendarPermission(): Promise<string> {
    const permissions: Permissions[] = ['ohos.permission.READ_CALENDAR', 'ohos.permission.WRITE_CALENDAR'];
    let atManager = abilityAccessCtrl.createAtManager();
    return new Promise((resolve, reject) => {
      atManager.requestPermissionsFromUser(this.context, permissions).then((result: PermissionRequestResult) => {
        resolve('success')
      }).catch((error: BusinessError) => {
        reject('failed')
        console.error(`get Permission error, error. Code: ${error.code}, message: ${error.message}`);
      })
    })
  }

  /*
    * 添加提醒到日曆
    * */
  private async calendarEvent(calendar: calendarManager.Calendar,
    calendarInfo: UserEventItem): Promise<CalendarInfo> {
    const event: calendarManager.Event = {
      title: calendarInfo.content,
      type: calendarManager.EventType.NORMAL,
      id: calendarInfo.eventId,
      isLunar:calendarInfo.date[0].isLunar,
      startTime: new Date(dayjs(calendarInfo.date[0].date).format('YYYY-MM-DD') + ' ' +
      calendarInfo.date[0].time).getTime(),
      endTime: new Date(dayjs(calendarInfo.date[1].date).format('YYYY-MM-DD') + ' ' +
      calendarInfo.date[1].time).getTime(),
      reminderTime: CalendarManage.getReminderTime(calendarInfo.remindList),
      recurrenceRule: {
        recurrenceFrequency: repeatMap[calendarInfo.repeatType],
      },
    };
    return new Promise(async (resolve, reject) => {
      if (calendarInfo.eventId) {
        calendar.updateEvent(event).then(() => {
          resolve({
            status: 'success',
          })
        }).catch((err: BusinessError) => {
          console.error(`Failed to update event. Code: ${err.code}, message: ${err.message}`);
        });
      } else {
        calendar.addEvent(event).then((data: number) => {
          console.info(`Succeeded in adding event, id -> ${data}`);
          resolve({
            status: 'success',
            data: data,
          })
        }).catch((err: BusinessError) => {
          resolve({
            status: 'failed',
          })
        });
      }
    })
  }
  /*
    * 根據提醒參數創建日曆參數
    * */
  public async calendarEventCreate(calendarInfo: UserEventItem, operationType?: string): Promise<CalendarInfo> {
    if (calendarInfo.remindList[0] === '不提醒') {
      return {
        status: 'not need calendar',
      }
    }
    let permission = await this.getCalendarPermission()
    if (permission !== 'success') {
      return {
        status: 'permission failed',
      }
    }
    let calendar: calendarManager.Calendar | undefined = undefined;
    // 指定日曆賬户信息
    const calendarAccount: calendarManager.CalendarAccount = {
      name: '日曆模板',
      type: calendarManager.CalendarType.LOCAL,
      // 日曆賬户顯示名稱,該字段如果不填,創建的日曆賬户在界面顯示為空字符串。
      displayName: '日曆模板',
    };
    let calendarMgr: calendarManager.CalendarManager | null = calendarManager.getCalendarManager(this.context);
    // 創建日曆賬户
    try {
      calendar = await calendarMgr?.createCalendar(calendarAccount)
      let res: CalendarInfo
      if (operationType === 'delete') {
        res = await this.calendarEventDelete(calendar, calendarInfo)
      } else {
        res = await this.calendarEvent(calendar, calendarInfo)
      }
      return res
    } catch (e) {
      return {
        status: 'calendar operation failed',
      }
    }
  }
  /*
    * 刪除已經添加到日曆的提醒
    * */
  private async calendarEventDelete(calendar: calendarManager.Calendar,
    calendarInfo: UserEventItem): Promise<CalendarInfo> {
    try {
      await calendar.deleteEvent(calendarInfo.eventId)
      return {
        status: 'success',
      }
    } catch (e) {
      return {
        status: 'failed',
      }
    }
  }
}

2)黃曆

  • 重要提醒
typescript
/**
 * 拋出句柄
 */
export class CalendarController {
     public static vm: CalendarVM = CalendarVM.instance;
   
     public setSelectDate(date: Date) {
       CalendarController.vm.changeDate(date)
     }
   
     public getTodayYiJi() {
       CalendarController.vm.getTodayYiJi()
     }
}
  • 切換日期
typescript
/**
 * 切換日期
 */  
public changeDate(date: Date) {
    let gap = (date.getFullYear() - this.curDate.year()) * 12 + date.getMonth() - this.curDate.month()
    this.dateListSource.clearData()
    let i = -2
    while (i <= 2) {
      let month = this.getDateList(i + gap)
      this.dateListSource.pushData(month)
      i++
    }
    this.curIndex = 2
    this.selectDate = dayjs(date)
 }
  • 獲取今日宜和忌
typescript
/**
 * 獲取今日宜和忌
 */  
public getTodayYiJi() {
    const todayLunar = Lunar.fromDate(new Date(this.selectDate.format('YYYY-MM-DD')));
    const yi = todayLunar.getDayYi();
    const ji = todayLunar.getDayJi();
    this.todayYiJi = {
      yi,
      ji,
    }
  }

3)萬年曆

  • 展示日曆
typescript
/**
 * 展示日曆
 */  
Swiper(this.swiperController) {
        LazyForEach(this.vm.dateListSource, (item: DateModelList) => {
          Grid() {
            ForEach(item, (item: DateModel) => {
              GridItem() {
              }
            },(item: DateModel) => JSON.stringify(item));
          }
          .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
          .maxCount(7)
          .columnsGap(0)
          .rowsGap(0)
          .padding({ bottom: 20 })
        },(item: DateModelList) => JSON.stringify(item));
}
  • 獲取位置權限
typescript
/**
 * 獲取位置權限
 */  
getCurrentLocation() {
    this.permissionRequestUtils.locationPermissionRequest().then(async (res) => {
      if (res === 'success') {
        this.locationPermission = res
        this.permissionRequestUtils.getCurrentLocation().then((res: string) => {
          this.location = res
        }).catch((err:BusinessError) => {
          this.location = '北京'
        })
      } else {
        this.dealError()
      }
    }).catch(() => {
      this.dealError()
    })
}

3. 模板集成

本模板提供了兩種代碼集成方式,供開發者自由選用。

1)整體集成(下載模板

開發者可以選擇直接基於模板工程開發自己的應用工程。

  • 模板代碼獲取:
    • 通過IDE插件創建模板工程,開發指導。
    • 通過生態市場下載源碼, 下載模板。
    • 通過開源倉訪問源碼,倉庫地址。
  • 打開模板工程,根據README説明中的快速入門章節,將自己的應用信息配置在模板工程內,即可運行並查看模板效果。

  • 對接開發者自己的服務器接口,轉換數據結構,展示真實的雲側數據。

commons/lib_common/src/main/ets/httprequest/HttpRequestApi.ets文件中的mock接口替換為真實的服務器接口。

commons/lib_common/src/main/ets/httprequest/HttpRequest.ets文件中將雲側開發者自定義的數據結構轉換為端側數據結構。

根據自己的業務內容修改模板,進行定製化開發。

2)按需集成

若開發者已搭建好自己的應用工程,但暫未實現其中的部分場景能力,可以選擇取用其中的業務組件,集成在自己的工程中。

  • 組件代碼獲取:
    • 通過IDE插件下載組件源碼。開發指導
    • 通過生態市場下載組件源碼。 下載地址
  • 下載組件源碼,根據README中的説明,將組件包配置在自己的工程中。

  • 根據API參考和示例代碼,將組件集成在自己的對應場景中。

以上是第五期“工具行業-日曆應用”行業優秀案例的內容,更多行業敬請期待~

歡迎下載使用行業模板“點擊下載”,若您有體驗和開發問題,或者迫不及待想了解XX行業的優秀案例,歡迎在評論區留言,小編會快馬加鞭為您解答~

同時誠邀您添加下方二維碼加入“組件模板活動社羣”,精彩上新&活動不錯過!

👉 本系列持續更新,歡迎點擊帖子末尾左下角收藏本帖!

期數

帖子

鏈接

第1期

HarmonyOS官方模板優秀案例 | 便捷生活行業 · 購物中心

點擊查看

第2期

HarmonyOS官方模板優秀案例 | 新聞行業 · 綜合新聞

點擊查看

第3期

HarmonyOS官方模板優秀案例 | 教育行業 · 教育備考

點擊查看

第4期

HarmonyOS官方模板優秀案例 | 餐飲行業 · 美食菜譜

點擊查看

第5期

HarmonyOS官方模板優秀案例 | 工具行業 · 日曆應用

點擊查看

第6期

小編加急整理中,敬請期待

 

👉 HarmonyOS組件模板相關推薦

  • 【活動ing】HarmonyOS組件/模板集成創新活動,報名時間截止2025年8月30日,點擊查看
  • 鴻蒙應用開發者激勵計劃2025,點擊查看
user avatar notobarth 頭像
點贊 1 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.