feat: 添加二维码生成功能并简化架构
- 新增二维码生成服务层,支持自定义内容和配置 - 移除 Platform 抽象层,简化为三层架构 - 更新开发文档和架构说明 - 添加前端二维码生成页面和状态管理
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"Bash(cargo check:*)",
|
"Bash(cargo check:*)",
|
||||||
"Bash(pnpm build:*)",
|
"Bash(pnpm build:*)",
|
||||||
"Bash(tree:*)"
|
"Bash(tree:*)",
|
||||||
|
"Bash(pnpm add:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
292
docs/开发指南.md
292
docs/开发指南.md
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
## 架构概述
|
## 架构概述
|
||||||
|
|
||||||
### 四层架构
|
### 三层架构
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────┐
|
||||||
@@ -25,10 +25,6 @@
|
|||||||
│ 职责:业务逻辑、流程编排、状态管理 │
|
│ 职责:业务逻辑、流程编排、状态管理 │
|
||||||
│ 文件:src/services/*.rs │
|
│ 文件:src/services/*.rs │
|
||||||
├─────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────┤
|
||||||
│ Platform 层 │
|
|
||||||
│ 职责:平台差异抽象、统一接口 │
|
|
||||||
│ 文件:src/platforms/*.rs │
|
|
||||||
├─────────────────────────────────────────────────┤
|
|
||||||
│ Utils 层 │
|
│ Utils 层 │
|
||||||
│ 职责:纯算法、无状态工具函数 │
|
│ 职责:纯算法、无状态工具函数 │
|
||||||
│ 文件:src/utils/*.rs │
|
│ 文件:src/utils/*.rs │
|
||||||
@@ -38,9 +34,9 @@
|
|||||||
### 依赖原则
|
### 依赖原则
|
||||||
|
|
||||||
- **单向依赖**:上层依赖下层,下层不依赖上层
|
- **单向依赖**:上层依赖下层,下层不依赖上层
|
||||||
- **依赖链**:Command → Service → Platform → Utils
|
- **依赖链**:Command → Service → Utils
|
||||||
- **Utils 完全独立**:只依赖标准库和 models
|
- **Utils 完全独立**:只依赖标准库和 models
|
||||||
- **Platform 通过 trait 解耦**:Service 层通过 trait 调用,不关心具体实现
|
- **Service 可以直接调用 Windows API**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -53,8 +49,7 @@
|
|||||||
1. **功能描述**:这个功能做什么?
|
1. **功能描述**:这个功能做什么?
|
||||||
2. **用户交互**:用户如何触发这个功能?
|
2. **用户交互**:用户如何触发这个功能?
|
||||||
3. **输入输出**:需要什么参数?返回什么结果?
|
3. **输入输出**:需要什么参数?返回什么结果?
|
||||||
4. **平台差异**:不同平台是否需要不同实现?
|
4. **错误场景**:哪些情况下会出错?如何处理?
|
||||||
5. **错误场景**:哪些情况下会出错?如何处理?
|
|
||||||
|
|
||||||
### 步骤 2:设计数据模型(如需要)
|
### 步骤 2:设计数据模型(如需要)
|
||||||
|
|
||||||
@@ -140,88 +135,7 @@ mod tests {
|
|||||||
- ✅ 包含单元测试
|
- ✅ 包含单元测试
|
||||||
- ✅ 不依赖任何外部状态
|
- ✅ 不依赖任何外部状态
|
||||||
|
|
||||||
### 步骤 4:定义平台抽象(如果需要平台特定实现)
|
### 步骤 4:实现业务逻辑(Service 层)
|
||||||
|
|
||||||
如果功能需要访问平台 API,定义 trait:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/platforms/new_feature.rs
|
|
||||||
|
|
||||||
use crate::error::AppResult;
|
|
||||||
|
|
||||||
/// 新功能平台抽象 trait
|
|
||||||
pub trait NewFeatureAccessor {
|
|
||||||
/// 执行平台特定的操作
|
|
||||||
///
|
|
||||||
/// # 参数
|
|
||||||
///
|
|
||||||
/// * `param1` - 参数 1 说明
|
|
||||||
///
|
|
||||||
/// # 返回
|
|
||||||
///
|
|
||||||
/// 返回操作结果
|
|
||||||
///
|
|
||||||
/// # 错误
|
|
||||||
///
|
|
||||||
/// 操作失败时返回错误
|
|
||||||
fn execute_platform_operation(&self, param1: &str) -> AppResult<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 平台特定实现类型别名
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub type PlatformNewFeature = crate::platforms::windows::new_feature_impl::WindowsNewFeature;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub type PlatformNewFeature = crate::platforms::windows::new_feature_impl::DummyNewFeature;
|
|
||||||
```
|
|
||||||
|
|
||||||
**规范**:
|
|
||||||
- ✅ 使用 trait 定义接口
|
|
||||||
- ✅ 所有方法返回 `AppResult<T>`
|
|
||||||
- ✅ 方法参数使用引用(`&str` 而不是 `String`)
|
|
||||||
- ✅ 添加详细的文档注释
|
|
||||||
|
|
||||||
### 步骤 5:实现平台特定代码
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/platforms/windows/new_feature_impl.rs
|
|
||||||
|
|
||||||
use crate::error::{AppError, AppResult};
|
|
||||||
use crate::platforms::new_feature::NewFeatureAccessor;
|
|
||||||
|
|
||||||
/// Windows 平台实现
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub struct WindowsNewFeature;
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl NewFeatureAccessor for WindowsNewFeature {
|
|
||||||
fn execute_platform_operation(&self, param1: &str) -> AppResult<String> {
|
|
||||||
// Windows 特定实现
|
|
||||||
Ok(format!("Windows: {}", param1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 其他平台占位实现
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub struct DummyNewFeature;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
impl NewFeatureAccessor for DummyNewFeature {
|
|
||||||
fn execute_platform_operation(&self, _param1: &str) -> AppResult<String> {
|
|
||||||
Err(AppError::PlatformNotSupported(
|
|
||||||
"此平台暂不支持该功能".to_string()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**规范**:
|
|
||||||
- ✅ 使用 `#[cfg(windows)]` 条件编译
|
|
||||||
- ✅ 提供其他平台的占位实现
|
|
||||||
- ✅ 占位实现返回 `PlatformNotSupported` 错误
|
|
||||||
- ✅ 使用中文错误消息
|
|
||||||
|
|
||||||
### 步骤 6:实现业务逻辑(Service 层)
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/services/new_feature_service.rs
|
// src/services/new_feature_service.rs
|
||||||
@@ -252,7 +166,6 @@ impl NewFeatureService {
|
|||||||
/// # 错误
|
/// # 错误
|
||||||
///
|
///
|
||||||
/// - 配置无效时返回 `AppError::InvalidColorData`
|
/// - 配置无效时返回 `AppError::InvalidColorData`
|
||||||
/// - 平台不支持时返回 `AppError::PlatformNotSupported`
|
|
||||||
pub fn execute(&self, config: &NewFeatureConfig) -> AppResult<NewFeatureResult> {
|
pub fn execute(&self, config: &NewFeatureConfig) -> AppResult<NewFeatureResult> {
|
||||||
// 1. 参数验证
|
// 1. 参数验证
|
||||||
if config.option1.is_empty() {
|
if config.option1.is_empty() {
|
||||||
@@ -285,10 +198,11 @@ impl NewFeatureService {
|
|||||||
- ✅ 使用 struct 命名空间(如 `NewFeatureService`)
|
- ✅ 使用 struct 命名空间(如 `NewFeatureService`)
|
||||||
- ✅ 所有方法返回 `AppResult<T>`
|
- ✅ 所有方法返回 `AppResult<T>`
|
||||||
- ✅ 参数验证放在 Service 层
|
- ✅ 参数验证放在 Service 层
|
||||||
|
- ✅ 可以直接调用 Windows API
|
||||||
- ✅ 添加详细的文档注释
|
- ✅ 添加详细的文档注释
|
||||||
- ✅ 包含同步和异步两个版本(如需要)
|
- ✅ 包含同步和异步两个版本(如需要)
|
||||||
|
|
||||||
### 步骤 7:创建 Tauri 命令(Command 层)
|
### 步骤 5:创建 Tauri 命令(Command 层)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/commands/new_feature_commands.rs
|
// src/commands/new_feature_commands.rs
|
||||||
@@ -366,7 +280,7 @@ pub async fn execute_new_feature_async(config: NewFeatureConfig) -> Result<NewFe
|
|||||||
- ✅ 参数使用结构体,便于扩展
|
- ✅ 参数使用结构体,便于扩展
|
||||||
- ✅ 添加详细的文档注释
|
- ✅ 添加详细的文档注释
|
||||||
|
|
||||||
### 步骤 8:注册模块
|
### 步骤 6:注册模块
|
||||||
|
|
||||||
更新各模块的 `mod.rs` 和 `lib.rs`:
|
更新各模块的 `mod.rs` 和 `lib.rs`:
|
||||||
|
|
||||||
@@ -396,7 +310,7 @@ pub mod new_feature_commands; // 新增
|
|||||||
- ✅ 导出常用类型
|
- ✅ 导出常用类型
|
||||||
- ✅ 注册所有新命令
|
- ✅ 注册所有新命令
|
||||||
|
|
||||||
### 步骤 9:错误处理扩展(如需要)
|
### 步骤 7:错误处理扩展(如需要)
|
||||||
|
|
||||||
如果需要新的错误类型,在 `error.rs` 中添加:
|
如果需要新的错误类型,在 `error.rs` 中添加:
|
||||||
|
|
||||||
@@ -426,7 +340,7 @@ impl fmt::Display for AppError {
|
|||||||
- ✅ 携带详细的错误信息(`String`)
|
- ✅ 携带详细的错误信息(`String`)
|
||||||
- ✅ 在 `Display` 实现中添加上下文
|
- ✅ 在 `Display` 实现中添加上下文
|
||||||
|
|
||||||
### 步骤 10:更新前端类型定义
|
### 步骤 8:更新前端类型定义
|
||||||
|
|
||||||
在 `src/types/` 或相关位置添加 TypeScript 类型定义:
|
在 `src/types/` 或相关位置添加 TypeScript 类型定义:
|
||||||
|
|
||||||
@@ -460,7 +374,7 @@ export type NewFeatureCommands = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 步骤 11:编写测试
|
### 步骤 9:编写测试
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/services/new_feature_service.rs 中的测试
|
// src/services/new_feature_service.rs 中的测试
|
||||||
@@ -500,7 +414,7 @@ mod tests {
|
|||||||
- ✅ 使用 `assert!` 和 `assert_eq!` 断言
|
- ✅ 使用 `assert!` 和 `assert_eq!` 断言
|
||||||
- ✅ 运行 `cargo test` 验证
|
- ✅ 运行 `cargo test` 验证
|
||||||
|
|
||||||
### 步骤 12:验证和测试
|
### 步骤 10:验证和测试
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 检查代码编译
|
# 1. 检查代码编译
|
||||||
@@ -588,33 +502,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Platform 层规范
|
|
||||||
|
|
||||||
**职责**:抽象平台差异,提供统一接口
|
|
||||||
|
|
||||||
**规范清单**:
|
|
||||||
- ✅ 使用 trait 定义接口
|
|
||||||
- ✅ 通过类型别名选择实现
|
|
||||||
- ✅ 提供所有平台的占位实现
|
|
||||||
- ✅ 使用 `#[cfg(windows)]` 条件编译
|
|
||||||
- ✅ 占位实现返回 `PlatformNotSupported`
|
|
||||||
|
|
||||||
**示例**:
|
|
||||||
```rust
|
|
||||||
// trait 定义
|
|
||||||
pub trait FileAccessor {
|
|
||||||
fn read_file(&self, path: &str) -> AppResult<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows 实现
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub type PlatformFile = WindowsFile;
|
|
||||||
|
|
||||||
// 占位实现
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub type PlatformFile = DummyFile;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service 层规范
|
### Service 层规范
|
||||||
|
|
||||||
**职责**:业务逻辑实现和流程编排
|
**职责**:业务逻辑实现和流程编排
|
||||||
@@ -623,7 +510,7 @@ pub type PlatformFile = DummyFile;
|
|||||||
- ✅ 使用 struct 命名空间
|
- ✅ 使用 struct 命名空间
|
||||||
- ✅ 所有方法返回 `AppResult<T>`
|
- ✅ 所有方法返回 `AppResult<T>`
|
||||||
- ✅ 参数验证在 Service 层进行
|
- ✅ 参数验证在 Service 层进行
|
||||||
- ✅ 通过 trait 调用 Platform 层
|
- ✅ 可以直接调用 Windows API
|
||||||
- ✅ 可以调用 Utils 层函数
|
- ✅ 可以调用 Utils 层函数
|
||||||
- ✅ 提供同步和异步版本(如需要)
|
- ✅ 提供同步和异步版本(如需要)
|
||||||
- ✅ 使用详细的中文档注释
|
- ✅ 使用详细的中文档注释
|
||||||
@@ -639,8 +526,8 @@ impl FileService {
|
|||||||
return Err(AppError::InvalidData("路径不能为空".to_string()));
|
return Err(AppError::InvalidData("路径不能为空".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 调用 Platform 层
|
// 2. 调用 Windows API 或第三方库
|
||||||
let content = PlatformFile::read_file(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
// 3. 调用 Utils 层处理
|
// 3. 调用 Utils 层处理
|
||||||
let processed = utils::text::trim_whitespace(&content);
|
let processed = utils::text::trim_whitespace(&content);
|
||||||
@@ -709,55 +596,7 @@ pub struct ScreenshotResult {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. 定义平台抽象
|
#### 2. 实现 Service
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/platforms/screenshot.rs
|
|
||||||
|
|
||||||
use crate::error::AppResult;
|
|
||||||
use crate::models::screenshot::ScreenshotConfig;
|
|
||||||
|
|
||||||
/// 屏幕截图 trait
|
|
||||||
pub trait ScreenshotCapturer {
|
|
||||||
/// 截取整个屏幕
|
|
||||||
///
|
|
||||||
/// # 参数
|
|
||||||
///
|
|
||||||
/// * `config` - 截图配置
|
|
||||||
///
|
|
||||||
/// # 返回
|
|
||||||
///
|
|
||||||
/// 返回截图结果,包含 Base64 编码的图片数据
|
|
||||||
fn capture_screen(&self, config: &ScreenshotConfig) -> AppResult<ScreenshotResult>;
|
|
||||||
|
|
||||||
/// 截取指定区域
|
|
||||||
///
|
|
||||||
/// # 参数
|
|
||||||
///
|
|
||||||
/// * `x` - 起始 X 坐标
|
|
||||||
/// * `y` - 起始 Y 坐标
|
|
||||||
/// * `width` - 宽度
|
|
||||||
/// * `height` - 高度
|
|
||||||
/// * `config` - 截图配置
|
|
||||||
fn capture_region(
|
|
||||||
&self,
|
|
||||||
x: u32,
|
|
||||||
y: u32,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
config: &ScreenshotConfig,
|
|
||||||
) -> AppResult<ScreenshotResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 类型别名
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::WindowsScreenshot;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::DummyScreenshot;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 实现 Service
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/services/screenshot_service.rs
|
// src/services/screenshot_service.rs
|
||||||
@@ -768,7 +607,6 @@ pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::DummyS
|
|||||||
|
|
||||||
use crate::error::{AppError, AppResult};
|
use crate::error::{AppError, AppResult};
|
||||||
use crate::models::screenshot::{ScreenshotConfig, ScreenshotResult};
|
use crate::models::screenshot::{ScreenshotConfig, ScreenshotResult};
|
||||||
use crate::platforms::screenshot::ScreenshotCapturer;
|
|
||||||
|
|
||||||
/// 截图服务
|
/// 截图服务
|
||||||
pub struct ScreenshotService;
|
pub struct ScreenshotService;
|
||||||
@@ -793,8 +631,30 @@ impl ScreenshotService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用平台实现
|
// 调用 Windows API 或使用第三方库
|
||||||
PlatformScreenshot::capture_screen(config)
|
// 例如:使用 screenshots-rs crate
|
||||||
|
let screenshots = screenshots::Screen::all().map_err(|e| {
|
||||||
|
AppError::InvalidData(format!("获取屏幕失败: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(screen) = screenshots.first() {
|
||||||
|
let image = screen.capture().map_err(|e| {
|
||||||
|
AppError::InvalidData(format!("截图失败: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 转换为 Base64
|
||||||
|
let buffer = image.buffer();
|
||||||
|
let base64_data = base64::encode(buffer);
|
||||||
|
|
||||||
|
Ok(ScreenshotResult {
|
||||||
|
data: base64_data,
|
||||||
|
format: "png".to_string(),
|
||||||
|
width: image.width(),
|
||||||
|
height: image.height(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(AppError::InvalidData("未找到屏幕".to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 截取指定区域
|
/// 截取指定区域
|
||||||
@@ -811,12 +671,19 @@ impl ScreenshotService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformScreenshot::capture_region(x, y, width, height, config)
|
// 调用 Windows API 实现区域截图
|
||||||
|
// ...
|
||||||
|
Ok(ScreenshotResult {
|
||||||
|
data: "base64_data".to_string(),
|
||||||
|
format: "png".to_string(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. 创建命令
|
#### 3. 创建命令
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/commands/screenshot_commands.rs
|
// src/commands/screenshot_commands.rs
|
||||||
@@ -881,7 +748,7 @@ pub fn capture_region(
|
|||||||
| 函数 | snake_case | `get_pixel_color`, `toggle_window` |
|
| 函数 | snake_case | `get_pixel_color`, `toggle_window` |
|
||||||
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
||||||
| Trait | PascalCase + 能力 | `ScreenAccessor`, `CursorController` |
|
| Trait | PascalCase + 能力 | `ScreenAccessor`, `CursorController` |
|
||||||
| 类型别名 | PascalCase + Platform/Type | `PlatformScreen`, `AppResult` |
|
| 类型别名 | PascalCase + Type | `AppResult`, `JsonResult` |
|
||||||
|
|
||||||
### 2. 文档注释规范
|
### 2. 文档注释规范
|
||||||
|
|
||||||
@@ -975,33 +842,7 @@ impl SomeService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. 平台检测规范
|
### 6. 代码组织规范
|
||||||
|
|
||||||
```rust
|
|
||||||
// ✅ 使用条件编译和 trait
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn platform_specific() -> AppResult<()> {
|
|
||||||
WindowsImpl::do_something()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn platform_specific() -> AppResult<()> {
|
|
||||||
Err(AppError::PlatformNotSupported(
|
|
||||||
"此平台暂不支持".to_string()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ 不推荐:运行时检测
|
|
||||||
fn platform_specific() -> AppResult<()> {
|
|
||||||
if cfg!(windows) {
|
|
||||||
// Windows 代码
|
|
||||||
} else {
|
|
||||||
Err(AppError::PlatformNotSupported("...".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 代码组织规范
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// ✅ 推荐:按功能分组
|
// ✅ 推荐:按功能分组
|
||||||
@@ -1114,27 +955,23 @@ impl fmt::Display for AppError {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q: 如何在多个平台实现同一个功能?
|
### Q: 如何调用 Windows API?
|
||||||
|
|
||||||
A: 为每个平台创建独立的实现文件,并通过条件编译选择:
|
A: 在 Service 层直接使用 Windows 相关的 crate 或调用 Windows API:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// platforms/windows/impl.rs
|
// 使用 windows-rs crate
|
||||||
#[cfg(windows)]
|
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||||
pub struct PlatformImpl;
|
|
||||||
impl PlatformTrait for PlatformImpl { }
|
|
||||||
|
|
||||||
// platforms/macos/impl.rs
|
impl CursorService {
|
||||||
#[cfg(target_os = "macos")]
|
pub fn get_cursor_position(&self) -> AppResult<(i32, i32)> {
|
||||||
pub struct PlatformImpl;
|
let mut pos = POINT { x: 0, y: 0 };
|
||||||
impl PlatformTrait for PlatformImpl { }
|
unsafe {
|
||||||
|
GetCursorPos(&mut pos)?;
|
||||||
// platforms/mod.rs
|
}
|
||||||
#[cfg(windows)]
|
Ok((pos.x, pos.y))
|
||||||
pub type Platform = crate::platforms::windows::impl::PlatformImpl;
|
}
|
||||||
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub type Platform = crate::platforms::macos::impl::PlatformImpl;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q: 如何处理异步操作?
|
### Q: 如何处理异步操作?
|
||||||
@@ -1168,7 +1005,6 @@ pub async fn get_data() -> Result<Data, String> {
|
|||||||
- [ ] 文档注释包含示例代码
|
- [ ] 文档注释包含示例代码
|
||||||
- [ ] 错误消息使用中文
|
- [ ] 错误消息使用中文
|
||||||
- [ ] 遵循依赖原则(上层依赖下层)
|
- [ ] 遵循依赖原则(上层依赖下层)
|
||||||
- [ ] 平台特定代码正确使用条件编译
|
|
||||||
- [ ] 新增类型已导出(如需要)
|
- [ ] 新增类型已导出(如需要)
|
||||||
- [ ] 命令已在 `lib.rs` 中注册
|
- [ ] 命令已在 `lib.rs` 中注册
|
||||||
|
|
||||||
|
|||||||
52
docs/快速参考.md
52
docs/快速参考.md
@@ -5,12 +5,10 @@
|
|||||||
```
|
```
|
||||||
1. 定义 Models → src/models/new_feature.rs
|
1. 定义 Models → src/models/new_feature.rs
|
||||||
2. 实现 Utils → src/utils/new_algorithm.rs (可选)
|
2. 实现 Utils → src/utils/new_algorithm.rs (可选)
|
||||||
3. 定义 Platform → src/platforms/new_feature.rs
|
3. 实现 Service → src/services/new_feature_service.rs
|
||||||
4. 实现平台代码 → src/platforms/windows/new_feature_impl.rs
|
4. 创建 Command → src/commands/new_feature_commands.rs
|
||||||
5. 实现 Service → src/services/new_feature_service.rs
|
5. 注册模块 → 更新 mod.rs 和 lib.rs
|
||||||
6. 创建 Command → src/commands/new_feature_commands.rs
|
6. 测试验证 → cargo check && cargo test
|
||||||
7. 注册模块 → 更新 mod.rs 和 lib.rs
|
|
||||||
8. 测试验证 → cargo check && cargo test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📁 文件模板
|
## 📁 文件模板
|
||||||
@@ -64,31 +62,13 @@ pub fn execute_feature(input: FeatureData) -> Result<Output, String> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Platform Trait 模板
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/platforms/feature.rs
|
|
||||||
use crate::error::AppResult;
|
|
||||||
|
|
||||||
/// Trait 说明
|
|
||||||
pub trait FeatureAccessor {
|
|
||||||
fn do_something(&self, param: &str) -> AppResult<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub type PlatformFeature = crate::platforms::windows::feature_impl::WindowsFeature;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub type PlatformFeature = crate::platforms::windows::feature_impl::DummyFeature;
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ 代码规范清单
|
## ✅ 代码规范清单
|
||||||
|
|
||||||
### 命名规范
|
### 命名规范
|
||||||
- [ ] 模块文件: `snake_case`
|
- [ ] 模块文件: `snake_case`
|
||||||
- [ ] 结构体: `PascalCase`
|
- [ ] 结构体: `PascalCase`
|
||||||
- [ ] 函数: `snake_case`
|
- [ ] 函数: `snake_case`
|
||||||
- [ ] Trait: `PascalCase` + 能力描述
|
- [ ] Trait: `PascalCase` + 能力描述(可选)
|
||||||
|
|
||||||
### 文档规范
|
### 文档规范
|
||||||
- [ ] 所有公开 API 有 `///` 注释
|
- [ ] 所有公开 API 有 `///` 注释
|
||||||
@@ -103,7 +83,7 @@ pub type PlatformFeature = crate::platforms::windows::feature_impl::DummyFeature
|
|||||||
- [ ] 使用中文错误消息
|
- [ ] 使用中文错误消息
|
||||||
- [ ] 参数验证在 Service 层
|
- [ ] 参数验证在 Service 层
|
||||||
- [ ] Command 层简洁(仅适配)
|
- [ ] Command 层简洁(仅适配)
|
||||||
- [ ] 使用 `#[cfg(windows)]` 条件编译
|
- [ ] Service 层可直接调用 Windows API
|
||||||
|
|
||||||
## 🔧 常用命令
|
## 🔧 常用命令
|
||||||
|
|
||||||
@@ -148,8 +128,6 @@ cargo doc --open
|
|||||||
```
|
```
|
||||||
models/color.rs → ColorInfo, RgbInfo, HslInfo
|
models/color.rs → ColorInfo, RgbInfo, HslInfo
|
||||||
utils/color_conversion.rs → rgb_to_hsl()
|
utils/color_conversion.rs → rgb_to_hsl()
|
||||||
platforms/screen.rs → ScreenAccessor trait
|
|
||||||
platforms/windows/screen_impl.rs → WindowsScreen
|
|
||||||
services/color_service.rs → ColorService
|
services/color_service.rs → ColorService
|
||||||
commands/color_commands.rs → pick_color_interactive
|
commands/color_commands.rs → pick_color_interactive
|
||||||
lib.rs → 注册命令
|
lib.rs → 注册命令
|
||||||
@@ -160,8 +138,6 @@ lib.rs → 注册命令
|
|||||||
```
|
```
|
||||||
models/screenshot.rs → ScreenshotConfig, ScreenshotResult
|
models/screenshot.rs → ScreenshotConfig, ScreenshotResult
|
||||||
utils/image_utils.rs → (可选) 图像处理工具
|
utils/image_utils.rs → (可选) 图像处理工具
|
||||||
platforms/screenshot.rs → ScreenshotCapturer trait
|
|
||||||
platforms/windows/screenshot_impl.rs → WindowsScreenshot
|
|
||||||
services/screenshot_service.rs → ScreenshotService
|
services/screenshot_service.rs → ScreenshotService
|
||||||
commands/screenshot_commands.rs → capture_screen
|
commands/screenshot_commands.rs → capture_screen
|
||||||
lib.rs → 注册 capture_screen
|
lib.rs → 注册 capture_screen
|
||||||
@@ -169,19 +145,7 @@ lib.rs → 注册 capture_screen
|
|||||||
|
|
||||||
## ⚠️ 常见错误
|
## ⚠️ 常见错误
|
||||||
|
|
||||||
### 错误 1: Trait 方法未找到
|
### 错误 1: 类型不匹配
|
||||||
```rust
|
|
||||||
// ❌ 错误
|
|
||||||
use crate::platforms::screen::PlatformScreen;
|
|
||||||
PlatformScreen::get_pixel_color(x, y)?; // 找不到方法
|
|
||||||
|
|
||||||
// ✅ 正确
|
|
||||||
use crate::platforms::screen::PlatformScreen;
|
|
||||||
use crate::platforms::screen::ScreenAccessor; // 导入 trait
|
|
||||||
PlatformScreen::get_pixel_color(x, y)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 错误 2: 类型不匹配
|
|
||||||
```rust
|
```rust
|
||||||
// ❌ 错误
|
// ❌ 错误
|
||||||
pub fn toggle_window(window: &WebviewWindow) { }
|
pub fn toggle_window(window: &WebviewWindow) { }
|
||||||
@@ -190,7 +154,7 @@ pub fn toggle_window(window: &WebviewWindow) { }
|
|||||||
pub fn toggle_window(window: &Window) { }
|
pub fn toggle_window(window: &Window) { }
|
||||||
```
|
```
|
||||||
|
|
||||||
### 错误 3: 忘记注册命令
|
### 错误 2: 忘记注册命令
|
||||||
```rust
|
```rust
|
||||||
// ❌ 错误:命令未注册,前端无法调用
|
// ❌ 错误:命令未注册,前端无法调用
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2",
|
"@tauri-apps/plugin-global-shortcut": "^2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
@@ -27,7 +30,8 @@
|
|||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^7.8.2",
|
"react-router-dom": "^7.8.2",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.12"
|
"tailwindcss": "^4.1.12",
|
||||||
|
"zustand": "^5.0.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
|||||||
648
src-tauri/Cargo.lock
generated
648
src-tauri/Cargo.lock
generated
@@ -17,6 +17,24 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||||
|
dependencies = [
|
||||||
|
"as-slice",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned-vec"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
|
||||||
|
dependencies = [
|
||||||
|
"equator",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloc-no-stdlib"
|
name = "alloc-no-stdlib"
|
||||||
version = "2.0.4"
|
version = "2.0.4"
|
||||||
@@ -47,6 +65,38 @@ version = "1.0.101"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arg_enum_proc_macro"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "as-slice"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@@ -213,6 +263,49 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av-scenechange"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
|
||||||
|
dependencies = [
|
||||||
|
"aligned",
|
||||||
|
"anyhow",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"pastey",
|
||||||
|
"rayon",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"v_frame",
|
||||||
|
"y4m",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av1-grain"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"nom",
|
||||||
|
"num-rational",
|
||||||
|
"v_frame",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "avif-serialize"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
@@ -225,6 +318,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit_field"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -240,6 +339,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitstream-io"
|
||||||
|
version = "4.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
|
||||||
|
dependencies = [
|
||||||
|
"core2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -292,6 +400,12 @@ dependencies = [
|
|||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "built"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
@@ -310,6 +424,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -393,6 +513,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -441,6 +563,12 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -516,6 +644,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core2"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -568,6 +705,12 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -843,6 +986,26 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
|
||||||
|
dependencies = [
|
||||||
|
"equator-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator-macro"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -891,12 +1054,47 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exr"
|
||||||
|
version = "1.74.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
|
||||||
|
dependencies = [
|
||||||
|
"bit_field",
|
||||||
|
"half",
|
||||||
|
"lebe",
|
||||||
|
"miniz_oxide",
|
||||||
|
"rayon-core",
|
||||||
|
"smallvec",
|
||||||
|
"zune-inflate",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
|
||||||
|
dependencies = [
|
||||||
|
"fax_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax_derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -1267,6 +1465,16 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"weezl",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gio"
|
name = "gio"
|
||||||
version = "0.18.4"
|
version = "0.18.4"
|
||||||
@@ -1433,6 +1641,17 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1604,7 +1823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1721,6 +1940,46 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"color_quant",
|
||||||
|
"exr",
|
||||||
|
"gif",
|
||||||
|
"image-webp",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png 0.18.0",
|
||||||
|
"qoi",
|
||||||
|
"ravif",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
"tiff",
|
||||||
|
"zune-core 0.5.1",
|
||||||
|
"zune-jpeg 0.5.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image-webp"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder-lite",
|
||||||
|
"quick-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imgref"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@@ -1753,6 +2012,17 @@ dependencies = [
|
|||||||
"cfb",
|
"cfb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "interpolate_name"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -1788,6 +2058,15 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@@ -1839,6 +2118,16 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.85"
|
||||||
@@ -1906,6 +2195,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lebe"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libappindicator"
|
name = "libappindicator"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1936,6 +2231,16 @@ version = "0.2.181"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libfuzzer-sys"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
@@ -1983,6 +2288,15 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loop9"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||||
|
dependencies = [
|
||||||
|
"imgref",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac"
|
name = "mac"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2020,6 +2334,16 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-rayon"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -2062,6 +2386,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
@@ -2077,7 +2411,7 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
@@ -2125,6 +2459,21 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noop_proc_macro"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2134,12 +2483,53 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2471,6 +2861,18 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pastey"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -2672,6 +3074,19 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "3.11.0"
|
version = "3.11.0"
|
||||||
@@ -2794,6 +3209,58 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
||||||
|
dependencies = [
|
||||||
|
"profiling-procmacros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling-procmacros"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qoi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qrcode"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
|
||||||
|
dependencies = [
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.4"
|
version = "0.38.4"
|
||||||
@@ -2843,6 +3310,16 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2863,6 +3340,16 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -2881,6 +3368,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_hc"
|
name = "rand_hc"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2899,6 +3395,56 @@ dependencies = [
|
|||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rav1e"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"arbitrary",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"av-scenechange",
|
||||||
|
"av1-grain",
|
||||||
|
"bitstream-io",
|
||||||
|
"built",
|
||||||
|
"cfg-if",
|
||||||
|
"interpolate_name",
|
||||||
|
"itertools",
|
||||||
|
"libc",
|
||||||
|
"libfuzzer-sys",
|
||||||
|
"log",
|
||||||
|
"maybe-rayon",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"noop_proc_macro",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"profiling",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"simd_helpers",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"v_frame",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ravif"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
|
||||||
|
dependencies = [
|
||||||
|
"avif-serialize",
|
||||||
|
"imgref",
|
||||||
|
"loop9",
|
||||||
|
"quick-error",
|
||||||
|
"rav1e",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -3028,6 +3574,12 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.52"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -3342,6 +3894,15 @@ version = "0.3.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@@ -3654,6 +4215,9 @@ dependencies = [
|
|||||||
name = "tauri-app"
|
name = "tauri-app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"image",
|
||||||
|
"qrcode",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3699,7 +4263,7 @@ dependencies = [
|
|||||||
"ico",
|
"ico",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"plist",
|
"plist",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -3948,6 +4512,20 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
||||||
|
dependencies = [
|
||||||
|
"fax",
|
||||||
|
"flate2",
|
||||||
|
"half",
|
||||||
|
"quick-error",
|
||||||
|
"weezl",
|
||||||
|
"zune-jpeg 0.4.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.47"
|
version = "0.3.47"
|
||||||
@@ -4204,7 +4782,7 @@ dependencies = [
|
|||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
@@ -4347,6 +4925,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "v_frame"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -4624,6 +5213,12 @@ dependencies = [
|
|||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -5401,6 +5996,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "y4m"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -5565,6 +6166,45 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-inflate"
|
||||||
|
version = "0.2.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core 0.4.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.9.2"
|
version = "5.9.2"
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ tauri-plugin-global-shortcut = "2"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { version = "1", features = ["preserve_order"] }
|
serde_json = { version = "1", features = ["preserve_order"] }
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
|
qrcode = "0.14"
|
||||||
|
image = "0.25"
|
||||||
|
base64 = "0.22"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.58", features = [
|
windows = { version = "0.58", features = [
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
|
|
||||||
pub mod json_format_commands;
|
pub mod json_format_commands;
|
||||||
pub mod picker_color_commands;
|
pub mod picker_color_commands;
|
||||||
|
pub mod qrcode_commands;
|
||||||
pub mod system_info_commands;
|
pub mod system_info_commands;
|
||||||
pub mod window_commands;
|
pub mod window_commands;
|
||||||
|
|||||||
71
src-tauri/src/commands/qrcode_commands.rs
Normal file
71
src-tauri/src/commands/qrcode_commands.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//! 二维码生成命令
|
||||||
|
//!
|
||||||
|
//! 定义二维码生成相关的 Tauri 命令
|
||||||
|
|
||||||
|
use crate::models::qrcode::{QrConfig, QrResult};
|
||||||
|
use crate::services::qrcode_service::QrCodeService;
|
||||||
|
|
||||||
|
/// 生成二维码预览
|
||||||
|
///
|
||||||
|
/// Tauri 命令,用于从前端调用生成二维码预览
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `config` - 二维码配置
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 返回二维码生成结果,包含 Base64 编码的图片数据
|
||||||
|
///
|
||||||
|
/// # 前端调用示例
|
||||||
|
///
|
||||||
|
/// ```typescript
|
||||||
|
/// import { invoke } from '@tauri-apps/api/core';
|
||||||
|
///
|
||||||
|
/// const result = await invoke('generate_qr_preview', {
|
||||||
|
/// config: {
|
||||||
|
/// content: 'https://example.com',
|
||||||
|
/// size: 512,
|
||||||
|
/// margin: 4,
|
||||||
|
/// errorCorrection: 'M'
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
/// console.log(result.data); // "data:image/png;base64,..."
|
||||||
|
/// ```
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn generate_qr_preview(config: QrConfig) -> Result<QrResult, String> {
|
||||||
|
QrCodeService::generate_preview(&config).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 生成二维码并保存到文件
|
||||||
|
///
|
||||||
|
/// Tauri 命令,用于将生成的二维码保存为文件
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `config` - 二维码配置
|
||||||
|
/// * `output_path` - 输出文件路径
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 成功时返回 Ok(()),失败时返回错误字符串
|
||||||
|
///
|
||||||
|
/// # 前端调用示例
|
||||||
|
///
|
||||||
|
/// ```typescript
|
||||||
|
/// import { invoke } from '@tauri-apps/api/core';
|
||||||
|
///
|
||||||
|
/// await invoke('generate_qr_file', {
|
||||||
|
/// config: {
|
||||||
|
/// content: 'https://example.com',
|
||||||
|
/// size: 1024,
|
||||||
|
/// margin: 4,
|
||||||
|
/// errorCorrection: 'H'
|
||||||
|
/// },
|
||||||
|
/// outputPath: '/path/to/output.png'
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn generate_qr_file(config: QrConfig, output_path: String) -> Result<(), String> {
|
||||||
|
QrCodeService::generate_to_file(&config, &output_path).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
@@ -43,6 +43,21 @@ pub enum AppError {
|
|||||||
///
|
///
|
||||||
/// 表示获取系统信息时失败
|
/// 表示获取系统信息时失败
|
||||||
SystemInfoFailed(String),
|
SystemInfoFailed(String),
|
||||||
|
|
||||||
|
/// 无效数据
|
||||||
|
///
|
||||||
|
/// 表示提供的数据无效或不符合要求
|
||||||
|
InvalidData(String),
|
||||||
|
|
||||||
|
/// IO 错误
|
||||||
|
///
|
||||||
|
/// 表示文件或网络 IO 操作失败
|
||||||
|
IoError(String),
|
||||||
|
|
||||||
|
/// 二维码生成失败
|
||||||
|
///
|
||||||
|
/// 表示二维码生成过程失败
|
||||||
|
QrCodeGenerationFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for AppError {
|
impl fmt::Display for AppError {
|
||||||
@@ -55,6 +70,9 @@ impl fmt::Display for AppError {
|
|||||||
AppError::InvalidColorData(msg) => write!(f, "颜色数据无效: {}", msg),
|
AppError::InvalidColorData(msg) => write!(f, "颜色数据无效: {}", msg),
|
||||||
AppError::ColorConversionFailed(msg) => write!(f, "颜色转换失败: {}", msg),
|
AppError::ColorConversionFailed(msg) => write!(f, "颜色转换失败: {}", msg),
|
||||||
AppError::SystemInfoFailed(msg) => write!(f, "系统信息获取失败: {}", msg),
|
AppError::SystemInfoFailed(msg) => write!(f, "系统信息获取失败: {}", msg),
|
||||||
|
AppError::InvalidData(msg) => write!(f, "数据无效: {}", msg),
|
||||||
|
AppError::IoError(msg) => write!(f, "IO 错误: {}", msg),
|
||||||
|
AppError::QrCodeGenerationFailed(msg) => write!(f, "二维码生成失败: {}", msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ pub fn run() {
|
|||||||
commands::json_format_commands::compact_json,
|
commands::json_format_commands::compact_json,
|
||||||
// 操作系统信息命令
|
// 操作系统信息命令
|
||||||
commands::system_info_commands::get_system_info,
|
commands::system_info_commands::get_system_info,
|
||||||
|
// 二维码生成命令
|
||||||
|
commands::qrcode_commands::generate_qr_preview,
|
||||||
|
commands::qrcode_commands::generate_qr_file,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("运行 Tauri 应用时出错");
|
.expect("运行 Tauri 应用时出错");
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod json_format;
|
pub mod json_format;
|
||||||
|
pub mod qrcode;
|
||||||
pub mod system_info;
|
pub mod system_info;
|
||||||
|
|||||||
63
src-tauri/src/models/qrcode.rs
Normal file
63
src-tauri/src/models/qrcode.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//! 二维码生成相关数据模型
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// 二维码配置
|
||||||
|
///
|
||||||
|
/// 定义生成二维码所需的参数
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct QrConfig {
|
||||||
|
/// 二维码内容
|
||||||
|
pub content: String,
|
||||||
|
/// 输出尺寸(像素)
|
||||||
|
pub size: u32,
|
||||||
|
/// 边距(模块数)
|
||||||
|
pub margin: u32,
|
||||||
|
/// 容错级别: "L", "M", "Q", "H"
|
||||||
|
pub error_correction: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 二维码样式(阶段 2 使用)
|
||||||
|
///
|
||||||
|
/// 定义二维码的视觉样式
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct QrStyle {
|
||||||
|
/// 点形状: "square", "circle", "rounded"
|
||||||
|
pub dot_shape: String,
|
||||||
|
/// 码眼形状: "square", "circle", "rounded"
|
||||||
|
pub eye_shape: String,
|
||||||
|
/// 前景色(Hex 颜色代码)
|
||||||
|
pub foreground_color: String,
|
||||||
|
/// 背景色(Hex 颜色代码)
|
||||||
|
pub background_color: String,
|
||||||
|
/// 是否使用渐变
|
||||||
|
pub is_gradient: bool,
|
||||||
|
/// 渐变颜色列表(如果 is_gradient 为 true)
|
||||||
|
pub gradient_colors: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logo 配置(阶段 2 使用)
|
||||||
|
///
|
||||||
|
/// 定义 Logo 的位置和样式
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LogoConfig {
|
||||||
|
/// Logo 文件路径
|
||||||
|
pub path: String,
|
||||||
|
/// 缩放比例 (0.1 - 0.3)
|
||||||
|
pub scale: f32,
|
||||||
|
/// 是否添加边框
|
||||||
|
pub has_border: bool,
|
||||||
|
/// 边框宽度(像素)
|
||||||
|
pub border_width: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 二维码生成结果
|
||||||
|
///
|
||||||
|
/// 包含生成的二维码图片数据
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct QrResult {
|
||||||
|
/// Base64 编码的图片数据
|
||||||
|
pub data: String,
|
||||||
|
/// 图片格式(如 "png")
|
||||||
|
pub format: String,
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
//! 提供应用的核心业务逻辑实现
|
//! 提供应用的核心业务逻辑实现
|
||||||
|
|
||||||
pub mod json_format_service;
|
pub mod json_format_service;
|
||||||
|
pub mod qrcode_service;
|
||||||
pub mod system_info_service;
|
pub mod system_info_service;
|
||||||
pub mod window_service;
|
pub mod window_service;
|
||||||
|
|||||||
136
src-tauri/src/services/qrcode_service.rs
Normal file
136
src-tauri/src/services/qrcode_service.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
//! 二维码生成服务
|
||||||
|
//!
|
||||||
|
//! 提供二维码生成的核心业务逻辑
|
||||||
|
|
||||||
|
use crate::error::{AppError, AppResult};
|
||||||
|
use crate::models::qrcode::{QrConfig, QrResult};
|
||||||
|
use crate::utils::qrcode_renderer::{image_to_base64, render_basic_qr};
|
||||||
|
|
||||||
|
/// 二维码生成服务
|
||||||
|
pub struct QrCodeService;
|
||||||
|
|
||||||
|
impl QrCodeService {
|
||||||
|
/// 生成二维码预览
|
||||||
|
///
|
||||||
|
/// 根据配置生成二维码并返回 Base64 编码的图片数据
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `config` - 二维码配置
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 返回二维码生成结果,包含 Base64 编码的图片数据
|
||||||
|
///
|
||||||
|
/// # 错误
|
||||||
|
///
|
||||||
|
/// - 配置无效时返回 `AppError::InvalidData`
|
||||||
|
/// - 图片编码失败时返回 `AppError::IoError`
|
||||||
|
pub fn generate_preview(config: &QrConfig) -> AppResult<QrResult> {
|
||||||
|
// 验证尺寸
|
||||||
|
if config.size < 128 || config.size > 4096 {
|
||||||
|
return Err(AppError::InvalidData(
|
||||||
|
"尺寸必须在 128 到 4096 之间".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证边距
|
||||||
|
if config.margin > 50 {
|
||||||
|
return Err(AppError::InvalidData("边距不能超过 50".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染二维码
|
||||||
|
let img = render_basic_qr(config)?;
|
||||||
|
|
||||||
|
// 转换为 Base64
|
||||||
|
let base64_data = image_to_base64(&img)?;
|
||||||
|
|
||||||
|
Ok(QrResult {
|
||||||
|
data: base64_data,
|
||||||
|
format: "png".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 生成二维码并保存到文件
|
||||||
|
///
|
||||||
|
/// 根据配置生成二维码并保存为 PNG 文件
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `config` - 二维码配置
|
||||||
|
/// * `output_path` - 输出文件路径
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 成功时返回 Ok(()),失败时返回错误
|
||||||
|
///
|
||||||
|
/// # 错误
|
||||||
|
///
|
||||||
|
/// - 配置无效时返回 `AppError::InvalidData`
|
||||||
|
/// - 文件写入失败时返回 `AppError::IoError`
|
||||||
|
pub fn generate_to_file(config: &QrConfig, output_path: &str) -> AppResult<()> {
|
||||||
|
// 验证尺寸
|
||||||
|
if config.size < 128 || config.size > 4096 {
|
||||||
|
return Err(AppError::InvalidData(
|
||||||
|
"尺寸必须在 128 到 4096 之间".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染二维码
|
||||||
|
let img = render_basic_qr(config)?;
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
img.save(output_path)
|
||||||
|
.map_err(|e| AppError::IoError(format!("保存文件失败: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_preview() {
|
||||||
|
let config = QrConfig {
|
||||||
|
content: "https://example.com".to_string(),
|
||||||
|
size: 512,
|
||||||
|
margin: 4,
|
||||||
|
error_correction: "M".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = QrCodeService::generate_preview(&config);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let qr_result = result.unwrap();
|
||||||
|
assert!(qr_result.data.starts_with("data:image/png;base64,"));
|
||||||
|
assert_eq!(qr_result.format, "png");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_preview_invalid_size() {
|
||||||
|
let config = QrConfig {
|
||||||
|
content: "https://example.com".to_string(),
|
||||||
|
size: 50, // 太小
|
||||||
|
margin: 4,
|
||||||
|
error_correction: "M".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = QrCodeService::generate_preview(&config);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_preview_invalid_margin() {
|
||||||
|
let config = QrConfig {
|
||||||
|
content: "https://example.com".to_string(),
|
||||||
|
size: 512,
|
||||||
|
margin: 100, // 太大
|
||||||
|
error_correction: "M".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = QrCodeService::generate_preview(&config);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
|
|
||||||
pub mod color_conversion;
|
pub mod color_conversion;
|
||||||
pub mod json_formatter;
|
pub mod json_formatter;
|
||||||
|
pub mod qrcode_renderer;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
pub mod shortcut;
|
pub mod shortcut;
|
||||||
|
|||||||
153
src-tauri/src/utils/qrcode_renderer.rs
Normal file
153
src-tauri/src/utils/qrcode_renderer.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
//! 二维码渲染工具函数
|
||||||
|
//!
|
||||||
|
//! 提供二维码矩阵到图像的渲染功能
|
||||||
|
|
||||||
|
use crate::error::{AppError, AppResult};
|
||||||
|
use crate::models::qrcode::QrConfig;
|
||||||
|
use base64::Engine;
|
||||||
|
use image::Luma;
|
||||||
|
use image::Rgba;
|
||||||
|
use image::RgbaImage;
|
||||||
|
use qrcode::QrCode;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
/// 渲染基础黑白二维码
|
||||||
|
///
|
||||||
|
/// 根据配置生成黑白二维码图片
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `config` - 二维码配置
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 返回生成的图片数据
|
||||||
|
///
|
||||||
|
/// # 错误
|
||||||
|
///
|
||||||
|
/// - 二维码内容为空时返回 `InvalidData`
|
||||||
|
/// - 二维码生成失败时返回相应错误
|
||||||
|
pub fn render_basic_qr(config: &QrConfig) -> AppResult<RgbaImage> {
|
||||||
|
// 验证内容
|
||||||
|
if config.content.trim().is_empty() {
|
||||||
|
return Err(AppError::InvalidData(
|
||||||
|
"二维码内容不能为空".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析容错级别
|
||||||
|
let ec_level = match config.error_correction.as_str() {
|
||||||
|
"L" => qrcode::EcLevel::L,
|
||||||
|
"M" => qrcode::EcLevel::M,
|
||||||
|
"Q" => qrcode::EcLevel::Q,
|
||||||
|
"H" => qrcode::EcLevel::H,
|
||||||
|
_ => qrcode::EcLevel::M, // 默认使用 M 级别
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
let qr_code = QrCode::with_error_correction_level(config.content.as_bytes(), ec_level)
|
||||||
|
.map_err(|e| AppError::InvalidData(format!("二维码生成失败: {}", e)))?;
|
||||||
|
|
||||||
|
// 计算包含边距的尺寸
|
||||||
|
let qr_size = qr_code.width() as u32;
|
||||||
|
let total_size = qr_size + 2 * config.margin;
|
||||||
|
|
||||||
|
// 创建图像缓冲区(Luma 格式,用于生成二维码)
|
||||||
|
let qr_image = qr_code.render::<Luma<u8>>().quiet_zone(false).min_dimensions(total_size, total_size).max_dimensions(total_size, total_size).build();
|
||||||
|
|
||||||
|
// 获取二维码图像尺寸
|
||||||
|
let (width, height) = qr_image.dimensions();
|
||||||
|
|
||||||
|
// 计算缩放比例以匹配目标尺寸
|
||||||
|
let scale = (config.size as f32 / width as f32).max(1.0) as u32;
|
||||||
|
let scaled_width = width * scale;
|
||||||
|
let scaled_height = height * scale;
|
||||||
|
|
||||||
|
// 创建 RGBA 图像并填充白色背景
|
||||||
|
let mut img = RgbaImage::new(scaled_width, scaled_height);
|
||||||
|
for pixel in img.pixels_mut() {
|
||||||
|
*pixel = Rgba([255, 255, 255, 255]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染二维码
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let pixel = qr_image.get_pixel(x, y);
|
||||||
|
// 如果是黑色像素
|
||||||
|
if pixel[0] == 0 {
|
||||||
|
// 计算缩放后的区域
|
||||||
|
let start_x = x * scale;
|
||||||
|
let start_y = y * scale;
|
||||||
|
let end_x = start_x + scale;
|
||||||
|
let end_y = start_y + scale;
|
||||||
|
|
||||||
|
// 绘制黑色矩形
|
||||||
|
for py in start_y..end_y.min(scaled_height) {
|
||||||
|
for px in start_x..end_x.min(scaled_width) {
|
||||||
|
img.put_pixel(px, py, Rgba([0, 0, 0, 255]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将图片转换为 Base64 字符串
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
///
|
||||||
|
/// * `img` - 图片数据
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
///
|
||||||
|
/// 返回 Base64 编码的 PNG 图片数据(带 data URL 前缀)
|
||||||
|
pub fn image_to_base64(img: &RgbaImage) -> AppResult<String> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
// 写入 PNG 格式
|
||||||
|
let mut cursor = Cursor::new(&mut bytes);
|
||||||
|
img.write_to(&mut cursor, image::ImageFormat::Png)
|
||||||
|
.map_err(|e| AppError::IoError(format!("图片编码失败: {}", e)))?;
|
||||||
|
|
||||||
|
// Base64 编码
|
||||||
|
let base64_str = base64::engine::general_purpose::STANDARD.encode(&bytes);
|
||||||
|
|
||||||
|
Ok(format!("data:image/png;base64,{}", base64_str))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_basic_qr() {
|
||||||
|
let config = QrConfig {
|
||||||
|
content: "https://example.com".to_string(),
|
||||||
|
size: 512,
|
||||||
|
margin: 4,
|
||||||
|
error_correction: "M".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = render_basic_qr(&config);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let img = result.unwrap();
|
||||||
|
assert_eq!(img.dimensions().0, 512);
|
||||||
|
assert_eq!(img.dimensions().1, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_empty_content() {
|
||||||
|
let config = QrConfig {
|
||||||
|
content: "".to_string(),
|
||||||
|
size: 512,
|
||||||
|
margin: 4,
|
||||||
|
error_correction: "M".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = render_basic_qr(&config);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { CommandPalette } from "@/components/command-palette/CommandPalette";
|
|||||||
import { ColorPickerPage } from "@/components/features/ColorPicker/ColorPickerPage";
|
import { ColorPickerPage } from "@/components/features/ColorPicker/ColorPickerPage";
|
||||||
import { JsonFormatterPage } from "@/components/features/JsonFormatter/JsonFormatterPage";
|
import { JsonFormatterPage } from "@/components/features/JsonFormatter/JsonFormatterPage";
|
||||||
import { SystemInfoPage } from "@/components/features/SystemInfo/SystemInfoPage";
|
import { SystemInfoPage } from "@/components/features/SystemInfo/SystemInfoPage";
|
||||||
|
import { QrCodeGeneratorPage } from "@/components/features/QrCodeGenerator/QrCodeGeneratorPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +24,7 @@ function App() {
|
|||||||
<Route path="/feature/color-picker" element={<ColorPickerPage />} />
|
<Route path="/feature/color-picker" element={<ColorPickerPage />} />
|
||||||
<Route path="/feature/json-formatter" element={<JsonFormatterPage />} />
|
<Route path="/feature/json-formatter" element={<JsonFormatterPage />} />
|
||||||
<Route path="/feature/system-info" element={<SystemInfoPage />} />
|
<Route path="/feature/system-info" element={<SystemInfoPage />} />
|
||||||
|
<Route path="/feature/qr-generator" element={<QrCodeGeneratorPage />} />
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 二维码生成器主页面
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useDebounce } from '@uidotdev/usehooks';
|
||||||
|
import { useQrStore } from '@/stores/qrcodeStore';
|
||||||
|
import { QrConfigPanel } from './QrConfigPanel';
|
||||||
|
import { QrPreview } from './QrPreview';
|
||||||
|
|
||||||
|
export function QrCodeGeneratorPage() {
|
||||||
|
const { config, updateConfig, generatePreview } = useQrStore();
|
||||||
|
|
||||||
|
// 防抖配置(300ms)
|
||||||
|
const debouncedConfig = useDebounce(config, 300);
|
||||||
|
|
||||||
|
// 当配置改变时自动生成预览
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedConfig.content.trim()) {
|
||||||
|
generatePreview();
|
||||||
|
}
|
||||||
|
}, [debouncedConfig]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full bg-background">
|
||||||
|
{/* 左侧配置面板 */}
|
||||||
|
<div className="w-96 border-r border-border bg-card p-6 overflow-y-auto">
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1 className="text-2xl font-bold">二维码生成器</h1>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
生成自定义二维码图片
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<QrConfigPanel
|
||||||
|
config={config}
|
||||||
|
onConfigChange={updateConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧预览区域 */}
|
||||||
|
<div className="flex-1 flex items-center justify-center p-8 bg-muted/20">
|
||||||
|
<QrPreview />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
134
src/components/features/QrCodeGenerator/QrConfigPanel.tsx
Normal file
134
src/components/features/QrCodeGenerator/QrConfigPanel.tsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 二维码配置面板
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import { useQrStore } from '@/stores/qrcodeStore';
|
||||||
|
import type { QrConfig } from '@/types/qrcode';
|
||||||
|
import { ERROR_CORRECTION_OPTIONS, SIZE_PRESETS } from '@/types/qrcode';
|
||||||
|
import { Download, RotateCcw } from 'lucide-react';
|
||||||
|
import { save } from '@tauri-apps/plugin-dialog';
|
||||||
|
|
||||||
|
interface QrConfigPanelProps {
|
||||||
|
config: QrConfig;
|
||||||
|
onConfigChange: (updates: Partial<QrConfig>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QrConfigPanel({ config, onConfigChange }: QrConfigPanelProps) {
|
||||||
|
const { exportToFile, resetConfig, isGenerating } = useQrStore();
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
const outputPath = await save({
|
||||||
|
title: '保存二维码',
|
||||||
|
defaultPath: `qrcode-${Date.now()}.png`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'PNG 图片',
|
||||||
|
extensions: ['png'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (outputPath) {
|
||||||
|
await exportToFile(outputPath);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('保存失败:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 基本配置 */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6 space-y-4">
|
||||||
|
{/* 内容输入 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="content">二维码内容</Label>
|
||||||
|
<Input
|
||||||
|
id="content"
|
||||||
|
placeholder="输入网址、文本或其他内容"
|
||||||
|
value={config.content}
|
||||||
|
onChange={(e) => onConfigChange({ content: e.target.value })}
|
||||||
|
className="font-mono text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 尺寸选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>尺寸</Label>
|
||||||
|
<Tabs
|
||||||
|
value={config.size.toString()}
|
||||||
|
onValueChange={(value) => onConfigChange({ size: Number(value) })}
|
||||||
|
>
|
||||||
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
|
{SIZE_PRESETS.map((preset) => (
|
||||||
|
<TabsTrigger key={preset.value} value={preset.value.toString()}>
|
||||||
|
{preset.label.split(' ')[0]}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
当前: {config.size}px
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 容错级别 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>容错级别</Label>
|
||||||
|
<Tabs
|
||||||
|
value={config.errorCorrection}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
onConfigChange({ errorCorrection: value as QrConfig['errorCorrection'] })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
|
{ERROR_CORRECTION_OPTIONS.map((option) => (
|
||||||
|
<TabsTrigger key={option.value} value={option.value}>
|
||||||
|
{option.value}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{ERROR_CORRECTION_OPTIONS.find((opt) => opt.value === config.errorCorrection)
|
||||||
|
?.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 边距 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="margin">边距: {config.margin}</Label>
|
||||||
|
<Input
|
||||||
|
id="margin"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="20"
|
||||||
|
value={config.margin}
|
||||||
|
onChange={(e) => onConfigChange({ margin: Number(e.target.value) })}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button onClick={handleExport} disabled={isGenerating} className="flex-1">
|
||||||
|
<Download className="w-4 h-4 mr-2" />
|
||||||
|
导出 PNG
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={resetConfig}>
|
||||||
|
<RotateCcw className="w-4 h-4 mr-2" />
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/components/features/QrCodeGenerator/QrPreview.tsx
Normal file
50
src/components/features/QrCodeGenerator/QrPreview.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 二维码预览组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
import { useQrStore } from '@/stores/qrcodeStore';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
export function QrPreview() {
|
||||||
|
const { previewUrl, isGenerating, error, config } = useQrStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
{/* 预览卡片 */}
|
||||||
|
<Card className="p-4 shadow-lg">
|
||||||
|
{isGenerating ? (
|
||||||
|
<div className="flex items-center justify-center w-[300px] h-[300px]">
|
||||||
|
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
) : previewUrl ? (
|
||||||
|
<img
|
||||||
|
src={previewUrl}
|
||||||
|
alt="二维码预览"
|
||||||
|
className="w-[300px] h-[300px] object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center w-[300px] h-[300px] text-muted-foreground">
|
||||||
|
<p className="text-sm">输入内容后自动生成预览</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 错误提示 */}
|
||||||
|
{error && (
|
||||||
|
<div className="text-sm text-destructive bg-destructive/10 px-4 py-2 rounded-md">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 配置信息 */}
|
||||||
|
{previewUrl && !error && (
|
||||||
|
<div className="text-xs text-muted-foreground space-y-1">
|
||||||
|
<p>尺寸: {config.size}px</p>
|
||||||
|
<p>容错级别: {config.errorCorrection}</p>
|
||||||
|
<p>边距: {config.margin}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/components/ui/label.tsx
Normal file
24
src/components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
@@ -19,6 +19,16 @@ export const featuresData: Feature[] = [
|
|||||||
tags: ['颜色', '取色', 'color', 'hex', 'rgb', 'hsl', '拾色器'],
|
tags: ['颜色', '取色', 'color', 'hex', 'rgb', 'hsl', '拾色器'],
|
||||||
implemented: true,
|
implemented: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'qr-generator',
|
||||||
|
name: '二维码生成器',
|
||||||
|
description: '生成自定义二维码,支持多种尺寸和容错级别',
|
||||||
|
icon: 'QrCode',
|
||||||
|
category: 'tool',
|
||||||
|
route: '/feature/qr-generator',
|
||||||
|
tags: ['二维码', 'QR', '生成', 'qrcode', 'generator'],
|
||||||
|
implemented: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'screenshot',
|
id: 'screenshot',
|
||||||
name: '截图工具',
|
name: '截图工具',
|
||||||
|
|||||||
103
src/stores/qrcodeStore.ts
Normal file
103
src/stores/qrcodeStore.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 二维码生成器状态管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import type { QrConfig, QrResult } from '@/types/qrcode';
|
||||||
|
import { DEFAULT_QR_CONFIG } from '@/types/qrcode';
|
||||||
|
|
||||||
|
interface QrState {
|
||||||
|
/** 当前配置 */
|
||||||
|
config: QrConfig;
|
||||||
|
/** 预览图片 URL */
|
||||||
|
previewUrl: string;
|
||||||
|
/** 是否正在生成 */
|
||||||
|
isGenerating: boolean;
|
||||||
|
/** 错误信息 */
|
||||||
|
error: string | null;
|
||||||
|
/** 更新配置 */
|
||||||
|
updateConfig: (updates: Partial<QrConfig>) => void;
|
||||||
|
/** 重置配置 */
|
||||||
|
resetConfig: () => void;
|
||||||
|
/** 生成预览 */
|
||||||
|
generatePreview: () => Promise<void>;
|
||||||
|
/** 导出到文件 */
|
||||||
|
exportToFile: (outputPath: string) => Promise<void>;
|
||||||
|
/** 清除错误 */
|
||||||
|
clearError: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useQrStore = create<QrState>((set, get) => ({
|
||||||
|
config: DEFAULT_QR_CONFIG,
|
||||||
|
previewUrl: '',
|
||||||
|
isGenerating: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
updateConfig: (updates) => {
|
||||||
|
set((state) => ({
|
||||||
|
config: { ...state.config, ...updates },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
resetConfig: () => {
|
||||||
|
set({
|
||||||
|
config: DEFAULT_QR_CONFIG,
|
||||||
|
previewUrl: '',
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
generatePreview: async () => {
|
||||||
|
const { config } = get();
|
||||||
|
|
||||||
|
// 验证内容
|
||||||
|
if (!config.content.trim()) {
|
||||||
|
set({ error: '二维码内容不能为空' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ isGenerating: true, error: null });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = (await invoke('generate_qr_preview', {
|
||||||
|
config,
|
||||||
|
})) as QrResult;
|
||||||
|
|
||||||
|
set({ previewUrl: result.data, error: null });
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
set({ error: `生成失败: ${errorMessage}` });
|
||||||
|
} finally {
|
||||||
|
set({ isGenerating: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
exportToFile: async (outputPath) => {
|
||||||
|
const { config } = get();
|
||||||
|
|
||||||
|
if (!config.content.trim()) {
|
||||||
|
set({ error: '二维码内容不能为空' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ isGenerating: true, error: null });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invoke('generate_qr_file', {
|
||||||
|
config,
|
||||||
|
outputPath,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
set({ error: `导出失败: ${errorMessage}` });
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
set({ isGenerating: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearError: () => {
|
||||||
|
set({ error: null });
|
||||||
|
},
|
||||||
|
}));
|
||||||
99
src/types/qrcode.ts
Normal file
99
src/types/qrcode.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* 二维码生成相关类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二维码配置
|
||||||
|
*/
|
||||||
|
export interface QrConfig {
|
||||||
|
/** 二维码内容 */
|
||||||
|
content: string;
|
||||||
|
/** 输出尺寸(像素) */
|
||||||
|
size: number;
|
||||||
|
/** 边距(模块数) */
|
||||||
|
margin: number;
|
||||||
|
/** 容错级别 */
|
||||||
|
errorCorrection: 'L' | 'M' | 'Q' | 'H';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二维码样式(阶段 2 使用)
|
||||||
|
*/
|
||||||
|
export interface QrStyle {
|
||||||
|
/** 点形状 */
|
||||||
|
dotShape: 'square' | 'circle' | 'rounded';
|
||||||
|
/** 码眼形状 */
|
||||||
|
eyeShape: 'square' | 'circle' | 'rounded';
|
||||||
|
/** 前景色(Hex 颜色代码) */
|
||||||
|
foregroundColor: string;
|
||||||
|
/** 背景色(Hex 颜色代码) */
|
||||||
|
backgroundColor: string;
|
||||||
|
/** 是否使用渐变 */
|
||||||
|
isGradient: boolean;
|
||||||
|
/** 渐变颜色列表 */
|
||||||
|
gradientColors?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logo 配置(阶段 2 使用)
|
||||||
|
*/
|
||||||
|
export interface LogoConfig {
|
||||||
|
/** Logo 文件路径 */
|
||||||
|
path: string;
|
||||||
|
/** 缩放比例 (0.1 - 0.3) */
|
||||||
|
scale: number;
|
||||||
|
/** 是否添加边框 */
|
||||||
|
hasBorder: boolean;
|
||||||
|
/** 边框宽度(像素) */
|
||||||
|
borderWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二维码生成结果
|
||||||
|
*/
|
||||||
|
export interface QrResult {
|
||||||
|
/** Base64 编码的图片数据(带 data URL 前缀) */
|
||||||
|
data: string;
|
||||||
|
/** 图片格式(如 "png") */
|
||||||
|
format: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tauri 命令类型声明
|
||||||
|
*/
|
||||||
|
export type QrCodeCommands = {
|
||||||
|
/** 生成二维码预览 */
|
||||||
|
generate_qr_preview: (config: QrConfig) => Promise<QrResult>;
|
||||||
|
/** 生成二维码并保存到文件 */
|
||||||
|
generate_qr_file: (config: QrConfig, outputPath: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认二维码配置
|
||||||
|
*/
|
||||||
|
export const DEFAULT_QR_CONFIG: QrConfig = {
|
||||||
|
content: 'https://example.com',
|
||||||
|
size: 512,
|
||||||
|
margin: 4,
|
||||||
|
errorCorrection: 'M',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容错级别选项
|
||||||
|
*/
|
||||||
|
export const ERROR_CORRECTION_OPTIONS = [
|
||||||
|
{ value: 'L', label: 'L (7%)', description: '低容错率' },
|
||||||
|
{ value: 'M', label: 'M (15%)', description: '中容错率(推荐)' },
|
||||||
|
{ value: 'Q', label: 'Q (25%)', description: '高容错率' },
|
||||||
|
{ value: 'H', label: 'H (30%)', description: '最高容错率' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预设尺寸选项
|
||||||
|
*/
|
||||||
|
export const SIZE_PRESETS = [
|
||||||
|
{ value: 256, label: '小 (256px)' },
|
||||||
|
{ value: 512, label: '中 (512px)' },
|
||||||
|
{ value: 1024, label: '大 (1024px)' },
|
||||||
|
{ value: 2048, label: '超大 (2048px)' },
|
||||||
|
] as const;
|
||||||
Reference in New Issue
Block a user