Skip to main content

添加新功能

本指南描述在 Elftia 中实现一个完整功能的端到端流程。在 添加 IPC 接口 的基础上,覆盖规划、测试、文档更新等完整生命周期。

概览:功能开发流程

规划 → 后端实现 → 前端实现 → 质量检查 → 测试 → 文档更新
flowchart LR
A[规划阶段] --> B[后端实现]
B --> C[前端实现]
C --> D[质量检查]
D --> E[测试]
E --> F[文档更新]
F --> G[提交 PR]

第一阶段:规划

开始编码之前,明确以下问题:

需求分析清单

问题作用
需要哪些共享类型?确定 @shared/contracts/ 下的类型文件
涉及数据库表操作吗?是否需要新增迁移
需要哪些 Service 逻辑?确定后端模块
需要哪些 IPC 通道?确定 Router 和 Preload API
需要哪些 UI 组件?确定前端文件结构
是否新增路由页面?确定是否需要懒加载和路由注册
是否用户可见?确定是否需要更新知识库文档

文件规划模板

以「笔记功能」为例:

packages/
├── desktop/app/
│ ├── shared/contracts/
│ │ └── note-types.ts # 共享类型
│ └── main/
│ ├── services/content/workspace/notes/
│ │ ├── NoteFileService.ts # 文件读写 Service
│ │ └── NoteWatcher.ts # 文件监听 Service
│ └── services/routers/
│ └── NoteRouter.ts # IPC Router

├── renderer/src/
│ ├── features/notes/
│ │ ├── hooks/
│ │ │ └── useNotes.ts # 数据获取 Hook
│ │ └── components/
│ │ ├── NotesList.tsx # 列表组件
│ │ ├── NoteEditor.tsx # 编辑器组件
│ │ └── index.ts # Barrel export
│ └── locales/
│ ├── en/notes.json
│ ├── zh/notes.json
│ └── ja/notes.json

第二阶段:后端实现

按以下顺序创建后端代码:

2.1 共享类型

{/* packages/desktop/app/shared/contracts/note-types.ts */}

export interface Note {
id: string;
title: string;
content: string;
tags: string[];
createdAt: string;
updatedAt: string;
}

export interface CreateNoteInput {
title: string;
content?: string;
tags?: string[];
}

2.2 Service

Service 封装所有业务逻辑。遵循单一职责原则 — 文件读写和文件监听分成两个独立的 Service。

services/content/workspace/notes/
├── NoteFileService.ts # 文件 CRUD 操作
└── NoteWatcher.ts # 文件系统变更监听

2.3 Router

使用 secureHandle 模式注册 IPC 处理器,所有参数必须经过 Zod 校验。

详细步骤参见 添加 IPC 接口

2.4 注册 + Preload

  1. routers/index.tsregisterAllRouters() 中创建 Service 实例和 Router 实例
  2. 调用 router.register()
  3. preload/index.tsapi 对象中暴露方法

第三阶段:前端实现

3.1 创建 Hook

将数据获取和状态管理逻辑封装到自定义 Hook 中:

{/* packages/renderer/src/features/notes/hooks/useNotes.ts */}

export function useNotes() {
const [notes, setNotes] = useState<Note[]>([]);
const [loading, setLoading] = useState(true);

// 数据获取、CRUD 操作...
return { notes, loading, create, update, remove };
}

3.2 创建组件

组件只负责 UI 渲染,业务逻辑委托给 Hook:

components/notes/
├── index.ts # Barrel export
├── NotesList.tsx # 列表展示 (< 400 行)
├── NoteEditor.tsx # 编辑器 (< 400 行)
└── NoteCard.tsx # 单条笔记卡片

组件规范

  • 使用语义化 Token(bg-surface-1text-foreground),不使用硬编码颜色
  • 使用项目 UI 组件(ButtonInputSelect),不使用原生 HTML 控件
  • 使用 useTranslation() 进行文案国际化

3.3 路由注册(新页面时)

如果功能需要独立页面:

{/* packages/renderer/src/app/lazy/pages.tsx */}

export const LazyNotesPage = lazy(() => import('../../features/notes/NotesPage'));
export const SuspenseNotesPage = withSuspense(LazyNotesPage, PageSkeleton);
{/* packages/renderer/src/app/App.tsx — 经 lazy/index.ts 的 barrel re-export,旧 import 路径仍然有效 */}

