鸿蒙会议签到小程序代码分析报告

一、引言

随着鸿蒙操作系统(HarmonyOS)生态的快速发展,轻量化、原生适配的应用开发逐渐成为企业数字化工具的重要方向。本报告以 “鸿蒙会议签到小程序” 为分析对象,从开发工具、技术框架、项目结构、功能实现、代码逻辑等维度展开详细解析,旨在完整呈现该小程序的技术实现路径与业务价值,为同类鸿蒙应用开发提供参考。

二、开发工具和框架

2.1 开发工具

本项目采用鸿蒙官方指定的集成开发环境 DevEco Studio 5.0+,其核心能力包括:

  • 鸿蒙 SDK 支持:内置 API Level 12+(对应 HarmonyOS 3.0 及以上版本),提供 ArkTS、ArkUI 等原生开发工具链;
  • 实时预览功能:支持界面代码与设备 / 模拟器界面的实时同步,提升 UI 开发效率;
  • 调试工具链:包含日志打印、断点调试、性能分析等功能,支持真机 / 模拟器双环境调试;
  • 项目管理能力:自动生成鸿蒙应用标准目录结构,支持模块拆分、依赖管理等工程化需求。

2.2 核心技术框架

本小程序基于鸿蒙原生技术栈开发,核心框架包括:

  • ArkTS 语言:TypeScript 的超集,支持静态类型检查、装饰器语法、面向对象编程等特性,是鸿蒙应用开发的首选语言;
  • ArkUI 框架(声明式范式):鸿蒙官方 UI 开发框架,采用 “数据驱动 UI” 的设计理念,通过组件化、布局系统、状态管理实现界面快速构建;
  • 鸿蒙系统 API:涵盖存储、权限、系统时间、路由等能力,是应用与系统交互的核心接口;
  • 关系型数据库(RelationalStore):鸿蒙原生本地数据库方案,基于 SQLite 封装,支持结构化数据的 CRUD 操作。

2.3 技术栈优势

  1. 原生适配性:ArkUI 与鸿蒙系统深度绑定,相比 H5 等跨端方案,在启动速度、运行流畅度、系统能力调用上更具优势;
  1. 开发效率:声明式语法 + 组件化思想,大幅减少 UI 代码量,同时支持组件复用;
  1. 轻量性:依赖本地存储而非服务端,适合线下会议等无网络 / 弱网络场景;
  1. 多终端兼容:通过 ArkUI 的自适应布局与鸿蒙分布式能力,可快速扩展至平板、智慧屏等终端。

三、项目介绍

3.1 项目结构分析

从截图中的项目目录(左侧文件树)可知,本项目遵循鸿蒙应用的标准工程结构,核心目录如下:

目录 / 文件

功能说明

entry

应用主模块(鸿蒙应用的入口模块),包含代码、资源、配置等核心内容

entry/src/main/ets

代码根目录,采用 TypeScript/ArkTS 编写业务逻辑与界面代码

entry/src/main/ets/entryability

应用入口能力(EntryAbility),负责应用启动、生命周期管理

entry/src/main/ets/pages

页面目录,包含 4 个核心页面(登录页、签到页、签到成功页、统计页)的代码

entry/src/main/ets/components

通用组件目录(如签到按钮、统计卡片等可复用组件)

entry/src/main/ets/model

数据模型目录,定义用户、签到记录等数据结构

entry/src/main/ets/service

服务层目录,封装数据存储、业务逻辑等通用能力

entry/src/main/resources

资源目录,包含字符串、图片、布局等静态资源

entry/src/main/module.json5

模块配置文件,声明应用权限、页面路由、能力配置等

3.2 核心配置文件解析

以module.json5为例,其核心配置项如下:


{

"module": {

"name": "entry",

"type": "entry",

"description": "会议签到小程序主模块",

"mainElement": "EntryAbility",

"deviceTypes": ["phone"], // 优先适配手机端

"pages": [ // 页面路由配置

"pages/LoginPage", // 登录/会议选择页

"pages/SignInPage", // 签到页

"pages/SignSuccessPage", // 签到成功页

"pages/AdminStatsPage" // 管理员统计页

],

"abilities": [

{

"name": "EntryAbility",

"srcEntry": "./ets/entryability/EntryAbility.ets",

"description": "应用入口能力",

"icon": "$media:icon",

"label": "会议签到系统",

"startWindowIcon": "$media:icon",

"startWindowBackground": "$color:start_window_background"

}

],

"requestPermissions": [ // 权限申请

{

"name": "ohos.permission.WRITE_USER_STORAGE", // 存储权限

"reason": "需要存储签到记录",

"usedScene": {

"when": "always"

}

}

]

}

}

