【案例+1】HarmonyOS官方模板優秀案例 (第7期:金融理財 · 記賬應用)
?? 鴻蒙生態為開發者提供海量的HarmonyOS模板/組件,助力開發效率原地起飛 ??
★ 一鍵直達生態市場組件&模板市場 , 快速應用DevEco Studio插件市場集成組件&模板 ★
實戰分享:如何基于模板快速開發一款記賬應用?本期案例為您解答。
?? 覆蓋20+行業,點擊查看往期案例匯總貼,持續更新,點擊收藏!一鍵三連!常看常新!
【第7期】金融理財 · 記賬應用
一、概述
1. 行業洞察
1)行業訴求:
- 功能冗余:普通用戶剛需功能簡單分類、預算管理、賬單總結;部分 APP 堆砌 “投資分析”“信貸推薦” 等功能。
- 用戶習慣培養難,留存率低:部分APP頁面簡陋、廣告過多、分類復雜導致用戶放棄使用。
- 盈利模式與用戶體驗博弈: 運營及開發成本依賴廣告收益,用戶付費意愿弱。
- 數據安全與合規風險凸顯。
2)行業常用三方SDK
|
分類 |
三方庫名稱 |
功能 |
支持情況 |
SDK鏈接 |
|
媒體 |
阿里云視頻播放器SDK |
音視頻 |
已支持 |
|
|
登錄認證 |
中國移動一鍵登錄SDK/易盾一鍵登錄SDK/創藍閃驗/極光安全認證/阿里云號碼認證SDK/中國電信一鍵登錄SDK |
登錄 |
已支持 |
|
|
分享 |
友盟/ShareSDK/微信分享/QQ分享/新浪微博SDK/MobTech ShareSDK |
統計/推送/分享 |
已支持 |
|
|
支付 |
支付寶支付/微信支付/銀聯支付 |
支付 |
已支持 |
|
|
數據分析 |
友盟移動統計SD/神策數據SDK |
數據收集、處理、分析、運用 |
已支持 |
|
|
性能監控 |
騰訊Bugly SDK/聽云SDK/岳鷹全景監控SDK/友盟應用性能監控SDK/ |
異常上報和運營統計 |
已支持 |
|
|
推送 |
個推/華為推送/極光PUSH/阿里推送SDK |
消息推送 |
已支持 |
|
|
存儲 |
七牛云存儲-SDK/騰訊MMKV組件 |
音視頻 |
已支持 |
|
|
安全 |
火山設備安全SDK/Utdid SDK/ |
安全風控 |
已支持 |
|
|
廣告 |
穿山甲廣告SDK |
廣告 |
已支持 |
|
|
休閑娛樂 |
ThinkingSDK |
游戲 |
已支持 |
說明:“以上三方庫及鏈接僅為示例,三方庫由三方開發者獨立提供,以其官方內容為準”
2. 案例概覽(下載模板)
基于以上行業分析,本期將介紹鴻蒙生態市場金融類行業模板——記賬應用模板,為行業提供常用功能的開發案例,模板主要分首頁、統計和資產三大模塊。
- Stage開發模型 + 聲明式UI開發范式。
- 分層架構設計 + 組件化拆分,支持開發者在開發時既可以選擇完整使用模板,也可以根據需求單獨選用其中的業務組件。

