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

@@ -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())
}