chore: 整理构建文件
- 移除备份文件 - 清理未跟踪文件
This commit is contained in:
147
dist/RELEASE-v0.2.0.md
vendored
Normal file
147
dist/RELEASE-v0.2.0.md
vendored
Normal file
@@ -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 <damai@foshanhuiya.com>*
|
||||||
55
dist/RELEASE.md
vendored
Normal file
55
dist/RELEASE.md
vendored
Normal file
@@ -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
|
||||||
BIN
dist/readflow-0.2.0-macos-x86_64.zip
vendored
Normal file
BIN
dist/readflow-0.2.0-macos-x86_64.zip
vendored
Normal file
Binary file not shown.
22
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Info.plist
vendored
Normal file
22
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Info.plist
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>readflow</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.readflow.readflow</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>ReadFlow</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>ReadFlow</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/MacOS/readflow
vendored
Executable file
BIN
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/MacOS/readflow
vendored
Executable file
Binary file not shown.
1
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/PkgInfo
vendored
Normal file
1
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/PkgInfo
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
APPL????
|
||||||
177
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Resources/assets/style.css
vendored
Normal file
177
dist/readflow-0.2.0-macos-x86_64/readflow.app/Contents/Resources/assets/style.css
vendored
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -273,28 +273,28 @@ fn get_app_css() -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light {
|
.app-container.light {
|
||||||
background: #f5f5f5;
|
background: #f7fafc;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.dark {
|
.app-container.dark {
|
||||||
background: #1a1a2e;
|
background: #0f172a;
|
||||||
color: #eee;
|
color: #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
background: #16213e;
|
background: #1e293b;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: 1px solid #0f3460;
|
border-right: 1px solid #475569;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .sidebar {
|
.app-container.light .sidebar {
|
||||||
background: #fff;
|
background: #ffffff;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
border-right: #ddd;
|
border-right: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
@@ -302,7 +302,7 @@ fn get_app_css() -> &'static str {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid #0f3460;
|
border-bottom: 1px solid #475569;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-btn {
|
.settings-btn {
|
||||||
@@ -322,13 +322,13 @@ fn get_app_css() -> &'static str {
|
|||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #0f3460;
|
background: #475569;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .search-box input {
|
.app-container.light .search-box input {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-buttons {
|
.filter-buttons {
|
||||||
@@ -342,15 +342,15 @@ fn get_app_css() -> &'static str {
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #0f3460;
|
background: #475569;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .filter-buttons button {
|
.app-container.light .filter-buttons button {
|
||||||
background: #e0e0e0;
|
background: #e0e0e0;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-buttons button.active {
|
.filter-buttons button.active {
|
||||||
@@ -374,7 +374,7 @@ fn get_app_css() -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-item:hover {
|
.file-item:hover {
|
||||||
background: #0f3460;
|
background: #475569;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .file-item:hover {
|
.app-container.light .file-item:hover {
|
||||||
@@ -444,8 +444,8 @@ fn get_app_css() -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-panel {
|
.settings-panel {
|
||||||
background: #16213e;
|
background: #1e293b;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
@@ -453,8 +453,8 @@ fn get_app_css() -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .settings-panel {
|
.app-container.light .settings-panel {
|
||||||
background: #fff;
|
background: #ffffff;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-panel h2 {
|
.settings-panel h2 {
|
||||||
@@ -467,7 +467,7 @@ fn get_app_css() -> &'static str {
|
|||||||
right: 10px;
|
right: 10px;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -485,15 +485,15 @@ fn get_app_css() -> &'static str {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #0f3460;
|
border: 1px solid #475569;
|
||||||
background: #0f3460;
|
background: #475569;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-container.light .setting-item select {
|
.app-container.light .setting-item select {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
color: #333;
|
color: #1a202c;
|
||||||
border-color: #ddd;
|
border-color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
@@ -507,7 +507,7 @@ fn get_app_css() -> &'static str {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #e94560;
|
background: #e94560;
|
||||||
color: #fff;
|
color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|||||||
@@ -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::<String>);
|
|
||||||
|
|
||||||
// 过滤器
|
|
||||||
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::<String>);
|
|
||||||
|
|
||||||
// 主题
|
|
||||||
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<LibraryItem>, selected: Signal<Option<String>>) -> 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<Option<String>>) -> 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<Library>, filter: FilterType, query: String) -> Vec<LibraryItem> {
|
|
||||||
let library_guard = library.read();
|
|
||||||
let all_items: Vec<LibraryItem> = 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<Library>, mut opened_document: Signal<Option<String>>) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user