本模板主要頁面及核心功能如下所示:
記賬模板
|-- 首頁
| |-- 賬單查詢
| |-- 新增賬單
| |-- 賬單類型管理
| |-- 編輯賬單
| |-- 刪除賬單
| └-- 賬單詳情查看
|-- 統計
| |-- 賬單報表查看
| |-- 賬單分類查看
| └-- 日歷視圖
└-- 資產
|-- 資產查詢
|-- 新增資產
|-- 編輯資產
|-- 刪除資產
└-- 資產內記賬
二、應用架構設計
1. 分層模塊化設計
- 產品定制層:專注于滿足不同設備或使用場景的個性化需求,作為應用的入口,是用戶直接互動的界面。
- 本實踐暫時只支持直板機,為單HAP包形式,包含路由根節點、底部導航欄等。
- 基礎特性層:用于存放相對獨立的功能UI和業務邏輯實現。
- 本實踐的基礎特性層將應用底部導航欄的每個選項拆分成一個獨立的業務功能模塊。
- 每個功能模塊都具備高內聚、低耦合、可定制的特點,支持產品的靈活部署。
- 公共能力層:存放公共能力,包括公共UI組件、數據管理、外部交互和工具庫等共享功能。
- 本實踐的公共能力層分為公共基礎能力和可分可合組件,均打包為HAR包被上層業務組件引用。
- 公共基礎能力包含日志、文件處理等工具類,公共類型定義,網絡庫,以及彈窗、加載等公共組件。
- 可分可合組件將包含行業特點、可完全自閉環的能力抽出獨立的組件模塊,支持開發者在開發中單獨集成使用,詳見業務組件設計章節。

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

三、行業場景技術方案
1. 賬單數據管理
1)場景說明
- 支持賬單、資產數據本地存儲和管理。
- 未對接云側時實現應用數據不丟失,僅在卸載后清空本地數據。
2)技術方案
- 應用ArkData關系型數據庫實現數據持久化。
2. 賬單圖表
1)場景說明
- 通過餅圖、排行榜、柱狀圖、報表的形式呈現當月賬單的數據分析。
- 通過日歷視圖呈現每日收支詳情。

2)技術方案
- 使用開源三方庫@ohos/mpchart呈現多類型圖表
- 使用開源三方庫lunar實現農歷日期、節假日數據的獲取,使用開源三方庫dayjs實現日期數據格式化。
- 使用Grid組件循環渲染實現日歷視圖的開發。
3. 動態卡片
1)場景說明
- 支持在桌面展示2\*2 和 2\*4大小的服務卡片,展示當前月的收支情況。
- 點擊記一筆拉起本模板應用主頁面,新增賬單后,在桌面同步刷新獲取最新的收支數據。

