Files
Thing/AI_DEV_GUIDE.md
T
2026-08-06 10:33:16 +08:00

13 KiB
Raw Permalink Blame History

AI Agent 开发指南

本文档为 AI Agent 开发本项目提供详细的规范和模板,帮助快速理解项目结构、技术栈和开发流程。

项目概览

  • 项目名称: Thing
  • 技术栈: Tauri 2 + Vue 3 + Vite + Pinia + shadcn-vue + Rust
  • 平台: Windows
  • 包管理: Bun

核心架构

模块系统

项目采用统一的模块注册机制。每个模块通过 src/modules/<name>/index.ts 导出 ModuleConfig 配置,由 src/modules/registry.ts 中的 moduleRegistry 单例统一管理。

src/modules/<module-name>/
├── index.ts              # 模块配置(ModuleConfig
├── ModuleComponent.vue   # 前端组件
src-tauri/src/            # Rust 后端逻辑(按功能分文件/模块目录)

模块配置类型

// src/types/module.ts
interface ModuleConfig {
  id: string                    // 唯一标识
  name: string                  // 显示名称
  icon: string                  // 图标标识(对应 icons.ts 中的 key
  description: string           // 模块描述
  category: ModuleCategory      // 分类:'network' | 'tool' | 'system' | 'media'
  defaultEnabled?: boolean      // 默认是否启用
  builtin?: boolean             // 是否内置模块(不可禁用)
  loader?: () => Promise<{ default: Component }>  // 懒加载
  component?: Component         // 直接组件引用(内置模块)
  searchItems?: SearchIndexItem[]  // 全局搜索项
  process?: ModuleProcessConfig    // 进程配置(需要子进程的模块)
  lifecycle?: ModuleLifecycle      // 生命周期钩子
  order?: number                   // 排序权重
}

注册新模块

  1. 创建 src/modules/<name>/index.ts,导出 moduleConfig
  2. src/modules/index.ts 中添加导入
  3. src/modules/icons.ts 中添加图标映射

IPC 通信

前端通过 Tauri API 调用 Rust 命令:

import { invoke } from '@tauri-apps/api/core'

// 调用 Rust 命令
const result = await invoke('module_command', { param: 'value' })

状态管理

使用 Pinia 管理全局状态,每个模块可拥有独立的 store:

// src/stores/moduleName.ts
import { defineStore } from 'pinia'

export const useModuleStore = defineStore('moduleName', {
  state: () => ({
    // 状态定义
  }),
  actions: {
    // 操作方法
  }
})

开发规范

命名规范

类型 格式 示例
组件 PascalCase ProxyManager.vue
函数 camelCase getSystemProxy()
文件 kebab-case proxy-manager.ts
常量 UPPER_CASE MAX_HISTORY_COUNT
Rust 模块 snake_case proxy.rs
Tauri 命令 snake_case get_system_proxy

代码风格

  • TypeScript: 使用 ESLint,配置在 .eslintrc.cjs
  • Rust: 使用 cargo fmt 格式化
  • 提交信息: Conventional Commits 格式

模块开发模板

前端模块模板

创建 src/modules/<module-name>/ 目录,包含以下文件:

// index.ts - 模块配置
import type { ModuleConfig } from '@/types/module'

export const moduleConfig: ModuleConfig = {
  id: 'module-name',
  name: '模块名称',
  icon: 'module-name',       // 需在 icons.ts 中添加映射
  description: '模块描述',
  category: 'tool',           // 'network' | 'tool' | 'system' | 'media'
  defaultEnabled: true,
  loader: () => import('./ModuleComponent.vue'),
  searchItems: [
    {
      title: '功能名称',
      description: '功能描述',
      keywords: ['关键词1', '关键词2']
    }
  ],
  order: 100
}

注册模块时,还需在 src/modules/index.ts 中添加导入,在 src/modules/icons.ts 中添加图标映射。

<!-- ModuleComponent.vue - 主组件 -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useModuleStore } from '@/stores/moduleName'

const store = useModuleStore()

// 组件逻辑
const data = ref(null)

const loadData = async () => {
  // 调用 Rust 命令
}