该文件定义了应用的入口、页面路由、运行设备、权限需求等核心信息,是鸿蒙应用的 “配置中枢”。

3.3 入口能力(EntryAbility)分析

entryability/EntryAbility.ets是应用的启动入口,负责应用生命周期管理,核心代码逻辑如下:


import Ability from '@ohos.app.ability.UIAbility';

import hilog from '@ohos.hilog';

import window from '@ohos.window';

export default class EntryAbility extends Ability {

onCreate(want, launchParam) {

// 应用创建时执行:初始化数据存储、加载配置

hilog.info(0x0000, 'EntryAbility', 'Application onCreate');

this.initDatabase(); // 初始化本地数据库

}

onWindowStageCreate(windowStage: window.WindowStage) {

// 窗口创建时执行:加载首页

hilog.info(0x0000, 'EntryAbility', 'WindowStage onCreate');

windowStage.loadContent('pages/LoginPage', (err, data) => {

if (err.code) {

hilog.error(0x0000, 'EntryAbility', 'Failed to load content. Cause: %{public}s', JSON.stringify(err));

return;

}

});

}

onDestroy() {

// 应用销毁时执行:释放资源

hilog.info(0x0000, 'EntryAbility', 'Application onDestroy');

}

private initDatabase() {

// 初始化签到记录数据库

// 调用RelationalStore API创建表结构

}

}

其核心作用是:应用启动时初始化资源(如数据库),创建窗口并加载首页,管理应用的生命周期状态。

四、功能介绍(代码级实现)

本小程序包含 “登录 / 会议选择、用户签到、签到成功反馈、管理员统计” 四大功能模块,以下从界面代码、业务逻辑、数据交互三个维度解析每个功能的实现。

4.1 登录 / 会议选择页

4.1.1 界面实现(pages/LoginPage.ets)

该页面负责角色选择、账户输入、会议类型选择,核心代码如下:


@Entry

@Component

struct LoginPage {

// 状态管理:角色(0=职工,1=管理员)、账户、会议类型

@State role: number = 0;

@State account: string = '';

@State meetingType: string = '';

build() {

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, padding: 20 }) {

// 标题

Text('会议签到系统')

.fontSize(30)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 40 });

// 会议类型选择

Text('选择会议类型')

.fontSize(16)

.margin({ bottom: 10, alignSelf: ItemAlign.Start });

TextInput({ placeholder: '输入会议类型' })

.width('100%')

.height(40)

.border({ width: 1, radius: 5, color: '#E5E5E5' })

.margin({ bottom: 20 })

.onChange((value) => this.meetingType = value);

// 角色选择

Flex({ justifyContent: FlexAlign.SpaceAround, width: '100%', margin: { bottom: 20 } }) {

Radio({ value: '0', group: 'role' })

.checked(true)

.onChange((isChecked) => { if (isChecked) this.role = 0; })

.suffixText('职工');

Radio({ value: '1', group: 'role' })

.onChange((isChecked) => { if (isChecked) this.role = 1; })

.suffixText('管理员');

}

// 账户输入

TextInput({ placeholder: '输入账户' })

.width('100%')

.height(40)

.border({ width: 1, radius: 5, color: '#E5E5E5' })

.margin({ bottom: 20 })

.onChange((value) => this.account = value);

// 确定按钮

Button('确定')

.width('100%')

.height(45)

.backgroundColor('#1677FF')

.fontColor('#FFFFFF')

.borderRadius(5)

.onClick(() => {

// 校验输入

if (!this.account) {

promptAction.showToast({ message: '请输入账户' });

return;

}

// 保存角色与账户,跳转至签到页

AppStorage.SetOrCreate('role', this.role);

AppStorage.SetOrCreate('account', this.account);

AppStorage.SetOrCreate('meetingType', this.meetingType);

router.pushUrl({ url: 'pages/SignInPage' });

});

}

.width('100%')

.height('100%');

}

}

代码逻辑说明

  • 采用@Entry+@Component装饰器定义页面组件;
  • 通过@State装饰器管理页面状态(角色、账户等),状态变化会自动触发 UI 更新;
  • 利用Flex布局实现界面的垂直居中与元素排列;
  • 点击 “确定” 按钮时,先校验输入合法性,再通过AppStorage存储用户信息,最后调用router.pushUrl跳转到签到页。