2)技術方案
- 通過Form Kit創建動態卡片。
- 通過commonEventManager公共事件管理實現卡片事件的注冊和實時通信。
四、模板代碼
1. 工程結構(下載模板)
詳細代碼結構如下所示:
MoneyTrack
|--commons // 公共能力層
| └--commonlib // 基礎能力包
| └--src/main
| |--ets
| | |--components // 公共組件
| | | |-- CommonButton.ets // 公共按鈕
| | | |-- CommonDivider.ets // 公共分割線
| | | |-- CommonHeader.ets // 公共標題欄
| | | |-- CommonMonthPicker.ets // 月份選擇
| | | |-- ContainerColumn.ets // 垂直卡片容器
| | | └-- ContainerRow.ets // 水平卡片容器
| | |--constants // 公共靜態變量
| | | |-- CommonConstants.ets // 公共常量
| | | └-- CommonEnums.ets // 公共枚舉
| | |
| | |--dialogs // 公共彈窗
| | | └-- CommonConfirmDialog.ets // 二次確認彈窗
| | |
| | └--utils // 公共方法
| | |-- eventbus // 全局事件管理
| | |-- framework // 全局框架管理
| | |-- logger // 日志
| | |-- router // 路由
| | └-- window // 窗口
| |
| └-- resources/base/element
| |-- color.json // 全局顏色
| |-- font.json // 全局字號
| └-- style.json // 全局樣式
|
|--components // 可分可合組件包
| |-- asset_base // 資產通用基礎包
| |-- asset_card // 資產卡片
| |-- asset_manage // 資產管理
| |-- bill_base // 賬單通用基礎包
| |-- bill_card // 賬單卡片
| |-- bill_chart // 賬單圖表
| |-- bill_data_processing // 賬單數據處理
| └-- bill_manage // 賬單管理
|
|--features // 基礎特性層
| |-- assets // 資產
| | └--src/main/ets/views
| | |--AssetDetailPage.ets // 資產詳情頁
| | └--AssetsView.ets // 資產頁
| |-- home // 首頁明細
| | └--src/main/ets/views
| | |--BillDetailPage.ets // 賬單詳情頁
| | └--HomeView.ets // 首頁
| └-- statistics // 統計
| └--src/main/ets/views
| |--BillByResourceView.ets // 分類賬單詳情
| └--StatisticsView.ets // 統計頁
└--products // 設備入口層
└-- entry
└--src/main/ets
|-- pages
| └-- MainEntry.ets // 主入口
└-- widgets
|-- MiddleCard.ets // 2*4中號卡片
└-- MiniCard.ets // 2*2小號卡片
2. 關鍵代碼解讀
本篇代碼非應用的全量代碼,只包括應用的部分能力的關鍵代碼。
1)賬單數據管理
- 封裝通用數據庫類
ts
// MoneyTrack/components/bill_data_processing/src/main/ets/utils/basedb/BaseDB.ets
const TAG = '[BaseDB]';
// 基礎數據庫操作類
export abstract class BaseDB {
protected rdbStore: relationalStore.RdbStore | null = null;
protected abstract dbConfig: relationalStore.StoreConfig;
protected abstract tableSchemas: TableSchema[];
// 初始化數據庫
public async initialize(context: Context) {
try {
this.rdbStore = await relationalStore.getRdbStore(context, this.dbConfig);
await this._createTables();
Logger.info(TAG, `[${this.dbConfig.name}] database initialized success`);
} catch (err) {
Logger.error(
TAG,
`database initialized failed. error: ${JSON.stringify(err)}`,
);
}
}
// 創建表結構
private async _createTables() {
if (!this.rdbStore) {
return;
}
try {
for (const schema of this.tableSchemas) {
await this.rdbStore.executeSql(schema.createSQL);
if (schema.indexes) {
for (const indexSQL of schema.indexes) {
await this.rdbStore.executeSql(indexSQL);
}
}
}
} catch (err) {
Logger.error(TAG, `create table failed. error: ${JSON.stringify(err)}`);
}
}
// 通用插入方法
protected async insert<T>(tableName: string, values: T): Promise<number> {...}
// 通用更新方法
protected async update<T>(
tableName: string,
values: T,
conditions: TablePredicateParams[],
): Promise<number> {...}
// 通用刪除方法
protected async delete(
tableName: string,
conditions: TablePredicateParams[],
): Promise<number> {...}
// 通用查詢方法
protected async query<T>(
tableName: string,
conditions: TablePredicateParams[],
orderBy?: TableOrderByParams,
limit?: number,
): Promise<T[]> {...}
}
- 創建賬單表
ts
// MoneyTrack/components/bill_data_processing/src/main/ets/utils/accountingdb/AccountingDB.ets
const TAG = '[AccountingDB]';
class AccountingDB extends BaseDB {
protected dbConfig: relationalStore.StoreConfig =
AccountingDBConstants.DB_CONFIG;
protected tableSchemas: TableSchema[] = [
{
tableName: AccountingDBConstants.ACCOUNT_TABLE_NAME,
createSQL: AccountingDBConstants.ACCOUNT_TABLE_SQL_CREATE,
indexes: AccountingDBConstants.ACCOUNT_TABLE_INDEXES_CREATE,
},
{
tableName: AccountingDBConstants.TRANSACTION_TABLE_NAME,
createSQL: AccountingDBConstants.TRANSACTION_TABLE_SQL_CREATE,
indexes: AccountingDBConstants.TRANSACTION_TABLE_INDEXES_CREATE,
},
{
tableName: AccountingDBConstants.ASSET_TABLE_NAME,
createSQL: AccountingDBConstants.ASSET_TABLE_SQL_CREATE,
indexes: AccountingDBConstants.ASSET_TABLE_INDEXES_CREATE,
},
];
public async initialize(context: Context) {
await super.initialize(context);
await this._initDefaultAccounts();
}
// 初始化賬本
private async _initDefaultAccounts() {
const accountTable: AccountTableBasis = {
accountId: AccountID.DEFAULT,
name: '默認賬本',
type: 'default',
};
const existing = await this.query<Account>(
AccountingDBConstants.ACCOUNT_TABLE_NAME,
[
{
field: AccountTableFields.NAME,
operator: DBOperator.EQUAL,
value: accountTable.name,
},
{
field: AccountTableFields.TYPE,
operator: DBOperator.EQUAL,
value: accountTable.type,
},
],
);
if (existing.length === 0) {
await this.insert(AccountingDBConstants.ACCOUNT_TABLE_NAME, accountTable);
Logger.info(TAG, 'create account table success');
}
}
// 新增交易記錄
public async addTransaction(userTx: UserTransaction): Promise<void> {
const tx: TransactionTableBasis = {
transactionId: new Date().getTime(),
accountId: userTx.accountId,
type: userTx.type,
resource: userTx.resource,
amount: userTx.amount,
date: userTx.date,
note: userTx.note,
excluded: userTx.excluded,
assetId: userTx.assetId,
};
return this.transaction(async () => {
try {
await this.insert(AccountingDBConstants.TRANSACTION_TABLE_NAME, tx);
promptAction.showToast({ message: '交易記錄新增成功~' });
await this.updateAssetAccountFromTransaction(userTx);
Logger.info(TAG, 'insert transaction success.');
} catch (err) {
promptAction.showToast({ message: '交易記錄新增失敗,請稍后重試~' });
Logger.error(
TAG,
'insert transaction failed. error:' + JSON.stringify(err),
);
}
});
}
// ...
}
const accountingDB = new AccountingDB();
export { accountingDB as AccountingDB };
2)動態卡片
- 封裝卡片事件工具
ts
// MoneyTrack/products/entry/src/main/ets/common/WidgetUtil.ets
import { preferences } from '@kit.ArkData';
import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
import { formBindingData, formProvider } from '@kit.FormKit';
import { AmountSummary, BillProcessingModel } from 'bill_data_processing';
import { Logger } from 'commonlib';
const TAG = '[WidgetUtil]';
export class WidgetUtil {
private static readonly _fileName: string = 'accounting_form_id_file';
private static readonly _formIdKey: string = 'accounting_form_id_key';
private static readonly _formIdEventName: string = 'form_id_event_name';
private static _billProcessing: BillProcessingModel =
new BillProcessingModel();
public static getFormIds(ctx: Context) {
const store = WidgetUtil._getStore(ctx);
return store.getSync(WidgetUtil._formIdKey, []) as string[];
}
public static async addFormId(formId: string, cxt: Context) {
const list = WidgetUtil.getFormIds(cxt);
if (!list.some((id) => id === formId)) {
list.push(formId);
const store = WidgetUtil._getStore(cxt);
store.putSync(WidgetUtil._formIdKey, list);
await store.flush();
}
}
public static async delFormId(formId: string, cxt: Context) {
const list = WidgetUtil.getFormIds(cxt);
const index = list.findIndex((id) => id === formId);
if (index !== -1) {
list.splice(index, 1);
const store = WidgetUtil._getStore(cxt);
store.putSync(WidgetUtil._formIdKey, list);
await store.flush();
}
}
// 發布公共事件跨進程傳遞卡片id
public static publishFormId(formId: string, isDelete: boolean) {
commonEventManager.publish(
WidgetUtil._formIdEventName,
{ data: formId, parameters: { isDelete } },
(err: BusinessError) => {
if (err) {
Logger.error(
TAG,
`Failed to publish common event. Code is ${err.code}, message is ${err.message}`,
);
} else {
Logger.info(TAG, 'Succeeded in publishing common event.');
}
},
);
}
// 訂閱獲取卡片id
public static async subscribeFormId(ctx: Context) {
let subscriber: commonEventManager.CommonEventSubscriber | undefined =
undefined;
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: [WidgetUtil._formIdEventName],
publisherPermission: '',
};
commonEventManager.createSubscriber(subscribeInfo, (err1, data1) => {
if (err1) {
Logger.error(
TAG,
`Failed to create subscriber. Code is ${err1.code}, message is ${err1.message}`,
);
return;
}
subscriber = data1;
// 訂閱公共事件回調
commonEventManager.subscribe(subscriber, async (err2, data2) => {
if (err2) {
Logger.error(
TAG,
`Failed to subscribe common event. Code is ${err2.code}, message is ${err2.message}`,
);
return;
} else {
if (data2.parameters?.isDelete) {
WidgetUtil.delFormId(data2.data as string, ctx);
} else {
WidgetUtil.addFormId(data2.data as string, ctx);
WidgetUtil.updateWidgetsWhenChange();
}
Logger.info(TAG, 'Succeeded in creating subscriber1.');
}
});
});
}
public static async updateWidgetsWhenChange() {
await WidgetUtil._billProcessing.getBillReport();
const summary: AmountSummary = {
totalExpense: Number(WidgetUtil._billProcessing.totalExpense),
totalIncome: Number(WidgetUtil._billProcessing.totalIncome),
};
WidgetUtil.getFormIds(getContext()).forEach((id) => {
const income = summary.totalIncome;
const expense = summary.totalExpense;
class TempForm {
date: Date = new Date();
income: number = 0;
expense: number = 0;
}
const formData: TempForm = {
date: new Date(),
income,
expense,
};
formProvider.updateForm(
id,
formBindingData.createFormBindingData(formData),
);
});
}
private static _getStore(ctx: Context) {
return preferences.getPreferencesSync(ctx, { name: WidgetUtil._fileName });
}
}
- 在EntryFormAbility中的生命周期進行事件管理
ts
// MoneyTrack/products/entry/src/main/ets/entryformability/EntryFormAbility.ets
import { Want } from '@kit.AbilityKit';
import { emitter } from '@kit.BasicServicesKit';
import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { WidgetUtil } from '../common/WidgetUtil';
export default class EntryFormAbility extends FormExtensionAbility {
public onAddForm(want: Want) {
let formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string | undefined;
if (formId) {
WidgetUtil.addFormId(formId, this.context);
WidgetUtil.publishFormId(formId, false);
}
return formBindingData.createFormBindingData('');
}
public onUpdateForm() {
emitter.emit({ eventId: 1 });
}
public onRemoveForm(formId: string) {
WidgetUtil.delFormId(formId, this.context);
WidgetUtil.publishFormId(formId, true);
}
}
以上代碼展示了商務筆記應用的核心功能實現,包括多選管理、富文本編輯、分類管理和響應式布局等關鍵技術方案。
3. 模板集成
本模板提供了兩種代碼集成方式,供開發者自由選用。
1)整體集成(下載模板)
開發者可以選擇直接基于模板工程開發自己的應用工程。
- 模板代碼獲取:
- 打開模板工程,根據README說明中的快速入門章節,將自己的應用信息配置在模板工程內,即可運行并查看模板效果。