onMounted(() => {
  loadData()
})

onUnmounted(() => {
  // 清理资源
})
</script>

<template>
  <div class="module-container">
    <!-- 模块内容 -->
  </div>
</template>

<style scoped>
.module-container {
  padding: 20px;
}
</style>

Rust 命令模板

src-tauri/src/ 中创建模块文件(如 src-tauri/src/my_module.rssrc-tauri/src/my_module/ 目录),并在 lib.rs 中注册:

// src-tauri/src/my_module.rs
use serde::Serialize;
use tauri::State;

#[derive(Serialize)]
pub struct CommandResult<T> {
    pub success: bool,
    pub data: Option<T>,
    pub error: Option<String>,
}

#[tauri::command]
pub async fn module_get_data() -> CommandResult<Vec<String>> {
    match fetch_data() {
        Ok(data) => CommandResult {
            success: true,
            data: Some(data),
            error: None,
        },
        Err(e) => CommandResult {
            success: false,
            data: None,
            error: Some(e.to_string()),
        },
    }
}

#[tauri::command]
pub async fn module_set_data(data: String) -> CommandResult<bool> {
    match save_data(&data) {
        Ok(_) => CommandResult {
            success: true,
            data: Some(true),
            error: None,
        },
        Err(e) => CommandResult {
            success: false,
            data: None,
            error: Some(e.to_string()),
        },
    }
}

fn fetch_data() -> Result<Vec<String>, Box<dyn std::error::Error>> {
    // 实现逻辑
    Ok(vec![])
}

fn save_data(data: &str) -> Result<(), Box<dyn std::error::Error>> {
    // 实现逻辑
    Ok(())
}

注册 Rust 命令

src-tauri/src/lib.rs 中注册命令:

// lib.rs
mod my_module;

use my_module::{module_get_data, module_set_data};

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            greet,
            module_get_data,
            module_set_data
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

常用 Tauri 插件

项目当前使用的 Tauri 插件(见 Cargo.toml):

插件 用途
tauri-plugin-autostart 开机自启
tauri-plugin-opener 打开文件/目录/URL(绕过 IPC scope 限制)
tauri-plugin-dialog 文件/目录选择对话框

系统托盘(tray)和窗口效果(mica/acrylic)是 Tauri 2 内置能力,无需额外插件。日志系统为自建(src-tauri/src/logger.rs),未使用 tauri-plugin-log

进程管理

对于需要管理外部子进程的模块(如 mihomo),使用内置的 ProcessManager。下载器模块使用进程内自建下载引擎(src-tauri/src/download_engine/),不涉及外部子进程:

架构

  • Rust 端 (src-tauri/src/process_manager.rs)ProcessManager 通过 std::process::Command 管理子进程,支持启动、停止、崩溃检测和自动重启
  • 前端 (src/stores/processStore.ts)Pinia store,通过 invoke 调用 Rust 命令,通过 listen 接收进程状态变更事件
  • 模块配置:在模块的 index.ts 中通过 ModuleProcessConfig 声明进程信息

模块配置

// src/modules/proxy/index.ts
export const moduleConfig: ModuleConfig = {
  // ...
  process: {
    name: 'mihomo',
    executable: '',           // 运行时确定
    args: ['-f', 'config.yaml'],
    autoStart: false,         // 模块启用时是否自动启动
    restartOnCrash: true,     // 崩溃后自动重启
    maxRestarts: 3            // 最大重启次数(0 = 不限制)
  }
}

前端 API

import { useProcessStore } from '@/stores/processStore'

const processStore = useProcessStore()

// 通过模块 ID 启动进程(自动读取模块配置)
await processStore.startByModule('proxy')

// 停止进程
await processStore.stopByModule('proxy')

// 获取进程状态
const status = processStore.getProcessStatus('proxy')

// 监听状态变更(在 main.ts 中已初始化)
// processStore.initListener()

Rust 命令

命令 参数 返回值
process_start StartProcessParams ProcessInfo
process_stop id: String ()
process_status id: String Option<ProcessInfo>
process_all_status - Vec<ProcessInfo>
process_stop_all - ()