4.1.2 数据存储逻辑

用户信息(角色、账户)通过鸿蒙AppStorage实现跨页面共享:

  • AppStorage是鸿蒙提供的应用级状态管理工具,支持键值对存储,数据在应用生命周期内有效;
  • 后续页面可通过AppStorage.Get('role')获取当前用户角色,实现权限控制。

4.2 用户签到页

4.2.1 界面实现(pages/SignInPage.ets)

该页面负责用户签到操作,核心代码如下:


@Entry

@Component

struct SignInPage {

// 从AppStorage获取用户信息

@StorageProp('role') role: number = 0;

@StorageProp('account') account: string = '';

@StorageProp('meetingType') meetingType: string = '';

// 当前时间

@State currentTime: string = '';

build() {

Flex({ direction: FlexDirection.Column, padding: 20 }) {

// 头部导航

Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center, margin: { bottom: 30 } }) {

Text('返回')

.fontSize(16)

.onClick(() => router.back());

Text('欢迎,' + (this.role === 0 ? '职工' : '管理员'))

.fontSize(16)

.fontWeight(FontWeight.Bold);

}

// 页面标题

Text('用户签到')

.fontSize(24)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 10 });

Text('请点击下方按钮进行签到')

.fontSize(14)

.fontColor('#999999')

.margin({ bottom: 40 });

// 签到按钮

Button('签到')

.width('100%')

.height(50)

.backgroundColor('#36D399')

.fontColor('#FFFFFF')

.borderRadius(5)

.onClick(() => this.handleSignIn());

// 签到时间

Text('当前时间:' + this.currentTime)

.fontSize(12)

.fontColor('#999999')

.margin({ top: 20 });

}

.width('100%')

.height('100%');

}

// 页面加载时获取当前时间

aboutToAppear() {

this.updateCurrentTime();

// 每秒更新时间

setInterval(() => this.updateCurrentTime(), 1000);

}

// 更新当前时间

private updateCurrentTime() {

const date = new Date();

this.currentTime = date.toLocaleString('zh-CN', {

year: 'numeric', month: '2-digit', day: '2-digit',

hour: '2-digit', minute: '2-digit', second: '2-digit'

});

}

// 处理签到逻辑

private async handleSignIn() {

try {

// 1. 获取当前时间

const signTime = this.currentTime;

// 2. 构造签到记录

const signRecord = {

account: this.account,

role: this.role,

meetingType: this.meetingType,

signTime: signTime,

status: 1 // 1=已签到

};

// 3. 写入本地数据库

await SignInService.addSignRecord(signRecord);

// 4. 跳转至签到成功页,并传递签到信息

router.pushUrl({

url: 'pages/SignSuccessPage',

params: { signRecord: signRecord }

});

} catch (err) {

hilog.error(0x0000, 'SignInPage', 'Sign in failed: %{public}s', JSON.stringify(err));

promptAction.showToast({ message: '签到失败,请重试' });

}

}

}

代码逻辑说明

  • 通过@StorageProp装饰器从AppStorage中读取用户信息(角色、账户),实现跨页面数据共享;
  • aboutToAppear生命周期函数在页面加载前执行,用于初始化当前时间,并通过setInterval每秒更新;
  • 点击 “签到” 按钮时,调用handleSignIn方法:构造签到记录→调用服务层写入数据库→跳转至签到成功页;
  • 签到记录包含 “账户、角色、会议类型、签到时间、状态” 等字段,确保数据的完整性。
4.2.2 服务层逻辑(service/SignInService.ets)

服务层封装了签到记录的数据库操作,核心代码如下:


import relationalStore from '@ohos.data.relationalStore';

import hilog from '@ohos.hilog';

// 数据库名称

const DB_NAME = 'sign_in.db';

// 签到记录表名

const SIGN_TABLE_NAME = 'sign_records';

// 初始化数据库

let rdbStore: relationalStore.RdbStore | null = null;

