feat(desktop): add sort/filter, update center, and local sync workspace

This commit is contained in:
2026-02-18 20:07:21 +08:00
parent d4818a78d3
commit 32a66e6c77
8 changed files with 1184 additions and 39 deletions

View File

@@ -6,7 +6,7 @@ use std::env;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::Duration;
use std::time::{Duration, UNIX_EPOCH};
struct ApiState {
client: reqwest::Client,
@@ -595,6 +595,122 @@ async fn api_native_download(
})
}
#[tauri::command]
async fn api_check_client_update(
state: tauri::State<'_, ApiState>,
base_url: String,
current_version: String,
platform: Option<String>,
channel: Option<String>,
) -> Result<BridgeResponse, String> {
let normalized_platform = platform
.unwrap_or_else(|| "windows-x64".to_string())
.trim()
.to_string();
let normalized_channel = channel
.unwrap_or_else(|| "stable".to_string())
.trim()
.to_string();
let api_url = format!(
"{}?currentVersion={}&platform={}&channel={}",
join_api_url(&base_url, "/api/client/desktop-update"),
urlencoding::encode(current_version.trim()),
urlencoding::encode(&normalized_platform),
urlencoding::encode(&normalized_channel)
);
request_json(&state.client, Method::GET, api_url, None, None).await
}
#[tauri::command]
async fn api_list_local_files(dir_path: String) -> Result<BridgeResponse, String> {
let trimmed = dir_path.trim().to_string();
if trimmed.is_empty() {
return Err("本地目录不能为空".to_string());
}
let root = PathBuf::from(&trimmed);
if !root.exists() {
return Err("本地目录不存在".to_string());
}
if !root.is_dir() {
return Err("请选择有效的目录路径".to_string());
}
let mut items: Vec<Value> = Vec::new();
for entry in walkdir::WalkDir::new(&root)
.follow_links(false)
.into_iter()
.filter_map(Result::ok)
{
if !entry.file_type().is_file() {
continue;
}
let full_path = entry.path();
let relative = full_path.strip_prefix(&root).unwrap_or(full_path);
let relative_path = relative.to_string_lossy().replace('\\', "/");
if relative_path.trim().is_empty() {
continue;
}
let metadata = match entry.metadata() {
Ok(meta) => meta,
Err(_) => continue,
};
let modified_ms_u128 = metadata
.modified()
.ok()
.and_then(|value| value.duration_since(UNIX_EPOCH).ok())
.map(|duration| duration.as_millis())
.unwrap_or(0);
let modified_ms = std::cmp::min(modified_ms_u128, u128::from(u64::MAX)) as u64;
let mut row = Map::new();
row.insert(
"path".to_string(),
Value::String(full_path.to_string_lossy().to_string()),
);
row.insert("relativePath".to_string(), Value::String(relative_path));
row.insert(
"size".to_string(),
Value::Number(serde_json::Number::from(metadata.len())),
);
row.insert(
"modifiedMs".to_string(),
Value::Number(serde_json::Number::from(modified_ms)),
);
items.push(Value::Object(row));
}
items.sort_by(|a, b| {
let av = a
.get("relativePath")
.and_then(Value::as_str)
.unwrap_or_default();
let bv = b
.get("relativePath")
.and_then(Value::as_str)
.unwrap_or_default();
av.cmp(bv)
});
let mut data = Map::new();
data.insert("success".to_string(), Value::Bool(true));
data.insert("rootPath".to_string(), Value::String(trimmed));
data.insert(
"count".to_string(),
Value::Number(serde_json::Number::from(items.len() as u64)),
);
data.insert("items".to_string(), Value::Array(items));
Ok(BridgeResponse {
ok: true,
status: 200,
data: Value::Object(data),
})
}
#[tauri::command]
async fn api_upload_file(
state: tauri::State<'_, ApiState>,
@@ -685,6 +801,7 @@ pub fn run() {
tauri::Builder::default()
.manage(ApiState { client })
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
api_login,
@@ -701,6 +818,8 @@ pub fn run() {
api_delete_share,
api_create_direct_link,
api_native_download,
api_check_client_update,
api_list_local_files,
api_upload_file
])
.run(tauri::generate_context!())