docs: 添加项目开发规范文档
- 添加项目概览文档 - 添加前端开发规范 - 添加后端开发规范 - 添加数据库设计规范 - 添加UI/UX设计规范 - 添加开发指南 - 为项目提供完整的开发标准和最佳实践
This commit is contained in:
215
.promptx/README.md
Normal file
215
.promptx/README.md
Normal 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 设计系统
|
||||
|
||||
### 后续更新
|
||||
- 规范文档会根据项目发展持续更新
|
||||
- 重大变更会在版本发布说明中体现
|
||||
- 建议定期查看文档更新
|
||||
|
||||
---
|
||||
|
||||
**注意**: 本规范文档是项目开发的重要参考,所有开发者都应该熟悉并遵循这些规范。如有疑问或建议,欢迎通过项目仓库进行讨论。
|
||||
950
.promptx/backend-coding-standards.md
Normal file
950
.promptx/backend-coding-standards.md
Normal 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最佳实践
|
||||
- [ ] 输入验证是否完整
|
||||
- [ ] 是否存在安全漏洞
|
||||
579
.promptx/database-design-standards.md
Normal file
579
.promptx/database-design-standards.md
Normal 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'))
|
||||
);
|
||||
```
|
||||
531
.promptx/development-guide.md
Normal file
531
.promptx/development-guide.md
Normal 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. **无障碍性**: 支持键盘导航和屏幕阅读器
|
||||
644
.promptx/frontend-coding-standards.md
Normal file
644
.promptx/frontend-coding-standards.md
Normal 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类型是否完整准确
|
||||
- [ ] 是否有适当的错误处理
|
||||
- [ ] 是否有性能优化考虑
|
||||
- [ ] 是否有相应的测试覆盖
|
||||
- [ ] 代码是否符合团队规范
|
||||
347
.promptx/project-overview.md
Normal file
347
.promptx/project-overview.md
Normal 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年)
|
||||
- 多平台支持
|
||||
- 微服务架构
|
||||
- 商业化功能
|
||||
- 生态系统建设
|
||||
499
.promptx/ui-ux-design-standards.md
Normal file
499
.promptx/ui-ux-design-standards.md
Normal 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网格系统
|
||||
- [ ] 阴影和圆角是否一致
|
||||
- [ ] 图标风格是否统一
|
||||
|
||||
### 交互设计
|
||||
- [ ] 交互反馈是否及时明确
|
||||
- [ ] 加载状态是否友好
|
||||
- [ ] 错误提示是否清晰
|
||||
- [ ] 操作流程是否简洁
|
||||
- [ ] 快捷键是否合理
|
||||
|
||||
### 响应式设计
|
||||
- [ ] 移动端适配是否完整
|
||||
- [ ] 平板端布局是否合理
|
||||
- [ ] 桌面端是否充分利用空间
|
||||
- [ ] 断点切换是否平滑
|
||||
- [ ] 触摸目标是否足够大
|
||||
|
||||
### 无障碍设计
|
||||
- [ ] 颜色对比度是否符合标准
|
||||
- [ ] 键盘导航是否完整
|
||||
- [ ] 屏幕阅读器支持是否充分
|
||||
- [ ] 焦点指示是否清晰
|
||||
- [ ] 语义化标签是否正确
|
||||
|
||||
### 性能优化
|
||||
- [ ] 图片是否优化
|
||||
- [ ] 动画是否流畅
|
||||
- [ ] 大列表是否虚拟化
|
||||
- [ ] 资源是否懒加载
|
||||
- [ ] 关键渲染路径是否优化
|
||||
Reference in New Issue
Block a user