async function initDatabase() {

if (rdbStore) return;

try {

const context = getContext(this) as common.UIAbilityContext;

rdbStore = await relationalStore.getRdbStore(context, {

name: DB_NAME,

securityLevel: relationalStore.SecurityLevel.S1

});

// 创建签到记录表

await rdbStore.executeSql(`

CREATE TABLE IF NOT EXISTS ${SIGN_TABLE_NAME} (

id INTEGER PRIMARY KEY AUTOINCREMENT,

account TEXT NOT NULL,

role INTEGER NOT NULL,

meetingType TEXT NOT NULL,

signTime TEXT NOT NULL,

status INTEGER NOT NULL

)

`);

hilog.info(0x0000, 'SignInService', 'Database initialized successfully');

} catch (err) {

hilog.error(0x0000, 'SignInService', 'Failed to initialize database: %{public}s', JSON.stringify(err));

throw err;

}

}

// 新增签到记录

export async function addSignRecord(record: any) {

await initDatabase();

if (!rdbStore) throw new Error('Database not initialized');

try {

const valuesBucket = {

account: record.account,

role: record.role,

meetingType: record.meetingType,

signTime: record.signTime,

status: record.status

};

const rowId = await rdbStore.insert(SIGN_TABLE_NAME, valuesBucket);

hilog.info(0x0000, 'SignInService', 'Sign record added, rowId: %{public}d', rowId);

return rowId;

} catch (err) {

hilog.error(0x0000, 'SignInService', 'Failed to add sign record: %{public}s', JSON.stringify(err));

throw err;

}

}

// 查询签到记录(管理员用)

export async function getSignRecords(meetingType: string) {

await initDatabase();

if (!rdbStore) throw new Error('Database not initialized');

try {

const predicates = new relationalStore.RdbPredicates(SIGN_TABLE_NAME);

predicates.equalTo('meetingType', meetingType);

const resultSet = await rdbStore.query(predicates);

// 解析结果集

const records: any[] = [];

while (resultSet.goToNextRow()) {

records.push({

id: resultSet.getLong(resultSet.getColumnIndex('id')),

account: resultSet.getString(resultSet.getColumnIndex('account')),

role: resultSet.getLong(resultSet.getColumnIndex('role')),

signTime: resultSet.getString(resultSet.getColumnIndex('signTime')),

status: resultSet.getLong(resultSet.getColumnIndex('status'))

});

}

resultSet.close();

return records;

} catch (err) {

hilog.error(0x0000, 'SignInService', 'Failed to get sign records: %{public}s', JSON.stringify(err));

throw err;

}

}

代码逻辑说明

  • initDatabase方法负责创建数据库与签到记录表,表结构包含 “id(主键自增)、account、role、meetingType、signTime、status” 等字段;
  • addSignRecord方法将签到记录转换为valuesBucket(键值对),通过rdbStore.insert写入数据库;
  • getSignRecords方法通过RdbPredicates构建查询条件(按会议类型筛选),从数据库中查询并返回签到记录,供管理员统计页使用。

4.3 签到成功页

4.3.1 界面实现(pages/SignSuccessPage.ets)

该页面负责展示签到成功的反馈信息,核心代码如下:


@Entry

@Component

struct SignSuccessPage {

// 从路由参数中获取签到记录

@State signRecord: any = {};

build() {

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, padding: 20 }) {

// 图标

Image($r('app.media.success_icon')) // 成功图标(需在resources/media中配置)

.width(80)

.height(80)

.margin({ bottom: 20 });

// 提示文字

Text('签到成功!')

.fontSize(24)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 10 });

// 签到信息

Text(`签到时间: ${this.signRecord.signTime}`)

.fontSize(16)

.margin({ bottom: 5 });

Text(`会议时间: ${this.signRecord.signTime}`) // 示例中会议时间与签到时间一致,实际可从会议配置中读取

.fontSize(16);

// 返回按钮(普通用户返回首页,管理员返回统计页)

Button(this.signRecord.role === 0 ? '返回首页' : '查看统计')

.width('80%')

.height(45)

.backgroundColor('#1677FF')

.fontColor('#FFFFFF')

.borderRadius(5)

.margin({ top: 40 })

.onClick(() => {

if (this.signRecord.role === 0) {

router.backTo({ url: 'pages/LoginPage' });

} else {

router.pushUrl({ url: 'pages/AdminStatsPage', params: { meetingType: this.signRecord.meetingType } });

}

});

}

.width('100%')

.height('100%');

}

// 页面加载时获取路由参数

aboutToAppear() {

const params = router.getParams();

if (params && params.signRecord) {

this.signRecord = params.signRecord;

}

}

}

