添加新功能
本指南描述在 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
- 在
routers/index.ts的registerAllRouters()中创建 Service 实例和 Router 实例 - 调用
router.register() - 在
preload/index.ts的api对象中暴露方法
第三阶段:前端实现
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-1、text-foreground),不使用硬编码颜色 - 使用项目 UI 组件(
Button、Input、Select),不使用原生 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 行 |
| 自定义 Hook | 300 行 | 400 行 | 600 行 |
| 工具函数 | 150 行 | 200 行 | 300 行 |
| 类型定义 | 100 行 | 150 行 | 200 行 |
| Service | 300 行 | 400 行 | 600 行 |
超过警戒线必须在当前任务中拆分,不得留到以后。
TypeScript
npm run typecheck
第五阶段:测试
测试命令
| 命令 | 说明 |
|---|---|
npm run test | Vitest 全量测试 |
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-indexSkill 已更新 -
elfi-kb知识库已更新(如有用户可见功能) - PR 描述清晰说明变更内容