docs: 添加项目开发规范文档

- 添加项目概览文档
- 添加前端开发规范
- 添加后端开发规范
- 添加数据库设计规范
- 添加UI/UX设计规范
- 添加开发指南
- 为项目提供完整的开发标准和最佳实践
This commit is contained in:
2025-07-31 13:19:53 +08:00
parent 10f3d93a19
commit dc2d287124
7 changed files with 3765 additions and 0 deletions

215
.promptx/README.md Normal file
View File

@@ -0,0 +1,215 @@
# MixVideo 开发规范文档
本目录包含 MixVideo 项目的完整开发规范和指南,旨在确保代码质量、开发效率和团队协作的一致性。
## 📁 文档结构
### 核心规范文档
#### 1. [项目概览](./project-overview.md)
- 项目简介和技术架构
- 核心功能模块介绍
- 四层架构设计详解
- 开发环境配置
- 核心设计原则
#### 2. [前端开发规范](./frontend-coding-standards.md)
- React + TypeScript 开发规范
- 组件设计模式和最佳实践
- 状态管理 (Zustand) 规范
- 服务层设计原则
- 样式规范 (TailwindCSS)
- 自定义 Hooks 开发指南
- 测试策略和规范
- 性能优化技巧
#### 3. [后端开发规范](./backend-coding-standards.md)
- Rust + Tauri 开发规范
- 四层架构实现指南
- 数据模型设计原则
- 仓库模式实现
- 业务服务层设计
- Tauri 命令开发规范
- 错误处理策略
- 测试和性能优化
#### 4. [数据库设计规范](./database-design-standards.md)
- SQLite 数据库设计规范
- 表结构设计原则
- 索引优化策略
- 数据库迁移管理
- 查询性能优化
- 备份和恢复策略
- 安全和审计规范
#### 5. [UI/UX 设计规范](./ui-ux-design-standards.md)
- 视觉设计系统
- 组件设计规范
- 布局和响应式设计
- 交互设计原则
- 动画和过渡效果
- 无障碍设计指南
- 性能优化规范
#### 6. [开发指南](./development-guide.md)
- 快速开始指南
- 开发工作流程
- 核心开发模式
- 测试策略
- 调试技巧
- 性能优化
- 常见问题解决
- 部署指南
## 🎯 使用指南
### 新开发者入门
1. 首先阅读 [项目概览](./project-overview.md) 了解项目整体架构
2. 根据开发角色阅读对应的规范文档:
- 前端开发者:[前端开发规范](./frontend-coding-standards.md)
- 后端开发者:[后端开发规范](./backend-coding-standards.md)
- 全栈开发者:阅读所有技术规范
3. 查看 [开发指南](./development-guide.md) 了解具体开发流程
4. 参考 [UI/UX 设计规范](./ui-ux-design-standards.md) 确保界面一致性
### 日常开发参考
- **开发新功能前**:查看相关规范确保设计符合项目标准
- **代码审查时**:使用规范文档中的检查清单
- **遇到问题时**:查看开发指南中的常见问题解决方案
- **性能优化时**:参考各规范文档中的性能优化章节
### 规范更新
- 规范文档会随着项目发展持续更新
- 重大变更会在团队会议中讨论
- 所有开发者都应关注规范文档的更新
## 🔧 技术栈概览
### 前端技术栈
- **框架**: React 18 + TypeScript 5.8
- **构建工具**: Vite 6.0
- **状态管理**: Zustand 4.4
- **UI框架**: TailwindCSS 3.4
- **图标**: Lucide React + Heroicons
- **路由**: React Router DOM 6.20
- **测试**: Vitest + Testing Library
### 后端技术栈
- **框架**: Tauri 2.0 + Rust 1.70+
- **数据库**: SQLite (WAL模式)
- **AI集成**: Google Gemini API
- **多媒体**: FFmpeg
- **异步**: Tokio + async/await
- **序列化**: Serde + JSON
### 开发工具
- **包管理**: PNPM 8.15 (Workspace)
- **代码检查**: ESLint + Clippy
- **格式化**: Prettier + rustfmt
- **版本控制**: Git + GitHub
## 📋 开发原则
### 1. 安全第一
- 严格的输入验证
- 安全的数据存储
- 防范常见安全漏洞
- 敏感信息保护
### 2. 性能优化
- 数据库查询优化
- 前端渲染优化
- 内存使用优化
- 网络请求优化
### 3. 用户体验
- 响应式设计
- 流畅的交互
- 清晰的反馈
- 无障碍支持
### 4. 代码质量
- 清晰的架构设计
- 完善的测试覆盖
- 详细的文档说明
- 一致的编码风格
### 5. 可维护性
- 模块化设计
- 松耦合架构
- 标准化接口
- 版本化管理
## 🚀 快速参考
### 常用命令
```bash
# 开发环境
pnpm tauri:dev
# 构建应用
pnpm tauri:build
# 运行测试
pnpm test
# 代码检查
pnpm lint
# 格式化代码
pnpm format
```
### 项目结构
```
mixvideo/
├── .promptx/ # 开发规范文档
├── apps/desktop/ # 主应用
│ ├── src/ # React前端
│ └── src-tauri/ # Rust后端
├── docs/ # 项目文档
└── packages/ # 共享包
```
### 重要链接
- [项目仓库](https://github.com/mixvideo/mixvideo)
- [问题追踪](https://github.com/mixvideo/mixvideo/issues)
- [发布记录](https://github.com/mixvideo/mixvideo/releases)
- [贡献指南](../CONTRIBUTING.md)
## 📞 支持和反馈
### 获取帮助
- **技术问题**: 在项目仓库创建 Issue
- **规范建议**: 通过 Pull Request 提交改进建议
- **团队讨论**: 参加定期的技术分享会议
### 贡献规范
1. Fork 项目仓库
2. 创建功能分支
3. 遵循开发规范
4. 提交 Pull Request
5. 通过代码审查
### 联系方式
- **项目负责人**: imeepos
- **技术讨论**: 项目 Discussions
- **紧急问题**: 项目 Issues
## 📝 更新日志
### v1.0.0 (2024-01-01)
- 初始版本发布
- 完整的开发规范体系
- 四层架构设计指南
- 前后端开发规范
- UI/UX 设计系统
### 后续更新
- 规范文档会根据项目发展持续更新
- 重大变更会在版本发布说明中体现
- 建议定期查看文档更新
---
**注意**: 本规范文档是项目开发的重要参考,所有开发者都应该熟悉并遵循这些规范。如有疑问或建议,欢迎通过项目仓库进行讨论。

View File

@@ -0,0 +1,950 @@
# MixVideo 后端开发规范 (Rust/Tauri)
## 技术栈规范
### 核心技术
- **Tauri 2.0**: 跨平台桌面应用框架
- **Rust 1.70+**: 系统编程语言
- **SQLite**: 嵌入式数据库 (WAL模式)
- **Tokio**: 异步运行时
- **Serde**: 序列化/反序列化
- **anyhow/thiserror**: 错误处理
### 依赖管理
```toml
[workspace.dependencies]
# Tauri 相关
tauri = { version = "2.0", features = ["api-all"] }
tauri-build = { version = "2.0", features = [] }
# 异步和并发
tokio = { version = "1.0", features = ["full"] }
futures = "0.3"
async-trait = "0.1"
# 序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 错误处理
anyhow = "1.0"
thiserror = "1.0"
# 数据库
rusqlite = { version = "0.29", features = ["bundled", "chrono"] }
chrono = { version = "0.4", features = ["serde"] }
# 日志
tracing = "0.1"
tracing-subscriber = "0.3"
```
## 四层架构设计
### 1. 基础设施层 (Infrastructure)
```rust
// src/infrastructure/mod.rs
pub mod database;
pub mod logging;
pub mod file_system;
pub mod gemini_service;
pub mod ffmpeg;
pub mod event_bus;
pub mod performance;
// 基础设施层职责:
// - 数据库连接管理
// - 外部服务集成
// - 文件系统操作
// - 日志和监控
// - 事件总线
```
### 2. 数据访问层 (Data)
```rust
// src/data/mod.rs
pub mod models;
pub mod repositories;
// 数据访问层职责:
// - 数据模型定义
// - 数据库操作封装
// - 查询优化
// - 事务管理
```
### 3. 业务逻辑层 (Business)
```rust
// src/business/mod.rs
pub mod services;
pub mod errors;
// 业务逻辑层职责:
// - 核心业务逻辑
// - 业务规则验证
// - 工作流编排
// - 领域模型
```
### 4. 表现层 (Presentation)
```rust
// src/presentation/mod.rs
pub mod commands;
// 表现层职责:
// - Tauri命令定义
// - 参数验证
// - 响应格式化
// - 错误转换
```
## 代码组织规范
### 模块结构
```rust
// lib.rs - 库入口
extern crate lazy_static;
// 四层架构模块
pub mod infrastructure;
pub mod data;
pub mod business;
pub mod presentation;
pub mod services;
// 应用配置
pub mod app_state;
pub mod config;
use app_state::AppState;
use presentation::commands;
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.manage(AppState::new())
.invoke_handler(tauri::generate_handler![
// 注册所有命令
commands::project_commands::create_project,
commands::material_commands::get_all_materials,
// ... 其他命令
])
.setup(|app| {
// 应用初始化逻辑
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
### 文件命名规范
- **模块文件**: snake_case (如 `material_service.rs`)
- **结构体**: PascalCase (如 `MaterialRepository`)
- **函数**: snake_case (如 `create_material`)
- **常量**: SCREAMING_SNAKE_CASE (如 `DEFAULT_PAGE_SIZE`)
## 数据模型规范
### 实体模型设计
```rust
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
/// 素材实体模型
/// 遵循 Tauri 开发规范的数据模型设计原则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Material {
pub id: String,
pub name: String,
pub path: String,
pub material_type: MaterialType,
pub size: u64,
pub duration: Option<u64>,
pub project_id: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MaterialType {
Video,
Audio,
Image,
}
impl Material {
/// 创建新的素材实例
pub fn new(request: CreateMaterialRequest) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
name: request.name,
path: request.path,
material_type: request.material_type,
size: request.size,
duration: request.duration,
project_id: request.project_id,
created_at: now,
updated_at: now,
is_active: true,
}
}
/// 验证素材数据
pub fn validate(&self) -> Result<(), String> {
if self.name.trim().is_empty() {
return Err("素材名称不能为空".to_string());
}
if self.path.trim().is_empty() {
return Err("素材路径不能为空".to_string());
}
Ok(())
}
/// 更新素材信息
pub fn update(&mut self, request: UpdateMaterialRequest) {
if let Some(name) = request.name {
self.name = name;
}
if let Some(material_type) = request.material_type {
self.material_type = material_type;
}
self.updated_at = Utc::now();
}
}
/// 创建素材请求
#[derive(Debug, Deserialize)]
pub struct CreateMaterialRequest {
pub name: String,
pub path: String,
pub material_type: MaterialType,
pub size: u64,
pub duration: Option<u64>,
pub project_id: String,
}
/// 更新素材请求
#[derive(Debug, Deserialize)]
pub struct UpdateMaterialRequest {
pub name: Option<String>,
pub material_type: Option<MaterialType>,
}
```
### 模型设计原则
1. **不可变性**: 优先使用不可变字段
2. **验证**: 内置数据验证方法
3. **序列化**: 支持JSON序列化
4. **文档**: 详细的文档注释
5. **测试**: 为模型编写单元测试
## 仓库模式规范
### 仓库接口设计
```rust
use async_trait::async_trait;
use anyhow::Result;
/// 素材仓库接口
/// 定义素材数据访问的抽象接口
#[async_trait]
pub trait MaterialRepositoryTrait {
async fn create(&self, material: &Material) -> Result<()>;
async fn get_by_id(&self, id: &str) -> Result<Option<Material>>;
async fn get_all(&self) -> Result<Vec<Material>>;
async fn get_by_project_id(&self, project_id: &str) -> Result<Vec<Material>>;
async fn update(&self, material: &Material) -> Result<()>;
async fn delete(&self, id: &str) -> Result<()>;
async fn search(&self, query: &MaterialQueryParams) -> Result<Vec<Material>>;
}
/// 素材仓库实现
/// 遵循 Tauri 开发规范的仓库模式
pub struct MaterialRepository {
database: Arc<Database>,
}
impl MaterialRepository {
pub fn new(database: Arc<Database>) -> Self {
Self { database }
}
}
#[async_trait]
impl MaterialRepositoryTrait for MaterialRepository {
async fn create(&self, material: &Material) -> Result<()> {
let conn = self.database.get_connection().await?;
conn.execute(
r#"
INSERT INTO materials (
id, name, path, material_type, size, duration,
project_id, created_at, updated_at, is_active
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
"#,
params![
material.id,
material.name,
material.path,
serde_json::to_string(&material.material_type)?,
material.size,
material.duration,
material.project_id,
material.created_at.to_rfc3339(),
material.updated_at.to_rfc3339(),
material.is_active,
],
)?;
Ok(())
}
async fn get_by_id(&self, id: &str) -> Result<Option<Material>> {
let conn = self.database.get_read_connection().await?;
let mut stmt = conn.prepare(
r#"
SELECT id, name, path, material_type, size, duration,
project_id, created_at, updated_at, is_active
FROM materials
WHERE id = ?1 AND is_active = 1
"#
)?;
let material = stmt.query_row(params![id], |row| {
Ok(Material {
id: row.get(0)?,
name: row.get(1)?,
path: row.get(2)?,
material_type: serde_json::from_str(&row.get::<_, String>(3)?)
.map_err(|e| rusqlite::Error::InvalidColumnType(3, "material_type".to_string(), rusqlite::types::Type::Text))?,
size: row.get(4)?,
duration: row.get(5)?,
project_id: row.get(6)?,
created_at: DateTime::parse_from_rfc3339(&row.get::<_, String>(7)?)
.map_err(|e| rusqlite::Error::InvalidColumnType(7, "created_at".to_string(), rusqlite::types::Type::Text))?
.with_timezone(&Utc),
updated_at: DateTime::parse_from_rfc3339(&row.get::<_, String>(8)?)
.map_err(|e| rusqlite::Error::InvalidColumnType(8, "updated_at".to_string(), rusqlite::types::Type::Text))?
.with_timezone(&Utc),
is_active: row.get(9)?,
})
}).optional()?;
Ok(material)
}
}
```
### 仓库设计原则
1. **接口分离**: 定义清晰的仓库接口
2. **异步操作**: 所有数据库操作都是异步的
3. **错误处理**: 统一的错误处理机制
4. **连接管理**: 合理使用数据库连接
5. **查询优化**: 优化SQL查询性能
## 业务服务规范
### 服务层设计
```rust
use anyhow::{anyhow, Result};
use crate::data::repositories::material_repository::MaterialRepository;
use crate::data::models::material::{Material, CreateMaterialRequest, UpdateMaterialRequest};
/// 素材管理服务
/// 遵循 Tauri 开发规范的业务服务设计
pub struct MaterialService;
impl MaterialService {
/// 创建素材
pub async fn create_material(
repository: &MaterialRepository,
request: CreateMaterialRequest,
) -> Result<Material> {
// 业务验证
if request.name.trim().is_empty() {
return Err(anyhow!("素材名称不能为空"));
}
if !std::path::Path::new(&request.path).exists() {
return Err(anyhow!("素材文件不存在"));
}
// 创建素材实例
let material = Material::new(request);
// 验证素材数据
material.validate()
.map_err(|e| anyhow!("素材验证失败: {}", e))?;
// 保存到数据库
repository.create(&material).await
.map_err(|e| anyhow!("创建素材失败: {}", e))?;
Ok(material)
}
/// 获取素材详情
pub async fn get_material_by_id(
repository: &MaterialRepository,
id: &str,
) -> Result<Option<Material>> {
repository.get_by_id(id).await
.map_err(|e| anyhow!("获取素材详情失败: {}", e))
}
/// 更新素材
pub async fn update_material(
repository: &MaterialRepository,
id: &str,
request: UpdateMaterialRequest,
) -> Result<Material> {
// 获取现有素材
let mut material = repository.get_by_id(id).await?
.ok_or_else(|| anyhow!("素材不存在"))?;
// 更新素材信息
material.update(request);
// 验证更新后的数据
material.validate()
.map_err(|e| anyhow!("素材验证失败: {}", e))?;
// 保存更新
repository.update(&material).await
.map_err(|e| anyhow!("更新素材失败: {}", e))?;
Ok(material)
}
/// 删除素材
pub async fn delete_material(
repository: &MaterialRepository,
id: &str,
) -> Result<()> {
// 检查素材是否存在
let material = repository.get_by_id(id).await?
.ok_or_else(|| anyhow!("素材不存在"))?;
// 执行删除
repository.delete(id).await
.map_err(|e| anyhow!("删除素材失败: {}", e))?;
Ok(())
}
}
```
### 服务设计原则
1. **业务封装**: 封装复杂的业务逻辑
2. **验证**: 完整的业务规则验证
3. **事务**: 合理使用数据库事务
4. **错误处理**: 详细的错误信息
5. **测试**: 完整的单元测试覆盖
## Tauri命令规范
### 命令定义
```rust
use tauri::{command, State};
use crate::app_state::AppState;
use crate::business::services::material_service::MaterialService;
use crate::data::models::material::{Material, CreateMaterialRequest, UpdateMaterialRequest};
/// 创建素材命令
/// 遵循 Tauri 开发规范的命令设计模式
#[command]
pub async fn create_material(
state: State<'_, AppState>,
request: CreateMaterialRequest,
) -> Result<Material, String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
MaterialService::create_material(repository, request)
.await
.map_err(|e| e.to_string())
}
/// 获取所有素材命令
#[command]
pub async fn get_all_materials(
state: State<'_, AppState>,
) -> Result<Vec<Material>, String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
repository.get_all()
.await
.map_err(|e| e.to_string())
}
/// 根据ID获取素材命令
#[command]
pub async fn get_material_by_id(
state: State<'_, AppState>,
id: String,
) -> Result<Option<Material>, String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
MaterialService::get_material_by_id(repository, &id)
.await
.map_err(|e| e.to_string())
}
/// 更新素材命令
#[command]
pub async fn update_material(
state: State<'_, AppState>,
id: String,
request: UpdateMaterialRequest,
) -> Result<Material, String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
MaterialService::update_material(repository, &id, request)
.await
.map_err(|e| e.to_string())
}
/// 删除素材命令
#[command]
pub async fn delete_material(
state: State<'_, AppState>,
id: String,
) -> Result<(), String> {
let repository_guard = state.get_material_repository()
.map_err(|e| format!("获取素材仓库失败: {}", e))?;
let repository = repository_guard.as_ref()
.ok_or("素材仓库未初始化")?;
MaterialService::delete_material(repository, &id)
.await
.map_err(|e| e.to_string())
}
```
### 命令设计原则
1. **参数验证**: 严格验证输入参数
2. **错误转换**: 将内部错误转换为用户友好的消息
3. **状态管理**: 正确使用Tauri状态管理
4. **异步处理**: 所有命令都是异步的
5. **文档**: 详细的命令文档
## 错误处理规范
### 错误类型定义
```rust
use thiserror::Error;
/// 业务错误类型
#[derive(Error, Debug)]
pub enum BusinessError {
#[error("验证错误: {message}")]
ValidationError { message: String },
#[error("资源不存在: {resource}")]
NotFound { resource: String },
#[error("权限不足: {operation}")]
PermissionDenied { operation: String },
#[error("业务规则冲突: {rule}")]
BusinessRuleViolation { rule: String },
#[error("外部服务错误: {service} - {message}")]
ExternalServiceError { service: String, message: String },
}
/// 数据库错误类型
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("连接错误: {0}")]
ConnectionError(#[from] rusqlite::Error),
#[error("事务错误: {message}")]
TransactionError { message: String },
#[error("查询错误: {query}")]
QueryError { query: String },
#[error("数据完整性错误: {constraint}")]
IntegrityError { constraint: String },
}
```
### 错误处理模式
```rust
use anyhow::{Context, Result};
/// 带上下文的错误处理
pub async fn process_material(id: &str) -> Result<Material> {
let material = repository.get_by_id(id)
.await
.with_context(|| format!("获取素材失败: {}", id))?
.ok_or_else(|| anyhow!("素材不存在: {}", id))?;
validate_material(&material)
.with_context(|| "素材验证失败")?;
Ok(material)
}
/// 错误转换
impl From<BusinessError> for String {
fn from(error: BusinessError) -> Self {
match error {
BusinessError::ValidationError { message } => {
format!("输入验证失败: {}", message)
}
BusinessError::NotFound { resource } => {
format!("找不到资源: {}", resource)
}
_ => error.to_string(),
}
}
}
```
## 测试规范
### 单元测试模板
```rust
#[cfg(test)]
mod tests {
use super::*;
use crate::infrastructure::database::Database;
use std::sync::Arc;
async fn setup_test_database() -> Arc<Database> {
let db = Database::new_in_memory().unwrap();
db.migrate().await.unwrap();
Arc::new(db)
}
#[tokio::test]
async fn test_create_material_success() {
// Arrange
let db = setup_test_database().await;
let repository = MaterialRepository::new(db);
let request = CreateMaterialRequest {
name: "Test Material".to_string(),
path: "/test/path".to_string(),
material_type: MaterialType::Video,
size: 1024,
duration: Some(60),
project_id: "project-1".to_string(),
};
// Act
let result = MaterialService::create_material(&repository, request).await;
// Assert
assert!(result.is_ok());
let material = result.unwrap();
assert_eq!(material.name, "Test Material");
assert_eq!(material.size, 1024);
}
#[tokio::test]
async fn test_create_material_validation_error() {
// Arrange
let db = setup_test_database().await;
let repository = MaterialRepository::new(db);
let request = CreateMaterialRequest {
name: "".to_string(), // 空名称应该失败
path: "/test/path".to_string(),
material_type: MaterialType::Video,
size: 1024,
duration: Some(60),
project_id: "project-1".to_string(),
};
// Act
let result = MaterialService::create_material(&repository, request).await;
// Assert
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("名称不能为空"));
}
}
```
### 集成测试
```rust
#[cfg(test)]
mod integration_tests {
use super::*;
use crate::app_state::AppState;
use tauri::test::{mock_app, MockRuntime};
#[tokio::test]
async fn test_material_crud_workflow() {
// 设置测试应用
let app = mock_app();
let state = AppState::new();
// 测试创建
let create_request = CreateMaterialRequest {
name: "Integration Test Material".to_string(),
path: "/test/integration".to_string(),
material_type: MaterialType::Video,
size: 2048,
duration: Some(120),
project_id: "integration-project".to_string(),
};
let created = create_material(state.clone(), create_request).await.unwrap();
// 测试读取
let retrieved = get_material_by_id(state.clone(), created.id.clone()).await.unwrap();
assert!(retrieved.is_some());
// 测试更新
let update_request = UpdateMaterialRequest {
name: Some("Updated Material".to_string()),
material_type: None,
};
let updated = update_material(state.clone(), created.id.clone(), update_request).await.unwrap();
assert_eq!(updated.name, "Updated Material");
// 测试删除
delete_material(state.clone(), created.id).await.unwrap();
}
}
```
## 性能优化规范
### 数据库优化
```rust
// 使用连接池
pub struct Database {
pool: Arc<ConnectionPool>,
}
impl Database {
pub async fn get_connection(&self) -> Result<PooledConnection> {
self.pool.get().await
.map_err(|e| anyhow!("获取数据库连接失败: {}", e))
}
// 读写分离
pub async fn get_read_connection(&self) -> Result<PooledConnection> {
self.pool.get_read_only().await
.map_err(|e| anyhow!("获取只读连接失败: {}", e))
}
}
// 批量操作优化
impl MaterialRepository {
pub async fn batch_create(&self, materials: &[Material]) -> Result<()> {
let conn = self.database.get_connection().await?;
let tx = conn.transaction()?;
for material in materials {
tx.execute(
"INSERT INTO materials (...) VALUES (...)",
// 参数
)?;
}
tx.commit()?;
Ok(())
}
}
```
### 内存优化
```rust
// 使用流式处理大量数据
pub async fn process_large_dataset<F>(
repository: &MaterialRepository,
processor: F,
) -> Result<()>
where
F: Fn(&Material) -> Result<()>,
{
let mut offset = 0;
const BATCH_SIZE: usize = 100;
loop {
let materials = repository.get_batch(offset, BATCH_SIZE).await?;
if materials.is_empty() {
break;
}
for material in materials {
processor(&material)?;
}
offset += BATCH_SIZE;
}
Ok(())
}
```
## 日志和监控规范
### 结构化日志
```rust
use tracing::{info, warn, error, debug, instrument};
#[instrument(skip(repository))]
pub async fn create_material(
repository: &MaterialRepository,
request: CreateMaterialRequest,
) -> Result<Material> {
info!(
material_name = %request.name,
project_id = %request.project_id,
"开始创建素材"
);
let material = Material::new(request);
match repository.create(&material).await {
Ok(_) => {
info!(
material_id = %material.id,
material_name = %material.name,
"素材创建成功"
);
Ok(material)
}
Err(e) => {
error!(
error = %e,
material_name = %material.name,
"素材创建失败"
);
Err(e)
}
}
}
```
### 性能监控
```rust
use std::time::Instant;
pub struct PerformanceMonitor {
metrics: Arc<Mutex<HashMap<String, Vec<Duration>>>>,
}
impl PerformanceMonitor {
pub fn record_operation<F, R>(&self, operation: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
let duration = start.elapsed();
let mut metrics = self.metrics.lock().unwrap();
metrics.entry(operation.to_string())
.or_insert_with(Vec::new)
.push(duration);
result
}
}
```
## 安全规范
### 输入验证
```rust
use validator::{Validate, ValidationError};
#[derive(Debug, Deserialize, Validate)]
pub struct CreateMaterialRequest {
#[validate(length(min = 1, max = 255, message = "名称长度必须在1-255字符之间"))]
pub name: String,
#[validate(custom = "validate_file_path")]
pub path: String,
#[validate(range(min = 1, message = "文件大小必须大于0"))]
pub size: u64,
}
fn validate_file_path(path: &str) -> Result<(), ValidationError> {
if !std::path::Path::new(path).exists() {
return Err(ValidationError::new("文件不存在"));
}
// 防止路径遍历攻击
if path.contains("..") {
return Err(ValidationError::new("非法路径"));
}
Ok(())
}
```
### SQL注入防护
```rust
// ✅ 使用参数化查询
conn.execute(
"SELECT * FROM materials WHERE name = ?1 AND project_id = ?2",
params![name, project_id],
)?;
// ❌ 避免字符串拼接
// let sql = format!("SELECT * FROM materials WHERE name = '{}'", name);
```
## 代码质量规范
### Clippy配置
```toml
# Cargo.toml
[lints.clippy]
all = "warn"
pedantic = "warn"
nursery = "warn"
cargo = "warn"
# 允许的lint
too_many_arguments = "allow"
module_name_repetitions = "allow"
```
### 代码审查清单
- [ ] 是否遵循四层架构设计
- [ ] 错误处理是否完整
- [ ] 是否有适当的日志记录
- [ ] 数据库操作是否优化
- [ ] 是否有相应的测试覆盖
- [ ] 文档注释是否完整
- [ ] 是否符合Rust最佳实践
- [ ] 输入验证是否完整
- [ ] 是否存在安全漏洞

View File

@@ -0,0 +1,579 @@
# MixVideo 数据库设计规范
## 数据库技术栈
### 核心技术
- **SQLite 3.40+**: 嵌入式数据库
- **WAL模式**: 提高并发性能
- **外键约束**: 保证数据一致性
- **连接池**: 支持高并发访问
- **读写分离**: 优化查询性能
### 数据库配置
```sql
-- 启用WAL模式
PRAGMA journal_mode = WAL;
-- 启用外键约束
PRAGMA foreign_keys = ON;
-- 设置同步模式
PRAGMA synchronous = NORMAL;
-- 设置缓存大小
PRAGMA cache_size = 10000;
-- 设置超时时间
PRAGMA busy_timeout = 5000;
-- 启用安全删除
PRAGMA secure_delete = ON;
-- 设置临时存储
PRAGMA temp_store = MEMORY;
```
## 表设计规范
### 命名规范
- **表名**: snake_case复数形式 (如 `materials`, `projects`)
- **字段名**: snake_case (如 `created_at`, `project_id`)
- **索引名**: `idx_表名_字段名` (如 `idx_materials_project_id`)
- **外键名**: `fk_表名_引用表名` (如 `fk_materials_projects`)
### 字段设计规范
```sql
-- 主键设计
id TEXT PRIMARY KEY NOT NULL, -- 使用UUID字符串
-- 时间戳字段
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
-- 软删除字段
is_active INTEGER NOT NULL DEFAULT 1,
-- 外键字段
project_id TEXT NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
-- 枚举字段使用TEXT存储JSON
material_type TEXT NOT NULL CHECK (material_type IN ('video', 'audio', 'image')),
-- 可选字段
description TEXT,
duration INTEGER, -- 可为NULL
-- 文件路径字段
path TEXT NOT NULL,
size INTEGER NOT NULL CHECK (size > 0),
```
## 核心表结构
### 1. 项目表 (projects)
```sql
CREATE TABLE projects (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
path TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_projects_name ON projects(name);
CREATE INDEX idx_projects_path ON projects(path);
CREATE INDEX idx_projects_created_at ON projects(created_at);
```
### 2. 素材表 (materials)
```sql
CREATE TABLE materials (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
path TEXT NOT NULL,
material_type TEXT NOT NULL CHECK (material_type IN ('video', 'audio', 'image')),
size INTEGER NOT NULL CHECK (size > 0),
duration INTEGER, -- 视频/音频时长(秒)
width INTEGER, -- 图片/视频宽度
height INTEGER, -- 图片/视频高度
fps REAL, -- 视频帧率
project_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);
-- 索引
CREATE INDEX idx_materials_project_id ON materials(project_id);
CREATE INDEX idx_materials_type ON materials(material_type);
CREATE INDEX idx_materials_name ON materials(name);
CREATE INDEX idx_materials_created_at ON materials(created_at);
CREATE UNIQUE INDEX idx_materials_path_project ON materials(path, project_id);
```
### 3. 模特表 (models)
```sql
CREATE TABLE models (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
stage_name TEXT,
gender TEXT NOT NULL CHECK (gender IN ('male', 'female', 'other')),
age INTEGER CHECK (age > 0 AND age < 150),
height INTEGER CHECK (height > 0 AND height < 300), -- 厘米
weight INTEGER CHECK (weight > 0 AND weight < 500), -- 公斤
measurements TEXT, -- JSON格式存储三围
description TEXT,
tags TEXT, -- JSON数组格式存储标签
avatar_path TEXT,
contact_info TEXT, -- JSON格式存储联系信息
social_media TEXT, -- JSON格式存储社交媒体
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'archived')),
rating REAL CHECK (rating >= 1.0 AND rating <= 5.0),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_models_name ON models(name);
CREATE INDEX idx_models_gender ON models(gender);
CREATE INDEX idx_models_status ON models(status);
CREATE INDEX idx_models_rating ON models(rating);
```
### 4. 模特照片表 (model_photos)
```sql
CREATE TABLE model_photos (
id TEXT PRIMARY KEY NOT NULL,
model_id TEXT NOT NULL,
photo_type TEXT NOT NULL CHECK (photo_type IN ('portrait', 'full_body', 'casual', 'formal', 'swimwear', 'other')),
path TEXT NOT NULL,
description TEXT,
is_primary INTEGER NOT NULL DEFAULT 0, -- 是否为主要照片
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);
-- 索引
CREATE INDEX idx_model_photos_model_id ON model_photos(model_id);
CREATE INDEX idx_model_photos_type ON model_photos(photo_type);
CREATE INDEX idx_model_photos_primary ON model_photos(is_primary);
CREATE UNIQUE INDEX idx_model_photos_path ON model_photos(path);
```
### 5. 模板表 (templates)
```sql
CREATE TABLE templates (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
canvas_config TEXT NOT NULL, -- JSON格式存储画布配置
duration INTEGER NOT NULL, -- 模板总时长(微秒)
fps REAL NOT NULL,
materials TEXT, -- JSON格式存储模板素材
tracks TEXT, -- JSON格式存储轨道信息
import_status TEXT NOT NULL DEFAULT 'pending' CHECK (import_status IN ('pending', 'processing', 'completed', 'failed')),
source_file_path TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_templates_name ON templates(name);
CREATE INDEX idx_templates_status ON templates(import_status);
CREATE INDEX idx_templates_created_at ON templates(created_at);
```
### 6. AI分类记录表 (video_classifications)
```sql
CREATE TABLE video_classifications (
id TEXT PRIMARY KEY NOT NULL,
material_id TEXT NOT NULL,
classification_result TEXT NOT NULL, -- JSON格式存储分类结果
confidence_score REAL,
processing_time INTEGER, -- 处理时间(毫秒)
ai_model_version TEXT,
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE
);
-- 索引
CREATE INDEX idx_video_classifications_material_id ON video_classifications(material_id);
CREATE INDEX idx_video_classifications_status ON video_classifications(status);
CREATE INDEX idx_video_classifications_created_at ON video_classifications(created_at);
```
### 7. 穿搭图片表 (outfit_images)
```sql
CREATE TABLE outfit_images (
id TEXT PRIMARY KEY NOT NULL,
model_id TEXT NOT NULL,
image_path TEXT NOT NULL,
prompt TEXT,
negative_prompt TEXT,
generation_params TEXT, -- JSON格式存储生成参数
is_favorite INTEGER NOT NULL DEFAULT 0,
tags TEXT, -- JSON数组格式存储标签
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);
-- 索引
CREATE INDEX idx_outfit_images_model_id ON outfit_images(model_id);
CREATE INDEX idx_outfit_images_favorite ON outfit_images(is_favorite);
CREATE INDEX idx_outfit_images_created_at ON outfit_images(created_at);
CREATE UNIQUE INDEX idx_outfit_images_path ON outfit_images(image_path);
```
### 8. 对话记录表 (conversations)
```sql
CREATE TABLE conversations (
id TEXT PRIMARY KEY NOT NULL,
session_id TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
content TEXT NOT NULL,
metadata TEXT, -- JSON格式存储元数据
created_at TEXT NOT NULL DEFAULT (datetime('now')),
is_active INTEGER NOT NULL DEFAULT 1
);
-- 索引
CREATE INDEX idx_conversations_session_id ON conversations(session_id);
CREATE INDEX idx_conversations_role ON conversations(role);
CREATE INDEX idx_conversations_created_at ON conversations(created_at);
```
## 数据库迁移规范
### 迁移文件结构
```
migrations/
├── 001_initial_schema.sql
├── 002_add_model_photos.sql
├── 003_add_video_classifications.sql
├── 004_add_outfit_images.sql
└── 005_add_conversations.sql
```
### 迁移脚本模板
```sql
-- Migration: 001_initial_schema.sql
-- Description: 创建初始数据库结构
-- Date: 2024-01-01
BEGIN TRANSACTION;
-- 创建项目表
CREATE TABLE projects (
-- 表结构定义
);
-- 创建索引
CREATE INDEX idx_projects_name ON projects(name);
-- 插入初始数据(如果需要)
INSERT INTO projects (id, name, path) VALUES
('default', 'Default Project', '/default/path');
COMMIT;
```
### 迁移管理
```rust
pub struct DatabaseMigration {
version: u32,
description: String,
sql: String,
}
impl Database {
pub async fn migrate(&self) -> Result<()> {
// 创建迁移记录表
self.create_migration_table().await?;
// 获取当前版本
let current_version = self.get_current_version().await?;
// 执行待处理的迁移
let migrations = self.get_pending_migrations(current_version)?;
for migration in migrations {
self.execute_migration(&migration).await?;
}
Ok(())
}
}
```
## 查询优化规范
### 索引策略
```sql
-- 单列索引
CREATE INDEX idx_materials_project_id ON materials(project_id);
-- 复合索引
CREATE INDEX idx_materials_project_type ON materials(project_id, material_type);
-- 唯一索引
CREATE UNIQUE INDEX idx_projects_path ON projects(path);
-- 部分索引
CREATE INDEX idx_active_materials ON materials(project_id) WHERE is_active = 1;
```
### 查询优化
```sql
-- ✅ 使用索引的查询
SELECT * FROM materials
WHERE project_id = ? AND material_type = 'video'
ORDER BY created_at DESC
LIMIT 20;
-- ✅ 使用EXISTS而不是IN
SELECT * FROM projects p
WHERE EXISTS (
SELECT 1 FROM materials m
WHERE m.project_id = p.id AND m.is_active = 1
);
-- ✅ 使用适当的JOIN
SELECT p.name, COUNT(m.id) as material_count
FROM projects p
LEFT JOIN materials m ON p.id = m.project_id AND m.is_active = 1
WHERE p.is_active = 1
GROUP BY p.id, p.name;
```
### 分页查询
```sql
-- 基于OFFSET的分页适用于小数据集
SELECT * FROM materials
WHERE project_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?;
-- 基于游标的分页(适用于大数据集)
SELECT * FROM materials
WHERE project_id = ? AND created_at < ?
ORDER BY created_at DESC
LIMIT ?;
```
## 数据完整性规范
### 外键约束
```sql
-- 级联删除
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
-- 限制删除
FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE RESTRICT
-- 设置NULL
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
```
### 检查约束
```sql
-- 数值范围检查
CHECK (age > 0 AND age < 150)
-- 枚举值检查
CHECK (status IN ('active', 'inactive', 'archived'))
-- 字符串长度检查
CHECK (length(name) > 0 AND length(name) <= 255)
```
### 触发器
```sql
-- 自动更新时间戳
CREATE TRIGGER update_materials_timestamp
AFTER UPDATE ON materials
FOR EACH ROW
BEGIN
UPDATE materials SET updated_at = datetime('now') WHERE id = NEW.id;
END;
-- 软删除触发器
CREATE TRIGGER soft_delete_materials
INSTEAD OF DELETE ON materials
FOR EACH ROW
BEGIN
UPDATE materials SET is_active = 0, updated_at = datetime('now') WHERE id = OLD.id;
END;
```
## 备份和恢复规范
### 备份策略
```rust
pub struct DatabaseBackup {
source_path: PathBuf,
backup_dir: PathBuf,
}
impl DatabaseBackup {
pub async fn create_backup(&self) -> Result<PathBuf> {
let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
let backup_name = format!("mixvideo_backup_{}.db", timestamp);
let backup_path = self.backup_dir.join(backup_name);
// 使用SQLite的备份API
let source = Connection::open(&self.source_path)?;
let backup = Connection::open(&backup_path)?;
let backup_handle = rusqlite::backup::Backup::new(&source, &backup)?;
backup_handle.run_to_completion(5, Duration::from_millis(250), None)?;
Ok(backup_path)
}
pub async fn restore_backup(&self, backup_path: &Path) -> Result<()> {
// 验证备份文件
self.validate_backup(backup_path)?;
// 创建当前数据库的备份
let current_backup = self.create_backup().await?;
// 恢复数据库
std::fs::copy(backup_path, &self.source_path)?;
// 验证恢复的数据库
self.validate_database().await?;
Ok(())
}
}
```
### 数据验证
```sql
-- 检查数据完整性
PRAGMA integrity_check;
-- 检查外键约束
PRAGMA foreign_key_check;
-- 分析数据库统计信息
ANALYZE;
```
## 性能监控规范
### 查询性能分析
```sql
-- 启用查询计划分析
EXPLAIN QUERY PLAN SELECT * FROM materials WHERE project_id = ?;
-- 查看表统计信息
SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index';
-- 检查表大小
SELECT
name,
COUNT(*) as row_count,
SUM(length(sql)) as size_estimate
FROM sqlite_master
WHERE type = 'table'
GROUP BY name;
```
### 性能监控
```rust
pub struct DatabaseMetrics {
query_times: HashMap<String, Vec<Duration>>,
connection_pool_stats: ConnectionPoolStats,
}
impl DatabaseMetrics {
pub fn record_query_time(&mut self, query: &str, duration: Duration) {
self.query_times
.entry(query.to_string())
.or_insert_with(Vec::new)
.push(duration);
}
pub fn get_slow_queries(&self, threshold: Duration) -> Vec<(String, Duration)> {
self.query_times
.iter()
.filter_map(|(query, times)| {
let avg_time = times.iter().sum::<Duration>() / times.len() as u32;
if avg_time > threshold {
Some((query.clone(), avg_time))
} else {
None
}
})
.collect()
}
}
```
## 安全规范
### 数据加密
```rust
// 敏感数据加密存储
pub fn encrypt_sensitive_data(data: &str, key: &[u8]) -> Result<String> {
// 使用AES加密
let cipher = Aes256Gcm::new(key.into());
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher.encrypt(&nonce, data.as_bytes())?;
// 组合nonce和密文
let mut result = nonce.to_vec();
result.extend_from_slice(&ciphertext);
Ok(base64::encode(result))
}
```
### 访问控制
```sql
-- 创建只读视图
CREATE VIEW materials_readonly AS
SELECT id, name, material_type, size, created_at
FROM materials
WHERE is_active = 1;
-- 限制敏感字段访问
CREATE VIEW models_public AS
SELECT id, name, stage_name, gender, description, tags, avatar_path, rating
FROM models
WHERE is_active = 1;
```
### 审计日志
```sql
CREATE TABLE audit_logs (
id TEXT PRIMARY KEY NOT NULL,
table_name TEXT NOT NULL,
operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
record_id TEXT NOT NULL,
old_values TEXT, -- JSON格式
new_values TEXT, -- JSON格式
user_id TEXT,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
```

View File

@@ -0,0 +1,531 @@
# MixVideo 开发指南
## 快速开始
### 环境准备
```bash
# 检查环境要求
node --version # >= 18.0.0
rustc --version # >= 1.70.0
pnpm --version # >= 8.0.0
# 克隆项目
git clone <repository-url>
cd mixvideo
# 安装依赖
pnpm install
# 启动开发环境
pnpm tauri:dev
```
### 项目结构概览
```
mixvideo/
├── .promptx/ # 开发规范文档
├── apps/desktop/ # 主应用
│ ├── src/ # React前端
│ └── src-tauri/ # Rust后端
├── docs/ # 项目文档
├── package.json # 根配置
├── pnpm-workspace.yaml # 工作空间配置
└── Cargo.toml # Rust工作空间
```
## 开发工作流
### 1. 功能开发流程
```bash
# 1. 创建功能分支
git checkout -b feature/新功能名称
# 2. 开发和测试
pnpm tauri:dev # 启动开发服务器
pnpm test # 运行测试
# 3. 代码检查
pnpm lint # ESLint检查
pnpm format # Prettier格式化
cargo clippy # Rust代码检查
# 4. 提交代码
git add .
git commit -m "feat: 添加新功能描述"
# 5. 推送和合并
git push origin feature/新功能名称
# 创建Pull Request
```
### 2. 提交信息规范
```bash
# 提交类型
feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式调整
refactor: 重构代码
test: 测试相关
chore: 构建工具或辅助工具的变动
# 示例
git commit -m "feat: 添加AI视频分类功能"
git commit -m "fix: 修复素材导入时的路径问题"
git commit -m "docs: 更新API文档"
```
### 3. 代码审查清单
- [ ] 功能是否按需求实现
- [ ] 代码是否遵循项目规范
- [ ] 是否有适当的错误处理
- [ ] 是否有相应的测试覆盖
- [ ] 性能是否有考虑
- [ ] 安全性是否有保障
- [ ] 文档是否更新
## 核心开发模式
### 前端开发模式
#### 1. 组件开发
```typescript
// 1. 定义类型
interface ComponentProps {
id: string;
title: string;
onAction?: (data: any) => void;
}
// 2. 实现组件
export const Component: FC<ComponentProps> = ({ id, title, onAction }) => {
// Hooks
const [state, setState] = useState();
const { data, loading } = useCustomHook();
// 事件处理
const handleAction = useCallback(() => {
onAction?.(data);
}, [onAction, data]);
// 渲染
return (
<div className="component-container">
{/* 组件内容 */}
</div>
);
};
```
#### 2. 状态管理
```typescript
// 使用Zustand进行状态管理
export const useFeatureStore = create<FeatureState>((set, get) => ({
// 状态
data: [],
loading: false,
error: null,
// 操作
fetchData: async () => {
set({ loading: true });
try {
const data = await api.getData();
set({ data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
}));
```
#### 3. 服务层调用
```typescript
// 调用Tauri命令
export class FeatureService {
static async getData(): Promise<Data[]> {
try {
return await invoke<Data[]>('get_data');
} catch (error) {
throw new Error(`获取数据失败: ${error}`);
}
}
}
```
### 后端开发模式
#### 1. 数据模型
```rust
// 定义数据模型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entity {
pub id: String,
pub name: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Entity {
pub fn new(name: String) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
name,
created_at: now,
updated_at: now,
}
}
}
```
#### 2. 仓库层
```rust
// 数据访问层
pub struct EntityRepository {
database: Arc<Database>,
}
impl EntityRepository {
pub async fn create(&self, entity: &Entity) -> Result<()> {
let conn = self.database.get_connection().await?;
// 数据库操作
Ok(())
}
pub async fn get_by_id(&self, id: &str) -> Result<Option<Entity>> {
let conn = self.database.get_read_connection().await?;
// 查询操作
Ok(None)
}
}
```
#### 3. 业务服务
```rust
// 业务逻辑层
pub struct EntityService;
impl EntityService {
pub async fn create_entity(
repository: &EntityRepository,
request: CreateEntityRequest,
) -> Result<Entity> {
// 业务验证
if request.name.trim().is_empty() {
return Err(anyhow!("名称不能为空"));
}
// 创建实体
let entity = Entity::new(request.name);
// 保存到数据库
repository.create(&entity).await?;
Ok(entity)
}
}
```
#### 4. Tauri命令
```rust
// 表现层命令
#[command]
pub async fn create_entity(
state: State<'_, AppState>,
request: CreateEntityRequest,
) -> Result<Entity, String> {
let repository = state.get_entity_repository()
.map_err(|e| e.to_string())?;
EntityService::create_entity(&repository, request)
.await
.map_err(|e| e.to_string())
}
```
## 测试策略
### 前端测试
```typescript
// 组件测试
describe('Component', () => {
it('应该正确渲染', () => {
render(<Component id="1" title="Test" />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
it('应该处理用户交互', async () => {
const onAction = vi.fn();
render(<Component id="1" title="Test" onAction={onAction} />);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(onAction).toHaveBeenCalled();
});
});
});
// Hook测试
describe('useCustomHook', () => {
it('应该返回正确的数据', async () => {
const { result } = renderHook(() => useCustomHook());
await waitFor(() => {
expect(result.current.data).toBeDefined();
});
});
});
```
### 后端测试
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_create_entity() {
// 准备测试数据
let db = setup_test_database().await;
let repository = EntityRepository::new(db);
let request = CreateEntityRequest {
name: "Test Entity".to_string(),
};
// 执行测试
let result = EntityService::create_entity(&repository, request).await;
// 验证结果
assert!(result.is_ok());
let entity = result.unwrap();
assert_eq!(entity.name, "Test Entity");
}
}
```
## 调试技巧
### 前端调试
```typescript
// 1. 使用浏览器开发者工具
console.log('调试信息:', data);
console.error('错误信息:', error);
// 2. React DevTools
// 安装React DevTools浏览器扩展
// 3. Zustand DevTools
const useStore = create(
devtools((set, get) => ({
// store实现
}))
);
// 4. 网络请求调试
// 在Network面板查看Tauri命令调用
```
### 后端调试
```rust
// 1. 使用tracing进行日志记录
use tracing::{info, warn, error, debug};
#[instrument]
async fn debug_function() {
info!("函数开始执行");
debug!("调试信息: {:?}", data);
warn!("警告信息");
error!("错误信息: {}", error);
}
// 2. 使用dbg!宏
let result = dbg!(some_calculation());
// 3. 条件编译调试代码
#[cfg(debug_assertions)]
println!("调试模式下的输出");
```
### 数据库调试
```sql
-- 查看查询计划
EXPLAIN QUERY PLAN SELECT * FROM materials WHERE project_id = ?;
-- 检查索引使用
.schema materials
.indices materials
-- 分析表统计
ANALYZE;
```
## 性能优化
### 前端性能优化
```typescript
// 1. 组件优化
const OptimizedComponent = memo(({ data }) => {
const memoizedValue = useMemo(() => {
return expensiveCalculation(data);
}, [data]);
const memoizedCallback = useCallback(() => {
handleAction(data);
}, [data]);
return <div>{memoizedValue}</div>;
});
// 2. 懒加载
const LazyComponent = lazy(() => import('./LazyComponent'));
// 3. 虚拟化长列表
import { FixedSizeList as List } from 'react-window';
const VirtualList = ({ items }) => (
<List
height={600}
itemCount={items.length}
itemSize={80}
>
{({ index, style }) => (
<div style={style}>
<Item data={items[index]} />
</div>
)}
</List>
);
```
### 后端性能优化
```rust
// 1. 数据库连接池
let pool = ConnectionPool::new(database_url, pool_config)?;
// 2. 批量操作
async fn batch_insert(items: &[Item]) -> Result<()> {
let conn = pool.get().await?;
let tx = conn.transaction()?;
for item in items {
tx.execute("INSERT INTO ...", params![...])?;
}
tx.commit()?;
Ok(())
}
// 3. 异步并发
use futures::future::join_all;
let futures: Vec<_> = items.iter()
.map(|item| process_item(item))
.collect();
let results = join_all(futures).await;
```
## 常见问题解决
### 1. 构建问题
```bash
# 清理缓存
pnpm clean
rm -rf node_modules
rm -rf target
pnpm install
# Rust编译问题
cargo clean
cargo build
```
### 2. 开发环境问题
```bash
# 端口冲突
lsof -ti:1420 | xargs kill -9
# 权限问题
sudo chown -R $(whoami) ~/.cargo
```
### 3. 数据库问题
```rust
// 数据库锁定
// 检查是否有其他进程占用数据库
// 使用WAL模式减少锁定
// 迁移失败
// 检查SQL语法
// 验证外键约束
```
### 4. 前端问题
```typescript
// 状态更新问题
// 检查依赖数组
// 使用useCallback和useMemo
// 渲染性能问题
// 使用React DevTools Profiler
// 检查不必要的重渲染
```
## 部署指南
### 开发环境部署
```bash
# 启动开发服务器
pnpm tauri:dev
```
### 生产环境构建
```bash
# 构建应用
pnpm tauri:build
# 构建产物位置
# Windows: target/release/bundle/msi/
# macOS: target/release/bundle/dmg/
# Linux: target/release/bundle/deb/
```
### 发布流程
```bash
# 1. 更新版本号
# 修改 package.json 和 Cargo.toml 中的版本号
# 2. 创建发布标签
git tag v0.2.1
git push origin v0.2.1
# 3. GitHub Actions自动构建和发布
# 查看 .github/workflows/ 中的配置
```
## 最佳实践总结
### 代码质量
1. **遵循规范**: 严格按照项目编码规范开发
2. **测试驱动**: 为核心功能编写测试
3. **代码审查**: 所有代码都要经过审查
4. **文档更新**: 及时更新相关文档
### 性能考虑
1. **数据库优化**: 合理使用索引和查询优化
2. **前端优化**: 使用虚拟化、懒加载等技术
3. **内存管理**: 避免内存泄漏和过度使用
4. **网络优化**: 减少不必要的网络请求
### 安全意识
1. **输入验证**: 严格验证所有用户输入
2. **错误处理**: 不泄露敏感信息
3. **权限控制**: 实施适当的访问控制
4. **数据保护**: 保护用户数据安全
### 用户体验
1. **响应式设计**: 支持多种设备和屏幕尺寸
2. **加载状态**: 提供清晰的加载反馈
3. **错误提示**: 友好的错误信息和恢复建议
4. **无障碍性**: 支持键盘导航和屏幕阅读器

View File

@@ -0,0 +1,644 @@
# MixVideo 前端开发规范
## 技术栈规范
### 核心技术
- **React 18**: 函数组件 + Hooks
- **TypeScript 5.8**: 严格类型检查
- **Vite 6.0**: 构建工具
- **TailwindCSS 3.4**: 样式框架
- **Zustand 4.4**: 状态管理
- **React Router 6.20**: 路由管理
### 开发工具
- **ESLint**: 代码检查
- **Prettier**: 代码格式化
- **Vitest**: 单元测试
- **Testing Library**: 组件测试
## 项目结构规范
### 目录结构
```
src/
├── components/ # 可复用组件
│ ├── ui/ # 基础UI组件
│ ├── business/ # 业务组件
│ └── __tests__/ # 组件测试
├── pages/ # 页面组件
├── hooks/ # 自定义Hooks
├── services/ # 业务服务层
├── store/ # Zustand状态管理
├── types/ # TypeScript类型定义
├── utils/ # 工具函数
├── styles/ # 样式文件
│ ├── design-system.css
│ └── animations.css
└── tests/ # 测试文件
├── setup.ts
├── components/
├── integration/
└── e2e/
```
### 文件命名规范
- **组件文件**: PascalCase (如 `MaterialCard.tsx`)
- **Hook文件**: camelCase (如 `useMaterialSearch.ts`)
- **服务文件**: camelCase (如 `materialService.ts`)
- **类型文件**: camelCase (如 `material.ts`)
- **工具文件**: camelCase (如 `imagePathUtils.ts`)
## 组件开发规范
### 组件结构模板
```typescript
import React from 'react';
import { clsx } from 'clsx';
// 类型定义
interface ComponentProps {
// 必需属性
id: string;
title: string;
// 可选属性
description?: string;
className?: string;
// 事件处理
onClick?: () => void;
onSubmit?: (data: FormData) => void;
}
/**
* 组件描述
* 遵循 Tauri 开发规范的组件设计原则
*/
export const Component: React.FC<ComponentProps> = ({
id,
title,
description,
className,
onClick,
onSubmit,
}) => {
// Hooks (按顺序)
const [state, setState] = useState<StateType>(initialState);
const { data, loading, error } = useCustomHook();
// 事件处理函数
const handleClick = useCallback(() => {
onClick?.();
}, [onClick]);
// 副作用
useEffect(() => {
// 副作用逻辑
}, [dependencies]);
// 条件渲染
if (loading) {
return <LoadingSpinner />;
}
if (error) {
return <ErrorMessage error={error} />;
}
// 主渲染
return (
<div className={clsx('base-classes', className)}>
<h2 className="text-lg font-semibold">{title}</h2>
{description && (
<p className="text-gray-600">{description}</p>
)}
</div>
);
};
// 默认导出
export default Component;
```
### 组件设计原则
1. **单一职责**: 每个组件只负责一个功能
2. **可复用性**: 通过props实现组件的灵活配置
3. **可测试性**: 组件逻辑清晰,易于测试
4. **性能优化**: 合理使用memo、useMemo、useCallback
5. **无障碍性**: 支持键盘导航和屏幕阅读器
### Props设计规范
```typescript
// ✅ 好的Props设计
interface GoodProps {
// 必需属性在前
id: string;
title: string;
// 可选属性在后
description?: string;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
// 事件处理器
onClick?: (event: MouseEvent) => void;
onSubmit?: (data: FormData) => Promise<void>;
// 样式相关
className?: string;
style?: CSSProperties;
}
// ❌ 避免的Props设计
interface BadProps {
data: any; // 避免使用any
config: object; // 避免使用object
callback: Function; // 避免使用Function
}
```
## TypeScript 规范
### 类型定义规范
```typescript
// 基础类型定义
export interface Material {
id: string;
name: string;
path: string;
type: MaterialType;
size: number;
duration?: number;
createdAt: Date;
updatedAt: Date;
}
// 枚举定义
export enum MaterialType {
VIDEO = 'video',
AUDIO = 'audio',
IMAGE = 'image',
}
// 联合类型
export type Status = 'idle' | 'loading' | 'success' | 'error';
// 泛型接口
export interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
// 工具类型
export type CreateMaterialRequest = Omit<Material, 'id' | 'createdAt' | 'updatedAt'>;
export type UpdateMaterialRequest = Partial<Pick<Material, 'name' | 'type'>>;
```
### 类型导入导出规范
```typescript
// 类型导入
import type { Material, MaterialType } from '../types/material';
import type { ComponentProps } from 'react';
// 值导入
import { MaterialService } from '../services/materialService';
import { useMaterialStore } from '../store/materialStore';
// 混合导入
import React, { type FC, type ReactNode } from 'react';
```
## 状态管理规范
### Zustand Store 设计
```typescript
import { create } from 'zustand';
import type { Material } from '../types/material';
interface MaterialState {
// 状态数据
materials: Material[];
selectedMaterial: Material | null;
loading: boolean;
error: string | null;
// 同步操作
setMaterials: (materials: Material[]) => void;
setSelectedMaterial: (material: Material | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
// 异步操作
fetchMaterials: () => Promise<void>;
createMaterial: (request: CreateMaterialRequest) => Promise<void>;
updateMaterial: (id: string, request: UpdateMaterialRequest) => Promise<void>;
deleteMaterial: (id: string) => Promise<void>;
}
export const useMaterialStore = create<MaterialState>((set, get) => ({
// 初始状态
materials: [],
selectedMaterial: null,
loading: false,
error: null,
// 同步操作
setMaterials: (materials) => set({ materials }),
setSelectedMaterial: (selectedMaterial) => set({ selectedMaterial }),
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
// 异步操作
fetchMaterials: async () => {
try {
set({ loading: true, error: null });
const materials = await MaterialService.getAllMaterials();
set({ materials, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
createMaterial: async (request) => {
try {
set({ loading: true, error: null });
const material = await MaterialService.createMaterial(request);
set((state) => ({
materials: [...state.materials, material],
loading: false,
}));
} catch (error) {
set({ error: error.message, loading: false });
}
},
}));
```
### 状态管理原则
1. **单一数据源**: 每个状态只有一个来源
2. **不可变更新**: 使用不可变的方式更新状态
3. **异步处理**: 统一的异步操作模式
4. **错误处理**: 完善的错误状态管理
## 服务层规范
### 服务类设计
```typescript
import { invoke } from '@tauri-apps/api/core';
import type { Material, CreateMaterialRequest, UpdateMaterialRequest } from '../types/material';
/**
* 素材管理服务
* 遵循 Tauri 开发规范的前端服务层设计
*/
export class MaterialService {
/**
* 获取所有素材
*/
static async getAllMaterials(): Promise<Material[]> {
try {
const materials = await invoke<Material[]>('get_all_materials');
return materials;
} catch (error) {
console.error('获取素材列表失败:', error);
throw new Error(`获取素材列表失败: ${error}`);
}
}
/**
* 根据ID获取素材
*/
static async getMaterialById(id: string): Promise<Material | null> {
try {
const material = await invoke<Material | null>('get_material_by_id', { id });
return material;
} catch (error) {
console.error('获取素材详情失败:', error);
throw new Error(`获取素材详情失败: ${error}`);
}
}
/**
* 创建素材
*/
static async createMaterial(request: CreateMaterialRequest): Promise<Material> {
try {
const material = await invoke<Material>('create_material', { request });
return material;
} catch (error) {
console.error('创建素材失败:', error);
throw new Error(`创建素材失败: ${error}`);
}
}
}
```
### 服务层原则
1. **静态方法**: 使用静态方法避免实例化
2. **错误处理**: 统一的错误处理和日志记录
3. **类型安全**: 严格的类型定义和检查
4. **文档注释**: 详细的JSDoc注释
## 样式规范
### TailwindCSS 使用规范
```typescript
// ✅ 推荐的样式写法
const buttonStyles = {
base: 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
variants: {
primary: 'bg-primary-600 text-white hover:bg-primary-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
sizes: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
};
// 使用clsx进行条件样式
const className = clsx(
buttonStyles.base,
buttonStyles.variants[variant],
buttonStyles.sizes[size],
disabled && 'opacity-50 cursor-not-allowed',
className
);
```
### CSS变量使用
```css
/* 使用设计系统中定义的CSS变量 */
.custom-component {
background-color: var(--primary-500);
color: var(--gray-50);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
}
```
## 自定义Hooks规范
### Hook设计模板
```typescript
import { useState, useEffect, useCallback } from 'react';
import type { Material } from '../types/material';
import { MaterialService } from '../services/materialService';
interface UseMaterialSearchOptions {
initialQuery?: string;
autoSearch?: boolean;
}
interface UseMaterialSearchReturn {
materials: Material[];
query: string;
loading: boolean;
error: string | null;
search: (query: string) => Promise<void>;
clearResults: () => void;
}
/**
* 素材搜索Hook
* 提供素材搜索功能的封装
*/
export const useMaterialSearch = (
options: UseMaterialSearchOptions = {}
): UseMaterialSearchReturn => {
const { initialQuery = '', autoSearch = false } = options;
const [materials, setMaterials] = useState<Material[]>([]);
const [query, setQuery] = useState(initialQuery);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const search = useCallback(async (searchQuery: string) => {
try {
setLoading(true);
setError(null);
setQuery(searchQuery);
const results = await MaterialService.searchMaterials(searchQuery);
setMaterials(results);
} catch (err) {
setError(err instanceof Error ? err.message : '搜索失败');
setMaterials([]);
} finally {
setLoading(false);
}
}, []);
const clearResults = useCallback(() => {
setMaterials([]);
setQuery('');
setError(null);
}, []);
useEffect(() => {
if (autoSearch && initialQuery) {
search(initialQuery);
}
}, [autoSearch, initialQuery, search]);
return {
materials,
query,
loading,
error,
search,
clearResults,
};
};
```
## 测试规范
### 组件测试模板
```typescript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { MaterialCard } from '../MaterialCard';
import type { Material } from '../../types/material';
const mockMaterial: Material = {
id: '1',
name: 'Test Material',
path: '/test/path',
type: 'video',
size: 1024,
createdAt: new Date(),
updatedAt: new Date(),
};
describe('MaterialCard', () => {
it('应该正确渲染素材信息', () => {
render(<MaterialCard material={mockMaterial} />);
expect(screen.getByText('Test Material')).toBeInTheDocument();
expect(screen.getByText('video')).toBeInTheDocument();
});
it('应该处理点击事件', async () => {
const onClickMock = vi.fn();
render(<MaterialCard material={mockMaterial} onClick={onClickMock} />);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(onClickMock).toHaveBeenCalledWith(mockMaterial);
});
});
it('应该显示加载状态', () => {
render(<MaterialCard material={mockMaterial} loading />);
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
});
});
```
### 测试原则
1. **测试行为**: 测试组件的行为而不是实现细节
2. **用户视角**: 从用户的角度编写测试
3. **覆盖率**: 关键功能100%覆盖
4. **可维护性**: 测试代码也要易于维护
## 性能优化规范
### React性能优化
```typescript
// 使用memo优化组件重渲染
export const MaterialCard = memo<MaterialCardProps>(({ material, onClick }) => {
// 组件实现
});
// 使用useMemo优化计算
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
// 使用useCallback优化函数引用
const handleClick = useCallback((id: string) => {
onClick?.(id);
}, [onClick]);
// 使用lazy加载组件
const LazyComponent = lazy(() => import('./LazyComponent'));
```
### 列表优化
```typescript
// 使用react-window进行虚拟化
import { FixedSizeList as List } from 'react-window';
const VirtualizedList: FC<{ items: Material[] }> = ({ items }) => (
<List
height={600}
itemCount={items.length}
itemSize={80}
itemData={items}
>
{({ index, style, data }) => (
<div style={style}>
<MaterialCard material={data[index]} />
</div>
)}
</List>
);
```
## 错误处理规范
### 错误边界
```typescript
import React, { Component, type ReactNode } from 'react';
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<
{ children: ReactNode },
ErrorBoundaryState
> {
constructor(props: { children: ReactNode }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2></h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
```
### 异步错误处理
```typescript
// 在组件中处理异步错误
const [error, setError] = useState<string | null>(null);
const handleAsyncOperation = async () => {
try {
setError(null);
await someAsyncOperation();
} catch (err) {
setError(err instanceof Error ? err.message : '操作失败');
}
};
// 在服务层统一错误处理
export const handleApiError = (error: unknown): never => {
if (error instanceof Error) {
throw new Error(`API错误: ${error.message}`);
}
throw new Error('未知错误');
};
```
## 代码质量规范
### ESLint配置
```json
{
"extends": [
"@vitejs/eslint-config-react",
"@typescript-eslint/recommended"
],
"rules": {
"react-hooks/exhaustive-deps": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn"
}
}
```
### 代码审查清单
- [ ] 组件是否遵循单一职责原则
- [ ] TypeScript类型是否完整准确
- [ ] 是否有适当的错误处理
- [ ] 是否有性能优化考虑
- [ ] 是否有相应的测试覆盖
- [ ] 代码是否符合团队规范

View File

@@ -0,0 +1,347 @@
# MixVideo 项目概览与开发规范
## 项目简介
MixVideo 是一个基于 Tauri 2.0 构建的跨平台AI视频创作桌面应用采用 Rust + React + TypeScript 技术栈旨在构建全球领先的AI驱动视频创作生态平台。
### 核心愿景
**"构建全球领先的AI驱动视频创作生态平台让每个人都能轻松创作出专业级的视频内容"**
## 技术架构
### 整体架构
```
mixvideo/
├── apps/
│ └── desktop/ # Tauri桌面应用
│ ├── src/ # React前端代码
│ │ ├── components/ # UI组件
│ │ ├── pages/ # 页面组件
│ │ ├── store/ # 状态管理(Zustand)
│ │ ├── services/ # 业务服务
│ │ ├── types/ # TypeScript类型定义
│ │ ├── hooks/ # React Hooks
│ │ ├── utils/ # 工具函数
│ │ └── styles/ # 样式文件
│ └── src-tauri/ # Rust后端代码
│ ├── src/
│ │ ├── infrastructure/ # 基础设施层
│ │ ├── data/ # 数据访问层
│ │ ├── business/ # 业务逻辑层
│ │ ├── presentation/ # 表现层(Commands)
│ │ └── services/ # 服务层
│ └── Cargo.toml
├── packages/ # 共享包(未来扩展)
├── docs/ # 项目文档
└── .promptx/ # 开发规范文档
```
### 前端技术栈
- **框架**: React 18 + TypeScript 5.8
- **构建工具**: Vite 6.0
- **状态管理**: Zustand 4.4
- **UI框架**: TailwindCSS 3.4
- **图标**: Lucide React + Heroicons
- **路由**: React Router DOM 6.20
- **测试**: Vitest + Testing Library
- **包管理**: PNPM 8.15 (Workspace)
### 后端技术栈
- **框架**: Tauri 2.0 + Rust 1.70+
- **数据库**: SQLite (WAL模式支持连接池)
- **AI集成**: Google Gemini API
- **多媒体**: FFmpeg
- **异步**: Tokio + async/await
- **序列化**: Serde + JSON
- **错误处理**: anyhow + thiserror
## 核心功能模块
### 1. 项目管理
- 多项目管理,本地路径绑定
- 素材管理:视频、音频、图片统一管理
- 版本控制:素材版本追踪,支持回滚
### 2. AI智能分析
- 基于Gemini AI的视频内容自动分类
- 场景检测:自动识别视频场景变化,智能分割片段
- 质量评估AI评估视频质量提供优化建议
### 3. 模板系统
- 可视化模板编辑器
- AI驱动的素材与模板自动匹配
- 批量处理:一键应用模板到多个素材
### 4. 模特管理
- 完整的模特信息管理系统
- 支持多类型照片分类存储
- 素材与模特自动关联
### 5. 穿搭推荐
- AI驱动的穿搭搭配推荐
- 智能搜索和筛选
- 收藏和比较功能
### 6. 视频生成
- 基于模板的视频自动生成
- 支持多种输出格式
- 批量导出处理
## 四层架构设计
### 1. 基础设施层 (Infrastructure)
- **数据库管理**: SQLite连接池读写分离
- **外部服务**: Gemini API, FFmpeg集成
- **文件系统**: 文件操作,路径管理
- **日志系统**: 结构化日志,性能监控
- **事件总线**: 组件间通信
### 2. 数据访问层 (Data)
- **模型定义**: 所有业务实体模型
- **仓库模式**: 数据访问抽象
- **数据库迁移**: 版本管理
- **连接管理**: 连接池优化
### 3. 业务逻辑层 (Business)
- **服务层**: 核心业务逻辑
- **领域模型**: 业务规则封装
- **工作流**: 复杂业务流程
- **验证**: 数据验证规则
### 4. 表现层 (Presentation)
- **Tauri Commands**: 前后端通信接口
- **参数验证**: 输入参数校验
- **错误处理**: 统一错误响应
- **权限控制**: 操作权限管理
## 开发环境配置
### 环境要求
- Node.js >= 18.0.0
- Rust >= 1.70.0
- PNPM >= 8.0.0
- Tauri CLI >= 2.0.0
### 开发命令
```bash
# 安装依赖
pnpm install
# 开发模式
pnpm tauri:dev
# 构建应用
pnpm tauri:build
# 运行测试
pnpm test
# 代码格式化
pnpm format
# 代码检查
pnpm lint
```
## 核心设计原则
### 1. 安全第一
- 数据库文件安全存储
- 输入参数严格验证
- 错误信息不泄露敏感数据
### 2. 性能优化
- 数据库连接池
- 读写分离
- 虚拟化列表
- 懒加载
### 3. 用户体验
- 响应式设计
- 优雅的加载状态
- 友好的错误提示
- 流畅的动画效果
### 4. 可维护性
- 清晰的分层架构
- 统一的编码规范
- 完善的测试覆盖
- 详细的文档说明
### 5. 可扩展性
- 模块化设计
- 插件化架构
- 配置化管理
- 微服务准备
## 数据库设计
### 核心表结构
- **projects**: 项目管理
- **materials**: 素材管理
- **models**: 模特信息
- **templates**: 模板定义
- **video_classifications**: AI分类记录
- **outfit_images**: 穿搭图片
- **conversations**: 对话记录
### 数据库特性
- WAL模式提高并发性能
- 外键约束:保证数据一致性
- 索引优化:提升查询性能
- 连接池:支持高并发访问
## 状态管理规范
### Zustand Store 设计
- **uiStore**: UI状态管理
- **projectStore**: 项目状态
- **materialStore**: 素材状态
- **outfitSearchStore**: 穿搭搜索状态
- **videoClassificationStore**: 视频分类状态
### 状态管理原则
- 单一数据源
- 不可变更新
- 异步操作处理
- 错误状态管理
## UI/UX 设计规范
### 设计系统
- **颜色系统**: 现代蓝色主题,语义化颜色
- **字体系统**: Inter + JetBrains Mono
- **间距系统**: 8px基础网格
- **圆角系统**: 统一的圆角规范
- **阴影系统**: 层次化阴影效果
### 组件规范
- 原子化设计
- 可复用组件
- 一致的交互模式
- 无障碍访问支持
### 动画规范
- 流畅的过渡效果
- 有意义的动画反馈
- 性能优化的动画
- 可配置的动画偏好
## 测试策略
### 测试类型
- **单元测试**: 核心业务逻辑
- **集成测试**: API和数据库集成
- **组件测试**: React组件测试
- **E2E测试**: 用户界面端到端测试
### 测试工具
- **前端**: Vitest + Testing Library
- **后端**: Rust内置测试框架
- **E2E**: 计划使用Playwright
### 测试覆盖率
- 目标覆盖率: 80%+
- 关键路径: 100%覆盖
- 持续集成: 自动化测试
## 版本管理
### 版本号规范
- 语义化版本: MAJOR.MINOR.PATCH
- 当前版本: v0.2.1
- 发布周期: 迭代式发布
### Git工作流
- 主分支: master
- 功能分支: feature/功能名
- 修复分支: hotfix/问题描述
- 发布分支: release/版本号
## 部署与发布
### 构建产物
- Windows: .msi + .exe
- macOS: .dmg + .app
- Linux: .deb + .rpm + .AppImage
### 自动化发布
- GitHub Actions
- 自动构建
- 自动发布
- 版本标签管理
## 性能监控
### 监控指标
- 应用启动时间
- 数据库查询性能
- AI分类处理时间
- 内存使用情况
### 优化策略
- 数据库查询优化
- 图片懒加载
- 虚拟化列表
- 缓存策略
## 错误处理
### 错误分类
- 用户输入错误
- 系统运行错误
- 网络连接错误
- 外部服务错误
### 处理策略
- 统一错误格式
- 友好错误提示
- 错误日志记录
- 自动重试机制
## 国际化支持
### 多语言支持
- 中文(简体): 主要语言
- 英文: 计划支持
- 其他语言: 未来扩展
### 本地化策略
- 文本外部化
- 日期时间格式
- 数字格式
- 文化适配
## 安全考虑
### 数据安全
- 本地数据加密
- 敏感信息保护
- 安全的文件操作
- API密钥管理
### 应用安全
- 输入验证
- SQL注入防护
- 文件路径验证
- 权限控制
## 未来规划
### 短期目标 (3个月)
- 完善AI分类功能
- 优化用户界面
- 提升性能表现
- 增加测试覆盖
### 中期目标 (6个月)
- 添加更多AI功能
- 支持更多文件格式
- 云端同步功能
- 插件系统
### 长期目标 (1年)
- 多平台支持
- 微服务架构
- 商业化功能
- 生态系统建设

View File

@@ -0,0 +1,499 @@
# MixVideo UI/UX 设计规范
## 设计理念
### 核心原则
1. **用户至上**: 以用户体验为中心的设计
2. **简洁高效**: 简洁的界面,高效的操作流程
3. **一致性**: 统一的视觉语言和交互模式
4. **可访问性**: 支持无障碍访问和多设备适配
5. **现代化**: 符合现代设计趋势的视觉风格
### 设计目标
- **专业性**: 体现专业视频创作工具的品质
- **易用性**: 降低学习成本,提升操作效率
- **美观性**: 现代化的视觉设计,愉悦的用户体验
- **功能性**: 功能导向的界面布局和交互设计
## 视觉设计系统
### 颜色系统
```css
/* 主色调 - 现代蓝色系 */
:root {
--primary-50: #eff6ff;
--primary-100: #dbeafe;
--primary-200: #bfdbfe;
--primary-300: #93c5fd;
--primary-400: #60a5fa;
--primary-500: #3b82f6; /* 主品牌色 */
--primary-600: #2563eb;
--primary-700: #1d4ed8;
--primary-800: #1e40af;
--primary-900: #1e3a8a;
/* 中性色系 */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* 语义色彩 */
--success-500: #10b981;
--warning-500: #f59e0b;
--error-500: #ef4444;
--info-500: #3b82f6;
}
```
### 颜色使用规范
- **主色**: 用于主要操作按钮、链接、重要状态指示
- **中性色**: 用于文本、边框、背景、次要元素
- **语义色**: 用于状态反馈、警告、错误、成功提示
### 字体系统
```css
/* 字体族 */
font-family: {
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'Monaco', 'Consolas', 'monospace']
}
/* 字体大小 */
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }], /* 12px */
sm: ['0.875rem', { lineHeight: '1.25rem' }], /* 14px */
base: ['1rem', { lineHeight: '1.5rem' }], /* 16px */
lg: ['1.125rem', { lineHeight: '1.75rem' }], /* 18px */
xl: ['1.25rem', { lineHeight: '1.75rem' }], /* 20px */
'2xl': ['1.5rem', { lineHeight: '2rem' }], /* 24px */
'3xl': ['1.875rem', { lineHeight: '2.25rem' }], /* 30px */
'4xl': ['2.25rem', { lineHeight: '2.5rem' }], /* 36px */
}
```
### 字体使用规范
- **标题**: 使用较大字号font-weight: 600-700
- **正文**: 使用base字号font-weight: 400
- **说明文字**: 使用sm字号颜色为gray-600
- **代码**: 使用mono字体族
### 间距系统
```css
/* 基于8px网格的间距系统 */
spacing: {
0: '0px',
1: '0.25rem', /* 4px */
2: '0.5rem', /* 8px */
3: '0.75rem', /* 12px */
4: '1rem', /* 16px */
5: '1.25rem', /* 20px */
6: '1.5rem', /* 24px */
8: '2rem', /* 32px */
10: '2.5rem', /* 40px */
12: '3rem', /* 48px */
16: '4rem', /* 64px */
20: '5rem', /* 80px */
24: '6rem', /* 96px */
}
```
### 圆角系统
```css
borderRadius: {
none: '0',
sm: '0.125rem', /* 2px */
DEFAULT: '0.25rem', /* 4px */
md: '0.375rem', /* 6px */
lg: '0.5rem', /* 8px */
xl: '0.75rem', /* 12px */
'2xl': '1rem', /* 16px */
'3xl': '1.5rem', /* 24px */
full: '9999px',
}
```
### 阴影系统
```css
boxShadow: {
xs: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
sm: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
DEFAULT: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
md: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
lg: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
xl: '0 25px 50px -12px rgb(0 0 0 / 0.25)',
glow: '0 0 20px rgb(59 130 246 / 0.15)',
}
```
## 组件设计规范
### 按钮设计
```typescript
// 按钮变体
const buttonVariants = {
primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
outline: 'border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-primary-500',
ghost: 'text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
};
// 按钮尺寸
const buttonSizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
// 按钮状态
const buttonStates = {
default: '',
loading: 'opacity-75 cursor-not-allowed',
disabled: 'opacity-50 cursor-not-allowed',
};
```
### 输入框设计
```typescript
const inputStyles = {
base: 'block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500',
error: 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500',
disabled: 'bg-gray-50 text-gray-500 cursor-not-allowed',
};
const inputSizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-3 py-2 text-base',
lg: 'px-4 py-3 text-lg',
};
```
### 卡片设计
```typescript
const cardStyles = {
base: 'bg-white rounded-lg shadow-sm border border-gray-200',
hover: 'hover:shadow-md transition-shadow duration-200',
interactive: 'cursor-pointer hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200',
selected: 'ring-2 ring-primary-500 border-primary-500',
};
```
### 模态框设计
```typescript
const modalStyles = {
overlay: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50',
container: 'bg-white rounded-lg shadow-xl max-w-md w-full max-h-screen overflow-y-auto',
header: 'px-6 py-4 border-b border-gray-200',
body: 'px-6 py-4',
footer: 'px-6 py-4 border-t border-gray-200 flex justify-end space-x-3',
};
```
## 布局设计规范
### 网格系统
```css
/* 12列网格系统 */
.grid-cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
.col-span-1 { grid-column: span 1 / span 1; }
.col-span-2 { grid-column: span 2 / span 2; }
/* ... */
.col-span-12 { grid-column: span 12 / span 12; }
/* 响应式断点 */
@media (min-width: 640px) { /* sm */ }
@media (min-width: 768px) { /* md */ }
@media (min-width: 1024px) { /* lg */ }
@media (min-width: 1280px) { /* xl */ }
@media (min-width: 1536px) { /* 2xl */ }
```
### 页面布局
```typescript
// 主布局结构
const layoutStructure = {
header: 'h-16 bg-white border-b border-gray-200',
sidebar: 'w-64 bg-gray-50 border-r border-gray-200',
main: 'flex-1 overflow-auto',
content: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6',
};
// 侧边栏导航
const sidebarStyles = {
nav: 'space-y-1 px-3 py-4',
item: 'flex items-center px-3 py-2 text-sm font-medium rounded-md',
active: 'bg-primary-100 text-primary-700',
inactive: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
};
```
### 内容区域
```typescript
const contentStyles = {
section: 'mb-8',
sectionHeader: 'mb-4',
sectionTitle: 'text-2xl font-bold text-gray-900',
sectionDescription: 'text-gray-600 mt-1',
card: 'bg-white rounded-lg shadow-sm border border-gray-200 p-6',
};
```
## 交互设计规范
### 动画系统
```css
/* 过渡动画 */
.transition-all { transition: all 0.2s ease-in-out; }
.transition-colors { transition: color, background-color, border-color 0.2s ease-in-out; }
.transition-transform { transition: transform 0.2s ease-in-out; }
/* 进入动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes scaleIn {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* 加载动画 */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
```
### 交互状态
```typescript
const interactionStates = {
hover: {
button: 'hover:bg-opacity-90 hover:shadow-md',
card: 'hover:shadow-lg hover:-translate-y-1',
link: 'hover:text-primary-600 hover:underline',
},
focus: {
input: 'focus:ring-2 focus:ring-primary-500 focus:border-primary-500',
button: 'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
},
active: {
button: 'active:transform active:scale-95',
tab: 'bg-primary-100 text-primary-700 border-primary-500',
},
disabled: {
button: 'opacity-50 cursor-not-allowed',
input: 'bg-gray-50 text-gray-500 cursor-not-allowed',
},
};
```
### 反馈机制
```typescript
// 加载状态
const loadingStates = {
spinner: 'animate-spin h-5 w-5 text-primary-600',
skeleton: 'animate-pulse bg-gray-200 rounded',
progress: 'bg-primary-600 h-2 rounded-full transition-all duration-300',
};
// 状态指示
const statusIndicators = {
success: 'bg-green-100 text-green-800 border border-green-200',
warning: 'bg-yellow-100 text-yellow-800 border border-yellow-200',
error: 'bg-red-100 text-red-800 border border-red-200',
info: 'bg-blue-100 text-blue-800 border border-blue-200',
};
// 通知样式
const notificationStyles = {
container: 'fixed top-4 right-4 z-50 space-y-2',
item: 'bg-white rounded-lg shadow-lg border p-4 max-w-sm',
success: 'border-l-4 border-green-500',
error: 'border-l-4 border-red-500',
warning: 'border-l-4 border-yellow-500',
info: 'border-l-4 border-blue-500',
};
```
## 响应式设计规范
### 断点系统
```typescript
const breakpoints = {
sm: '640px', // 小屏幕手机
md: '768px', // 平板
lg: '1024px', // 小型桌面
xl: '1280px', // 桌面
'2xl': '1536px', // 大屏桌面
};
// 响应式设计原则
const responsiveRules = {
mobile: {
// 移动端优先
padding: 'px-4',
fontSize: 'text-sm',
spacing: 'space-y-4',
},
tablet: {
// 平板适配
padding: 'md:px-6',
fontSize: 'md:text-base',
spacing: 'md:space-y-6',
},
desktop: {
// 桌面适配
padding: 'lg:px-8',
fontSize: 'lg:text-lg',
spacing: 'lg:space-y-8',
},
};
```
### 自适应布局
```typescript
// 网格自适应
const responsiveGrid = {
mobile: 'grid-cols-1',
tablet: 'md:grid-cols-2',
desktop: 'lg:grid-cols-3 xl:grid-cols-4',
};
// 侧边栏响应式
const responsiveSidebar = {
mobile: 'hidden', // 移动端隐藏
tablet: 'md:block md:w-64', // 平板显示
desktop: 'lg:w-72', // 桌面加宽
};
```
## 无障碍设计规范
### 键盘导航
```typescript
const keyboardNavigation = {
focusable: 'focus:outline-none focus:ring-2 focus:ring-primary-500',
skipLink: 'sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4',
tabIndex: {
interactive: '0',
nonInteractive: '-1',
},
};
```
### 屏幕阅读器支持
```typescript
const screenReaderSupport = {
srOnly: 'sr-only', // 仅屏幕阅读器可见
ariaLabel: 'aria-label', // 无障碍标签
ariaDescribedBy: 'aria-describedby', // 描述关联
role: 'role', // 元素角色
};
```
### 颜色对比度
```typescript
// 确保足够的颜色对比度
const contrastRatios = {
normalText: '4.5:1', // 普通文本最小对比度
largeText: '3:1', // 大文本最小对比度
uiComponents: '3:1', // UI组件最小对比度
};
```
## 性能优化规范
### 图片优化
```typescript
const imageOptimization = {
formats: ['webp', 'avif', 'jpg', 'png'],
sizes: {
thumbnail: '150x150',
small: '300x300',
medium: '600x600',
large: '1200x1200',
},
lazy: 'loading="lazy"',
responsive: 'sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"',
};
```
### 动画性能
```css
/* 使用transform和opacity进行动画 */
.animate-optimized {
will-change: transform, opacity;
transform: translateZ(0); /* 启用硬件加速 */
}
/* 避免动画布局属性 */
.avoid-layout-animation {
/* 避免动画这些属性width, height, padding, margin, top, left */
}
```
### 虚拟化列表
```typescript
// 大列表虚拟化
const virtualizationConfig = {
itemHeight: 80,
containerHeight: 600,
overscan: 5, // 预渲染项目数
threshold: 100, // 启用虚拟化的最小项目数
};
```
## 设计审查清单
### 视觉设计
- [ ] 是否符合品牌色彩规范
- [ ] 字体大小和层次是否清晰
- [ ] 间距是否遵循8px网格系统
- [ ] 阴影和圆角是否一致
- [ ] 图标风格是否统一
### 交互设计
- [ ] 交互反馈是否及时明确
- [ ] 加载状态是否友好
- [ ] 错误提示是否清晰
- [ ] 操作流程是否简洁
- [ ] 快捷键是否合理
### 响应式设计
- [ ] 移动端适配是否完整
- [ ] 平板端布局是否合理
- [ ] 桌面端是否充分利用空间
- [ ] 断点切换是否平滑
- [ ] 触摸目标是否足够大
### 无障碍设计
- [ ] 颜色对比度是否符合标准
- [ ] 键盘导航是否完整
- [ ] 屏幕阅读器支持是否充分
- [ ] 焦点指示是否清晰
- [ ] 语义化标签是否正确
### 性能优化
- [ ] 图片是否优化
- [ ] 动画是否流畅
- [ ] 大列表是否虚拟化
- [ ] 资源是否懒加载
- [ ] 关键渲染路径是否优化