格物统计:在鸿蒙 PC 上原生重建一套统计分析工作台
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
开源地址(AtomGit 仓库):https://atomgit.com/OpenHarmonyPCDeveloper/ohos_PSPP.git
开源组织(AtomGit):https://atomgit.com/OpenHarmonyPCDeveloper
统计分析软件和数据库客户端一样,是科研、教学与业务分析每天都在用、却很少被当成「基础设施」的基础设施。本文分享「格物统计」(based on GNU PSPP):它在鸿蒙 PC 上用
ArkTS + ArkUI重建了整套图形界面与交互,把统计计算放进一个由 OHOS NDK 编译的 C++ native 内核,再通过 NAPI 把两端接起来。文章侧重技术实现——为什么不能直接编译上游、架构怎么分层、数据层与 NAPI 边界怎么设计、CSV / XLSX / SAV 怎么读写、图表与 PDF 怎么做到三端刻度一致,以及 388 项数值断言是怎么来的。
一、项目定位:原生重建,不是把 GTK 塞进壳里
上游 GNU PSPP 是一套成熟的统计分析软件,核心代码二十余万行、定义了一百四十多条命令。但它的运行时依赖很重:GLib、GTK、GSL、Cairo、libxml2 一个都不少。鸿蒙 PC 上没有 GTK 运行时,也没有现成的 GSL 交叉编译产物,把上游 C 源码直接搬过来编译这条路基本走不通。
所以这里没有走「翻译源码」或「套壳运行」的路线,而是做了一个明确的取舍:
- 界面与交互:用 ArkTS + ArkUI 全新实现,遵循桌面工作台的操作习惯;
- 统计内核:以上游的算法、公式、数据结构与文件格式定义为参照,用 C++ 重写为
libpsppcore.so; - 跨语言边界:只保留一个 NAPI 桥接层,负责参数解析、JSON 序列化与进程内数据集管理;
- 交付形态:所有东西打成一个 HAP,装上即用,不存在独立进程、HNP、终端命令或外部服务。
它不是「把 Linux 程序塞进鸿蒙壳里跑」,而是一个纯原生 GUI 应用:界面、事件循环、状态管理全部是 ArkUI 声明式范式,统计计算是 HAP 内的 native 库。默认不申请任何权限、不发起网络请求,数据只在本地读取与计算。
项目的四个目标:
- 平台补位:为鸿蒙 PC 提供一套可日常使用的图形化统计分析工具;
- 原生体验:桌面级三栏布局、菜单栏 / 工具栏 / 状态栏,键鼠优先,同时保持触控可用;
- 对齐核心流程:覆盖「数据导入 → 变量定义 → 数据转换 → 统计分析 → 图表 → 结果归档 → 报告导出」全链路;
- 结果可核验:统计数值由 native 内核真实计算,并与 numpy / scipy 参考值逐项对拍,不用 mock 数据。
| 层次 | 技术 |
|---|---|
| UI | ArkTS + ArkUI 声明式范式,pages / view / common 分层 |
| 领域逻辑 | ArkTS:文件选择、分页取数、分析参数、结果归档、导出 |
| 跨语言桥接 | NAPI(C++):统一导出函数 + JSON 字符串 + ArrayBuffer |
| 统计内核 | C++17:core / io / stats / math / syntax / output |
| 数值基础 | 自研 math/:矩统计、分布函数、矩阵、特征分解、聚类 |
| 构建 | DevEco Studio + hvigor;native 层 CMake + OHOS NDK 工具链 |
功能特性(主线):
- 数据管理:CSV / TXT / TSV / XLSX 导入,SAV 读写;单元格编辑、变量增删改、个案增删、排序、筛选;
- 数据转换:计算变量、重编码、自动重编码、计数、秩变换、分组聚合、按权重展开个案、分组分析,共 13 项;
- 统计分析:描述统计、频数、交叉表、相关、T 检验、方差分析、线性回归、二元 Logistic 回归、探索性分析、分组均值、信度、非参数检验、因子分析、聚类分析,共 19 项分析过程;
- 语法:核心命令解析与执行,GUI 与语法共用同一统计引擎;
- 图表:柱状图、折线图、饼图、直方图、散点图、箱线图;
- 输出:统一结果查看器,HTML / CSV / SVG / PNG / PDF 导出。
功能矩阵中列出的 44 项功能全部落地;统计内核的 388 项断言与 numpy / scipy 参考值对拍通过。
二、架构分层与工程结构
整个系统的分层很干净:ArkTS 管文件、界面与渲染,C++ 只管内存中的数据集与统计计算,NAPI 做桥。
┌──────────────────────────────────────────────────────────────┐
│ ArkTS / ArkUI 表现层 │
│ pages/ MainWindowPage —— 菜单栏 / 工具栏 / 导航 / 状态栏 │
│ view/ DataGrid · VariableView · AnalysisPanel · │
│ SyntaxEditor · OutputViewer · ChartRenderer │
│ common/ PsppCore · FileService · XlsxService/Parser · │
│ OutputExport · PdfExport · ChartMath · Theme │
└───────────────────────────┬──────────────────────────────────┘
│ NAPI(libpsppcore.so)
│ JSON 字符串 + ArrayBuffer
┌───────────────────────────▼──────────────────────────────────┐
│ Native 统计内核(entry/src/main/cpp) │
│ napi_init.cpp 参数解析 / JSON 序列化 / 全局 Dataset │
│ core/ Dataset / Variable:列式存储、筛选、排序 │
│ io/ data_io(CSV/TXT)· sav_io(.sav 读写) │
│ stats/ engine:15 类过程实现(19 个分析入口) │
│ math/ 矩统计 · 分布/特殊函数 · 矩阵 · 特征分解 · │
│ 回归 · Logistic · 因子 · 聚类 · 顺序统计 │
│ syntax/ 词法 / 递归下降 / 表达式求值 │
│ output/ AnalysisResult(Table[] + Chart[]) │
└──────────────────────────────────────────────────────────────┘
对应的工程目录:
PSPP/
├── entry/src/main/
│ ├── ets/ # ArkTS / ArkUI 表现层
│ │ ├── entryability/ # EntryAbility(PC 窗口适配)
│ │ ├── pages/ # MainWindowPage 主窗口
│ │ ├── view/ # 数据网格 / 变量视图 / 分析面板
│ │ │ # 语法编辑器 / 结果查看器 / 图表渲染
│ │ └── common/ # NAPI 封装 / 文件服务 / XLSX
│ │ # HTML、SVG、PDF 导出 / 主题 / 类型
│ └── cpp/ # C++ native 统计内核
│ ├── napi_init.cpp # 唯一的跨语言边界
│ ├── core/ # Dataset / Variable
│ ├── io/ # CSV、TXT、SAV
│ ├── math/ # 数值基础(替代 GSL)
│ ├── stats/ # 15 类过程实现(19 个入口)
│ ├── syntax/ # 语法解析与表达式求值
│ ├── output/ # 统一结果模型
│ └── CMakeLists.txt # libpsppcore.so
├── tests/ # 宿主端测试、测试数据与报告
├── third_party/ # 上游源码快照(仅存档,不参与构建)
├── LICENSE · SOURCE_NOTICE.md · THIRD_PARTY_NOTICES.md
└── README.md
当前源码规模大约是 1 万行 C++ native 内核 + 7.4 千行 ArkTS 表现层。这个比例说明一件事:统计软件的工作量并不只在「页面上有几个按钮」,大量成本在数值算法的正确性、文件格式的兼容性和跨语言数据边界上。
这条路线最大的好处是双向隔离:
- native 内核不依赖任何 OHOS 平台 API,只依赖 C++ 标准库,因此可以在开发机上直接编译同一套源码做对照测试;
- ArkTS 侧只面对
PsppCore这一层强类型封装,不感知 native 句柄、内存与序列化细节; - 换统计过程或扩命令,主要改
stats/与syntax/;改界面则完全不触碰数值层。
三、桌面级工作台:界面不是「手机应用放大版」
应用启动后是一个 1440×900 的桌面窗口,deviceTypes 同时声明通用设备与 2in1,PC 上安装即按桌面形态呈现。布局是典型的统计软件工作台:
- 顶部:文件 / 编辑 / 数据 / 分析 / 图表 / 工具 / 帮助 七个菜单;
- 工具栏:导入 CSV/TXT、导入 Excel、打开 SAV、导出 CSV、保存 SAV、追加行、删除末行、升序 / 降序、描述统计快捷入口、结果;
- 左侧:数据 / 变量 / 分析 / Syntax / 结果 五个导航页;
- 底部:状态栏,实时显示数据集规模、筛选状态与操作结果。
1. 数据视图
数据视图是电子表格式网格:列头带类型标注,支持单元格就地编辑、追加行、删除末行、按选中列升降序排序,以及每列一个筛选条件的快速筛选。状态栏会提示当前是全部个案还是筛选后的可见个案。

