MindFlow 项目开发实战:项目启动与基础框架搭建
在AI的加持下,MindFlow 项目已完成 Phase 1: 项目启动与基础设施 阶段,所有既定目标均已达成:本次更新同步发布了 完整的技术文档体系,为项目的持续发展奠定坚实基础。详细的技术架构和选型说明,包含:技术选型对比方案优势劣势评估Electron生态成熟,跨平台好体积大,性能一般⭐⭐⭐Tauri轻量,安全,性能好生态较新⭐⭐⭐⭐⭐Flutter性能好,UI一致桌面支持一般⭐⭐⭐推荐方案
1. 本次更新内容详解
1.1 Phase 1 完成情况
在AI的加持下,MindFlow 项目已完成 Phase 1: 项目启动与基础设施 阶段,所有既定目标均已达成:
已完成任务清单
| 任务 | 工作量 | 状态 |
|---|---|---|
| 仓库初始化(Monorepo) | 1天 | ✅ 已完成 |
| Tauri桌面端脚手架 | 1天 | ✅ 已完成 |
| Web端脚手架搭建 | 1天 | ✅ 已完成 |
| CI/CD流程搭建 | 1天 | ✅ 已完成 |
| 代码规范配置(ESLint/Prettier) | 0.5天 | ✅ 已完成 |
| Git工作流规范文档 | 0.5天 | ✅ 已完成 |
1.2 主要交付物
- 可运行的桌面端和Web端空壳项目
- 基于 Tauri 2.x 的桌面端架构
- 基于 React 18 + Vite 的 Web 端架构
- 完整的 TypeScript 类型支持
- CI/CD 流程
- GitHub Actions 自动构建
- 自动化测试和部署流程
- 支持多平台构建
- 开发规范文档
- ESLint + Prettier 代码规范
- Git 工作流规范
- 代码注释规范
2. 新增文档体系
本次更新同步发布了 完整的技术文档体系,为项目的持续发展奠定坚实基础。
2.1 技术方案设计文档
详细的技术架构和选型说明,包含:
整体架构设计
┌─────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ 桌面端 │ │ Web端 │ │ 移动端 │ │未来扩展│ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
├─────────────────────────────────────────────────────────┤
│ 业务层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ 编辑器 │ │文件管理 │ │ 渲染引擎 │ │导出模块│ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
├─────────────────────────────────────────────────────────┤
│ 核心层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Markdown │ │插件系统 │ │ 主题系统 │ │配置管理│ │
│ │ 解析器 │ │ │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
├─────────────────────────────────────────────────────────┤
│ 平台层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Tauri │ │ 浏览器API │ │ Flutter │ │原生API │ │
│ │ 桌面API │ │ │ │ 移动API │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
技术选型对比
| 方案 | 优势 | 劣势 | 评估 |
|---|---|---|---|
| Electron | 生态成熟,跨平台好 | 体积大,性能一般 | ⭐⭐⭐ |
| Tauri | 轻量,安全,性能好 | 生态较新 | ⭐⭐⭐⭐⭐ |
| Flutter | 性能好,UI一致 | 桌面支持一般 | ⭐⭐⭐ |
推荐方案:Tauri + Flutter 混合架构
- 桌面端:Tauri(Rust + WebView)
- 移动端:Flutter
- Web端:React/Vue
核心技术栈
桌面端(Mac/Windows/Linux)
前端框架:React 18 / Vue 3
UI组件:TailwindCSS / shadcn/ui
构建工具:Vite
跨平台:Tauri 2.x
Web端
框架:React 18 / Vue 3
状态管理:Zustand / Pinia
路由:React Router / Vue Router
部署:Vercel / Netlify
移动端(iOS/Android)
框架:Flutter 3.x
状态管理:Riverpod
UI组件:Material Design 3
2.2 开发排期文档
制定了 6-8个月 的完整开发计划,分为 12个Phase:
📅 完整时间线
Phase 1: ████████ Week 1-2 项目启动 ✅
Phase 2: ████████████ Week 3-6 核心编辑器
Phase 3: ██████████ Week 7-9 文件管理
Phase 4: ████████████ Week 10-13 扩展语法
Phase 5: ██████████ Week 14-16 导出演示
Phase 6: ██████ Week 17-18 主题配置
Phase 7: ██████ Week 19-20 性能优化
Phase 8: ██████ Week 21-22 桌面端
Phase 9: ███ Week 23 Web端
Phase 10: ████████████ Week 24-29 移动端
Phase 11: ██████ Week 30-31 测试修复
Phase 12: ██████ Week 32-33 文档发布
🎯 版本规划
- v0.1.0(MVP) - Week 6:基础编辑功能
- v0.5.0(Beta) - Week 16:扩展语法支持
- v0.9.0(RC) - Week 22:桌面端完整功能
- v1.0.0(正式版) - Week 29:全平台支持
3. 项目架构详解
3.1 Monorepo 结构
MindFlow/
├── docs/ # 文档目录
│ ├── 需求文档.md
│ ├── 技术方案设计.md
│ ├── 开发排期.md
│ ├── Git工作流规范.md
│ └── 代码注释规范.md
├── packages/ # 应用包
│ ├── core/ # 核心编辑器功能
│ ├── desktop/ # 桌面端 (Tauri)
│ ├── web/ # Web端
│ ├── mobile/ # 移动端 (Flutter)
│ ├── parser/ # Markdown 解析器
│ ├── renderer/ # Markdown 渲染器
│ ├── themes/ # 主题定义
│ └── plugins/ # 编辑器插件
├── shared/ # 共享代码
│ ├── types/ # 类型定义
│ ├── constants/ # 常量定义
│ └── utils/ # 工具函数
├── scripts/ # 脚本工具
│ └── setup.sh # 环境设置脚本
├── .github/ # GitHub 配置
│ └── workflows/ # CI/CD 工作流
└── turbo.json # Turbo 配置
3.2 核心功能依赖
| 功能 | 技术方案 |
|---|---|
| Markdown解析 | marked / markdown-it |
| 语法高亮 | highlight.js / Shiki |
| 代码编辑 | CodeMirror 6 / Monaco Editor |
| LaTeX公式 | KaTeX / MathJax |
| Mermaid图表 | mermaid.js |
| PlantUML | PlantUML Server |
| 思维导图 | markmap-lib / d3 |
| 导出PDF | puppeteer / jsPDF |
| 导出PPT | reveal.js 自定义转换 |
| 文件管理 | chokidar(监听)+ 原生API |
4. 核心代码示例
4.1 Markdown 编辑器集成 (CodeMirror 6)
创建编辑器实例 (packages/core/src/editor.ts)
/**
* @fileoverview 核心编辑器模块
* @description 使用 CodeMirror 6 创建功能完整的 Markdown 编辑器
* @module @mindflow/core
*/
import { EditorState } from '@codemirror/state';
import { EditorView, keymap, ViewUpdate } from '@codemirror/view';
import { markdown } from '@codemirror/lang-markdown';
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
import { indentWithTab } from '@codemirror/commands';
import { markdownHighlight } from './syntax-highlighting';
/**
* 编辑器配置接口
*/
export interface EditorConfig {
/** 编辑器父容器元素 */
parent: HTMLElement;
/** 初始文档内容 */
doc: string;
/** 主题:'light' | 'dark' */
theme: 'light' | 'dark';
/** 是否只读模式 */
readonly?: boolean;
/** 是否显示行号 */
lineNumbers?: boolean;
/** 编辑器高度 */
height?: string;
}
/**
* 编辑器控制器
*/
export interface EditorController {
/** 获取当前内容 */
getContent(): string;
/** 设置内容 */
setContent(content: string): void;
/** 获取光标位置 */
getCursorPosition(): { line: number; column: number };
/** 设置光标位置 */
setCursorPosition(line: number, column: number): void;
/** 撤销 */
undo(): void;
/** 重做 */
redo(): void;
/** 销毁编辑器 */
destroy(): void;
/** 编辑器实例 */
view: EditorView;
}
/**
* 创建 Markdown 编辑器
* @description 使用 CodeMirror 6 创建一个功能完整的 Markdown 编辑器
*/
export function createEditor(config: EditorConfig): EditorController {
const {
parent,
doc,
theme,
readonly = false,
lineNumbers = true,
height = '100%',
} = config;
// 创建编辑器状态
const state = EditorState.create({
doc,
extensions: [
markdown({ base: markdownHighlight }),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
EditorView.lineWrapping,
EditorView.updateListener.of((update: ViewUpdate) => {
if (update // .docChanged) {
文档变化时的处理逻辑
console.log('文档已更新');
}
}),
EditorView.theme({
'&': {
height,
fontSize: '14px',
fontFamily: 'FiraCode, Monaco, Consolas, monospace',
},
'.cm-content': {
padding: '20px',
caretColor: theme === 'dark' ? '#fff' : '#000',
},
'.cm-focused': {
outline: 'none',
},
'.cm-scroller': {
overflow: 'auto',
},
}),
keymap.of([indentWithTab]),
EditorView.editable.of(!readonly),
],
});
// 创建编辑器视图
const view = new EditorView({
state,
parent,
});
return {
view,
getContent() {
return view.state.doc.toString();
},
setContent(content: string) {
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: content,
},
});
},
getCursorPosition() {
const pos = view.state.selection.main.head;
return view.state.doc.lineAt(pos);
},
setCursorPosition(line: number, column: number) {
const pos = view.state.doc.line(line).from + column;
view.dispatch({
selection: { anchor: pos },
});
view.focus();
},
undo() {
// 实现撤销逻辑
view.dispatch({ undo: true });
},
redo() {
// 实现重做逻辑
view.dispatch({ redo: true });
},
destroy() {
view.destroy();
},
};
}
4.2 Markdown 渲染引擎
Markdown 解析器 (packages/renderer/src/index.ts)
/**
* @fileoverview Markdown 渲染引擎
* @description 将 Markdown 转换为 HTML,支持扩展语法
*/
import { marked } from 'marked';
import KaTeX from 'katex';
import mermaid from 'mermaid';
/**
* 渲染器配置
*/
export interface RenderConfig {
/** 是否启用 LaTeX */
enableLaTeX?: boolean;
/** 是否启用 Mermaid */
enableMermaid?: boolean;
/** 是否启用代码高亮 */
enableHighlight?: boolean;
/** 主题 */
theme?: 'light' | 'dark';
}
/**
* 渲染结果
*/
export interface RenderResult {
/** 渲染后的 HTML */
html: string;
/** 提取的元数据 */
metadata: Record<string, any>;
/** 目录结构 */
toc: Array<{ level: number; text: string; id: string }>;
}
/**
* Markdown 渲染器类
*/
export class MarkdownRenderer {
private config: RenderConfig;
constructor(config: RenderConfig = {}) {
this.config = {
enableLaTeX: true,
enableMermaid: true,
enableHighlight: true,
theme: 'light',
...config,
};
this.setupMarked();
}
/**
* 配置 marked
*/
private setupMarked(): void {
// 配置 marked 选项
marked.setOptions({
gfm: true,
breaks: true,
headerIds: true,
mangle: false,
});
// 自定义渲染器
const renderer = new marked.Renderer();
// 代码块渲染
renderer.code = (code, language) => {
if (language === 'mermaid' && this.config.enableMermaid) {
return `<div class="mermaid">${code}</div>`;
}
if (this.config.enableHighlight) {
return `<pre><code class="language-${language}">${code}</code></pre>`;
}
return `<pre><code>${code}</code></pre>`;
};
// 内联代码渲染
renderer.codespan = (code) => {
return `<code class="inline-code">${code}</code>`;
};
// 表格渲染
renderer.table = (header, body) => {
return `
<div class="table-wrapper">
<table>
<thead>${header}</thead>
<tbody>${body}</tbody>
</table>
</div>
`;
};
// 标题渲染(添加锚点)
renderer.heading = (text, level) => {
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
return `<h${level} id="${id}">${text}</h${level}>`;
};
marked.use({ renderer });
}
/**
* 渲染 Markdown
*/
async render(markdown: string): Promise<RenderResult> {
// 预处理:提取元数据
const { content, metadata } = this.extractMetadata(markdown);
// 预处理:LaTeX 公式
const processedContent = this.processLaTeX(content);
// 渲染为 HTML
const html = marked(processedContent) as string;
// 提取目录结构
const toc = this.extractTOC(processedContent);
// 后处理:初始化 Mermaid
if (this.config.enableMermaid) {
await this.initializeMermaid();
}
return {
html,
metadata,
toc,
};
}
/**
* 提取元数据
*/
private extractMetadata(content: string): { content: string; metadata: Record<string, any> } {
const metadata: Record<string, any> = {};
const metadataRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
const match = content.match(metadataRegex);
if (match) {
const metadataText = match[1];
const lines = metadataText.split('\n');
for (const line of lines) {
const [key, ...valueParts] = line.split(':');
if (key && valueParts.length > 0) {
metadata[key.trim()] = valueParts.join(':').trim();
}
}
return {
content: content.replace(metadataRegex, ''),
metadata,
};
}
return { content, metadata };
}
/**
* 处理 LaTeX 公式
*/
private processLaTeX(content: string): string {
if (!this.config.enableLaTeX) {
return content;
}
// 处理行内公式 $...$
content = content.replace(/\$([^$]+)\$/g, (match, formula) => {
try {
return KaTeX.renderToString(formula, { throwOnError: false });
} catch (error) {
console.error('LaTeX 渲染错误:', error);
return match;
}
});
// 处理块级公式 $$...$$
content = content.replace(/\$\$([\s\S]+?)\$\$/g, (match, formula) => {
try {
return `<div class="latex-block">${KaTeX.renderToString(formula, {
throwOnError: false,
displayMode: true,
})}</div>`;
} catch (error) {
console.error('LaTeX 渲染错误:', error);
return match;
}
});
return content;
}
/**
* 提取目录结构
*/
private extractTOC(content: string): Array<{ level: number; text: string; id: string }> {
const toc: Array<{ level: number; text: string; id: string }> = [];
const lines = content.split('\n');
for (const line of lines) {
const match = line.match(/^(#{1,6})\s+(.+)/);
if (match) {
const level = match[1].length;
const text = match[2].trim();
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
toc.push({ level, text, id });
}
}
return toc;
}
/**
* 初始化 Mermaid
*/
private async initializeMermaid(): Promise<void> {
mermaid.initialize({
startOnLoad: false,
theme: this.config.theme === 'dark' ? 'dark' : 'default',
});
// 等待 DOM 加载完成后渲染
setTimeout(() => {
const mermaidElements = document.querySelectorAll('.mermaid');
mermaidElements.forEach((element) => {
try {
mermaid.init(undefined, element);
} catch (error) {
console.error('Mermaid 渲染错误:', error);
}
});
}, 0);
}
}
4.3 插件系统
插件接口定义 (packages/plugins/src/types.ts)
/**
* @fileoverview 插件系统类型定义
* @description 定义插件系统的核心接口和类型
*/
/**
* 插件上下文
*/
export interface PluginContext {
/** 编辑器实例 */
editor: any;
/** 渲染器实例 */
renderer: any;
/** 文件系统 */
fs: any;
/** 日志工具 */
log: {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
};
/** 配置 */
config: Record<string, any>;
}
/**
* 插件生命周期钩子
*/
export interface PluginHooks {
/** 插件初始化时调用 */
onInit?: (context: PluginContext) => void;
/** 编辑器内容变化时调用 */
onChange?: (content: string, context: PluginContext) => void;
/** 保存文件前调用 */
onBeforeSave?: (content: string, context: PluginContext) => string | Promise<string>;
/** 加载文件后调用 */
onAfterLoad?: (content: string, context: PluginContext) => void | Promise<void>;
/** 插件销毁时调用 */
onDestroy?: (context: PluginContext) => void;
}
/**
* 插件配置
*/
export interface PluginConfig {
/** 插件名称 */
name: string;
/** 插件版本 */
version: string;
/** 插件描述 */
description?: string;
/** 插件作者 */
author?: string;
/** 插件依赖 */
dependencies?: string[];
/** 插件配置 */
options?: Record<string, any>;
}
/**
* 插件接口
*/
export interface Plugin extends PluginConfig, PluginHooks {
/** 插件 ID */
id: string;
/** 插件状态 */
enabled: boolean;
/** 启用插件 */
enable: () => void;
/** 禁用插件 */
disable: () => void;
/** 更新配置 */
updateConfig: (config: Record<string, any>) => void;
}
/**
* 插件管理器
*/
export interface PluginManager {
/** 注册插件 */
register: (plugin: Plugin) => void;
/** 取消注册插件 */
unregister: (pluginId: string) => void;
/** 启用插件 */
enable: (pluginId: string) => void;
/** 禁用插件 */
disable: (pluginId: string) => void;
/** 获取插件 */
get: (pluginId: string) => Plugin | undefined;
/** 获取所有插件 */
getAll: () => Plugin[];
/** 触发钩子 */
trigger: (hookName: keyof PluginHooks, ...args: any[]) => Promise<void>;
}
插件管理器实现 (packages/plugins/src/manager.ts)
/**
* @fileoverview 插件管理器
* @description 负责插件的注册、管理和生命周期管理
*/
import { Plugin, PluginManager, PluginContext } from './types';
/**
* 插件管理器实现
*/
export class PluginManagerImpl implements PluginManager {
private plugins: Map<string, Plugin> = new Map();
private context: PluginContext;
constructor(context: PluginContext) {
this.context = context;
}
/**
* 注册插件
*/
register(plugin: Plugin): void {
if (this.plugins.has(plugin.id)) {
this.context.log.warn(`插件 ${plugin.id} 已存在,将被覆盖`);
}
// 检查依赖
if (plugin.dependencies) {
for (const dep of plugin.dependencies) {
if (!this.plugins.has(dep)) {
this.context.log.error(`插件 ${plugin.name} 依赖 ${dep},但该插件未注册`);
return;
}
}
}
this.plugins.set(plugin.id, plugin);
// 初始化插件
if (plugin.onInit) {
try {
plugin.onInit(this.context);
this.context.log.info(`插件 ${plugin.name} v${plugin.version} 初始化成功`);
} catch (error) {
this.context.log.error(`插件 ${plugin.name} 初始化失败:`, error);
}
}
}
/**
* 取消注册插件
*/
unregister(pluginId: string): void {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
this.context.log.warn(`插件 ${pluginId} 不存在`);
return;
}
// 销毁插件
if (plugin.onDestroy) {
try {
plugin.onDestroy(this.context);
} catch (error) {
this.context.log.error(`插件 ${plugin.name} 销毁失败:`, error);
}
}
this.plugins.delete(pluginId);
this.context.log.info(`插件 ${plugin.name} 已取消注册`);
}
/**
* 启用插件
*/
enable(pluginId: string): void {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
throw new Error(`插件 ${pluginId} 不存在`);
}
plugin.enabled = true;
this.context.log.info(`插件 ${plugin.name} 已启用`);
}
/**
* 禁用插件
*/
disable(pluginId: string): void {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
throw new Error(`插件 ${pluginId} 不存在`);
}
plugin.enabled = false;
this.context.log.info(`插件 ${plugin.name} 已禁用`);
}
/**
* 获取插件
*/
get(pluginId: string): Plugin | undefined {
return this.plugins.get(pluginId);
}
/**
* 获取所有插件
*/
getAll(): Plugin[] {
return Array.from(this.plugins.values());
}
/**
* 触发钩子
*/
async trigger(hookName: keyof PluginContext, ...args: any[]): Promise<void> {
const promises: Promise<any>[] = [];
for (const plugin of this.plugins.values()) {
if (!plugin.enabled) {
continue;
}
const hook = plugin[hookName as keyof Plugin];
if (typeof hook === 'function') {
const result = hook.apply(plugin, [this.context, ...args]);
if (result instanceof Promise) {
promises.push(result);
}
}
}
await Promise.all(promises);
}
}
4.4 文件管理系统
文件管理器 (packages/core/src/file-manager.ts)
/**
* @fileoverview 文件管理系统
* @description 负责文件的增删改查、监听和缓存
*/
import { readFile, writeFile, readDir, watch } from '@tauri-apps/api/fs';
import { invoke } from '@tauri-apps/api/tauri';
import { appDataDir, join } from '@tauri-apps/api/path';
import { debounce } from 'lodash';
/**
* 文件信息接口
*/
export interface FileInfo {
/** 文件路径 */
path: string;
/** 文件名 */
name: string;
/** 文件大小(字节) */
size: number;
/** 最后修改时间 */
modified: Date;
/** 是否为目录 */
isDirectory: boolean;
/** 子文件(如果是目录) */
children?: FileInfo[];
}
/**
* 文件变化事件
*/
export interface FileChangeEvent {
/** 变化类型:'create' | 'update' | 'delete' */
type: 'create' | 'update' | 'delete';
/** 文件路径 */
path: string;
/** 文件信息 */
fileInfo?: FileInfo;
}
/**
* 文件管理器类
*/
export class FileManager {
private watchers: Map<string, any> = new Map();
private cache: Map<string, string> = new Map();
private fileTree: Map<string, FileInfo> = new Map();
private listeners: Set<(event: FileChangeEvent) => void> = new Set();
/**
* 读取文件内容
*/
async readFile(path: string): Promise<string> {
// 先从缓存读取
if (this.cache.has(path)) {
return this.cache.get(path)!;
}
try {
const content = await readFile(path, { encoding: 'utf8' });
this.cache.set(path, content);
return content;
} catch (error) {
throw new Error(`读取文件失败: ${error}`);
}
}
/**
* 写入文件内容
*/
async writeFile(path: string, content: string): Promise<void> {
try {
// 写入文件
await writeFile({
path,
contents: content,
});
// 更新缓存
this.cache.set(path, content);
// 通知监听器
this.notifyListeners({
type: 'update',
path,
fileInfo: await this.getFileInfo(path),
});
// 触发保存钩子
await this.triggerHook('onFileSaved', path, content);
} catch (error) {
throw new Error(`写入文件失败: ${error}`);
}
}
/**
* 获取文件信息
*/
async getFileInfo(path: string): Promise<FileInfo> {
try {
const stats = await invoke<{
name: string;
size: number;
modified: number;
isDirectory: boolean;
}>('get_file_stats', { path });
return {
path,
name: stats.name,
size: stats.size,
modified: new Date(stats.modified),
isDirectory: stats.isDirectory,
};
} catch (error) {
throw new Error(`获取文件信息失败: ${error}`);
}
}
/**
* 读取目录
*/
async readDir(path: string): Promise<FileInfo[]> {
try {
const entries = await readDir(path);
const fileInfos: FileInfo[] = await Promise.all(
entries.map(async (entry) => {
const fullPath = await join(path, entry.name!);
if (entry.children) {
// 递归读取子目录
const children = await this.readDir(fullPath);
return {
path: fullPath,
name: entry.name!,
size: 0,
modified: new Date(),
isDirectory: true,
children,
};
} else {
return await this.getFileInfo(fullPath);
}
})
);
// 缓存文件树
this.fileTree.set(path, {
path,
name: path.split('/').pop() || '',
size: 0,
modified: new Date(),
isDirectory: true,
children: fileInfos,
});
return fileInfos;
} catch (error) {
throw new Error(`读取目录失败: ${error}`);
}
}
/**
* 监听文件变化
*/
async watch(path: string, callback: (event: FileChangeEvent) => void): Promise<void> {
// 取消之前的监听
if (this.watchers.has(path)) {
await this.unwatch(path);
}
// 创建新的监听器
const watcher = watch(path, (event) => {
if (event.type === 'create') {
callback({
type: 'create',
path: event.path,
});
} else if (event.type === 'modify') {
callback({
type: 'update',
path: event.path,
});
} else if (event.type === 'remove') {
callback({
type: 'delete',
path: event.path,
});
// 清除缓存
this.cache.delete(event.path);
}
// 清除文件树缓存
this.fileTree.delete(path);
});
this.watchers.set(path, watcher);
}
/**
* 取消监听
*/
async unwatch(path: string): Promise<void> {
const watcher = this.watchers.get(path);
if (watcher) {
await watcher.close();
this.watchers.delete(path);
}
}
/**
* 订阅文件变化
*/
subscribe(callback: (event: FileChangeEvent) => void): () => void {
this.listeners.add(callback);
return () => {
this.listeners.delete(callback);
};
}
/**
* 通知监听器
*/
private notifyListeners(event: FileChangeEvent): void {
this.listeners.forEach((listener) => {
try {
listener(event);
} catch (error) {
console.error('文件变化监听器执行错误:', error);
}
});
}
/**
* 触发钩子
*/
private async triggerHook(hookName: string, ...args: any[]): Promise<void> {
// 这里会调用插件管理器的钩子
// 暂时使用 debounce 优化性能
const debouncedTrigger = debounce(() => {
// 实现钩子触发逻辑
}, 100);
debouncedTrigger();
}
/**
* 获取应用数据目录
*/
async getAppDataDir(): Promise<string> {
return await appDataDir();
}
/**
* 清理缓存
*/
clearCache(path?: string): void {
if (path) {
this.cache.delete(path);
} else {
this.cache.clear();
}
}
/**
* 销毁文件管理器
*/
async destroy(): Promise<void> {
// 取消所有监听
for (const path of this.watchers.keys()) {
await this.unwatch(path);
}
// 清空缓存
this.cache.clear();
this.fileTree.clear();
this.listeners.clear();
}
}
4.5 主题系统
主题配置 (packages/themes/src/index.ts)
/**
* @fileoverview 主题系统
* @description 定义编辑器和 UI 的主题配置
*/
/**
* 主题颜色
*/
export interface ThemeColors {
/** 主背景色 */
background: string;
/** 前景色(文本色) */
foreground: string;
/** 主色调 */
primary: string;
/** 次要色 */
secondary: string;
/** 强调色 */
accent: string;
/** 成功色 */
success: string;
/** 警告色 */
warning: string;
/** 错误色 */
error: string;
/** 边框色 */
border: string;
/** 分割线色 */
divider: string;
/** 代码块背景色 */
codeBlockBg: string;
/** 行号色 */
lineNumber: string;
/** 光标色 */
cursor: string;
/** 选区色 */
selection: string;
/** 搜索高亮色 */
searchHighlight: string;
}
/**
* 字体配置
*/
export interface FontConfig {
/** 主字体族 */
fontFamily: string;
/** 代码字体族 */
codeFontFamily: string;
/** 字体大小 */
fontSize: number;
/** 行高 */
lineHeight: number;
/** 字重 */
fontWeight: number;
}
/**
* 编辑器配置
*/
export interface EditorThemeConfig {
/** 编辑器背景 */
editorBg: string;
/** 编辑器前景 */
editorFg: string;
/** 编辑器边框 */
editorBorder: string;
/** 滚动条样式 */
scrollbar?: {
width: string;
height: string;
background: string;
};
/** Gutter 样式 */
gutter?: {
background: string;
color: string;
};
}
/**
* 完整主题配置
*/
export interface ThemeConfig {
/** 主题名称 */
name: string;
/** 主题类型 */
type: 'light' | 'dark';
/** 颜色配置 */
colors: ThemeColors;
/** 字体配置 */
fonts: FontConfig;
/** 编辑器配置 */
editor: EditorThemeConfig;
}
/**
* 浅色主题
*/
export const lightTheme: ThemeConfig = {
name: 'Light',
type: 'light',
colors: {
background: '#ffffff',
foreground: '#1a1a1a',
primary: '#3b82f6',
secondary: '#64748b',
accent: '#8b5cf6',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
border: '#e2e8f0',
divider: '#f1f5f9',
codeBlockBg: '#f8fafc',
lineNumber: '#94a3b8',
cursor: '#3b82f6',
selection: '#bfdbfe',
searchHighlight: '#fef3c7',
},
fonts: {
fontFamily: 'FiraCode, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
codeFontFamily: 'FiraCode, Monaco, Consolas, "Courier New", monospace',
fontSize: 14,
lineHeight: 1.6,
fontWeight: 400,
},
editor: {
editorBg: '#ffffff',
editorFg: '#1a1a1a',
editorBorder: '#e2e8f0',
scrollbar: {
width: '8px',
height: '8px',
background: '#f1f5f9',
},
gutter: {
background: '#f8fafc',
color: '#64748b',
},
},
};
/**
* 深色主题
*/
export const darkTheme: ThemeConfig = {
name: 'Dark',
type: 'dark',
colors: {
background: '#0f172a',
foreground: '#e2e8f0',
primary: '#60a5fa',
secondary: '#94a3b8',
accent: '#a78bfa',
success: '#34d399',
warning: '#fbbf24',
error: '#f87171',
border: '#1e293b',
divider: '#1e293b',
codeBlockBg: '#1e293b',
lineNumber: '#475569',
cursor: '#60a5fa',
selection: '#1e40af',
searchHighlight: '#78350f',
},
fonts: {
fontFamily: 'FiraCode, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
codeFontFamily: 'FiraCode, Monaco, Consolas, "Courier New", monospace',
fontSize: 14,
lineHeight: 1.6,
fontWeight: 400,
},
editor: {
editorBg: '#0f172a',
editorFg: '#e2e8f0',
editorBorder: '#1e293b',
scrollbar: {
width: '8px',
height: '8px',
background: '#1e293b',
},
gutter: {
background: '#0f172a',
color: '#94a3b8',
},
},
};
/**
* 主题管理器
*/
export class ThemeManager {
private currentTheme: ThemeConfig;
private listeners: Set<(theme: ThemeConfig) => void> = new Set();
constructor(initialTheme: ThemeConfig = lightTheme) {
this.currentTheme = initialTheme;
}
/**
* 切换主题
*/
switchTheme(theme: ThemeConfig): void {
this.currentTheme = theme;
this.applyTheme();
this.notifyListeners();
}
/**
* 应用主题
*/
applyTheme(): void {
const root = document.documentElement;
// 应用颜色变量
Object.entries(this.currentTheme.colors).forEach(([key, value]) => {
root.style.setProperty(`--color-${key}`, value);
});
// 应用字体变量
Object.entries(this.currentTheme.fonts).forEach(([key, value]) => {
root.style.setProperty(`--font-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`, String(value));
});
// 应用编辑器主题
const editor = this.currentTheme.editor;
root.style.setProperty('--editor-bg', editor.editorBg);
root.style.setProperty('--editor-fg', editor.editorFg);
root.style.setProperty('--editor-border', editor.editorBorder);
// 添加主题类型 class
document.body.classList.remove('light-theme', 'dark-theme');
document.body.classList.add(`${this.currentTheme.type}-theme`);
}
/**
* 获取当前主题
*/
getCurrentTheme(): ThemeConfig {
return this.currentTheme;
}
/**
* 订阅主题变化
*/
subscribe(callback: (theme: ThemeConfig) => void): () => void {
this.listeners.add(callback);
return () => {
this.listeners.delete(callback);
};
}
/**
* 通知监听器
*/
private notifyListeners(): void {
this.listeners.forEach((listener) => {
try {
listener(this.currentTheme);
} catch (error) {
console.error('主题变化监听器执行错误:', error);
}
});
}
/**
* 生成 CSS
*/
generateCSS(): string {
const { colors, fonts, editor } = this.currentTheme;
return `
/* 主题 CSS */
.${this.currentTheme.type}-theme {
--color-background: ${colors.background};
--color-foreground: ${colors.foreground};
--color-primary: ${colors.primary};
--color-secondary: ${colors.secondary};
--color-accent: ${colors.accent};
--color-success: ${colors.success};
--color-warning: ${colors.warning};
--color-error: ${colors.error};
--color-border: ${colors.border};
--color-divider: ${colors.divider};
--color-code-block-bg: ${colors.codeBlockBg};
--color-line-number: ${colors.lineNumber};
--color-cursor: ${colors.cursor};
--color-selection: ${colors.selection};
--color-search-highlight: ${colors.searchHighlight};
}
/* 编辑器样式 */
.cm-editor {
background-color: ${editor.editorBg};
color: ${editor.editorFg};
border: 1px solid ${editor.editorBorder};
}
/* 滚动条样式 */
.cm-scroller::-webkit-scrollbar {
width: ${editor.scrollbar?.width || '8px'};
height: ${editor.scrollbar?.height || '8px'};
}
.cm-scroller::-webkit-scrollbar-track {
background: ${editor.scrollbar?.background || '#f1f5f9'};
}
.cm-scroller::-webkit-scrollbar-thumb {
background: ${editor.gutter?.color || '#cbd5e1'};
border-radius: 4px;
}
`;
}
}
4.6 导出功能
导出管理器 (packages/exporter/src/index.ts)
/**
* @fileoverview 导出功能模块
* @description 支持 PDF、HTML、图片等多种格式的导出
*/
import puppeteer from 'puppeteer';
/**
* 导出配置
*/
export interface ExportConfig {
/** 文件名 */
filename?: string;
/** 主题 */
theme?: 'light' | 'dark';
/** 页面尺寸 */
format?: 'A4' | 'A3' | 'Letter';
/** 边距 */
margin?: {
top: string;
right: string;
bottom: string;
left: string;
};
/** 是否打印背景 */
printBackground?: boolean;
/** 自定义 CSS */
customCSS?: string;
}
/**
* 导出结果
*/
export interface ExportResult {
/** 成功标志 */
success: boolean;
/** 文件路径 */
filePath?: string;
/** 错误信息 */
error?: string;
}
/**
* 导出管理器类
*/
export class ExportManager {
private htmlTemplate = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<style>
{{customCSS}}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 24px;
margin-bottom: 16px;
}
h1 { font-size: 2em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.25em; }
pre {
background: #f6f8fa;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
}
code {
background: #f6f8fa;
padding: 2px 4px;
border-radius: 3px;
font-family: 'FiraCode', Monaco, Consolas, monospace;
}
table {
border-collapse: collapse;
width: 100%;
}
table th, table td {
border: 1px solid #dfe2e5;
padding: 8px 12px;
}
blockquote {
border-left: 4px solid #dfe2e5;
padding-left: 16px;
color: #6a737d;
}
.mermaid {
text-align: center;
margin: 20px 0;
}
</style>
</head>
<body>
{{content}}
</body>
</html>
`;
/**
* 导出为 PDF
*/
async exportToPDF(
content: string,
config: ExportConfig = {}
): Promise<ExportResult> {
try {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
// 生成 HTML
const html = this.generateHTML(content, config);
// 设置内容
await page.setContent(html, { waitUntil: 'networkidle0' });
// 等待图片和 Mermaid 图表加载
await page.waitForTimeout(1000);
// 导出 PDF
const pdf = await page.pdf({
format: config.format || 'A4',
margin: config.margin || {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm',
},
printBackground: config.printBackground !== false,
displayHeaderFooter: false,
});
await browser.close();
// 保存文件
const filename = config.filename || `document-${Date.now()}.pdf`;
// 这里可以添加保存文件的逻辑
return {
success: true,
filePath: filename,
};
} catch (error) {
console.error('PDF 导出失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
/**
* 导出为 HTML
*/
async exportToHTML(
content: string,
config: ExportConfig = {}
): Promise<ExportResult> {
try {
const html = this.generateHTML(content, config);
const filename = config.filename || `document-${Date.now()}.html`;
// 保存 HTML 文件
// await writeFile(filename, html);
return {
success: true,
filePath: filename,
};
} catch (error) {
console.error('HTML 导出失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
/**
* 导出为图片
*/
async exportToImage(
content: string,
config: ExportConfig = {}
): Promise<ExportResult> {
try {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
const html = this.generateHTML(content, config);
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.waitForTimeout(1000);
// 截取整页
const screenshot = await page.screenshot({
fullPage: true,
type: 'png',
});
await browser.close();
const filename = config.filename || `document-${Date.now()}.png`;
// 保存图片文件
// await writeFile(filename, screenshot);
return {
success: true,
filePath: filename,
};
} catch (error) {
console.error('图片导出失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
/**
* 生成 HTML
*/
private generateHTML(content: string, config: ExportConfig): string {
let html = this.htmlTemplate;
// 替换标题
html = html.replace('{{title}}', 'MindFlow Document');
// 替换内容
html = html.replace('{{content}}', content);
// 添加自定义 CSS
const customCSS = config.customCSS || '';
html = html.replace('{{customCSS}}', customCSS);
// 应用主题
if (config.theme === 'dark') {
html = this.applyDarkTheme(html);
}
return html;
}
/**
* 应用深色主题
*/
private applyDarkTheme(html: string): string {
const darkCSS = `
body {
background: #0f172a;
color: #e2e8f0;
}
pre {
background: #1e293b;
}
code {
background: #1e293b;
}
table th, table td {
border-color: #334155;
}
blockquote {
border-color: #334155;
color: #94a3b8;
}
`;
return html.replace('</style>', `${darkCSS}</style>`);
}
}
4.7 性能优化 - 虚拟滚动
虚拟滚动组件 (packages/core/src/virtual-scroll.ts)
/**
* @fileoverview 虚拟滚动组件
* @description 用于处理大文件的性能优化
*/
/**
* 虚拟滚动配置
*/
export interface VirtualScrollConfig {
/** 容器高度 */
containerHeight: number;
/** 行高 */
rowHeight: number;
/** 缓冲区大小 */
bufferSize?: number;
/** 总行数 */
totalRows: number;
}
/**
* 虚拟滚动状态
*/
export interface VirtualScrollState {
/** 起始索引 */
startIndex: number;
/** 结束索引 */
endIndex: number;
/** 偏移量 */
offset: number;
}
/**
* 虚拟滚动类
*/
export class VirtualScroll {
private config: VirtualScrollConfig;
private bufferSize: number;
private state: VirtualScrollState;
constructor(config: VirtualScrollConfig) {
this.config = {
bufferSize: 5,
...config,
};
this.bufferSize = this.config.bufferSize || 5;
this.state = {
startIndex: 0,
endIndex: 0,
offset: 0,
};
}
/**
* 计算可见范围
*/
calculateVisibleRange(scrollTop: number): VirtualScrollState {
const { containerHeight, rowHeight, totalRows } = this.config;
// 计算当前可见区域
const startRow = Math.floor(scrollTop / rowHeight);
const endRow = Math.min(
startRow + Math.ceil(containerHeight / rowHeight),
totalRows
);
// 添加缓冲区
const bufferStart = Math.max(0, startRow - this.bufferSize);
const bufferEnd = Math.min(totalRows, endRow + this.bufferSize);
// 计算偏移量
const offset = startRow * rowHeight - (startRow - bufferStart) * rowHeight;
this.state = {
startIndex: bufferStart,
endIndex: bufferEnd,
offset,
};
return this.state;
}
/**
* 获取可见行数
*/
getVisibleCount(): number {
return this.state.endIndex - this.state.startIndex;
}
/**
* 获取总高度
*/
getTotalHeight(): number {
return this.config.totalRows * this.config.rowHeight;
}
/**
* 获取容器高度
*/
getContainerHeight(): number {
return this.config.containerHeight;
}
/**
* 更新配置
*/
updateConfig(config: Partial<VirtualScrollConfig>): void {
this.config = {
...this.config,
...config,
};
}
}
/**
* 虚拟滚动 Hook(React)
*/
export function useVirtualScroll(config: VirtualScrollConfig) {
const [scrollTop, setScrollTop] = React.useState(0);
const virtualScrollRef = React.useRef<VirtualScroll>();
if (!virtualScrollRef.current) {
virtualScrollRef.current = new VirtualScroll(config);
} else {
virtualScrollRef.current.updateConfig(config);
}
const handleScroll = React.useCallback((e: React.UIEvent<HTMLDivElement>) => {
const newScrollTop = e.currentTarget.scrollTop;
setScrollTop(newScrollTop);
}, []);
const visibleRange = virtualScrollRef.current.calculateVisibleRange(scrollTop);
return {
scrollTop,
handleScroll,
visibleRange,
};
}
4.8 配置管理
配置管理器 (packages/core/src/config.ts)
/**
* @fileoverview 配置管理系统
* @description 负责应用配置的加载、保存和同步
*/
/**
* 应用配置接口
*/
export interface AppConfig {
/** 编辑器配置 */
editor: {
theme: 'light' | 'dark';
fontSize: number;
fontFamily: string;
lineHeight: number;
tabSize: number;
wordWrap: boolean;
lineNumbers: boolean;
minimap: boolean;
autoSave: boolean;
autoSaveDelay: number;
};
/** 文件配置 */
file: {
defaultPath: string;
recentFiles: string[];
maxRecentFiles: number;
};
/** 界面配置 */
ui: {
sidebarWidth: number;
previewWidth: number;
showMinimap: boolean;
compactMode: boolean;
};
/** 快捷键配置 */
shortcuts: Record<string, string>;
}
/**
* 默认配置
*/
const DEFAULT_CONFIG: AppConfig = {
editor: {
theme: 'light',
fontSize: 14,
fontFamily: 'FiraCode',
lineHeight: 1.6,
tabSize: 2,
wordWrap: true,
lineNumbers: true,
minimap: false,
autoSave: true,
autoSaveDelay: 1000,
},
file: {
defaultPath: '',
recentFiles: [],
maxRecentFiles: 10,
},
ui: {
sidebarWidth: 250,
previewWidth: 400,
showMinimap: false,
compactMode: false,
},
shortcuts: {
'save': 'Ctrl+S',
'open': 'Ctrl+O',
'new': 'Ctrl+N',
'bold': 'Ctrl+B',
'italic': 'Ctrl+I',
'heading': 'Ctrl+H',
},
};
/**
* 配置管理器类
*/
export class ConfigManager {
private config: AppConfig;
private listeners: Set<(config: AppConfig) => void> = new Set();
private readonly STORAGE_KEY = 'mindflow-config';
constructor() {
this.config = this.loadConfig();
}
/**
* 加载配置
*/
private loadConfig(): AppConfig {
try {
const stored = localStorage.getItem(this.STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
return this.mergeConfig(DEFAULT_CONFIG, parsed);
}
} catch (error) {
console.error('加载配置失败:', error);
}
return { ...DEFAULT_CONFIG };
}
/**
* 保存配置
*/
private saveConfig(): void {
try {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));
} catch (error) {
console.error('保存配置失败:', error);
}
}
/**
* 合并配置
*/
private mergeConfig(defaultConfig: AppConfig, userConfig: Partial<AppConfig>): AppConfig {
const merged = { ...defaultConfig };
function deepMerge(target: any, source: any): any {
for (const key in source) {
if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) {
target[key] = deepMerge(target[key] || {}, source[key]);
} else if (source[key] !== undefined) {
target[key] = source[key];
}
}
return target;
}
return deepMerge(merged, userConfig);
}
/**
* 获取配置
*/
getConfig(): AppConfig {
return { ...this.config };
}
/**
* 更新配置
*/
updateConfig(updates: Partial<AppConfig>): void {
this.config = this.mergeConfig(this.config, updates);
this.saveConfig();
this.notifyListeners();
}
/**
* 重置配置
*/
resetConfig(): void {
this.config = { ...DEFAULT_CONFIG };
this.saveConfig();
this.notifyListeners();
}
/**
* 订阅配置变化
*/
subscribe(callback: (config: AppConfig) => void): () => void {
this.listeners.add(callback);
return () => {
this.listeners.delete(callback);
};
}
/**
* 通知监听器
*/
private notifyListeners(): void {
this.listeners.forEach((listener) => {
try {
listener(this.getConfig());
} catch (error) {
console.error('配置监听器执行错误:', error);
}
});
}
/**
* 导出配置
*/
exportConfig(): string {
return JSON.stringify(this.config, null, 2);
}
/**
* 导入配置
*/
importConfig(configJson: string): boolean {
try {
const config = JSON.parse(configJson);
this.updateConfig(config);
return true;
} catch (error) {
console.error('导入配置失败:', error);
return false;
}
}
}
4.9 Tauri Rust 后端示例
命令处理 (src-tauri/src/commands.rs)
/**
* Tauri 命令处理模块
* 处理文件系统、窗口管理等原生功能
*/
use std::fs;
use std::path::Path;
use tauri::{AppHandle, State};
use serde::{Deserialize, Serialize};
/// 文件信息结构体
#[derive(Debug, Serialize, Deserialize)]
pub struct FileInfo {
pub path: String,
pub name: String,
pub size: u64,
pub modified: u64,
pub is_directory: bool,
}
/// 文件变化事件
#[derive(Debug, Serialize, Deserialize)]
pub struct FileChangeEvent {
pub event_type: String, // "create", "modify", "remove"
pub path: String,
}
/// 读取文件内容
#[tauri::command]
pub async fn read_file(file_path: &str) -> Result<String, String> {
match fs::read_to_string(file_path) {
Ok(content) => Ok(content),
Err(e) => Err(e.to_string()),
}
}
/// 写入文件内容
#[tauri::command]
pub async fn write_file(file_path: &str, content: &str) -> Result<(), String> {
match fs::write(file_path, content) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
/// 获取文件统计信息
#[tauri::command]
pub async fn get_file_stats(file_path: &str) -> Result<FileInfo, String> {
let path = Path::new(file_path);
if !path.exists() {
return Err("文件不存在".to_string());
}
let metadata = fs::metadata(file_path)
.map_err(|e| e.to_string())?;
Ok(FileInfo {
path: file_path.to_string(),
name: path.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string(),
size: metadata.len(),
modified: metadata.modified()
.map(|t| t.elapsed().unwrap_or_default().as_secs())
.unwrap_or(0),
is_directory: metadata.is_dir(),
})
}
/// 读取目录
#[tauri::command]
pub async fn read_directory(dir_path: &str) -> Result<Vec<FileInfo>, String> {
let path = Path::new(dir_path);
if !path.exists() || !path.is_dir() {
return Err("目录不存在".to_string());
}
let entries = fs::read_dir(dir_path)
.map_err(|e| e.to_string())?;
let mut files = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
let metadata = entry.metadata().map_err(|e| e.to_string())?;
files.push(FileInfo {
path: path.to_str().unwrap_or("").to_string(),
name: path.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string(),
size: metadata.len(),
modified: metadata.modified()
.map(|t| t.elapsed().unwrap_or_default().as_secs())
.unwrap_or(0),
is_directory: metadata.is_dir(),
});
}
Ok(files)
}
/// 创建目录
#[tauri::command]
pub async fn create_directory(dir_path: &str) -> Result<(), String> {
match fs::create_dir_all(dir_path) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
/// 删除文件或目录
#[tauri::command]
pub async fn remove_path(path: &str) -> Result<(), String> {
let path = Path::new(path);
if path.is_dir() {
match fs::remove_dir_all(path) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
} else {
match fs::remove_file(path) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
}
/// 重命名文件
#[tauri::command]
pub async fn rename_path(old_path: &str, new_path: &str) -> Result<(), String> {
match fs::rename(old_path, new_path) {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
/// 检查文件是否存在
#[tauri::command]
pub async fn path_exists(path: &str) -> bool {
Path::new(path).exists()
}
/// 获取应用数据目录
#[tauri::command]
pub async fn get_app_data_dir(app: AppHandle) -> Result<String, String> {
let path = app.path_resolver()
.app_data_dir()
.ok_or("无法获取应用数据目录")?;
Ok(path.to_str().unwrap_or("").to_string())
}
/// 监听文件变化(示例)
#[tauri::command]
pub async fn watch_file(
file_path: &str,
app: AppHandle,
) -> Result<(), String> {
// 这里可以实现文件监听逻辑
// 使用 notify 或其他文件系统监控库
println!("监听文件变化: {}", file_path);
Ok(())
}
主入口文件 (src-tauri/src/main.rs)
/**
* MindFlow Tauri 应用主入口
*/
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod commands;
mod utils;
use commands::*;
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu, WindowEvent};
fn main() {
// 创建菜单
let app_menu = Submenu::new(
"应用",
Menu::new()
.add_native_item(MenuItem::About("MindFlow".to_string()))
.add_native_item(MenuItem::Separator)
.add_item(CustomMenuItem::new("preferences", "偏好设置"))
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Quit),
);
let edit_menu = Submenu::new(
"编辑",
Menu::new()
.add_native_item(MenuItem::Cut)
.add_native_item(MenuItem::Copy)
.add_native_item(MenuItem::Paste)
.add_native_item(MenuItem::SelectAll)
.add_native_item(MenuItem::Separator)
.add_item(CustomMenuItem::new("find", "查找")),
);
let view_menu = Submenu::new(
"视图",
Menu::new()
.add_item(CustomMenuItem::new("sidebar", "显示侧边栏"))
.add_item(CustomMenuItem::new("preview", "显示预览"))
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::ToggleFullScreen),
);
let menu = Menu::new()
.add_submenu(app_menu)
.add_submenu(edit_menu)
.add_submenu(view_menu);
tauri::Builder::default()
.menu(menu)
.on_menu_event(|event| match event.menu_item_id() {
"preferences" => {
println!("打开偏好设置");
// TODO: 实现偏好设置窗口
}
"find" => {
println!("打开查找对话框");
// TODO: 实现查找功能
}
"sidebar" => {
println!("切换侧边栏显示");
// TODO: 切换侧边栏
}
"preview" => {
println!("切换预览显示");
// TODO: 切换预览
}
_ => {}
})
.on_window_event(|event| match event.event() {
WindowEvent::CloseRequested { api, .. } => {
println!("窗口关闭请求");
// 可以在这里添加确认对话框
api.prevent_close();
}
_ => {}
})
.invoke_handler(tauri::generate_handler![
// 文件操作
read_file,
write_file,
get_file_stats,
read_directory,
create_directory,
remove_path,
rename_path,
path_exists,
get_app_data_dir,
watch_file,
])
.run(tauri::generate_context!())
.expect("运行 Tauri 应用失败");
}
应用配置 (src-tauri/Cargo.toml)
[package]
name = "mindflow"
version = "0.1.0"
description = "A minimalist Markdown editor with multi-platform support"
authors = ["MindFlow Team"]
license = "MIT"
repository = "https://github.com/mindflow/editor"
homepage = "https://mindflow.app"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2.0.0", features = [] }
[dependencies]
# Tauri 核心
tauri = { version = "2.0.0", features = ["shell-open"] }
tauri-plugin-shell = "2.0.0"
# 序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 文件系统
notify = "6.0"
# 日志
log = "0.4"
fern = "0.6"
# 错误处理
anyhow = "1.0"
[features]
# Tauri 功能
custom-protocol = ["tauri/custom-protocol"]
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
这组 Rust 代码展示了:
- 原生性能:使用 Rust 处理文件系统操作
- 安全权限:Tauri 的权限最小化原则
- 事件驱动:文件监听和窗口事件处理
- 菜单集成:原生菜单栏支持
官方网址:https://gpunexus.com/
咨询电话:010-53650986
联系邮箱:data@chengfangtech.com
更多推荐


所有评论(0)