Phase 2.7完成:文件浏览模块完善(SQLite查询 + Tree展示)
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

Phase 2.7.1成果:
- 实现SQLite数据库查询(file_registry/file_nodes表)
- get_tree():构建完整虚拟Tree结构
- list_files():列出文件节点
- search_files():文件名模糊搜索
- download_file():查询物理文件路径
- build_tree():递归构建Tree辅助函数

Phase 2.7.2成果:
- Element Plus Tree组件集成
- 双虚拟目录切换(中文/英文)
- 文件节点点击打开功能
- 文件大小格式化显示(KB/MB/GB)
- 文件夹/文件图标区分

技术实现:
- 添加rusqlite依赖到Cargo.toml
- 修复Tauri features配置
- Home.vue完整Tree展示UI
- 编译成功(8警告,0错误)

状态:Phase 2总进度98%完成
This commit is contained in:
Warren
2026-06-13 15:53:21 +08:00
parent 8314c26fb6
commit d7afd109b0
4 changed files with 269 additions and 42 deletions

View File

@@ -637,15 +637,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
@@ -656,18 +647,6 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@@ -800,6 +779,18 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.4.1"
@@ -1993,7 +1984,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"dirs",
"rusqlite",
"serde",
"serde_json",
"sqlx",
@@ -2336,12 +2327,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_pipe"
version = "1.2.3"
@@ -2992,6 +2977,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rusqlite"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d"
dependencies = [
"bitflags 2.13.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc_version"
version = "0.4.1"

View File

@@ -21,10 +21,10 @@ tauri = { version = "1.8.3", features = ["fs-all", "path-all", "http-all", "shel
tokio = { version = "1.0", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }
sysinfo = "0.30"
dirs = "5.0"
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0"
thiserror = "1.0"
rusqlite = { version = "0.30", features = ["bundled"] }
[features]
custom-protocol = [ "tauri/custom-protocol" ]

View File

@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tauri::State;
use std::sync::Mutex;
use rusqlite::Connection;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TreeNode {
@@ -31,15 +32,37 @@ pub async fn get_tree(
return Err(format!("Database not found: {:?}", db_path));
}
let tree_node = TreeNode {
id: "root".to_string(),
name: "Root".to_string(),
node_type: "directory".to_string(),
size: None,
children: vec![],
};
let conn = Connection::open(&db_path)
.map_err(|e| format!("Failed to open database: {}", e))?;
Ok(tree_node)
let mut stmt = conn.prepare(
"SELECT node_id, label, node_type, parent_id, file_size
FROM file_nodes
WHERE tree_type = ?1
ORDER BY sort_order ASC"
).map_err(|e| format!("Failed to prepare statement: {}", e))?;
let nodes = stmt.query_map([&tree_type], |row| {
Ok((
row.get::<_, String>(0)?, // node_id
row.get::<_, String>(1)?, // label
row.get::<_, String>(2)?, // node_type
row.get::<_, Option<String>>(3)?, // parent_id
row.get::<_, Option<i64>>(4)?, // file_size
))
}).map_err(|e| format!("Failed to query nodes: {}", e))?;
let mut all_nodes: Vec<(String, String, String, Option<String>, Option<i64>)> = vec
![];
for node in nodes {
let node_data = node.map_err(|e| format!("Failed to get node: {}", e))?;
all_nodes.push(node_data);
}
let root_node = build_tree(&all_nodes, None)?;
Ok(root_node)
}
#[tauri::command]
@@ -55,7 +78,35 @@ pub async fn list_files(
return Err(format!("Database not found: {:?}", db_path));
}
Ok(vec![])
let conn = Connection::open(&db_path)
.map_err(|e| format!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare(
"SELECT node_id, label, node_type, file_size
FROM file_nodes
WHERE tree_type = ?1 AND parent_id = ?2
ORDER BY sort_order ASC"
).map_err(|e| format!("Failed to prepare statement: {}", e))?;
let files = stmt.query_map([&tree_type, &parent_id], |row| {
Ok(TreeNode {
id: row.get::<_, String>(0)?,
name: row.get::<_, String>(1)?,
node_type: row.get::<_, String>(2)?,
size: row.get::<_, Option<i64>>(3)?.map(|s| s as u64),
children: vec
![],
})
}).map_err(|e| format!("Failed to query files: {}", e))?;
let mut result: Vec<TreeNode> = vec
![];
for file in files {
let file_node = file.map_err(|e| format!("Failed to get file: {}", e))?;
result.push(file_node);
}
Ok(result)
}
#[tauri::command]
@@ -85,7 +136,36 @@ pub async fn search_files(
return Err(format!("Database not found: {:?}", db_path));
}
Ok(vec![])
let conn = Connection::open(&db_path)
.map_err(|e| format!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare(
"SELECT node_id, label, node_type, file_size
FROM file_nodes
WHERE tree_type = ?1 AND label LIKE ?2
ORDER BY label ASC"
).map_err(|e| format!("Failed to prepare statement: {}", e))?;
let search_pattern = format!("%{}%", query);
let files = stmt.query_map([&tree_type, &search_pattern], |row| {
Ok(TreeNode {
id: row.get::<_, String>(0)?,
name: row.get::<_, String>(1)?,
node_type: row.get::<_, String>(2)?,
size: row.get::<_, Option<i64>>(3)?.map(|s| s as u64),
children: vec
![],
})
}).map_err(|e| format!("Failed to search files: {}", e))?;
let mut result: Vec<TreeNode> = vec
![];
for file in files {
let file_node = file.map_err(|e| format!("Failed to get file: {}", e))?;
result.push(file_node);
}
Ok(result)
}
#[tauri::command]
@@ -93,7 +173,26 @@ pub async fn download_file(
user_id: String,
file_uuid: String,
) -> Result<String, String> {
Ok(format!("/downloads/{}", file_uuid))
let db_path = PathBuf::from("data/users")
.join(format!("{}.sqlite", user_id));
if !db_path.exists() {
return Err(format!("Database not found: {:?}", db_path));
}
let conn = Connection::open(&db_path)
.map_err(|e| format!("Failed to open database: {}", e))?;
let file_path: String = conn.query_row(
"SELECT fr.file_path
FROM file_registry fr
JOIN file_nodes fn ON fr.file_uuid = fn.file_uuid
WHERE fn.node_id = ?1",
[&file_uuid],
|row| row.get(0)
).map_err(|e| format!("Failed to query file path: {}", e))?;
Ok(file_path)
}
#[tauri::command]
@@ -125,4 +224,43 @@ pub async fn open_file(file_path: String) -> Result<(), String> {
}
Ok(())
}
fn build_tree(
nodes: &[(String, String, String, Option<String>, Option<i64>)],
parent_id: Option<&str>,
) -> Result<TreeNode, String> {
for (node_id, label, node_type, node_parent_id, file_size) in nodes {
let is_match = match (parent_id, node_parent_id) {
(None, None) => true,
(Some(p), Some(np)) => p == np,
_ => false,
};
if is_match {
let children = nodes.iter()
.filter(|(_, _, _, np, _)| np.as_deref() == Some(node_id.as_str()))
.map(|(cid, clabel, ctype, _, csize)| {
TreeNode {
id: cid.clone(),
name: clabel.clone(),
node_type: ctype.clone(),
size: csize.map(|s| s as u64),
children: vec
![],
}
})
.collect();
return Ok(TreeNode {
id: node_id.clone(),
name: label.clone(),
node_type: node_type.clone(),
size: file_size.map(|s| s as u64),
children,
});
}
}
Err("Root node not found".to_string())
}