2. 变量视图
变量视图集中管理数据字典:变量名、数值类型、宽度、小数位、标签、测量尺度与值标签。导入 CSV 时会自动推断类型与格式,变量视图里的修改会即时同步到数据页。

3. 数据转换
「数据」菜单集成了 13 项预处理能力。以「计算变量」为例:填目标变量名与表达式,确定后新列即时写入数据表,状态栏提示影响的个案数与变量名。



4. 分析面板
「分析」页顶部下拉提供 19 项统计分析过程,按家族分组:描述与频数、比较均值、相关与回归、量表、非参数检验、降维与分类。统一交互模型是「选过程 → 勾变量 → 可选统计量/选项 → 运行分析」,结果自动进入结果页目录。


5. 结果与导出
结果页左侧按时间列出全部分析条目,右侧渲染统计表与图表。工具条提供导出 HTML 报告、导出 PDF 报告、导出当前 CSV、复制文本;单图还支持 SVG / PNG。

四、数据层:列式存储 + 可见行映射
统计软件的数据层有两个硬约束:一是要能扛住十万到百万行,二是筛选、排序、编辑不能把整表反复拷贝。Dataset 的设计围绕这两点展开。
4.1 列式存储与缺失值
数值列用 std::vector<double>,字符串列用 std::vector<std::string>,按列连续存放。这样做的好处是统计过程按列取值时局部性好,也避免了「一行一个对象」带来的指针跳转。
class Dataset {
public:
int variable_count() const;
const Variable& variable(int idx) const;
// case_count() 返回"当前可见个案数":无筛选时等于全部个案数,
// 启用筛选后仅统计未被排除的个案。所有行号参数均为可见行下标。
int case_count() const { return static_cast<int>(visible_.size()); }
int total_case_count() const { return n_cases_; }
void set_filter_kept(const std::vector<bool>& keep);
void clear_filter();
int case_index(int row) const; // 可见行下标 -> 实际个案下标
double get_num(int row, int col) const;
std::string get_str(int row, int col) const;
void set_cell_from_text(int row, int col, const std::string& text);
bool cell_is_missing(int row, int col) const;
std::vector<double> valid_numeric(int col) const;
void sort_cases(const std::vector<int>& cols, const std::vector<bool>& ascending);
private:
std::vector<Variable> vars_;
std::vector<std::vector<double>> num_cols_;
std::vector<std::vector<std::string>> str_cols_;
int n_cases_ = 0;
std::vector<int> visible_; // 可见个案的实际下标序列(升序)
std::vector<bool> filter_kept_;
bool filter_active_ = false;
};
缺失值统一用 SYSMIS 哨兵表示,字符串缺失用空串。valid_numeric() 返回非缺失序列,统计过程只需要关心「有效值」,不用在每个公式里重复判断缺失。变量元数据 Variable 则带着名称、标签、类型、宽度、小数位、格式、测量尺度、值标签与离散缺失值——这些信息既支撑变量视图,也支撑 SAV 读写。
4.2 筛选不重建数据,只重建映射
筛选是最容易把实现写「重」的地方。这里没有把符合条件的行拷贝成新表,而是维护一个 visible_ 数组:visible_[row] 给出可见行对应的实际个案下标。筛选时只重建映射,统计与分页都基于这层映射。
setFilter(col, op, value)
→ 逐行计算 keep[]
→ Dataset::set_filter_kept
→ rebuild_visible()
→ 返回 { totalRows, filtered, kept }
这样做的好处是:
- 十万行数据筛选不会触发一次全表拷贝;
- 取消筛选只需清空标记并重建映射;
- 统计过程看到的
case_count()天然就是「筛选后的样本量」。
删除与插入个案时也做了增量维护:插入位置之后的可见下标整体后移一位,删除时整体前移一位。排序只重排「可见个案占用的槽位」,被筛除的个案保持原样且不参与排序——这符合统计软件「只对当前样本排序」的语义。
4.3 分页取数
UI 不一次性拿全表。getPage(start, count) 返回当前页的二维文本数组与总数,ArkTS 侧只持有当前页。这样即使数据集很大,界面也不会因为一次巨大的 JSON 序列化而卡死。
{
"start": 0,
"total": 100000,
"rows": [["18", "85", "103"], ["21", "76", "97"]]
}
分页、筛选、排序三条路径都收敛在 Dataset 上,统计过程读到的始终是同一份内存数据。
五、NAPI 边界:把 C++ 内核变成 ArkTS 可调用的对象
5.1 一个全局数据集,一组导出函数
napi_init.cpp 是唯一的跨语言边界。它维护一个进程内全局 Dataset,把 ArkTS 传进来的参数解析成 C++ 值,调用 stats/engine 或 io/,再把结果序列化成 JSON 字符串返回。
Dataset g_dataset;
napi_value LoadText(napi_env env, napi_callback_info info) {
// 解析文本与导入选项
ImportResult r = import_delimited(g_dataset, text, opt);
if (!r.ok) return status_err(env, r.error);
return status_ok(env, "\"delimiter\":" + json_string(std::string(1, r.delimiter_used)));
}
导出接口按职责分成几组:
| 分类 | 接口 |
|---|---|
| 数据集 | newDataset loadText loadSav saveSav exportText |
| 查询 | getVariables getDatasetInfo getPage uniqueValues |
| 编辑 | setCell addVariable deleteVariable renameVariable setVariableProps insertCase deleteCase sortCases |
| 筛选 | setFilter clearFilter |
| 统计 | descriptives frequencies crosstabs correlations ttestOneSample ttestIndependent ttestPaired oneway regression logistic explore means reliability factor cluster nparChisquare nparMannWhitney nparKruskalWallis nparWilcoxon |
| 数据变换 | aggregate count autorecode rankVar weightExpand |
| 语法 | runSyntax |
返回约定很统一:普通操作返回 PsppStatus(ok / error / rows / cols),单次分析返回 PsppResult(表格 + 图表 + 元数据),语法批量执行返回 PsppSyntaxRun,分页返回 PsppPage,.sav 则走 ArrayBuffer。
5.2 ArkTS 侧强类型封装
PsppCore.ets 用 index.d.ts 的类型声明,把每个导出函数包成静态方法。上层不感知 NAPI 细节,只拿到 TypeScript 接口。
function parseResult(json: string): PsppResult {
try {
return JSON.parse(json) as PsppResult;
} catch (e) {
return {
command: '', title: '解析失败', description: '', ok: false,
error: '内核返回结果解析失败', tables: [], charts: [], metadata: {}
};
}
}
export class PsppCore {
static descriptives(vars: number[], stats: string[]): PsppResult {
return parseResult(psppcore.descriptives(JSON.stringify(vars), JSON.stringify(stats)));
}
static getPage(start: number, count: number): PsppPage {
try {
return JSON.parse(psppcore.getPage(start, count)) as PsppPage;
} catch (e) {
return { start: 0, total: 0, rows: [] };
}
}
}
这里的取舍很明确:用 JSON 字符串做 ABI,而不是在 NAPI 层构造复杂的 JS 对象图。
- 好处是边界简单、调试直观、C++ 侧不需要维护一整套
napi_value构造代码; - 代价是多一次序列化 / 反序列化,但通过分页把单次数据量控制住,实际体感可以接受;
- 出错时 ArkTS 侧统一降级为空结果并提示,不会因为一次解析失败把页面搞崩。
.sav 是例外:它是二进制格式,走 ArrayBuffer 直接传字节,避免把二进制塞进 JSON。
六、文件格式:CSV / XLSX / SAV 三条输入路径
| 格式 | 读取 | 写入 | 实现要点 |
|---|---|---|---|
| CSV / TXT / TSV | 支持 | 支持 | 自动分隔符探测、引号包裹、"" 转义、类型推断、缺失标记 |
| XLSX | 支持 | — | ZIP 解压 + 共享字符串 / 工作表 XML → CSV → 复用文本导入管线 |
| SAV | 支持 | 支持 | 176 字节文件头、字典记录、值标签、压缩模式 0/1/2 |
6.1 CSV:先猜分隔符,再逐行解析
分隔符探测取前 5 行,在 ,、\t、;、| 四个候选里逐个统计「每行字段数的分布」,选「字段数一致的行最多、字段数最多」的那个。统计时会跳过双引号内部的候选分隔符,避免把带逗号的文本字段误判成多列。
候选分隔符 d
→ 对每一行统计字段数 n(引号内不计)
→ 取出现次数最多的字段数 agree 与对应 fields
→ score = agree * fields
→ 选 score 最大的 d
确认分隔符后,逐行解析支持双引号包裹、"" 转义、CRLF / LF / 单独 CR 三种换行,以及可配置的缺失值标记。变量名会被清洗成合法标识符,数字列与字符串列在装载时自动推断。
6.2 XLSX:解压后回到同一条导入管线
XLSX 本质是一个 ZIP 包。XlsxService 用 @ohos.zlib 把文件解压到缓存目录,XlsxParser 解析 xl/sharedStrings.xml 与首个工作表 XML,把单元格还原成二维文本表,再由 rowsToCsv 转成 CSV,最后交给 native 的 loadText。
这条路径的关键设计是不产生第二套数据装载逻辑:XLSX → CSV → import_delimited,与普通 CSV 导入走完全相同的类型推断、变量名清洗与缺失处理。XlsxParser 本身是纯函数(只做 XML 文本 → 二维数组),因此可以在宿主端直接单测,不需要跑模拟器。
6.3 SAV:自包含的二进制编解码
.sav 是统计软件的系统文件格式,结构比 CSV 复杂得多:176 字节文件头、变量字典记录、值标签、文档记录、扩展记录、数据区。sav_io 没有依赖 zlib,而是内置了一个 DEFLATE 解压器(RFC 1951 + RFC 1950 封装),因此压缩模式 2 的文件也能直接读。
三种压缩模式的处理:
| 模式 | 含义 | 处理 |
|---|---|---|
| 0 | 无压缩 | 直接按 case 字节布局读取 |
| 1 | bytecode 压缩 | 按 opcode 还原:0 补齐、252 EOF、253 8 字节字面量、254 8 个空格、255 SYSMIS,其余为数值编码 |
| 2 | zlib 压缩 | 内置 inflate(stored / fixed / dynamic Huffman)解压后按 case 读取 |
超长字符串按 252 / 255 字节分段处理;变量标签、值标签、测量尺度、缺失值都从字典记录还原到 Variable 元数据。写入侧当前输出无压缩 .sav,但结构完整,可被上游工具回读。
七、图表与报告:同一套刻度,三种输出
统计结果里,图表最怕「屏幕上一个样、导出另一个样」。这里的做法是把坐标轴计算抽成纯函数 ChartMath.ets,Canvas 渲染、SVG 导出、PDF 导出三处共用。
export function niceAxis(min: number, max: number, tickCount: number): AxisRange {
const range = max - min;
const rawStep = range / Math.max(1, tickCount);
const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
const norm = rawStep / mag;
let step: number;
if (norm <= 1) step = 1 * mag;
else if (norm <= 2) step = 2 * mag;
else if (norm <= 5) step = 5 * mag;
else step = 10 * mag;
const niceMin = Math.floor(min / step) * step;
const niceMax = Math.ceil(max / step) * step;
// 生成 ticks……
return { min: niceMin, max: niceMax, ticks };
}
支持六类图表:柱状图、折线图、饼图、直方图、散点图、箱线图。图表数据由 native 侧以 JSON 形式带出(categories / series / points / boxes),ArkTS 侧只负责绘制。
7.1 自包含 PDF 1.4 生成器
PDF 导出没有引第三方库,而是直接生成 PDF 1.4 字节流:页面 595×842(A4)、对象表、xref、trailer、startxref。文字走两套字体:
- 纯 ASCII 用标准 Type1
Helvetica / Helvetica-Bold; - 含中文用 Type0 复合字体 +
STSong-Light(UniGB-UCS2-H编码,Adobe-GB1)。
这两套字体都是 PDF 规范里的标准字体 / 预定义 CMap,不需要内嵌字体文件,主流阅读器都能正常显示。表格是矢量网格,图表是矢量绘制,分页由 ensureSpace 控制。
7.2 HTML / CSV / PNG / SVG
- HTML 报告:完整还原全部表格与图表,散点图以 SVG 内嵌,可直接在浏览器打开;
- CSV:当前表格或全部结果表导出;
- SVG:数据驱动矢量图,适合放进论文与汇报;
- PNG:由
Canvas.toDataURL落盘。
八、构建、测试与能力边界
8.1 环境与构建
| 项目 | 要求 |
|---|---|
| IDE / SDK | DevEco Studio(含 HarmonyOS SDK) |
| Native 工具链 | OHOS NDK(clang / CMake),用于编译 arm64 native 内核 |
| 宿主端测试(可选) | clang++、python3、Node.js(XLSX / PDF 用例) |
| 目标设备 | 2in1 PC 与通用设备(deviceTypes 同时声明) |
命令行构建:
# 使用 DevEco Studio 自带 hvigorw
HVIGOR=/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw
export HVIGOR_USER_HOME="$PWD/.hvigor-home"
"$HVIGOR" assembleHap --mode module -p product=default
产物是签名或未签名的 HAP,native 内核 libpsppcore.so 随包分发,设备上不需要预装任何统计库。
8.2 测试
宿主端在开发机上直接编译同一套 native 源码(排除 NAPI 绑定)做对照测试:
bash tests/run_all_tests.sh
| 用例 | 内容 | 结果 |
|---|---|---|
| 统计内核对照测试 | 388 项断言:描述 / 频数 / 交叉表 / 相关 / T / ANOVA / 回归 / Logistic / 探索 / 分组均值 / 信度 / 非参数 / 因子 / 聚类 / 数据变换 / 排序筛选 / CSV·SAV 回环 / 语法 / 10 万行 | PASS |
| SAV 编解码回环 | 由 CSV 生成 .sav 并逐单元格回读比对 | PASS |
| XLSX 解析 | 解析真实 xlsx,与期望 CSV 逐字节对拍 | PASS |
| PDF 导出 | 内置结构校验(xref / 页数 / 中文可还原),可选 qpdf --check | PASS |
这套测试能成立的前提是 native 内核不依赖 OHOS 平台 API——它只依赖 C++ 标准库,所以开发机上用 clang++ 就能编译,测试不需要模拟器、不需要 HDC,也不需要每次打包。
8.3 当前能力状态(T0 / T1 / T2)
T0 基础能力(能不能编、能不能装、能不能起)——已完成:native 内核交叉编译通过;HAP 构建通过;2in1 设备形态与 1440×900 主窗口适配完成;默认不申请权限、不联网。
T1 主要能力——已完成:数据管理、13 项数据转换、19 项统计分析、6 类图表、语法编辑器、HTML / PDF / CSV / PNG / SVG 导出全部落地;功能矩阵列出的 44 项功能 100% 覆盖;388 项断言对拍通过。
T2 待办与取舍:原版 PSPP 定义了 141 条命令,本版覆盖核心统计与数据变换命令,GLM、CTABLES、宏等长尾命令尚未实现;.por(portable)与 .spv(输出)格式未支持;语法自动补全、输出树右键菜单等 GUI 辅助功能还未补齐。这些属于后续迭代项,不影响核心分析链路。
需要如实说明的边界:测试覆盖的是统计内核的数值与格式正确性,不等于所有 UI 路径都已被自动化覆盖;界面交互仍以人工验证与截图记录为准。
九、开源与合规
- 本项目是 GNU PSPP 的移植衍生作品,整体以 GPL-3.0-or-later 分发;
- 运行时只依赖 HarmonyOS SDK 与标准 C++ 运行库,未链接 GSL / GLib / GTK / Cairo / libxml2;
- 统计内核保留了上游的算法、公式、数据结构与文件格式定义,但代码是面向 OHOS NDK 的重新编写;
- 仓库内提供
LICENSE、SOURCE_NOTICE.md、THIRD_PARTY_NOTICES.md,说明源码来源、修改范围与获取方式。
十、总结
格物统计把「原生重建」这条路走通了:界面与领域逻辑用 ArkTS / ArkUI 实现,统计计算放进 C++ native 内核,NAPI 做桥,最终打成单一 HAP。它不是把桌面程序塞进兼容层,而是一套从头组织的鸿蒙 PC 桌面应用。
回头看有三点经验值得同类项目借鉴:
- 数值层与界面层必须解耦:统计内核只依赖 C++ 标准库,才能享受宿主端直接编译、直接对拍的红利;
- 跨语言边界越简单越好:一组导出函数 + JSON 字符串 + 少量
ArrayBuffer,比在 NAPI 层维护复杂对象图更容易长期维护; - 能力状态要按用户路径如实标注:能启动不等于能分析,界面出现不等于结果正确——T0 / T1 / T2 与 388 项断言的划分,就是为了把「已验证的」与「待完成的」说清楚。
开源鸿蒙 PC 需要的正是这类日常基础设施软件。无论是统计命令的扩展、长尾格式的兼容,还是桌面交互的打磨,都欢迎参与共建。
开源地址(AtomGit 仓库):https://atomgit.com/OpenHarmonyPCDeveloper/ohos_PSPP.git
更多推荐



所有评论(0)