Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
Expand Down
51 changes: 51 additions & 0 deletions src-tauri/src/commands/balance_commands.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// 余额查询相关命令
//
// 支持通过自定义 API 端点和提取器脚本查询余额信息
// 以及余额监控配置的持久化存储管理

use ::duckcoding::http_client::build_client;
use ::duckcoding::models::{BalanceConfig, BalanceStore};
use ::duckcoding::services::balance::BalanceManager;
use ::duckcoding::utils::config::apply_proxy_if_configured;
use std::collections::HashMap;

Expand Down Expand Up @@ -72,3 +75,51 @@ pub async fn fetch_api(

Ok(data)
}

// ========== 配置管理命令 ==========

/// 加载所有余额监控配置
#[tauri::command]
pub async fn load_balance_configs() -> Result<BalanceStore, String> {
let manager = BalanceManager::new().map_err(|e| e.to_string())?;
manager.load_store().map_err(|e| e.to_string())
}

/// 添加新的余额监控配置
#[tauri::command]
pub async fn save_balance_config(config: BalanceConfig) -> Result<(), String> {
let manager = BalanceManager::new().map_err(|e| e.to_string())?;
manager.add_config(config).map_err(|e| e.to_string())
}

/// 更新现有的余额监控配置
#[tauri::command]
pub async fn update_balance_config(config: BalanceConfig) -> Result<(), String> {
let manager = BalanceManager::new().map_err(|e| e.to_string())?;
manager.update_config(config).map_err(|e| e.to_string())
}

/// 删除余额监控配置
#[tauri::command]
pub async fn delete_balance_config(id: String) -> Result<(), String> {
let manager = BalanceManager::new().map_err(|e| e.to_string())?;
manager.delete_config(&id).map_err(|e| e.to_string())
}

/// 批量保存配置(用于从 localStorage 迁移)
///
/// 这个命令由前端在首次加载时自动调用,完成数据迁移
#[tauri::command]
pub async fn migrate_balance_from_localstorage(
configs: Vec<BalanceConfig>,
) -> Result<usize, String> {
let manager = BalanceManager::new().map_err(|e| e.to_string())?;

let count = configs.len();
manager
.save_all_configs(configs)
.map_err(|e| e.to_string())?;

tracing::info!("从 localStorage 迁移了 {} 个余额监控配置", count);
Ok(count)
}
5 changes: 5 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,11 @@ fn main() {
get_usage_stats,
get_user_quota,
fetch_api,
load_balance_configs,
save_balance_config,
update_balance_config,
delete_balance_config,
migrate_balance_from_localstorage,
handle_close_action,
// expose current proxy for debugging/testing
get_current_proxy,
Expand Down
98 changes: 98 additions & 0 deletions src-tauri/src/models/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Balance 监控数据模型
//
// 余额监控配置的持久化存储结构

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// 余额监控配置项
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceConfig {
/// 配置 ID
pub id: String,
/// 配置名称
pub name: String,
/// API 端点 URL
pub endpoint: String,
/// HTTP 方法(GET | POST)
pub method: String,
/// 静态请求头(持久化)
#[serde(skip_serializing_if = "Option::is_none")]
pub static_headers: Option<HashMap<String, String>>,
/// 提取器 JavaScript 代码
pub extractor_script: String,
/// 自动刷新间隔(秒),0 或 None 表示不自动刷新
#[serde(skip_serializing_if = "Option::is_none")]
pub interval_sec: Option<u32>,
/// 请求超时(毫秒)
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
/// 是否保存 API Key 到文件
#[serde(default)]
pub save_api_key: bool,
/// API Key(可选,明文存储)
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
/// 创建时间(Unix 时间戳,毫秒)
pub created_at: i64,
/// 更新时间(Unix 时间戳,毫秒)
pub updated_at: i64,
}

/// 余额监控存储结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceStore {
/// 存储格式版本
pub version: u32,
/// 所有配置列表
pub configs: Vec<BalanceConfig>,
}

impl Default for BalanceStore {
fn default() -> Self {
Self {
version: 1,
configs: Vec::new(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_balance_config_serialization() {
let config = BalanceConfig {
id: "test-id".to_string(),
name: "Test Config".to_string(),
endpoint: "https://api.example.com/balance".to_string(),
method: "GET".to_string(),
static_headers: Some(HashMap::from([(
"Authorization".to_string(),
"Bearer token".to_string(),
)])),
extractor_script: "return response.balance;".to_string(),
interval_sec: Some(300),
timeout_ms: Some(5000),
save_api_key: false,
api_key: None,
created_at: 1234567890000,
updated_at: 1234567890000,
};

let json = serde_json::to_string(&config).unwrap();
let deserialized: BalanceConfig = serde_json::from_str(&json).unwrap();

assert_eq!(config.id, deserialized.id);
assert_eq!(config.name, deserialized.name);
assert_eq!(config.save_api_key, deserialized.save_api_key);
}

#[test]
fn test_balance_store_default() {
let store = BalanceStore::default();
assert_eq!(store.version, 1);
assert_eq!(store.configs.len(), 0);
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod balance;
pub mod config;
pub mod proxy_config;
pub mod tool;
pub mod update;

pub use balance::*;
pub use config::*;
// 只导出新的 proxy_config 类型,避免与 config.rs 中的旧类型冲突
pub use proxy_config::{ProxyMetadata, ProxyStore};
Expand Down
Loading
Loading