12 KiB
12 KiB
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/commands/ # 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 // 排序权重
}
注册新模块
- 创建
src/modules/<name>/index.ts,导出moduleConfig - 在
src/modules/index.ts中添加导入 - 在
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/commands/<name>.rs:
// commands/module_name.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 commands;
use commands::module_name::{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-plugin-autostart |
开机自启 | cargo add tauri-plugin-autostart |
tauri-plugin-tray |
系统托盘 | cargo add tauri-plugin-tray |
tauri-plugin-log |
日志系统 | cargo add tauri-plugin-log |
tauri-plugin-shell |
执行命令 | cargo add tauri-plugin-shell |
tauri-plugin-clipboard-manager |
剪贴板 | cargo add tauri-plugin-clipboard-manager |
tauri-plugin-window-state |
窗口状态 | cargo add tauri-plugin-window-state |
进程管理
对于需要管理外部进程的模块(如 mihomo、aria2),使用内置的 ProcessManager:
架构
- 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 命令
| 命令 | 参数 | 返回值 |
|---|---|---|
start_process |
StartProcessParams |
ProcessInfo |
stop_process |
id: String |
() |
get_process_status |
id: String |
Option<ProcessInfo> |
get_all_process_status |
- | Vec<ProcessInfo> |
stop_all_processes |
- | () |
事件
| 事件名 | 载荷 | 触发时机 |
|---|---|---|
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": "../node_modules/@tauri-apps/cli/schemas/desktop-capability.json",
"identifier": "default",
"description": "Default capabilities for the app",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-execute",
"shell:allow-spawn",
"path:allow-app-data-dir",
"path:allow-read",
"path:allow-write"
]
}
调试指南
前端调试
- 使用 Chrome DevTools:
Ctrl+Shift+I - 打印日志:
console.log()
Rust 调试
- 使用
println!()输出到终端 - 使用
dbg!()宏调试变量 - 使用 Visual Studio Code 的 Rust 调试插件
构建问题排查
- Rust 编译错误:检查
Cargo.toml依赖版本 - 前端构建错误:检查
package.json依赖和 TypeScript 类型 - Tauri 配置错误:检查
tauri.conf.json路径和权限配置
常见问题
Windows 路径问题
使用 std::path::Path 处理路径,避免硬编码分隔符:
let path = Path::new("data").join("config.json");
Tauri 2 权限问题
确保在 capabilities 中声明所需权限,否则命令调用会失败。
WebView2 兼容性
确保用户安装了 Microsoft Edge WebView2 Runtime:
开发流程
- 创建模块目录:
src/modules/<module-name>/,编写组件和index.ts配置 - 注册模块:在
src/modules/index.ts中添加导入,在src/modules/icons.ts中添加图标映射 - 实现 Rust 命令:在
src-tauri/src/中编写后端逻辑,在lib.rs中注册命令 - 配置权限:更新
capabilities/default.json(如需要) - 测试:运行
bun run tauri dev测试 - 构建:运行
bun run tauri build构建生产版本
模板代码生成
快速生成模块
使用以下命令创建新模块结构:
# 创建前端模块目录
mkdir -p src/modules/new-module
# 创建 Rust 命令文件
touch src-tauri/src/commands/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!({}))
}
注意事项
- 避免阻塞主线程:Rust 命令使用
async避免阻塞 - 资源清理:在组件卸载时清理定时器和事件监听器
- 错误处理:所有操作都应有错误处理和用户提示
- 安全考虑:避免执行未验证的用户输入作为命令参数
- 性能优化:对于高频更新的数据使用
watch和computed