HarmonyOS PC实战:打造一款“原生级”分布式智能笔记应用
多窗口并行编辑:利用PC大屏优势,支持同时打开多个笔记窗口,支持拖拽分屏。AI智能语义搜索:集成端侧AI模型,支持自然语言搜索(如“上周关于鸿蒙开发的会议记录”),而非简单的关键词匹配。跨端剪贴板与拖拽:手机上复制图片/文字,PC端直接粘贴;手机相册图片直接拖拽进PC笔记。离线优先架构:数据本地加密存储(使用RDB + 向量索引),网络恢复后自动同步。PC端笔记列表可能包含数千条数据。直接使用Li
鸿蒙PC实战:打造一款“原生级”分布式智能笔记应用
文章目录
一、项目背景与需求分析
在Windows/macOS上,笔记应用通常功能强大但生态割裂;在移动端,笔记应用便捷但缺乏生产力。HarmonyOS PC的出现,让我们有机会打造一款**“生于云端,长在PC,活在多端”**的超级笔记应用。
1.1 核心功能定义
- 多窗口并行编辑:利用PC大屏优势,支持同时打开多个笔记窗口,支持拖拽分屏。
- AI智能语义搜索:集成端侧AI模型,支持自然语言搜索(如“上周关于鸿蒙开发的会议记录”),而非简单的关键词匹配。
- 跨端剪贴板与拖拽:手机上复制图片/文字,PC端直接粘贴;手机相册图片直接拖拽进PC笔记。
- 离线优先架构:数据本地加密存储(使用RDB + 向量索引),网络恢复后自动同步。
1.2 技术栈选型
- 开发语言:ArkTS (API 12+)
- UI框架:ArkUI (声明式)
- 状态管理:
@Observed,@ObjectLink,AppStorage - 数据存储:RelationalStore (RDB), VectorKit (端侧向量检索)
- 多窗口能力:WindowExtensionAbility
- AI能力:NLU (自然语言理解) Kit, Embedding Model
二、架构设计:MVVM + 仓库模式
为了保证代码的可维护性和可测试性,我们采用经典的 MVVM (Model-View-ViewModel) 架构,并引入 Repository (仓库) 层来屏蔽数据源细节。
┌─────────────────────────────────────────┐
│ View Layer (UI) │
│ (Pages, Components, Custom Views) │
└──────────────────┬──────────────────────┘
│ (Observes)
┌──────────────────▼──────────────────────┐
│ ViewModel Layer │
│ (State Management, Business Logic) │
└──────────────────┬──────────────────────┘
│ (Calls)
┌──────────────────▼──────────────────────┐
│ Repository Layer │
│ (Data Aggregation, Sync Logic) │
└─────────┬──────────────────┬────────────┘
│ │
┌─────────▼───────┐ ┌───────▼────────────┐
│ Local Source │ │ Remote Source │
│ (RDB, VectorDB) │ │ (Cloud Sync Service)│
└─────────────────┘ └────────────────────┘
三、核心功能实战编码
3.1 基础环境搭建与多窗口入口
首先,我们需要在module.json5中配置多窗口能力。
// entry/src/main/module.json5
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"launchType": "multiton", // 关键:设置为多实例,支持多窗口
"backgroundModes": ["dataTransfer"],
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
},
{
"name": "WindowExtensionAbility", // 用于处理特定的窗口扩展逻辑
"srcEntry": "./ets/windowextension/WindowExtensionAbility.ets",
"type": "windowExtension"
}
]
}
}
启动新窗口代码 (ViewModel层):
// viewModel/NoteWindowManager.ets
import window from '@ohos.window';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
export class NoteWindowManager {
private context: Context;
constructor(context: Context) {
this.context = context;
}
async openNewNoteWindow(noteId?: string) {
try {
const wantInfo = {
deviceId: '',
bundleName: this.context.bundleName,
abilityName: 'EntryAbility',
parameters: {
'noteId': noteId || 'new_note', // 传递参数区分是新开还是编辑
'timestamp': Date.now()
}
};
// 启动一个新的实例
await this.context.startAbility(wantInfo);
console.info('New note window opened successfully');
} catch (error) {
console.error('Failed to open new window', error);
}
}
}
3.2 高性能列表与自定义渲染 (PC端优化)
PC端笔记列表可能包含数千条数据。直接使用List可能会导致滚动卡顿。我们需要使用LazyForEach并进行组件复用优化。
// components/NoteListItem.ets
@Component
export struct NoteListItem {
@ObjectLink note: NoteItem; // 使用ObjectLink实现细粒度更新
private onDelete: (id: string) => void;
build() {
Row() {
Column() {
Text(this.note.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.note.preview)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.margin({ top: 4 })
Text(this.formatTime(this.note.updateTime))
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// PC端特有的悬停删除按钮
Button() {
Image($r('app.media.icon_delete'))
.width(20)
.height(20)
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.hoverEffect(true) // 开启悬停特效
.onClick(() => this.onDelete(this.note.id))
.visibility(this.note.isHovered ? Visibility.Visible : Visibility.Hidden) // 结合状态控制
}
.padding(16)
.backgroundColor(this.note.isSelected ? '#E6F7FF' : Color.Transparent)
.borderRadius(8)
.onHover((event) => {
// 更新悬停状态,触发局部刷新
this.note.isHovered = event.hover;
})
.onClick(() => {
// 选中逻辑
this.note.isSelected = true;
})
}
private formatTime(timestamp: number): string {
// 时间格式化逻辑...
return new Date(timestamp).toLocaleDateString();
}
}
// 主页面使用 LazyForEach
@Entry
@Component
struct NoteListPage {
@State notes: NoteItem[] = [];
build() {
Column() {
List() {
LazyForEach(this.notes, (item: NoteItem) => {
ListItem() {
NoteListItem({ note: item, onDelete: (id) => this.handleDelete(id) })
}
}, (item: NoteItem) => item.id) // 唯一键值
}
.layoutWeight(1)
.divider({ strokeWidth: 1, color: '#F0F0F0', startMargin: 16, endMargin: 16 })
}
}
}
3.3 端侧AI:实现语义搜索 (VectorKit实战)
这是本应用的亮点。我们不依赖云端,利用PC强大的NPU,在本地进行向量检索。
步骤1:初始化向量数据库
// utils/VectorDBHelper.ets
import vectorKit from '@ohos.vectorKit';
export class VectorDBHelper {
private static instance: VectorDBHelper;
private db: vectorKit.VectorDB | null = null;
private constructor() {}
static getInstance(): VectorDBHelper {
if (!VectorDBHelper.instance) {
VectorDBHelper.instance = new VectorDBHelper();
}
return VectorDBHelper.instance;
}
async init() {
if (this.db) return;
const config = {
dbPath: '/data/local/tmp/flownote_vector_db',
dimension: 768, // 使用通用的Embedding模型维度
distanceMetric: vectorKit.DistanceMetric.COSINE
};
this.db = await vectorKit.createVectorDB(config);
console.info('Vector DB initialized');
}
async addNoteEmbedding(id: string, text: string) {
if (!this.db) return;
// 调用端侧NLU模型生成向量 (伪代码,实际需调用NLU Kit)
const embedding = await this.generateEmbedding(text);
const vectorData = {
id: id,
vector: embedding,
metadata: { content: text }
};
await this.db.insert([vectorData]);
}
async searchNotes(query: string, topK: number = 5): Promise<NoteItem[]> {
if (!this.db) return [];
const queryEmbedding = await this.generateEmbedding(query);
const results = await this.db.search(queryEmbedding, topK);
// 将搜索结果映射回NoteItem对象
return results.map(res => ({
id: res.id,
title: res.metadata.content.substring(0, 20) + '...',
preview: res.metadata.content,
score: res.score
}));
}
private async generateEmbedding(text: string): Promise<number[]> {
// 实际项目中需集成具体的Embedding模型推理引擎
// 这里模拟返回一个768维向量
return new Array(768).fill(0).map(() => Math.random());
}
}
步骤2:在UI中集成搜索
// 在ViewModel中调用
async function search(keyword: string) {
const results = await VectorDBHelper.getInstance().searchNotes(keyword);
this.searchResults = results;
}
3.4 分布式协同:手机拍照,PC插入
利用鸿蒙的DataShare和Drag & Drop能力。
PC端接收拖拽数据:
// 在编辑器组件中
RichEditor() {
// 富文本编辑器内容
}
.onDrop((event) => {
// 获取拖拽数据
const data = event.getDataByMime('image/png');
if (data) {
// 将图片插入到光标位置
this.insertImage(data);
event.setDropResult(DropResult.SUCCESS);
}
})
手机端发送(无需额外代码,系统级支持):
用户在手机相册长按图片,拖拽(或“一抓一放”)到PC屏幕上的FlowNote窗口,系统自动完成跨设备数据传输,PC端onDrop回调被触发。
四、性能优化与用户体验打磨
4.1 内存管理与大图加载
PC端虽然内存大,但笔记应用可能包含大量高清截图。
- 策略:使用
ImageCache组件,并限制缓存大小。 - 缩略图机制:列表页只加载缩略图,点击详情后再加载原图。
- 懒加载:利用
LazyForEach确保只有可视区域内的图片被解码。
4.2 键盘快捷键适配
PC用户重度依赖键盘。我们需要全局监听按键事件。
// 全局快捷键注册 (在Ability或主页面)
import inputMethod from '@ohos.inputMethod';
// 简单示例:监听 Ctrl + N 新建笔记
.onKeyDown((event) => {
if (event.key === Key.N && event.ctrlKey) {
this.viewModel.createNewNote();
event.stop(); // 阻止默认行为
}
if (event.key === Key.F && event.ctrlKey) {
this.focusSearchBar();
event.stop();
}
})
4.3 深色模式与自适应布局
利用ArkUI的媒体查询,完美适配不同分辨率的PC屏幕和深色模式。
@media (prefers-color-scheme: dark) {
.container {
background-color: #1A1A1A;
text-color: #FFFFFF;
}
}
@media (min-width: 1200px) {
/* 大屏布局:三栏式 (列表-预览-编辑) */
.layout-mode {
flex-direction: row;
}
}
@media (max-width: 1199px) {
/* 小屏/平板模式:单栏或双栏切换 */
.layout-mode {
flex-direction: column;
}
}
五、打包发布与真机调试
5.1 签名与打包
- 在DevEco Studio中配置自动签名(Debug版)或手动签名(Release版)。
- 选择
Build > Build Hap(s)/APP(s) > Build APP(s)。 - 生成的
.app文件位于build/default/outputs/default目录下。
5.2 上架注意事项
- 隐私合规:必须在首次启动时明确告知用户AI模型会本地处理数据,不上传云端(除非用户开启同步)。
- 权限申请:仅在需要时申请
OHOS_PERMISSION_READ_MEDIA等权限,并在module.json5中详细说明用途。 - 多端测试:务必在折叠屏、普通笔记本、台式机等多种形态设备上测试布局适应性。
六、总结
通过“FlowNote”这个实战项目,我们验证了HarmonyOS PC在多窗口管理、端侧AI能力、分布式交互以及高性能渲染方面的巨大潜力。
- 架构层面:MVVM + Repository模式保证了代码的清晰度。
- 技术层面:
LazyForEach、VectorKit、WindowExtension等API的灵活运用,解决了性能与功能难题。 - 体验层面:键盘快捷键、拖拽交互、深色模式等细节,让应用具备了“原生PC应用”的质感。
更多推荐



所有评论(0)