- 對接開發者自己的服務器接口,轉換數據結構,展示真實的云側數據。
將commons/lib_common/src/main/ets/httprequest/HttpRequestApi.ets文件中的mock接口替換為真實的服務器接口。

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

根據自己的業務內容修改模板,進行定制化開發。
2)按需集成
若開發者已搭建好自己的應用工程,但暫未實現其中的部分場景能力,可以選擇取用其中的業務組件,集成在自己的工程中。

- 根據API參考和示例代碼,將組件集成在自己的對應場景中。
以上是第7期“金融理財-記賬應用”行業案例的內容,更多行業敬請期待~
歡迎下載使用行業模板“點擊下載”,若您有體驗和開發問題,或者迫不及待想了解XX行業的優秀案例,歡迎在評論區留言,小編會快馬加鞭為您解答~
同時誠邀您添加下方二維碼加入“組件模板開發者社群”,精彩上新&活動不錯過!

?? HarmonyOS官方模板優秀案例系列持續更新, 點擊查看往期案例匯總貼, 點擊收藏 “
”方便查找!
??【互動有禮】邀請你成為HarmonyOS官方模板產品經理,優化方案由你制定!點擊參加

鴻蒙生態為開發者提供海量的HarmonyOS模板/組件,助力開發效率原地起飛。一鍵直達生態市場組件&模板市場 , 快速應用DevEco Studio插件市場集成組件&模板。實戰分享:如何基于模板快速開發一款記賬應用?本期案例為您解答。
浙公網安備 33010602011771號