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 主要交付物

  1. 可运行的桌面端和Web端空壳项目
    • 基于 Tauri 2.x 的桌面端架构
    • 基于 React 18 + Vite 的 Web 端架构
    • 完整的 TypeScript 类型支持
  2. CI/CD 流程
    • GitHub Actions 自动构建
    • 自动化测试和部署流程
    • 支持多平台构建
  3. 开发规范文档
  • 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