代码逻辑说明

  • 通过router.getParams()获取从签到页传递的签到记录参数,并赋值给@State变量;
  • 界面展示 “签到时间、会议时间” 等信息,通过角色判断返回按钮的功能(普通用户返回首页,管理员跳转至统计页);
  • 依赖resources/media中的图标资源(如success_icon),需在资源目录中提前配置。

4.4 管理员统计页

4.4.1 界面实现(pages/AdminStatsPage.ets)

该页面负责展示签到统计数据与记录,核心代码如下:


@Entry

@Component

struct AdminStatsPage {

// 会议类型

@State meetingType: string = '';

// 签到统计(未签到/应到/已签到)

@State stats: { notSigned: number, total: number, signed: number } = { notSigned: 0, total: 0, signed: 0 };

// 签到记录列表

@State signRecords: any[] = [];

build() {

Flex({ direction: FlexDirection.Column, padding: 20 }) {

// 头部导航

Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center, margin: { bottom: 30 } }) {

Text('返回')

.fontSize(16)

.onClick(() => router.back());

Text('管理员界面')

.fontSize(16)

.fontWeight(FontWeight.Bold);

}

// 页面标题

Text('签到统计')

.fontSize(24)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 20 });

// 统计卡片

Flex({ justifyContent: FlexAlign.SpaceAround, width: '100%', margin: { bottom: 30 } }) {

[

{ label: '未签到', value: this.stats.notSigned, color: '#F56C6C' },

{ label: '应到', value: this.stats.total, color: '#409EFF' },

{ label: '已签到', value: this.stats.signed, color: '#36D399' }

].forEach(item => {

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {

Text(item.value.toString())

.fontSize(28)

.fontWeight(FontWeight.Bold)

.fontColor(item.color)

.margin({ bottom: 5 });

Text(item.label)

.fontSize(14)

.fontColor('#999999');

}

});

}

// 签到记录标题

Text('签到记录')

.fontSize(18)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 10, alignSelf: ItemAlign.Start });

// 签到记录列表

List({ space: 10 }) {

ForEach(this.signRecords, (item) => {

ListItem() {

Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center, padding: 15, border: { width: 1, radius: 5, color: '#E5E5E5' } }) {

Flex({ direction: FlexDirection.Column }) {

Text(item.account)

.fontSize(16)

.fontWeight(FontWeight.Bold);

Text(item.signTime)

.fontSize(12)

.fontColor('#999999');

}

Text(item.status === 1 ? '已签到' : '未签到')

.fontSize(14)

.fontColor(item.status === 1 ? '#36D399' : '#F56C6C');

}

}

});

}

.width('100%')

.flexGrow(1);

}

.width('100%')

.height('100%');

}

// 页面加载时查询数据

aboutToAppear() {

const params = router.getParams();

if (params && params.meetingType) {

this.meetingType = params.meetingType;

this.loadSignData();

}

}

// 加载签到数据(统计+记录)

private async loadSignData() {

try {

// 1. 查询当前会议的所有签到记录

const records = await SignInService.getSignRecords(this.meetingType);

this.signRecords = records;

// 2. 计算统计数据

const total = 10; // 示例中应到人数固定为10,实际可从会议配置中读取

const signed = records.filter(item => item.status === 1).length;

const notSigned = total - signed;

this.stats = { notSigned, total, signed };

} catch (err) {

hilog.error(0x0000, 'AdminStatsPage', 'Failed to load sign data: %{public}s', JSON.stringify(err));

promptAction.showToast({ message: '数据加载失败' });

}

}

}

代码逻辑说明

  • 页面加载时通过router.getParams()获取会议类型,调用loadSignData方法加载数据;
  • loadSignData方法从服务层获取签到记录,计算 “未签到 / 应到 / 已签到” 人数(示例中应到人数固定为 10,实际可从会议配置中读取);
  • 通过List+ForEach组件渲染签到记录列表,展示账户、签到时间、状态等信息;
  • 统计卡片采用循环渲染的方式,减少代码冗余,提升可维护性。

五、项目总结

本鸿蒙会议签到小程序是一款轻量化、实用化的企业工具,其代码实现符合鸿蒙应用开发规范,架构清晰、逻辑完整,充分体现了鸿蒙原生应用的开发特点。尽管存在功能与扩展性上的不足,但对于小型会议签到场景已能满足核心需求,是鸿蒙生态下轻量化应用开发的典型案例。

Logo

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

更多推荐