一个面向 鸿蒙 PC(HarmonyOS NEXT / 2in1) 的开源示例应用:导入签到结果 → 自动检查重复记录 → 统计参与情况 → 导出汇总报表。
全程本地处理,数据不出设备。
活动签到最常见的三个坑,这个应用一次处理掉:
| 坑 | 表现 | 本应用的做法 |
|---|---|---|
| 重复导入 | 同一份签到文件导入两次,或扫码后手工又补录一次,同一个人在同一秒出现多条 | 自动识别「完全重复」(同人+同活动+同时间),标红并给出清理建议 |
| 多次签到 | 一天里进出会场多次,或签退被当成签到记进来 | 标为「多次签到」提醒人工确认,不直接当成有效出席 |
| 脏数据 | 姓名缺了、时间写成「待定」、活动 ID 是空的 | 单独归为「数据异常」,保留行号方便回原表修正 |
同时给出数据质量分和出席率,让组织者一眼判断"这份数据能不能直接用"。
- 粘贴 CSV 文本即可导入,列名自动识别(含常见别名)
- 表头别名兼容:
活动ID/活动编号/场次ID;参与者ID/工号/学号/手机号;签到时间/打卡时间/创建时间 - 时间格式兼容:
2026-10-14 09:30:00、2026/10/14 9:30、2026年10月14日 09:30、10/13 位时间戳 - 分隔符自动嗅探:逗号 / 分号 / Tab / 竖线
- 正确处理引号转义、字段内换行、UTF-8 BOM
- 内置两套演示数据(12 行迷你示例 / 379 行完整数据),一键加载即可体验
三级判重规则,逐条可追溯到原始行号:
| 类型 | 判定条件 | 建议处置 |
|---|---|---|
| 完全重复 | 同一人 + 同一活动 + 同一时间 | 直接清理 |
| 多次签到 | 同一人 + 同一活动 + 不同时间 | 人工确认是否有效出席 |
| 数据异常 | 关键字段缺失 / 时间无法解析 | 回原表修正 |
每个「人 + 活动」组合保留时间最早的一条作为有效出席,因此重复记录不会污染实到人数。
- 活动维度:应到 / 实到 / 出席率 / 签到条数 / 重复数 / 异常数
- 参与者维度:跨活动累计有效签到次数、首次与末次签到时间、重复条数
- 签到时间分布:按小时聚合的柱状图,一眼看出签到高峰与异常集中的批量导入
三类 CSV,带 UTF-8 BOM,Excel 双击直接打开不乱码:
参与明细_时间戳.csv—— 每位参与者的有效签到次数、重复条数、时间区间活动汇总_时间戳.csv—— 每场活动的实到/应到与出席率重复记录清单_时间戳.csv—— 逐条列出重复记录、原始行号与处置建议
优先写入应用沙箱的 Download 目录(用户可在「文件管理」中直接找到);失败时自动落到沙箱 files/exports,路径在界面上可一键复制。
本应用不申请任何动态权限,因此不存在「拒绝权限后导出不可用」的情况。
五个页签,PC 宽屏与窄屏自适应(断点 840vp):
┌──────────────────────────────────────────────────────────┐
│ 活动签到与参与统计 [加载演示数据] [导入签到结果] │
├──────────────────────────────────────────────────────────┤
│ 数据源:当前 379 条原始记录 · 3 场活动 · 118 位参与者 │
├──────────────────────────────────────────────────────────┤
│ 总览 │ 签到明细 │ 重复检查 │ 参与情况 │ 导出汇总 │
├──────────────────────────────────────────────────────────┤
│ ┌─ 指标卡 ─┬─ 指标卡 ─┬─ 指标卡 ─┬─ 指标卡 ─┐ │
│ ├─ 数据质量检查 ────────┬─ 活动出席情况 ────┤ │
│ ├─ 签到时间分布 ─────────────────────────────┤ │
└──────────────────────────────────────────────────────────┘
| 项目 | 版本 |
|---|---|
| DevEco Studio | 5.0.0 及以上(支持 2in1 设备) |
| HarmonyOS SDK | API 12(5.0.0) |
| 目标设备 | 2in1(鸿蒙 PC)/ 平板 / 手机 |
工程已声明 deviceTypes: ["2in1", "tablet", "phone"],鸿蒙 PC 上安装即为桌面窗口应用。
git clone https://github.com/Hunshow/harmony-checkin.git
cd harmony-checkin同一仓库镜像(开放原子 AtomGit,国内访问更快):
git clone https://atomgit.com/gcw_husamPlM/harmony-checkin.git用 DevEco Studio 打开工程根目录(含 oh-package.json5 的那一层),等待 Sync 完成。
图标已随仓库提供。如需重新生成(或改成自己的配色):
node tools/gen-icons.mjs脚本手写 PNG 编码,不依赖任何图形库,在纯 Node 环境即可运行。
- 连接鸿蒙 PC / 模拟器,点击 Run 'entry'
- 首次运行建议先点「加载演示数据」,可立即看到完整统计效果
AppScope/app.json5 里的 bundleName 默认是 com.example.activitycheckin,
改成你自己的域名倒写形式(如 com.yourname.checkin),避免与他人冲突。
改完后在 DevEco Studio 里重新 Sync 一次即可。
harmony-checkin/
├── AppScope/
│ ├── app.json5 # 应用级配置(包名/版本/图标)
│ └── resources/base/
│ ├── element/string.json # 应用名称
│ └── media/app_icon.png # 应用图标(PNG 由脚本生成)
├── hvigor/
│ └── hvigor-config.json5 # hvigor 构建配置(modelVersion 需与 oh-package.json5 一致)
├── entry/
│ ├── build-profile.json5
│ ├── oh-package.json5
│ └── src/main/
│ ├── module.json5 # 模块配置(设备类型/权限/Ability)
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 应用入口,处理 PC 窗口尺寸
│ │ ├── model/
│ │ │ └── Types.ets # 领域模型定义
│ │ ├── utils/
│ │ │ ├── CsvParser.ets # CSV 解析 + 列名别名 + 时间解析
│ │ │ ├── Statistics.ets # 判重与统计引擎(纯函数)
│ │ │ ├── Exporter.ets # CSV 导出
│ │ │ ├── MockData.ets # 演示数据生成(固定种子,可复现)
│ │ │ └── Logger.ets # 日志
│ │ ├── common/
│ │ │ ├── Tokens.ets # 设计令牌(颜色/间距/字号)
│ │ │ └── AppStore.ets # 全局状态
│ │ ├── viewmodel/
│ │ │ ├── AppData.ets # 可观测状态
│ │ │ └── AppActions.ets # 业务动作编排
│ │ ├── components/ # 纯展示组件
│ │ │ ├── SectionCard.ets
│ │ │ ├── StatTile.ets
│ │ │ ├── ActivityRow.ets
│ │ │ ├── RecordTable.ets
│ │ │ ├── ParticipantList.ets
│ │ │ ├── QualityPanel.ets
│ │ │ └── TimeDistributionChart.ets
│ │ └── pages/
│ │ └── Index.ets # 主页面(页签 + 布局)
│ └── resources/ # 字符串/颜色/多语言
├── docs/
│ ├── sample-checkin.csv # 379 行示例数据(可直接导入)
│ └── baseline.txt # 示例数据的统计基线
└── tools/
├── gen-icons.mjs # 生成图标 PNG
└── verify.mjs # 核心算法自检(30 条断言)
pages/Index ← 只负责布局与事件绑定
↓
viewmodel/ ← 状态(AppData)+ 动作(AppActions)
↓
utils/Statistics ← 纯函数统计引擎(无 UI 依赖,可独立测试)
utils/CsvParser ← 纯函数解析器
↓
model/Types ← 纯数据定义
统计引擎是纯函数:analyze(records, roster?) → AnalysisResult,不碰任何系统能力。
所以它可以脱离设备、在 Node 里跑测试 —— 这正是 tools/verify.mjs 能成立的原因。
node tools/verify.mjs会在控制台输出结果,同时写入 verify-report.txt(UTF-8,避免 Windows 控制台中文乱码)。
覆盖 30 条断言,包括:
- CSV 解析:别名识别、分隔符嗅探、引号转义、字段内换行、BOM、空行
- 时间解析:多种格式一致性、非法值、时间戳
- 判重:完全重复 / 多次签到 / 异常的边界,同一秒多条,时间乱序
- 统计:实名去重口径、出席率计算、跨活动累计
- 鲁棒性:空输入、只有表头、2000 行以上的大数据集、结果可复现
tools/verify.mjs是Statistics.ets/CsvParser.ets的 JS 同构移植。 修改 ArkTS 侧算法后,请同步更新该文件,否则验证会失真。
对 docs/sample-checkin.csv(120 人名册 × 3 场活动)的统计结果:
数据行(不含表头): 379
完全重复: 34
多次签到: 16
异常: 9
ACT-2026-001 名册 120 人 / 出席 111 人 / 出席率 92.5%
ACT-2026-002 名册 120 人 / 出席 90 人 / 出席率 75.0%
ACT-2026-003 名册 120 人 / 出席 119 人 / 出席率 99.2%
这份数据刻意埋入了三类脏数据,适合用来验证你的统计口径是否正确。
最小可用格式(前两列必填):
活动ID,参与者ID,姓名,部门,签到时间,签到方式
ACT-2026-001,E1001,王伟,研发部,2026-10-14 09:05:12,扫码签到| 字段 | 必填 | 说明 | 可识别的列名 |
|---|---|---|---|
| 活动 ID | 是 | 唯一标识一场活动 | 活动ID、活动编号、场次ID、会议ID |
| 参与者 ID | 是 | 稳定唯一标识(工号/学号/手机号) | 参与者ID、工号、学号、手机号、用户ID |
| 姓名 | 否 | 展示用 | 姓名、参与者姓名、员工姓名 |
| 部门/分组 | 否 | 分组统计用 | 部门、班级、分组、所属部门 |
| 签到时间 | 否 | 用于判重与排序,缺失记为异常 | 签到时间、打卡时间、创建时间 |
| 签到方式 | 否 | 渠道统计用 | 签到方式、渠道、来源 |
为什么参与者 ID 必须稳定唯一? 判重的核心是「同一个人」的识别。如果 ID 是每次签到随机生成的流水号,同一人就无法被聚合, 重复检测会失效。建议用工号、学号或手机号。
理解这几个定义,才能看懂报表数字:
实到人数 = 同一活动下,去重后的唯一参与者数量
张三签到了 3 次,实到人数只 +1。这是「出席人数」而不是「签到次数」。
有效出席记录 = 每个「人 + 活动」组合中时间最早的那一条
后续的重复记录被标记但不重复计数,避免虚高。
出席率 = 实到人数 / 名册人数
未导入名册时显示「未提供名册」,而不是显示 0% —— 0% 会被误读成"没人来"。
数据质量分(0~100)= 100 − 扣分占比 × 100
扣分权重:异常记录 ×10、完全重复 ×3、多次签到 ×1。 分数低不代表程序出错,而是提示这份数据需要先清洗。
- 所有解析、统计、导出均在本机内存与沙箱中完成
- 不发起任何网络请求,不上传任何数据
- 导出的 CSV 只在用户主动点击导出时写入本地文件
- 不申请任何权限:导出走应用自己的沙箱 Download 目录,无需授权即可工作
| 想做什么 | 改哪里 |
|---|---|
| 接入新签到系统的列名 | CsvParser.ets 的 COLUMN_ALIASES |
| 调整判重规则 | Statistics.ets 的 analyze() 第二趟循环 |
| 改质量分权重 | Statistics.ets 的 calcQualityScore() |
| 加新的导出报表 | Statistics.ets 加 buildXxxCsvRows,Index.ets 的 exportTab 加卡片 |
| 换主题配色 | common/Tokens.ets(UI 层禁止硬编码色值) |
| 接真实数据源(HTTP / 数据库) | 在 AppActions.importFromText 前加一个拉取函数,复用下游全部逻辑 |
- 名册目前由演示数据内置生成,尚未提供名册导入 UI(
AppActions.importFromText已预留roster参数) - 导出依赖系统「下载目录」权限,部分设备上会只落到应用沙箱
- CSV 解析按 RFC 4180 实现,未支持
sep=声明式分隔符头 - 仅提供浅色主题(
EntryAbility中强制浅色,保证令牌一致性)
导入后数字和我手工数的不一样? 先看「重复检查」页签。你的原始文件里很可能有重复行,应用的实到人数是去重后的结果。
为什么有 34 条「完全重复」? 演示数据刻意注入了 10% 的重复率来展示查重能力。用你自己的数据时这个数字取决于导出方式。
能处理多大的文件? 解析与分析都是单趟线性复杂度。实测 2000+ 行无压力。首次跑起来后如需支持十万行级,建议改为分片解析。
签名怎么配?
build-profile.json5 的 signingConfigs 为空,DevEco Studio 首次运行时会引导你自动签名。
欢迎提 Issue 和 PR。提交前请确保:
node tools/verify.mjs全部通过- 若改了
Statistics.ets/CsvParser.ets,已同步更新tools/verify.mjs - 未引入新的第三方依赖(刻意保持零依赖,降低上手成本)