From 82a64787c3f9ea5c6184fc5e0118353f77e943fc Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Thu, 2 Jul 2026 00:47:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .reasonix/desktop-topic-title-sources.json | 5 +- .reasonix/desktop-topic-titles.json | 5 +- package.json | 1 + pnpm-lock.yaml | 10 + src-tauri/Cargo.lock | 144 ++++++++++++++- src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 3 +- src-tauri/src/lib.rs | 194 +++++++++++++++++++- src-tauri/tauri.conf.json | 3 +- src/App.vue | 203 +++++++++++++++------ src/components/DirectorySidebar.vue | 112 +++++------- src/components/MarkdownEditor.vue | 14 +- 12 files changed, 568 insertions(+), 127 deletions(-) diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json index aaa4562..41f4c3f 100644 --- a/.reasonix/desktop-topic-title-sources.json +++ b/.reasonix/desktop-topic-title-sources.json @@ -2,5 +2,8 @@ "topic_20260630-144952_fd0584fe8f266d6a": "auto", "topic_20260630-151112_1109fa65a65cc25f": "auto", "topic_20260630-160436_83c4100e5ffeaf1a": "auto", - "topic_20260630-161655_edfbcdeb0d6b655b": "auto" + "topic_20260630-161655_edfbcdeb0d6b655b": "auto", + "topic_20260701-155310_c69d2b8b32c78f89": "auto", + "topic_20260701-161459_aeaacf65cf653c96": "auto", + "topic_20260701-162849_a8bde34cd0ff69c1": "auto" } \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json index e58147c..8a60ef3 100644 --- a/.reasonix/desktop-topic-titles.json +++ b/.reasonix/desktop-topic-titles.json @@ -2,5 +2,8 @@ "topic_20260630-144952_fd0584fe8f266d6a": "PS D:\\Code\\yurou\u003e …", "topic_20260630-151112_1109fa65a65cc25f": "这个项目cargo安装的很慢,帮我配…", "topic_20260630-160436_83c4100e5ffeaf1a": "我要用这个项目做一个markdown…", - "topic_20260630-161655_edfbcdeb0d6b655b": "我现在需要给这个首页做一个三栏布局…" + "topic_20260630-161655_edfbcdeb0d6b655b": "我现在需要给这个首页做一个三栏布局…", + "topic_20260701-155310_c69d2b8b32c78f89": "MarkdownEditor组件应该…", + "topic_20260701-161459_aeaacf65cf653c96": "添加一下原生标题栏", + "topic_20260701-162849_a8bde34cd0ff69c1": "帮我完善一个功能,当用户直接打开软件…" } \ No newline at end of file diff --git a/package.json b/package.json index 0b9f3af..c1fac12 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-opener": "^2", "@tiptap/extension-placeholder": "^3.27.1", "@tiptap/pm": "^3.27.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1924afc..e594ffe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@tauri-apps/api': specifier: ^2 version: 2.11.1 + '@tauri-apps/plugin-dialog': + specifier: ^2.7.1 + version: 2.7.1 '@tauri-apps/plugin-opener': specifier: ^2 version: 2.5.4 @@ -563,6 +566,9 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-dialog@2.7.1': + resolution: {integrity: sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==} + '@tauri-apps/plugin-opener@2.5.4': resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==} @@ -1388,6 +1394,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 '@tauri-apps/cli-win32-x64-msvc': 2.11.4 + '@tauri-apps/plugin-dialog@2.7.1': + dependencies: + '@tauri-apps/api': 2.11.1 + '@tauri-apps/plugin-opener@2.5.4': dependencies: '@tauri-apps/api': 2.11.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 466de3c..8bdde8d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2180,6 +2180,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2696,6 +2697,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 = "rustc-hash" version = "2.1.2" @@ -3378,6 +3403,48 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-opener" version = "2.5.4" @@ -4419,6 +4486,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -4452,13 +4528,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -4489,6 +4582,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4501,6 +4600,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4513,12 +4618,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4531,6 +4648,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4543,6 +4666,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4555,6 +4684,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4567,6 +4702,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -4709,6 +4850,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-opener", "time", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f1c8361..d29482b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } +tauri-plugin-dialog = "2" tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 4cdbf49..778bfb5 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,6 +5,7 @@ "windows": ["main"], "permissions": [ "core:default", - "opener:default" + "opener:default", + "dialog:default" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4a277ef..eb0a844 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,64 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use std::fs; +use tauri::menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder}; +use tauri::Emitter; +use tauri_plugin_dialog::DialogExt; + +#[derive(Debug, Clone, serde::Serialize)] +struct DirEntry { + name: String, + path: String, + is_dir: bool, +} + +#[tauri::command] +fn pick_folder(app: tauri::AppHandle) -> Option { + app.dialog() + .file() + .blocking_pick_folder() + .map(|p| p.to_string()) +} + +#[tauri::command] +fn read_dir(path: String) -> Result, String> { + let entries = fs::read_dir(&path).map_err(|e| format!("无法读取目录: {}", e))?; + let mut result = Vec::new(); + for entry in entries { + let entry = entry.map_err(|e| format!("读取条目失败: {}", e))?; + let path = entry.path(); + let name = entry + .file_name() + .to_string_lossy() + .to_string(); + result.push(DirEntry { + name, + path: path.to_string_lossy().to_string(), + is_dir: path.is_dir(), + }); + } + // Sort: directories first, then alphabetical + result.sort_by(|a, b| b.is_dir.cmp(&a.is_dir).then_with(|| a.name.cmp(&b.name))); + Ok(result) +} + +#[tauri::command] +fn read_file(path: String) -> Result { + fs::read_to_string(&path).map_err(|e| format!("无法读取文件: {}", e)) +} + +#[tauri::command] +fn write_file(path: String, content: String) -> Result<(), String> { + fs::write(&path, &content).map_err(|e| format!("无法保存文件: {}", e)) +} + +#[tauri::command] +fn pick_save_file(app: tauri::AppHandle) -> Option { + app.dialog() + .file() + .blocking_save_file() + .map(|p| p.to_string()) +} + #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) @@ -8,7 +68,139 @@ fn greet(name: &str) -> String { pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) + .plugin(tauri_plugin_dialog::init()) + .invoke_handler(tauri::generate_handler![greet, pick_folder, read_dir, read_file, write_file, pick_save_file]) + .setup(|app| { + // ---- File menu ---- + let new = MenuItemBuilder::with_id("new", "新建") + .accelerator("CmdOrCtrl+N") + .build(app)?; + let open = MenuItemBuilder::with_id("open", "打开文件夹...") + .accelerator("CmdOrCtrl+O") + .build(app)?; + let save = MenuItemBuilder::with_id("save", "保存") + .accelerator("CmdOrCtrl+S") + .build(app)?; + let save_as = MenuItemBuilder::with_id("save_as", "另存为...") + .accelerator("CmdOrCtrl+Shift+S") + .build(app)?; + let quit = MenuItemBuilder::with_id("quit", "退出") + .accelerator("CmdOrCtrl+Q") + .build(app)?; + + let file_menu = SubmenuBuilder::new(app, "文件") + .item(&new) + .item(&open) + .item(&save) + .item(&save_as) + .separator() + .item(&quit) + .build()?; + + // ---- Edit menu ---- + let undo = MenuItemBuilder::with_id("undo", "撤销") + .accelerator("CmdOrCtrl+Z") + .build(app)?; + let redo = MenuItemBuilder::with_id("redo", "重做") + .accelerator("CmdOrCtrl+Shift+Z") + .build(app)?; + let cut = MenuItemBuilder::with_id("cut", "剪切") + .accelerator("CmdOrCtrl+X") + .build(app)?; + let copy = MenuItemBuilder::with_id("copy", "复制") + .accelerator("CmdOrCtrl+C") + .build(app)?; + let paste = MenuItemBuilder::with_id("paste", "粘贴") + .accelerator("CmdOrCtrl+V") + .build(app)?; + let select_all = MenuItemBuilder::with_id("select_all", "全选") + .accelerator("CmdOrCtrl+A") + .build(app)?; + + let edit_menu = SubmenuBuilder::new(app, "编辑") + .item(&undo) + .item(&redo) + .separator() + .item(&cut) + .item(©) + .item(&paste) + .separator() + .item(&select_all) + .build()?; + + // ---- View menu ---- + let toggle_outline = MenuItemBuilder::with_id("toggle_outline", "切换大纲") + .accelerator("CmdOrCtrl+B") + .build(app)?; + + let view_menu = SubmenuBuilder::new(app, "视图") + .item(&toggle_outline) + .build()?; + + // ---- Help menu ---- + let about = MenuItemBuilder::with_id("about", "关于") + .build(app)?; + + let help_menu = SubmenuBuilder::new(app, "帮助") + .item(&about) + .build()?; + + // ---- Build the menu bar ---- + let menu = MenuBuilder::new(app) + .item(&file_menu) + .item(&edit_menu) + .item(&view_menu) + .item(&help_menu) + .build()?; + + app.set_menu(menu)?; + + // ---- Handle menu events ---- + let app_handle = app.handle().clone(); + app.on_menu_event(move |_app_handle, event| { + let id = event.id().0.as_str(); + match id { + "quit" => { + std::process::exit(0); + } + "open" => { + let handle = app_handle.clone(); + // Spawn because dialog is blocking + std::thread::spawn(move || { + if let Some(folder) = handle + .dialog() + .file() + .blocking_pick_folder() + { + let path = folder.to_string(); + let _ = handle.emit("folder-opened", path); + } + }); + } + "save" => { + let _ = app_handle.emit("menu-save", ()); + } + "save_as" => { + let _ = app_handle.emit("menu-save-as", ()); + } + "new" => { + let _ = app_handle.emit("menu-new", ()); + } + "toggle_outline" => { + let _ = app_handle.emit("toggle-outline", ()); + } + "about" => { + let _ = app_handle.emit("show-about", ()); + } + _ => { + // Forward other menu events to frontend + let _ = app_handle.emit("menu-event", id); + } + } + }); + + Ok(()) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e4ec2d8..112c560 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,7 +14,8 @@ { "title": "yurou", "width": 800, - "height": 600 + "height": 600, + "decorations": true } ], "security": { diff --git a/src/App.vue b/src/App.vue index 9530170..0f2c1ab 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,7 @@ diff --git a/src/components/DirectorySidebar.vue b/src/components/DirectorySidebar.vue index 8faf7c0..872980d 100644 --- a/src/components/DirectorySidebar.vue +++ b/src/components/DirectorySidebar.vue @@ -1,64 +1,58 @@