diff --git a/dist/RELEASE-v0.2.0.md b/dist/RELEASE-v0.2.0.md new file mode 100644 index 0000000..86fc1de --- /dev/null +++ b/dist/RELEASE-v0.2.0.md @@ -0,0 +1,147 @@ +# 🎉 ReadFlow v0.2.0 - MVP 正式发布 + +## 📥 下载 + +### macOS (Intel) +- [readflow-0.2.0-macos-x86_64.zip](./readflow-0.2.0-macos-x86_64.zip) + +### macOS (Apple Silicon) +- [readflow-0.2.0-macos-aarch64.zip](./readflow-0.2.0-macos-aarch64.zip) + +### Linux +- [readflow-0.2.0-linux-x86_64.tar.gz](./readflow-0.2.0-linux-x86_64.tar.gz) +- [readflow-0.2.0-linux-x86_64.AppImage](./readflow-0.2.0-linux-x86_64.AppImage) + +### Windows +- [readflow-0.2.0-windows-x86_64-installer.exe](./readflow-0.2.0-windows-x86_64-installer.exe) +- [readflow-0.2.0-windows-x86_64.zip](./readflow-0.2.0-windows-x86_64.zip) + +--- + +## ✨ 新功能 + +### Phase 2 - 核心功能 +- ✅ **EPUB/MOBI/AZW3 格式支持** - 完整电子书解析与元数据提取 +- ✅ **Markdown 阅读模式** - 原生/渲染/分屏三模式,支持 Front Matter +- ✅ **双语翻译功能** - 阿里百炼/DeepL/Ollama 三provider,段落级对照 +- ✅ **笔记与书签系统** - 高亮/下划线/波浪线/边注,导出 Markdown/CSV/Anki + +### Phase 3 - 高级功能 +- ✅ **代码阅读器** - 20+ 编程语言语法高亮 +- ✅ **全文双语对照** - 并排/段落交错两种模式,响应式布局 +- ✅ **阅读进度同步** - 本地追踪 + 云端同步,多设备冲突解决 +- ✅ **插件系统** - 插件加载/卸载/依赖管理,内置主题/快捷键插件 + +### Phase 4 - 性能与生态 +- ✅ **性能优化** - 性能分析器 + LRU 缓存,自动优化建议 +- ✅ **主题商店** - 4 种内置主题 (深色/浅色/护眼/高对比度) +- ✅ **跨平台打包** - macOS DMG/App, Linux AppImage, Windows NSIS + +--- + +## 🛠️ 技术栈 + +| 类别 | 技术 | +|------|------| +| 语言 | Rust 2021 | +| GUI | Dioxus 0.5 | +| 存储 | sled (嵌入式数据库) | +| 代码高亮 | syntect 5.1 | +| Markdown | pulldown-cmark 0.9 | +| 文档解析 | epub 2.0, mobi 0.2, pdfium-render 0.8 | +| 翻译 | 阿里百炼 / DeepL / Ollama | +| HTTP | reqwest 0.11 | + +--- + +## 📋 系统要求 + +| 平台 | 最低要求 | +|------|----------| +| macOS | 10.15+ (Intel/Apple Silicon) | +| Windows | 10+ (64-bit) | +| Linux | glibc 2.31+ | + +--- + +## 📊 项目统计 + +| 指标 | 数量 | +|------|------| +| 核心模块 | 9 个 | +| 代码行数 | ~6,000 行 | +| 支持格式 | 10+ 种 | +| 内置主题 | 4 个 | +| 代码语言 | 20+ 种 | +| 依赖项 | 20+ 个 | + +--- + +## 📖 快速开始 + +### macOS +```bash +# 下载后解压 +unzip readflow-0.2.0-macos-x86_64.zip +# 拖拽到 Applications 文件夹或直接运行 +./readflow.app/Contents/MacOS/readflow +``` + +### Linux +```bash +# AppImage (推荐) +chmod +x readflow-0.2.0-linux-x86_64.AppImage +./readflow-0.2.0-linux-x86_64.AppImage + +# 或解压 tar.gz +tar -xzf readflow-0.2.0-linux-x86_64.tar.gz +./readflow +``` + +### Windows +```bash +# 运行安装程序 +readflow-0.2.0-windows-x86_64-installer.exe + +# 或使用便携版 +unzip readflow-0.2.0-windows-x86_64.zip +readflow.exe +``` + +--- + +## 🐛 已知问题 + +1. PDF 渲染功能待完善 (Phase 5 计划) +2. 云端同步服务需自行部署服务器 +3. 移动端应用开发中 (iOS/Android) + +--- + +## 📞 反馈与支持 + +- **Gitea**: http://192.168.120.110:4000/damai/readflow +- **Email**: damai@foshanhuiya.com +- **Issue 追踪**: http://192.168.120.110:4000/damai/readflow/issues + +--- + +## 📝 更新日志 + +### v0.2.0 (2026-03-10) +- 🎉 MVP 正式发布 +- ✅ 完成 16/16 开发任务 +- ✅ 支持 10+ 文档格式 +- ✅ 支持 20+ 编程语言 +- ✅ 跨平台打包发布 + +### v0.1.0 (2026-03-09) +- 项目初始化 +- 核心架构设计 + +--- + +**🚀 感谢使用 ReadFlow!** + +*发布日期:2026-03-10* +*作者:damai * diff --git a/dist/RELEASE.md b/dist/RELEASE.md new file mode 100644 index 0000000..606007d --- /dev/null +++ b/dist/RELEASE.md @@ -0,0 +1,55 @@ +# ReadFlow v0.1.0 发布说明 + +## 下载 + +### macOS +- [Intel](readflow-0.1.0-macos-x86_64.dmg) +- [Apple Silicon](readflow-0.1.0-macos-aarch64.dmg) + +### Linux +- [AppImage](readflow-0.1.0-linux-x86_64.AppImage) +- [tar.gz](readflow-0.1.0-linux-x86_64.tar.gz) + +### Windows +- [Installer](readflow-0.1.0-windows-x86_64-installer.exe) +- [Portable](readflow-0.1.0-windows-x86_64.zip) + +## 新功能 + +### Phase 2 - 核心功能 +- ✅ EPUB/MOBI/AZW3 格式支持 +- ✅ Markdown 阅读模式 +- ✅ 双语翻译功能 +- ✅ 笔记与书签系统 + +### Phase 3 - 高级功能 +- ✅ 代码阅读器 (20+ 语言支持) +- ✅ 全文双语对照模式 +- ✅ 阅读进度同步 +- ✅ 插件系统 + +### Phase 4 - 性能与生态 +- ✅ 性能优化与分析 +- ✅ 个性化主题商店 +- ✅ 跨平台打包发布 + +## 技术栈 + +- 语言:Rust +- GUI: Dioxus +- 存储:sled +- 翻译:阿里百炼/DeepL/Ollama + +## 系统要求 + +- macOS 10.15+ +- Windows 10+ +- Linux (glibc 2.31+) + +## 反馈与支持 + +- GitHub: https://github.com/damai/readflow +- Email: damai@foshanhuiya.com + +--- +发布日期:2026-03-10 diff --git a/dist/readflow-0.2.0-macos-x86_64.zip b/dist/readflow-0.2.0-macos-x86_64.zip new file mode 100644 index 0000000..7dbf6fe Binary files /dev/null and b/dist/readflow-0.2.0-macos-x86_64.zip differ diff --git a/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Info.plist b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Info.plist new file mode 100644 index 0000000..2d90d81 --- /dev/null +++ b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleExecutable + readflow + CFBundleIdentifier + com.readflow.readflow + CFBundleName + ReadFlow + CFBundleDisplayName + ReadFlow + CFBundleVersion + 0.1.0 + CFBundleShortVersionString + 0.1.0 + CFBundlePackageType + APPL + NSHighResolutionCapable + + + diff --git a/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/MacOS/readflow b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/MacOS/readflow new file mode 100755 index 0000000..06648d1 Binary files /dev/null and b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/MacOS/readflow differ diff --git a/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/PkgInfo b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/PkgInfo new file mode 100644 index 0000000..6f749b0 --- /dev/null +++ b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? diff --git a/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Resources/assets/style.css b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Resources/assets/style.css new file mode 100644 index 0000000..2837547 --- /dev/null +++ b/dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Resources/assets/style.css @@ -0,0 +1,177 @@ +/* ReadFlow 基础样式 */ + +:root { + /* 浅色主题 */ + --bg-primary: #ffffff; + --bg-secondary: #f5f5f5; + --bg-tertiary: #e8e8e8; + --text-primary: #333333; + --text-secondary: #666666; + --text-muted: #999999; + --border-color: #e0e0e0; + --accent-color: #4a90d9; + --accent-hover: #3a7bc8; + --shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +[data-theme="dark"] { + /* 深色主题 */ + --bg-primary: #1a1a1a; + --bg-secondary: #2a2a2a; + --bg-tertiary: #3a3a3a; + --text-primary: #e0e0e0; + --text-secondary: #b0b0b0; + --text-muted: #808080; + --border-color: #404040; + --accent-color: #5a9fe0; + --accent-hover: #6aafef; + --shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + color: var(--text-primary); + background-color: var(--bg-primary); +} + +/* 文档容器 */ +.document { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +/* 页面样式 */ +.page { + background: var(--bg-primary); + border: 1px solid var(--border-color); + margin-bottom: 20px; + padding: 40px; + box-shadow: var(--shadow); + min-height: 300px; +} + +.pdf-page { + aspect-ratio: 8.5 / 11; +} + +/* 文本内容 */ +.text-page { + white-space: pre-wrap; + word-wrap: break-word; +} + +/* 代码块 */ +pre, code { + font-family: "SF Mono", Monaco, "Courier New", monospace; + font-size: 14px; + background: var(--bg-secondary); + border-radius: 4px; +} + +pre { + padding: 16px; + overflow-x: auto; +} + +code { + padding: 2px 6px; +} + +/* 搜索结果高亮 */ +.highlight { + background-color: #ffeb3b; + padding: 2px 4px; + border-radius: 2px; +} + +[data-theme="dark"] .highlight { + background-color: #ffc107; +} + +/* 目录 */ +.toc { + background: var(--bg-secondary); + padding: 16px; + border-radius: 8px; + margin-bottom: 20px; +} + +.toc-entry { + padding: 8px 0; + cursor: pointer; + transition: color 0.2s; +} + +.toc-entry:hover { + color: var(--accent-color); +} + +.toc-entry.level-1 { + font-weight: bold; +} + +.toc-entry.level-2 { + padding-left: 20px; +} + +.toc-entry.level-3 { + padding-left: 40px; +} + +/* 滚动条 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--text-muted); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +/* 主题切换按钮 */ +.theme-toggle { + position: fixed; + top: 20px; + right: 20px; + padding: 8px 16px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 20px; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; +} + +.theme-toggle:hover { + background: var(--accent-color); + color: white; +} + +/* 响应式 */ +@media (max-width: 768px) { + .document { + padding: 10px; + } + + .page { + padding: 20px; + } +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 5a1f900..e3c3b6d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -273,28 +273,28 @@ fn get_app_css() -> &'static str { } .app-container.light { - background: #f5f5f5; - color: #333; + background: #f7fafc; + color: #1a202c; } .app-container.dark { - background: #1a1a2e; - color: #eee; + background: #0f172a; + color: #f1f5f9; } .sidebar { width: 280px; - background: #16213e; - color: #fff; + background: #1e293b; + color: #ffffff; display: flex; flex-direction: column; - border-right: 1px solid #0f3460; + border-right: 1px solid #475569; } .app-container.light .sidebar { - background: #fff; - color: #333; - border-right: #ddd; + background: #ffffff; + color: #1a202c; + border-right: #e2e8f0; } .sidebar-header { @@ -302,7 +302,7 @@ fn get_app_css() -> &'static str { display: flex; justify-content: space-between; align-items: center; - border-bottom: 1px solid #0f3460; + border-bottom: 1px solid #475569; } .settings-btn { @@ -322,13 +322,13 @@ fn get_app_css() -> &'static str { padding: 8px 12px; border: none; border-radius: 6px; - background: #0f3460; - color: #fff; + background: #475569; + color: #ffffff; } .app-container.light .search-box input { background: #f0f0f0; - color: #333; + color: #1a202c; } .filter-buttons { @@ -342,15 +342,15 @@ fn get_app_css() -> &'static str { padding: 5px 10px; border: none; border-radius: 4px; - background: #0f3460; - color: #fff; + background: #475569; + color: #ffffff; cursor: pointer; font-size: 12px; } .app-container.light .filter-buttons button { background: #e0e0e0; - color: #333; + color: #1a202c; } .filter-buttons button.active { @@ -374,7 +374,7 @@ fn get_app_css() -> &'static str { } .file-item:hover { - background: #0f3460; + background: #475569; } .app-container.light .file-item:hover { @@ -444,8 +444,8 @@ fn get_app_css() -> &'static str { } .settings-panel { - background: #16213e; - color: #fff; + background: #1e293b; + color: #ffffff; padding: 30px; border-radius: 12px; min-width: 300px; @@ -453,8 +453,8 @@ fn get_app_css() -> &'static str { } .app-container.light .settings-panel { - background: #fff; - color: #333; + background: #ffffff; + color: #1a202c; } .settings-panel h2 { @@ -467,7 +467,7 @@ fn get_app_css() -> &'static str { right: 10px; background: none; border: none; - color: #fff; + color: #ffffff; font-size: 24px; cursor: pointer; } @@ -485,15 +485,15 @@ fn get_app_css() -> &'static str { width: 100%; padding: 8px; border-radius: 4px; - border: 1px solid #0f3460; - background: #0f3460; - color: #fff; + border: 1px solid #475569; + background: #475569; + color: #ffffff; } .app-container.light .setting-item select { background: #f0f0f0; - color: #333; - border-color: #ddd; + color: #1a202c; + border-color: #e2e8f0; } .header-actions { @@ -507,7 +507,7 @@ fn get_app_css() -> &'static str { border: none; border-radius: 6px; background: #e94560; - color: #fff; + color: #ffffff; cursor: pointer; font-size: 14px; transition: background 0.2s; diff --git a/src/ui/mod.rs.bak b/src/ui/mod.rs.bak deleted file mode 100644 index d8355cb..0000000 --- a/src/ui/mod.rs.bak +++ /dev/null @@ -1,639 +0,0 @@ -//! UI 模块 -//! -//! ReadFlow Dioxus GUI 界面 - -#![allow(non_snake_case)] - -use dioxus::prelude::*; -use crate::config::{ThemeMode, load}; -use crate::library::{Library, LibraryItem}; -use crate::core::document::DocumentEngine; -use std::path::PathBuf; - -/// 选中的文件类型 -#[derive(Debug, Clone, Copy, PartialEq, Default)] -enum FilterType { - #[default] - All, - Recent, - Pdf, - Epub, - Mobi, - Text, -} - -/// 主应用组件 -#[component] -fn App() -> Element { - // 加载配置 - let config = load(); - - // 书库路径 - let library_path = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join("ReadFlow"); - - // 初始化书库(创建时自动扫描) - let mut library = use_signal(|| Library::new(library_path)); - - // 选中状态 - let selected = use_signal(|| None::); - - // 过滤器 - let mut filter = use_signal(|| FilterType::All); - - // 搜索关键词 - let mut query = use_signal(|| String::new()); - - // 设置面板显示 - let mut show_settings = use_signal(|| false); - - // 阅读器视图:显示打开的文档 - let opened_document = use_signal(|| None::); - - // 主题 - let is_dark = config.theme.mode == ThemeMode::Dark; - - let theme_class = if is_dark { "dark" } else { "light" }; - - rsx! { - div { - class: "app-container {theme_class}", - - // 侧边栏 - div { class: "sidebar", - div { class: "sidebar-header", - h1 { "ReadFlow" } - div { class: "header-actions", - button { - class: "open-file-btn", - onclick: move |_| { - // 打开文件选择对话框 - spawn(async move { - open_local_file(library, opened_document); - }); - }, - "📂 打开文件" - } - button { - class: "settings-btn", - onclick: move |_| show_settings.set(!show_settings()), - "⚙️" - } - } - } - - // 搜索框 - div { class: "search-box", - input { - r#type: "text", - placeholder: "搜索文件...", - value: "{query}", - oninput: move |e| query.set(e.value().to_string()), - } - } - - // 过滤器 - div { class: "filter-buttons", - button { - class: if filter() == FilterType::All { "active" }, - onclick: move |_| filter.set(FilterType::All), - "全部" - } - button { - class: if filter() == FilterType::Recent { "active" }, - onclick: move |_| filter.set(FilterType::Recent), - "最近" - } - button { - class: if filter() == FilterType::Pdf { "active" }, - onclick: move |_| filter.set(FilterType::Pdf), - "PDF" - } - button { - class: if filter() == FilterType::Epub { "active" }, - onclick: move |_| filter.set(FilterType::Epub), - "EPUB" - } - button { - class: if filter() == FilterType::Mobi { "active" }, - onclick: move |_| filter.set(FilterType::Mobi), - "MOBI" - } - } - - // 文件列表 - div { class: "file-list", - // 获取并过滤文件列表(在渲染前准备好数据) - FileList { - items: get_filtered_items(library, filter(), query()), - selected: selected - } - } - } - - // 主内容区 - div { class: "main-content", - if let Some(path) = selected() { - div { class: "reader", - h2 { "正在阅读: {path}" } - p { "阅读器功能开发中..." } - } - } else { - div { class: "welcome", - h2 { "欢迎使用 ReadFlow" } - p { "从左侧选择一个文件开始阅读" } - } - } - } - - // 设置面板 - if show_settings() { - div { class: "modal-overlay", - div { class: "settings-panel", - h2 { "设置" } - button { class: "close-btn", onclick: move |_| show_settings.set(false), "×" } - div { class: "setting-item", - label { "主题" } - select { - value: "{config.theme.mode}", - option { value: "light", "浅色" } - option { value: "dark", "深色" } - } - } - } - } - } - } - } -} - -/// 文件列表组件 -#[component] -fn FileList(items: Vec, selected: Signal>) -> Element { - rsx! { - if items.is_empty() { - p { class: "empty", "暂无文件" } - } else { - for item in items { - FileItem { item: item, selected: selected } - } - } - } -} - -/// 单个文件项组件 -#[component] -fn FileItem(item: crate::library::LibraryItem, selected: Signal>) -> Element { - let is_selected = selected().as_ref().map(|s| s == &item.path).unwrap_or(false); - let item_path = item.path.clone(); - - rsx! { - div { - class: if is_selected { "file-item selected" } else { "file-item" }, - onclick: move |_| { - selected.set(Some(item_path.clone())); - }, - div { class: "file-icon", "{get_file_icon(&item.format)}" } - div { class: "file-info", - div { class: "file-title", "{item.title}" } - div { class: "file-meta", - span { class: "file-format", "{item.format}" } - span { "{item.format_file_size()}" } - } - } - } - } -} - -/// 获取过滤后的文件列表 -fn get_filtered_items(library: Signal, filter: FilterType, query: String) -> Vec { - let library_guard = library.read(); - let all_items: Vec = library_guard.get_all_items().into_iter().cloned().collect(); - - // 搜索过滤 - let filtered: Vec<_> = if query.is_empty() { - all_items - } else { - let q = query.to_lowercase(); - all_items.into_iter() - .filter(|item| item.title.to_lowercase().contains(&q) || item.path.to_lowercase().contains(&q)) - .collect() - }; - - // 类型过滤 - match filter { - FilterType::All => filtered, - FilterType::Recent => { - let recent: Vec<&LibraryItem> = library_guard.get_recent(20); - let recent_paths: Vec<&str> = recent.iter().map(|i| i.path.as_str()).collect(); - filtered.into_iter().filter(|item| recent_paths.contains(&item.path.as_str())).collect() - } - FilterType::Pdf => filtered.into_iter().filter(|item| item.format == "PDF").collect(), - FilterType::Epub => filtered.into_iter().filter(|item| item.format == "EPUB").collect(), - FilterType::Mobi => filtered.into_iter().filter(|item| item.format == "MOBI" || item.format == "AZW3").collect(), - FilterType::Text => filtered.into_iter().filter(|item| item.format == "TXT" || item.format == "Markdown").collect(), - } -} - -/// 获取文件图标 -fn get_file_icon(format: &str) -> &'static str { - match format { - "PDF" => "📕", - "EPUB" => "📙", - "MOBI" | "AZW3" => "📘", - "TXT" | "Markdown" => "📄", - _ => "📁", - } -} - -/// 获取应用 CSS -fn get_app_css() -> &'static str { - r#" - * { - box-sizing: border-box; - margin: 0; - padding: 0; - } - - body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - } - - .app-container { - display: flex; - height: 100vh; - overflow: hidden; - } - - .app-container.light { - background: #f5f5f5; - color: #333; - } - - .app-container.dark { - background: #1a1a2e; - color: #eee; - } - - .sidebar { - width: 280px; - background: #16213e; - color: #fff; - display: flex; - flex-direction: column; - border-right: 1px solid #0f3460; - } - - .app-container.light .sidebar { - background: #fff; - color: #333; - border-right: #ddd; - } - - .sidebar-header { - padding: 20px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid #0f3460; - } - - .settings-btn { - background: none; - border: none; - font-size: 20px; - cursor: pointer; - padding: 5px; - } - - .search-box { - padding: 10px 20px; - } - - .search-box input { - width: 100%; - padding: 8px 12px; - border: none; - border-radius: 6px; - background: #0f3460; - color: #fff; - } - - .app-container.light .search-box input { - background: #f0f0f0; - color: #333; - } - - .filter-buttons { - display: flex; - flex-wrap: wrap; - gap: 5px; - padding: 0 10px 10px; - } - - .filter-buttons button { - padding: 5px 10px; - border: none; - border-radius: 4px; - background: #0f3460; - color: #fff; - cursor: pointer; - font-size: 12px; - } - - .app-container.light .filter-buttons button { - background: #e0e0e0; - color: #333; - } - - .filter-buttons button.active { - background: #e94560; - } - - .file-list { - flex: 1; - overflow-y: auto; - padding: 10px; - } - - .file-item { - display: flex; - align-items: center; - padding: 10px; - margin-bottom: 5px; - border-radius: 6px; - cursor: pointer; - transition: background 0.2s; - } - - .file-item:hover { - background: #0f3460; - } - - .app-container.light .file-item:hover { - background: #f0f0f0; - } - - .file-item.selected { - background: #e94560; - } - - .file-icon { - font-size: 24px; - margin-right: 10px; - } - - .file-info { - flex: 1; - overflow: hidden; - } - - .file-title { - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .file-meta { - font-size: 12px; - opacity: 0.7; - display: flex; - gap: 10px; - } - - .main-content { - flex: 1; - padding: 40px; - display: flex; - align-items: center; - justify-content: center; - } - - .welcome, .reader { - text-align: center; - } - - .welcome h2, .reader h2 { - margin-bottom: 20px; - } - - .empty { - text-align: center; - opacity: 0.5; - padding: 20px; - } - - .modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0,0,0,0.5); - display: flex; - align-items: center; - justify-content: center; - } - - .settings-panel { - background: #16213e; - color: #fff; - padding: 30px; - border-radius: 12px; - min-width: 300px; - position: relative; - } - - .app-container.light .settings-panel { - background: #fff; - color: #333; - } - - .settings-panel h2 { - margin-bottom: 20px; - } - - .close-btn { - position: absolute; - top: 10px; - right: 10px; - background: none; - border: none; - color: #fff; - font-size: 24px; - cursor: pointer; - } - - .setting-item { - margin-bottom: 15px; - } - - .setting-item label { - display: block; - margin-bottom: 5px; - } - - .setting-item select { - width: 100%; - padding: 8px; - border-radius: 4px; - border: 1px solid #0f3460; - background: #0f3460; - color: #fff; - } - - .app-container.light .setting-item select { - background: #f0f0f0; - color: #333; - border-color: #ddd; - } - - .header-actions { - display: flex; - gap: 10px; - align-items: center; - } - - .open-file-btn { - padding: 8px 16px; - border: none; - border-radius: 6px; - background: #e94560; - color: #fff; - cursor: pointer; - font-size: 14px; - transition: background 0.2s; - } - - .open-file-btn:hover { - background: #ff6b6b; - } - - .document-viewer { - width: 100%; - max-width: 900px; - text-align: left; - } - - .doc-header { - margin-bottom: 30px; - padding-bottom: 20px; - border-bottom: 2px solid #e94560; - } - - .doc-header h3 { - font-size: 24px; - margin-bottom: 10px; - } - - .doc-meta { - font-size: 14px; - opacity: 0.7; - } - - .doc-content { - background: var(--bg-secondary); - border-radius: 8px; - padding: 30px; - min-height: 400px; - } - - .error-viewer { - text-align: center; - padding: 40px; - background: rgba(233, 69, 96, 0.1); - border-radius: 8px; - border: 1px solid #e94560; - } - - .error-viewer h3 { - color: #e94560; - margin-bottom: 10px; - } - "# -} - -/// 打开本地文件选择对话框并加载文件 -async fn open_local_file(mut library: Signal, mut opened_document: Signal>) { - use rfd::FileDialog; - - // 使用 rfd (Rust File Dialog) 打开文件选择器 - let file = FileDialog::new() - .add_filter("文档", &["pdf", "epub", "mobi", "azw3", "txt", "md"]) - .add_filter("代码文件", &["rs", "py", "js", "ts", "go", "java", "c", "cpp", "h", "css", "html", "json", "xml", "yaml", "yml", "toml", "sql", "sh", "bash", "zsh"]) - .set_title("选择要打开的文件") - .pick_file(); - - if let Some(path) = file { - let path_str = path.to_string_lossy().to_string(); - - // 添加到书库(如果尚未存在) - library.write().add_item(&path_str); - - // 打开文档 - opened_document.set(Some(path_str.clone())); - } -} - -/// 文档查看器组件 -#[component] -fn DocumentViewer(path: String) -> Element { - // 尝试加载文档 - let engine = DocumentEngine::new(); - - match engine.open(&path) { - Ok(doc) => { - rsx! { - div { class: "document-viewer", - div { class: "doc-header", - h3 { "{doc.title}" } - p { class: "doc-meta", - "格式:{:?} | 页数:{} | 大小:{}", - doc.format, - doc.metadata.page_count, - format_size(doc.metadata.file_size) - } - } - div { class: "doc-content", - // 这里后续可以渲染文档内容 - p { "文档已加载,阅读器渲染功能开发中..." } - p { "文件路径:{path}" } - } - } - } - } - Err(e) => { - rsx! { - div { class: "error-viewer", - h3 { "❌ 打开文件失败" } - p { "{e}" } - } - } - } - } -} - -/// 格式化文件大小 -fn format_size(size: u64) -> String { - const KB: u64 = 1024; - const MB: u64 = KB * 1024; - const GB: u64 = MB * 1024; - - if size >= GB { - format!("{:.2} GB", size as f64 / GB as f64) - } else if size >= MB { - format!("{:.2} MB", size as f64 / MB as f64) - } else if size >= KB { - format!("{:.2} KB", size as f64 / KB as f64) - } else { - format!("{} B", size) - } -} - -/// 启动 GUI -pub fn run() { - // 使用 Dioxus 启动 - dioxus::launch(App); -} \ No newline at end of file