事件

事件名 载荷 触发时机
process-status-changed ProcessInfo 进程状态变更(崩溃、重启、停止)

数据存储

配置文件

使用 JSON 格式存储在应用数据目录:

// 获取应用数据目录
let app_dir = app.path().app_data_dir()?;
let config_path = app_dir.join("config.json");

SQLite

对于需要查询的数据(如剪贴板历史),使用 rusqlite crate

# Cargo.toml
rusqlite = { version = "0.30", features = ["bundled"] }

权限配置

src-tauri/capabilities/default.json 中配置权限(实际项目配置):

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "opener:default",
    "opener:allow-reveal-item-in-dir",
    "opener:allow-open-path",
    "dialog:default",
    "core:window:allow-minimize",
    "core:window:allow-maximize",
    "core:window:allow-close",
    "core:window:allow-toggle-maximize",
    "core:window:allow-hide",
    "core:window:allow-show",
    "core:window:allow-set-focus",
    "core:window:allow-start-dragging",
    "core:window:allow-set-effects",
    "core:window:allow-set-background-color",
    "core:window:allow-set-theme"
  ]
}

调试指南

前端调试

  • 使用 Chrome DevToolsCtrl+Shift+I
  • 打印日志:console.log()

Rust 调试

  • 使用 crate::logger::log_info / log_warn / log_error(或 log_line)写入统一日志文件,日志页面可过滤级别
  • 使用 dbg!() 宏调试变量(仅临时,提交前删除)
  • 使用 Visual Studio Code 的 Rust 调试插件

构建问题排查

  1. Rust 编译错误:检查 Cargo.toml 依赖版本
  2. 前端构建错误:检查 package.json 依赖和 TypeScript 类型
  3. Tauri 配置错误:检查 tauri.conf.json 路径和权限配置

常见问题

Windows 路径问题

使用 std::path::Path 处理路径,避免硬编码分隔符:

let path = Path::new("data").join("config.json");

Tauri 2 权限问题

确保在 capabilities 中声明所需权限,否则命令调用会失败。

WebView2 兼容性

确保用户安装了 Microsoft Edge WebView2 Runtime

开发流程

  1. 创建模块目录src/modules/<module-name>/,编写组件和 index.ts 配置
  2. 注册模块:在 src/modules/index.ts 中添加导入,在 src/modules/icons.ts 中添加图标映射
  3. 实现 Rust 命令:在 src-tauri/src/ 中编写后端逻辑,在 lib.rs 中注册命令
  4. 配置权限:更新 capabilities/default.json(如需要)
  5. 测试:运行 bun run tauri dev 测试
  6. 构建:运行 bun run tauri build 构建生产版本

模板代码生成

快速生成模块

使用以下命令创建新模块结构:

# 创建前端模块目录
mkdir -p src/modules/new-module

# 创建 Rust 模块文件
touch src-tauri/src/new_module.rs

模板文件

复制以下模板快速开始:

前端组件模板:

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const loading = ref(false)
const error = ref('')

const fetchData = async () => {
  loading.value = true
  try {
    // 调用 Rust 命令
  } catch (e) {
    error.value = (e as Error).message
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchData()
})
</script>

<template>
  <div v-if="loading" class="flex items-center justify-center py-8">
    <Loader2 class="animate-spin text-muted-foreground" :size="24" />
  </div>
  <div v-else-if="error" class="text-destructive p-4">{{ error }}</div>
  <div v-else class="content p-5">
    <!-- 内容 -->
  </div>
</template>

Rust 命令模板:

#[tauri::command]
pub async fn new_module_action() -> Result<serde_json::Value, String> {
    // 实现逻辑
    Ok(serde_json::json!({}))
}

注意事项

  1. 避免阻塞主线程Rust 命令使用 async 避免阻塞
  2. 资源清理:在组件卸载时清理定时器和事件监听器
  3. 错误处理:所有操作都应有错误处理和用户提示
  4. 安全考虑:避免执行未验证的用户输入作为命令参数
  5. 性能优化:对于高频更新的数据使用 watchcomputed

参考资源