import { SuspenseNotesPage as NotesPage } from './lazy';

<Route path="/notes" element={<NotesPage />} />

加新 workspace 类型(不是新 page)走另一条路径:编辑 features/chat/components/content/workspaces/registry.ts + 新增 definitions/<Name>Workspace.tsx,并在 app/lazy/workspaces.tsx 加对应 lazy import。详见 packages/renderer/CLAUDE.md 「🧩 添加新的 Workspace 类型」节。

3.4 i18n

为三种语言创建翻译文件,并在 i18n/index.ts 中注册命名空间:

locales/
├── en/notes.json
├── zh/notes.json
└── ja/notes.json

第四阶段:质量检查

ESLint

# 检查修改过的文件
npx eslint packages/renderer/src/features/notes/components/ packages/renderer/src/features/notes/hooks/

# 自动修复 import 排序等
npx eslint packages/renderer/src/features/notes/components/ --fix

文件大小限制

文件类型推荐上限警戒线禁止超过
React 组件400 行600 行800 行
自定义 Hook300 行400 行600 行
工具函数150 行200 行300 行
类型定义100 行150 行200 行
Service300 行400 行600 行

超过警戒线必须在当前任务中拆分,不得留到以后。

TypeScript

npm run typecheck

第五阶段:测试

测试命令

命令说明
npm run testVitest 全量测试
npm run test -- --filter notes按关键词过滤测试

手动测试清单

  • 功能的 happy path 正常工作
  • 错误情况有正确提示(网络错误、数据库错误等)
  • 深色/浅色模式下 UI 正确显示
  • 壁纸透明度模式下 UI 正确显示
  • 窗口缩放至最小宽度时布局不破裂

第六阶段:文档更新

必须更新的文档

条件需要更新的文档
新增了 Service / 组件 / Hook / Context.claude/skills/architecture-index/SKILL.md
新增了用户可见的页面功能docs/elfi-kb/ 知识库文档
新增了页面路由docs/elfi-kb/INDEX.md 映射表
修改了 IPC 接口本文档站的 IPC 通道清单

常见模式参考

模式一:CRUD Service

适用于实体管理类功能(笔记、收藏、模板等)。

共享类型 → Service(CRUD) → Router(Zod校验) → Preload → Hook(useState+CRUD) → 列表+编辑组件

模式二:流式事件

适用于需要实时推送的功能(聊天、生成任务等)。

后端 Service 发送事件 → 主进程 webContents.send(channel, data) → Preload onXxx 监听 → Hook 订阅/取消
{/* Preload 端 — 事件订阅模式 */}

onEvent: (cb: (data: any) => void) => {
const channel = 'myFeature:event';
const handler = (_event: IpcRendererEvent, data: any) => cb(data);
ipcRenderer.on(channel, handler);
return () => ipcRenderer.removeListener(channel, handler);
},
{/* Hook 端 — 订阅管理 */}

useEffect(() => {
const unsubscribe = window.api.myFeature.onEvent((data) => {
setMessages((prev) => [...prev, data]);
});
return unsubscribe;
}, []);

模式三:文件操作

适用于需要读写用户文件的功能(项目管理、导出等)。

前端传文件路径/配置 → 后端 Service 操作文件系统 → 返回结果 URL 或内容

关键原则:文件系统操作全部在后端完成,前端只传路径和参数,不直接操作文件。


完整检查清单

后端

  • 共享类型定义在 @shared/contracts/
  • Service 封装所有业务逻辑
  • Router 使用 secureHandle + Zod 校验
  • Router 已在 registerAllRouters() 中注册
  • Preload API 方法签名正确
  • DesktopApi 类型接口已更新

前端

  • 自定义 Hook 封装数据获取逻辑
  • 组件使用语义化 Token 和项目 UI 组件
  • 新页面使用 React.lazy 懒加载
  • i18n 三语言文件已创建

质量

  • npm run lint 无 error
  • npm run typecheck 通过
  • 文件大小在限制之内
  • 深色/浅色模式测试通过
  • 壁纸透明度模式测试通过

文档

  • architecture-index Skill 已更新
  • elfi-kb 知识库已更新(如有用户可见功能)
  • PR 描述清晰说明变更内容