feat(desktop): add sort/filter, update center, and local sync workspace
This commit is contained in:
10
desktop-client/package-lock.json
generated
10
desktop-client/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
@@ -1091,6 +1092,15 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-dialog": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz",
|
||||
"integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-opener": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz",
|
||||
|
||||
@@ -10,15 +10,16 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2"
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.3",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"@tauri-apps/cli": "^2"
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
68
desktop-client/src-tauri/Cargo.lock
generated
68
desktop-client/src-tauri/Cargo.lock
generated
@@ -688,8 +688,10 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-opener",
|
||||
"urlencoding",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -736,6 +738,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
@@ -3162,6 +3166,30 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
@@ -3927,6 +3955,46 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.18",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.18",
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.3"
|
||||
|
||||
@@ -20,7 +20,9 @@ tauri-build = { version = "2", features = [] }
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "cookies", "multipart", "stream", "rustls-tls"] }
|
||||
urlencoding = "2.1"
|
||||
walkdir = "2.5"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
"opener:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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!())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user