feat(desktop): add sort/filter, update center, and local sync workspace
This commit is contained in:
@@ -97,6 +97,9 @@ const DOWNLOAD_RESERVATION_TTL_MS = Number(process.env.DOWNLOAD_RESERVATION_TTL_
|
|||||||
const DOWNLOAD_LOG_RECONCILE_INTERVAL_MS = Number(process.env.DOWNLOAD_LOG_RECONCILE_INTERVAL_MS || (5 * 60 * 1000)); // 5分钟
|
const DOWNLOAD_LOG_RECONCILE_INTERVAL_MS = Number(process.env.DOWNLOAD_LOG_RECONCILE_INTERVAL_MS || (5 * 60 * 1000)); // 5分钟
|
||||||
const DOWNLOAD_LOG_MAX_FILES_PER_SWEEP = Number(process.env.DOWNLOAD_LOG_MAX_FILES_PER_SWEEP || 40);
|
const DOWNLOAD_LOG_MAX_FILES_PER_SWEEP = Number(process.env.DOWNLOAD_LOG_MAX_FILES_PER_SWEEP || 40);
|
||||||
const DOWNLOAD_LOG_LIST_MAX_KEYS = Number(process.env.DOWNLOAD_LOG_LIST_MAX_KEYS || 200);
|
const DOWNLOAD_LOG_LIST_MAX_KEYS = Number(process.env.DOWNLOAD_LOG_LIST_MAX_KEYS || 200);
|
||||||
|
const DEFAULT_DESKTOP_VERSION = process.env.DESKTOP_LATEST_VERSION || '0.1.0';
|
||||||
|
const DEFAULT_DESKTOP_INSTALLER_URL = process.env.DESKTOP_INSTALLER_URL || '';
|
||||||
|
const DEFAULT_DESKTOP_RELEASE_NOTES = process.env.DESKTOP_RELEASE_NOTES || '';
|
||||||
const RESUMABLE_UPLOAD_SESSION_TTL_MS = Number(process.env.RESUMABLE_UPLOAD_SESSION_TTL_MS || (24 * 60 * 60 * 1000)); // 24小时
|
const RESUMABLE_UPLOAD_SESSION_TTL_MS = Number(process.env.RESUMABLE_UPLOAD_SESSION_TTL_MS || (24 * 60 * 60 * 1000)); // 24小时
|
||||||
const RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES || (4 * 1024 * 1024)); // 4MB
|
const RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES || (4 * 1024 * 1024)); // 4MB
|
||||||
const RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES || (32 * 1024 * 1024)); // 32MB
|
const RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES || (32 * 1024 * 1024)); // 32MB
|
||||||
@@ -127,6 +130,56 @@ const SHOULD_USE_SECURE_COOKIES =
|
|||||||
COOKIE_SECURE_MODE === 'true' ||
|
COOKIE_SECURE_MODE === 'true' ||
|
||||||
(process.env.NODE_ENV === 'production' && COOKIE_SECURE_MODE !== 'false');
|
(process.env.NODE_ENV === 'production' && COOKIE_SECURE_MODE !== 'false');
|
||||||
|
|
||||||
|
function normalizeVersion(rawVersion, fallback = '0.0.0') {
|
||||||
|
const value = String(rawVersion || '').trim();
|
||||||
|
if (!value) return fallback;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareLooseVersion(left, right) {
|
||||||
|
const normalize = (value) => normalizeVersion(value, '0.0.0')
|
||||||
|
.replace(/^v/i, '')
|
||||||
|
.split('.')
|
||||||
|
.map((part) => parseInt(part, 10))
|
||||||
|
.map((num) => (Number.isFinite(num) ? num : 0));
|
||||||
|
|
||||||
|
const a = normalize(left);
|
||||||
|
const b = normalize(right);
|
||||||
|
const size = Math.max(a.length, b.length);
|
||||||
|
for (let i = 0; i < size; i += 1) {
|
||||||
|
const av = a[i] || 0;
|
||||||
|
const bv = b[i] || 0;
|
||||||
|
if (av > bv) return 1;
|
||||||
|
if (av < bv) return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDesktopUpdateConfig() {
|
||||||
|
const latestVersion = normalizeVersion(
|
||||||
|
SettingsDB.get('desktop_latest_version') || DEFAULT_DESKTOP_VERSION,
|
||||||
|
DEFAULT_DESKTOP_VERSION
|
||||||
|
);
|
||||||
|
const installerUrl = String(
|
||||||
|
SettingsDB.get('desktop_installer_url_win_x64') ||
|
||||||
|
SettingsDB.get('desktop_installer_url') ||
|
||||||
|
DEFAULT_DESKTOP_INSTALLER_URL ||
|
||||||
|
''
|
||||||
|
).trim();
|
||||||
|
const releaseNotes = String(
|
||||||
|
SettingsDB.get('desktop_release_notes') ||
|
||||||
|
DEFAULT_DESKTOP_RELEASE_NOTES ||
|
||||||
|
''
|
||||||
|
).trim();
|
||||||
|
const mandatory = SettingsDB.get('desktop_force_update') === 'true';
|
||||||
|
return {
|
||||||
|
latestVersion,
|
||||||
|
installerUrl,
|
||||||
|
releaseNotes,
|
||||||
|
mandatory
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getResolvedStorageRoot() {
|
function getResolvedStorageRoot() {
|
||||||
const configuredRoot = process.env.STORAGE_ROOT;
|
const configuredRoot = process.env.STORAGE_ROOT;
|
||||||
if (!configuredRoot) {
|
if (!configuredRoot) {
|
||||||
@@ -3358,6 +3411,39 @@ app.get('/api/config', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 桌面客户端更新信息(无需登录)
|
||||||
|
app.get('/api/client/desktop-update', (req, res) => {
|
||||||
|
try {
|
||||||
|
const currentVersion = normalizeVersion(req.query.currentVersion || '0.0.0', '0.0.0');
|
||||||
|
const platform = String(req.query.platform || 'windows-x64').trim();
|
||||||
|
const channel = String(req.query.channel || 'stable').trim();
|
||||||
|
const config = getDesktopUpdateConfig();
|
||||||
|
const hasDownload = Boolean(config.installerUrl);
|
||||||
|
const updateAvailable = hasDownload && compareLooseVersion(currentVersion, config.latestVersion) < 0;
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
currentVersion,
|
||||||
|
latestVersion: config.latestVersion,
|
||||||
|
updateAvailable,
|
||||||
|
downloadUrl: config.installerUrl,
|
||||||
|
releaseNotes: config.releaseNotes,
|
||||||
|
mandatory: config.mandatory && updateAvailable,
|
||||||
|
platform,
|
||||||
|
channel,
|
||||||
|
message: hasDownload
|
||||||
|
? (updateAvailable ? '检测到新版本' : '当前已是最新版本')
|
||||||
|
: '服务器暂未配置桌面端升级包地址'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[客户端更新] 获取升级信息失败:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: '获取客户端更新信息失败'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 生成验证码API
|
// 生成验证码API
|
||||||
app.get('/api/captcha', captchaRateLimitMiddleware, (req, res) => {
|
app.get('/api/captcha', captchaRateLimitMiddleware, (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -8479,6 +8565,7 @@ app.get('/api/admin/settings', authMiddleware, adminMiddleware, (req, res) => {
|
|||||||
const smtpHasPassword = !!SettingsDB.get('smtp_password');
|
const smtpHasPassword = !!SettingsDB.get('smtp_password');
|
||||||
const globalTheme = SettingsDB.get('global_theme') || 'dark';
|
const globalTheme = SettingsDB.get('global_theme') || 'dark';
|
||||||
const downloadSecurity = getDownloadSecuritySettings();
|
const downloadSecurity = getDownloadSecuritySettings();
|
||||||
|
const desktopUpdate = getDesktopUpdateConfig();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -8486,6 +8573,12 @@ app.get('/api/admin/settings', authMiddleware, adminMiddleware, (req, res) => {
|
|||||||
max_upload_size: maxUploadSize,
|
max_upload_size: maxUploadSize,
|
||||||
global_theme: globalTheme,
|
global_theme: globalTheme,
|
||||||
download_security: downloadSecurity,
|
download_security: downloadSecurity,
|
||||||
|
desktop_update: {
|
||||||
|
latest_version: desktopUpdate.latestVersion,
|
||||||
|
installer_url: desktopUpdate.installerUrl,
|
||||||
|
release_notes: desktopUpdate.releaseNotes,
|
||||||
|
force_update: desktopUpdate.mandatory
|
||||||
|
},
|
||||||
smtp: {
|
smtp: {
|
||||||
host: smtpHost || '',
|
host: smtpHost || '',
|
||||||
port: smtpPort ? parseInt(smtpPort, 10) : 465,
|
port: smtpPort ? parseInt(smtpPort, 10) : 465,
|
||||||
@@ -8516,7 +8609,8 @@ app.post('/api/admin/settings',
|
|||||||
max_upload_size,
|
max_upload_size,
|
||||||
smtp,
|
smtp,
|
||||||
global_theme,
|
global_theme,
|
||||||
download_security
|
download_security,
|
||||||
|
desktop_update
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (max_upload_size !== undefined) {
|
if (max_upload_size !== undefined) {
|
||||||
@@ -8572,6 +8666,29 @@ app.post('/api/admin/settings',
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (desktop_update !== undefined) {
|
||||||
|
if (!desktop_update || typeof desktop_update !== 'object') {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: '桌面端更新配置格式错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desktop_update.latest_version !== undefined) {
|
||||||
|
SettingsDB.set('desktop_latest_version', normalizeVersion(desktop_update.latest_version, DEFAULT_DESKTOP_VERSION));
|
||||||
|
}
|
||||||
|
if (desktop_update.installer_url !== undefined) {
|
||||||
|
SettingsDB.set('desktop_installer_url', String(desktop_update.installer_url || '').trim());
|
||||||
|
SettingsDB.set('desktop_installer_url_win_x64', String(desktop_update.installer_url || '').trim());
|
||||||
|
}
|
||||||
|
if (desktop_update.release_notes !== undefined) {
|
||||||
|
SettingsDB.set('desktop_release_notes', String(desktop_update.release_notes || '').trim());
|
||||||
|
}
|
||||||
|
if (desktop_update.force_update !== undefined) {
|
||||||
|
SettingsDB.set('desktop_force_update', desktop_update.force_update ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '系统设置已更新'
|
message: '系统设置已更新'
|
||||||
|
|||||||
10
desktop-client/package-lock.json
generated
10
desktop-client/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
@@ -1091,6 +1092,15 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@tauri-apps/plugin-opener": {
|
||||||
"version": "2.5.3",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz",
|
||||||
|
|||||||
@@ -10,15 +10,16 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.13",
|
|
||||||
"@tauri-apps/api": "^2",
|
"@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": {
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.3",
|
||||||
"vue-tsc": "^2.1.10",
|
"vue-tsc": "^2.1.10"
|
||||||
"@tauri-apps/cli": "^2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
desktop-client/src-tauri/Cargo.lock
generated
68
desktop-client/src-tauri/Cargo.lock
generated
@@ -688,8 +688,10 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -736,6 +738,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
|
"block2",
|
||||||
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3162,6 +3166,30 @@ dependencies = [
|
|||||||
"web-sys",
|
"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]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -3927,6 +3955,46 @@ dependencies = [
|
|||||||
"walkdir",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-plugin-opener"
|
name = "tauri-plugin-opener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ tauri-build = { version = "2", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = [] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["json", "cookies", "multipart", "stream", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["json", "cookies", "multipart", "stream", "rustls-tls"] }
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
walkdir = "2.5"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default"
|
"opener:default",
|
||||||
|
"dialog:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::env;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
|
||||||
struct ApiState {
|
struct ApiState {
|
||||||
client: reqwest::Client,
|
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]
|
#[tauri::command]
|
||||||
async fn api_upload_file(
|
async fn api_upload_file(
|
||||||
state: tauri::State<'_, ApiState>,
|
state: tauri::State<'_, ApiState>,
|
||||||
@@ -685,6 +801,7 @@ pub fn run() {
|
|||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(ApiState { client })
|
.manage(ApiState { client })
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
api_login,
|
api_login,
|
||||||
@@ -701,6 +818,8 @@ pub fn run() {
|
|||||||
api_delete_share,
|
api_delete_share,
|
||||||
api_create_direct_link,
|
api_create_direct_link,
|
||||||
api_native_download,
|
api_native_download,
|
||||||
|
api_check_client_update,
|
||||||
|
api_list_local_files,
|
||||||
api_upload_file
|
api_upload_file
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user