删除wiki

This commit is contained in:
DelLevin-Home
2026-07-02 20:23:56 +08:00
parent 24fb7aab55
commit 610c1f2581
12 changed files with 0 additions and 1192 deletions

View File

@@ -1,94 +0,0 @@
# 整体架构
## 分层架构
```
┌─────────────────────────────────────────────┐
│ Pages (UI) │ lib/pages/
│ home_page, movie_tab_page, book_tab_page, │
│ note_tab_page, detail/form/share pages... │
├─────────────────────────────────────────────┤
│ Widgets (共享组件) │ lib/widgets/
│ list_items, star_rating, drawer, bottom_nav │
├─────────────────────────────────────────────┤
│ Providers (状态管理) │ lib/providers/
│ AppProvider │
├─────────────────────────────────────────────┤
│ Utils (业务逻辑) │ lib/utils/
│ DAO层 │ 同步服务 │ 图片管理 │ 主题 │ 偏好设置 │
├─────────────────────────────────────────────┤
│ Models (数据模型) │ lib/models/
│ Movie │ Book │ Note │ Reviews │ Posters... │
├─────────────────────────────────────────────┤
│ Database (SQLite) │ lib/utils/database_helper.dart
│ sqflite │
└─────────────────────────────────────────────┘
```
## 目录职责
### lib/models/
数据模型定义,所有模型集中在 `data_models.dart` 中:`Movie``Book``Note``MovieReview``BookReview``MoviePoster``BookExcerpt`。每个模型提供 `fromJson` / `toJson` / `copyWith` 方法。
### lib/providers/
全局状态管理,仅 `AppProvider`ChangeNotifier一个文件。管理所有数据列表、UI 状态、CRUD 操作。
### lib/utils/
业务逻辑层:
- `database_helper.dart` — SQLite 数据库单例(版本 13
- `movie/``book/``note/` — 各实体 DAO
- `tag/tag_dao.dart` — 标签管理
- `sync/` — 同步服务ServerSyncService、ServerDataService、WebDAV、AutoBackup
- `theme/app_theme.dart` — 主题定义
- `user_prefs.dart` — SharedPreferences 包装
- `image_path_helper.dart` — 图片路径管理
- `app_router.dart` — 路由生成器
### lib/pages/
UI 页面,按功能域组织:
- `movies/` — 影视相关(列表、详情、表单、影评、海报墙、分享、豆瓣 WebView
- `book/` — 书籍相关(列表、详情、表单、书评、摘抄、分享)
- `note/` — 笔记相关(列表、详情、表单、分享)
- `sync/` — 同步页面备份、云同步、服务端同步、WebDAV
- `markdown_reader/` — Markdown 阅读器
### lib/widgets/
共享可复用 Widget列表项、星级评分、抽屉菜单、底部导航、骨架屏等。
## 启动流程
`main.dart` 中的初始化顺序:
```
1. WidgetsFlutterBinding.ensureInitialized()
2. UserPrefs.init() ← SharedPreferences 初始化
3. AppProvider() ← 创建全局状态
4. runApp(MyApp) ← 启动 UI
5. (异步) _validateSyncOnStartup() ← 校验同步激活码
6. (异步) appProvider.initDatabase() ← 加载数据(本地或远程)
7. (异步) appProvider.initMainTabIndex() ← 恢复用户默认标签
8. (异步) _initAutoBackup() ← 启动自动备份(如已启用)
9. (异步) _initUsageStats() ← 启动用户统计上报
```
步骤 5-9 均为 `unawaited`,不阻塞 UI 渲染。
## 本地/远程双模式
`AppProvider._useRemote` 决定数据来源:
```dart
bool get _useRemote {
final prefs = UserPrefs();
return prefs.syncEnabled &&
prefs.syncServerUrl.isNotEmpty &&
prefs.syncActivationCode.isNotEmpty &&
ServerDataService.instance.isAvailable;
}
```
所有 DAO 方法(增删改查)在 `AppProvider` 中都有双路径:
- `_useRemote == true` → 调用 `ServerDataService`HTTP API
- `_useRemote == false` → 调用本地 DAOSQLite
[返回首页](Home.md)

View File

@@ -1,81 +0,0 @@
# 数据访问层
## DAO 模式
每个实体有独立的 DAO 类,封装 SQLite CRUD 操作:
| DAO | 文件 | 实体 |
|-----|------|------|
| `MovieDao` | `lib/utils/movie/movie_dao.dart` | Movie |
| `BookDao` | `lib/utils/book/book_dao.dart` | Book |
| `NoteDao` | `lib/utils/note/note_dao.dart` | Note |
| `MovieReviewDao` | `lib/utils/movie/movie_review_dao.dart` | MovieReview |
| `MoviePosterDao` | `lib/utils/movie/movie_poster_dao.dart` | MoviePoster |
| `BookReviewDao` | `lib/utils/book/book_review_dao.dart` | BookReview |
| `BookExcerptDao` | `lib/utils/book/book_excerpt_dao.dart` | BookExcerpt |
| `TagDao` | `lib/utils/tag/tag_dao.dart` | Tag |
## DatabaseHelper
`lib/utils/database_helper.dart` — SQLite 数据库单例管理器。
```dart
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
Future<Database> get database; // 获取数据库实例(懒初始化)
Future<void> reopenDatabase(); // 关闭并重新打开WebDAV 同步后调用)
Future<void> close(); // 关闭数据库
}
```
- 数据库文件名:`mooknote.db`
- 当前版本13
- 初始化时执行 `_createDB`,版本不匹配时执行 `_onUpgrade` 迁移链
## DAO 通用方法
`MovieDao` 为例:
```dart
getAllMovies() // 获取所有未删除记录
getMoviesPaged({status, limit, offset}) // 分页查询
getMoviesByStatus(status) // 按状态筛选
getMoviesByDirector(director) // 按导演筛选
getMoviesByActor(actor) // 按演员筛选
insertMovie(movie) // 插入
updateMovie(movie) // 更新
deleteMovie(id) // 软删除
getDeletedMovies() // 获取已删除记录
restoreMovie(id) // 恢复
permanentDeleteMovie(id) // 彻底删除
```
## 软删除机制
所有实体使用 `is_deleted` 字段实现软删除:
```
删除操作 → UPDATE SET is_deleted = 1 WHERE id = ?
查询操作 → WHERE is_deleted = 0
恢复操作 → UPDATE SET is_deleted = 0 WHERE id = ?
彻底删除 → DELETE FROM table WHERE id = ?
```
回收站页面(`lib/pages/recycle_bin_page.dart`)展示所有 `is_deleted = 1` 的记录,支持恢复和彻底删除。
## TagDao
标签 DAO 管理 `tags` 表,支持:
- `getTagsByType(type)` — 获取某类型所有标签
- `addTag(name, type)` — 添加标签UNIQUE 约束防重复)
- `renameTag(tagId, newName)` — 重命名标签并级联更新关联条目
- `deleteTag(tagId, {replacementName})` — 删除标签,可选替换关联条目中的标签名
- `deleteTagOnly(tagId)` — 仅删除标签记录,不修改关联条目
- `getTagById(tagId)` — 获取单个标签
标签级联逻辑重命名或删除标签时会遍历关联实体表movies.genres / books.genres / notes.tags将旧标签名替换为新名称或移除。
[返回首页](Home.md)

View File

@@ -1,203 +0,0 @@
# 数据模型
所有数据模型定义在 `lib/models/data_models.dart` 中。
## 模型总览
| 模型 | 说明 | 关键字段 |
|------|------|---------|
| `Movie` | 影视条目 | title, posterPath, directors, actors, genres, rating, status |
| `Book` | 书籍条目 | title, coverPath, authors, publisher, genres, rating, status, isbn |
| `Note` | 笔记 | title, content, contentType, tags, images |
| `MovieReview` | 影评 | movieId, content, reviewer, source, reviewType (1=短评, 2=长评) |
| `BookReview` | 书评 | bookId, content, reviewer, source, reviewType |
| `MoviePoster` | 影视海报 | movieId, posterPath |
| `BookExcerpt` | 书籍摘抄 | bookId, chapter, content, comment |
## 公共字段
所有实体模型都包含:
- `id` — UUID 字符串(创建时生成)
- `isDeleted` — 软删除标记0/1
- `createdAt` / `updatedAt` — ISO 8601 时间戳UTC
## copyWith 模式
模型使用 `_CopyWithNullSentinel` 区分"未传参"和"传了 null"
```dart
class _CopyWithNullSentinel {
const _CopyWithNullSentinel();
}
const _copyWithNull = _CopyWithNullSentinel();
// 用法示例
Movie copyWith({
Object? posterPath = _copyWithNull, // 未传参时保留原值
Object? summary = _copyWithNull, // 传 null 时清除值
}) { ... }
```
这样 `movie.copyWith(title: '新标题')` 不会意外清空 `posterPath`
## JSON 编码列表字段
`directors``actors``genres``tags` 等列表字段在 SQLite 中存储为 JSON 字符串:
```dart
// 序列化
'genres': jsonEncode(['科幻', '动作'])
// 反序列化
static List<String> parseStringList(dynamic data) {
if (data == null) return [];
if (data is List) return data.map((e) => e.toString()).toList();
if (data is String) {
final decoded = jsonDecode(data);
if (decoded is List) return decoded.map((e) => e.toString()).toList();
}
return [];
}
```
## 状态值
### 影视状态 (Movie.status)
| 值 | 含义 |
|----|------|
| `want_to_watch` | 想看 |
| `watching` | 在看 |
| `watched` | 已看 |
### 书籍状态 (Book.status)
| 值 | 含义 |
|----|------|
| `want_to_read` | 想读 |
| `reading` | 在读 |
| `read` | 已读 |
## SQLite 表结构
数据库文件:`mooknote.db`当前版本13
### movies 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| title | TEXT NOT NULL | 标题 |
| poster_path | TEXT | 海报本地路径 |
| release_date | TEXT | 上映日期 |
| directors | TEXT | JSON 数组 |
| writers | TEXT | JSON 数组 |
| actors | TEXT | JSON 数组 |
| genres | TEXT | JSON 数组 |
| alternate_titles | TEXT | JSON 数组 |
| summary | TEXT | 剧情简介 |
| rating | REAL | 评分 1-10 |
| status | TEXT NOT NULL | 状态 |
| watch_date | TEXT | 观看日期 |
| created_at | TEXT NOT NULL | 创建时间 |
| updated_at | TEXT NOT NULL | 更新时间 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
### books 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| title | TEXT NOT NULL | 标题 |
| cover_path | TEXT | 封面本地路径 |
| authors | TEXT | JSON 数组 |
| alternate_titles | TEXT | JSON 数组 |
| publisher | TEXT | 出版社 |
| genres | TEXT | JSON 数组 |
| summary | TEXT | 简介 |
| rating | REAL | 评分 1-10 |
| status | TEXT NOT NULL | 状态 |
| isbn | TEXT | ISBN |
| publish_date | TEXT | 出版日期 |
| created_at | TEXT NOT NULL | 创建时间 |
| updated_at | TEXT NOT NULL | 更新时间 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
### notes 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| title | TEXT DEFAULT '' | 标题 |
| content | TEXT NOT NULL | 内容 |
| content_type | TEXT DEFAULT 'markdown' | 内容类型 |
| tags | TEXT | JSON 数组 |
| images | TEXT | JSON 数组(图片路径列表)|
| created_at | TEXT NOT NULL | 创建时间 |
| updated_at | TEXT NOT NULL | 更新时间 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
### movie_reviews 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| movie_id | TEXT NOT NULL FK | 关联影视 ID |
| content | TEXT NOT NULL | 评论内容 |
| reviewer | TEXT | 评论者 |
| source | TEXT | 来源 |
| review_type | INTEGER DEFAULT 1 | 1=短评, 2=长评 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
| created_at / updated_at | TEXT | 时间戳 |
### book_reviews 表(结构同 movie_reviews外键为 book_id
### movie_posters 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| movie_id | TEXT NOT NULL FK | 关联影视 ID |
| poster_path | TEXT NOT NULL | 海报本地路径 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
| created_at | TEXT | 创建时间 |
### book_excerpts 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| book_id | TEXT NOT NULL FK | 关联书籍 ID |
| chapter | TEXT | 章节 |
| content | TEXT NOT NULL | 摘抄内容 |
| comment | TEXT | 感悟评论 |
| is_deleted | INTEGER DEFAULT 0 | 软删除 |
| created_at / updated_at | TEXT | 时间戳 |
### tags 表
| 列名 | 类型 | 说明 |
|------|------|------|
| id | TEXT PK | UUID |
| name | TEXT NOT NULL | 标签名 |
| type | TEXT NOT NULL | movie_genre / book_genre / note_tag |
| created_at | TEXT NOT NULL | 创建时间 |
| UNIQUE | (name, type) | 同类型标签名唯一 |
## 数据库迁移链
| 版本 | 变更 |
|------|------|
| v1 | 初始版本 |
| v2 | 升级 movies 表结构(添加 directors, writers, actors, genres 等字段)|
| v3 | 升级 books 表结构(拆分 author → authors, 添加 alternateTitles, publisher, genres|
| v4 | 重建 notes 表(添加 content_type 字段)|
| v5 | 创建 movie_reviews 和 movie_posters 表 |
| v6 | 为 notes 表添加 is_deleted 字段 |
| v7 | 创建 book_reviews 和 book_excerpts 表 |
| v8 | 确保 book_reviews/book_excerpts 存在(兼容修复)|
| v9 | 为 notes 表添加 images 字段 |
| v10 | 为 movies 表添加 watch_date 字段 |
| v11 | 为 books 表添加 isbn 和 publish_date 字段 |
| v12 | 确保 notes 表有 title 列 |
| v13 | 创建 tags 表并回填已有数据 |
[返回首页](Home.md)

View File

@@ -1,102 +0,0 @@
# 快速开始
## 环境要求
- **Flutter SDK** 3.5.0+
- **Android Studio**(含 Android SDK Platform API 33+
- **Android SDK Build-Tools**
- Android 真机USB 调试)或模拟器
## 安装步骤
### 1. 克隆项目
```bash
cd mooknote
```
### 2. 安装依赖
```bash
flutter pub get
```
如果网络问题导致失败,使用国内镜像:
```powershell
$env:PUB_HOSTED_URL="https://pub.flutter-io.cn"
$env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
flutter pub get
```
或设置代理:
```powershell
$env:HTTP_PROXY="http://127.0.0.1:10808"
$env:HTTPS_PROXY="http://127.0.0.1:10808"
flutter pub get
```
### 3. 连接设备
- 连接 Android 真机(需开启 USB 调试)
- 或启动 Android 模拟器
### 4. 运行应用
```bash
flutter run
```
## 常用命令
```bash
# 安装依赖
flutter pub get
# 运行应用
flutter run
# 构建 release APK
flutter build apk --release
# 构建 App Bundle推荐用于 Google Play
flutter build appbundle --release
# 清理构建缓存
flutter clean
# 静态分析
flutter analyze
```
## 服务端部署(可选)
服务端用于用户统计和数据同步,位于 `server/` 目录。
```bash
cd server
pip install -r requirements.txt
python app.py
```
默认监听 `0.0.0.0:27047`,可通过环境变量 `PORT` 修改端口。
生产环境推荐 gunicorn
```bash
pip install gunicorn
gunicorn -w 2 -b 0.0.0.0:27047 app:app
```
## 用户统计服务器配置
`lib/utils/usage_stats_service.dart` 中配置统计服务器地址:
```dart
static String serverUrl = 'http://192.168.31.48:5000';
```
置空则禁用用户统计功能。
[返回首页](Home.md)

View File

@@ -1,38 +0,0 @@
# MookNote Wiki
极简风格的观影阅读笔记应用,基于 Flutter 框架开发。
## 功能概览
- **影视管理** — 增删改查、影评、海报墙、豆瓣链接爬取
- **书籍管理** — 增删改查、书评、摘抄
- **笔记管理** — Markdown 笔记、标签、图片
- **数据同步** — 服务端实时同步、WebDAV 同步、自动本地备份
- **回收站** — 软删除 + 恢复/彻底删除
- **标签管理** — 影视类型、书籍类型、笔记标签统一管理
- **统计分析** — 观影/阅读数据统计
## 技术栈
| 层级 | 技术 |
|------|------|
| 框架 | Flutter 3.5+, Dart |
| 状态管理 | Provider (ChangeNotifier) |
| 本地存储 | SQLite (sqflite) |
| 远程同步 | HTTP API (http 包) |
| 后端 | Python Flask |
| 主题 | Material 3, 极简黑白灰 |
## Wiki 目录
- [快速开始](Getting-Started.md) — 环境配置、安装与运行
- [整体架构](Architecture.md) — 分层架构、目录结构、启动流程
- [数据模型](Data-Models.md) — 模型定义、数据库表结构、迁移链
- [状态管理](State-Management.md) — AppProvider、Tab 状态、分页加载
- [数据访问层](Data-Access-Layer.md) — DAO 模式、软删除、回收站
- [图片存储](Image-Storage.md) — 图片路径管理、存储结构
- [同步系统](Sync-System.md) — 服务端同步、WebDAV、自动备份
- [服务端 API](Server-API.md) — Flask 后端、API 端点、激活码机制
- [页面与路由](Pages-and-Navigation.md) — 页面结构、路由表、过渡动画
- [主题与 UI](Theme-and-UI.md) — 主题配置、共享组件
- [用户偏好](User-Preferences.md) — SharedPreferences 配置项

View File

@@ -1,84 +0,0 @@
# 图片存储
## ImagePathHelper
`lib/utils/image_path_helper.dart` — 单例图片路径管理器。
```dart
class ImagePathHelper {
static final ImagePathHelper instance = ImagePathHelper._init();
}
```
## 存储目录结构
图片存储在应用文档目录下:
```
{appDocumentsDir}/images/
├── movies/
│ └── {movieId}/
│ ├── poster.jpg ← 影视海报
│ └── posterimgs/ ← 海报墙图片
│ ├── img1.jpg
│ └── img2.jpg
├── books/
│ └── {bookId}/
│ └── cover.jpg ← 书籍封面
└── notes/
└── {noteId}/
├── img1.jpg ← 笔记图片
└── img2.jpg
```
## 路径方法
### 影视
```dart
getMovieImagesDir(movieId) images/movies/{movieId}/
getMoviePosterPath(movieId, file) images/movies/{movieId}/{file}
getMoviePosterImgsDir(movieId) images/movies/{movieId}/posterimgs/
getMoviePosterImgPath(movieId, file) images/movies/{movieId}/posterimgs/{file}
```
### 书籍
```dart
getBookImagesDir(bookId) images/books/{bookId}/
getBookCoverPath(bookId, file) images/books/{bookId}/{file}
```
### 笔记
```dart
getNoteImagesDir(noteId) images/notes/{noteId}/
getNoteImagePath(noteId, file) images/notes/{noteId}/{file}
```
## 文件操作
```dart
ensureDirExists(dirPath) // 确保目录存在
moveFile(src, targetDir, fileName) // 移动文件
copyFile(src, targetDir, fileName) // 复制文件
deleteFile(filePath) // 删除单个文件
deleteMovieImages(movieId) // 删除影视所有图片目录
deleteBookImages(bookId) // 删除书籍所有图片目录
deleteNoteImages(noteId) // 删除笔记所有图片目录
```
## 远程图片同步
`_useRemote == true`CRUD 操作会自动调用 `ServerDataService.uploadLocalImages(paths)` 上传本地图片到服务端:
```dart
// AppProvider 中
await _uploadImagesIfRemote([movie.posterPath]);
```
## 图片显示
`FadeInLocalImage` Widget`lib/widgets/fade_in_local_image.dart`)处理本地图片的渐入显示。
[返回首页](Home.md)

View File

@@ -1,100 +0,0 @@
# 页面与路由
## 页面结构
### 主页框架
```
HomePage
├── CustomDrawer (侧边菜单)
├── PageView
│ ├── MainContentPage (主内容)
│ │ ├── MovieTabPage (观影列表, mainTabIndex=0)
│ │ ├── BookTabPage (阅读列表, mainTabIndex=1)
│ │ └── NoteTabPage (笔记列表, mainTabIndex=2)
│ └── ProfilePage (我的, bottomNavIndex=2)
└── BottomNavBar (底部导航)
```
底部导航切换 `bottomNavIndex`
- 0 → 主内容页MainContentPage
- 1 → 新增页(根据当前 mainTabIndex 跳转对应表单)
- 2 → 个人页ProfilePage
### 影视模块页面
| 页面 | 文件 | 说明 |
|------|------|------|
| MovieTabPage | `pages/movies/movie_tab_page.dart` | 影视列表(按状态筛选)|
| MovieFormPage | `pages/movies/movie_form_page.dart` | 新增/编辑影视 |
| MovieDetailPage | `pages/movies/movie_detail_page.dart` | 影视详情 |
| MovieReviewsPage | `pages/movies/movie_reviews_page.dart` | 影评列表 |
| MovieReviewFormPage | `pages/movies/movie_review_form_page.dart` | 新增/编辑影评 |
| MovieReviewDetailPage | `pages/movies/movie_review_detail_page.dart` | 影评详情 |
| MoviePostersPage | `pages/movies/movie_posters_page.dart` | 海报墙 |
| PosterGalleryPage | `pages/movies/poster_gallery_page.dart` | 海报画廊浏览 |
| MovieSharePage | `pages/movies/movie_share_page.dart` | 影视分享 |
| DoubanWebViewPage | `pages/movies/douban_webview_page.dart` | 豆瓣 WebView |
### 书籍模块页面
| 页面 | 文件 | 说明 |
|------|------|------|
| BookTabPage | `pages/book/book_tab_page.dart` | 书籍列表 |
| BookFormPage | `pages/book/book_form_page.dart` | 新增/编辑书籍 |
| BookDetailPage | `pages/book/book_detail_page.dart` | 书籍详情 |
| BookReviewsPage | `pages/book/book_reviews_page.dart` | 书评列表 |
| BookReviewFormPage | `pages/book/book_review_form_page.dart` | 新增/编辑书评 |
| BookReviewDetailPage | `pages/book/book_review_detail_page.dart` | 书评详情 |
| BookExcerptsPage | `pages/book/book_excerpts_page.dart` | 摘抄列表 |
| BookExcerptFormPage | `pages/book/book_excerpt_form_page.dart` | 新增/编辑摘抄 |
| BookSharePage | `pages/book/book_share_page.dart` | 书籍分享 |
### 笔记模块页面
| 页面 | 文件 | 说明 |
|------|------|------|
| NoteTabPage | `pages/note/note_tab_page.dart` | 笔记列表 |
| NoteFormPage | `pages/note/note_form_page.dart` | 新增/编辑笔记 |
| NoteDetailPage | `pages/note/note_detail_page.dart` | 笔记详情 |
| NoteSharePage | `pages/note/note_share_page.dart` | 笔记分享 |
### 其他页面
| 页面 | 文件 | 说明 |
|------|------|------|
| HomePage | `pages/home_page.dart` | 主页框架 |
| MainContentPage | `pages/main_content_page.dart` | 主内容区域 |
| ProfilePage | `pages/profile_page.dart` | 个人页 |
| SearchPage | `pages/search_page.dart` | 全局搜索 |
| StatisticsPage | `pages/statistics_page.dart` | 统计分析 |
| StrollPage | `pages/stroll_page.dart` | 浏览/发现 |
| RecycleBinPage | `pages/recycle_bin_page.dart` | 回收站 |
| TagManagementPage | `pages/tag_management_page.dart` | 标签管理 |
| AppIconPickerPage | `pages/app_icon_picker_page.dart` | 应用图标选择 |
| BackupPage | `pages/sync/backup_page.dart` | 本地备份 |
| CloudSyncPage | `pages/sync/cloud_sync_page.dart` | 云同步 |
| ServerSyncPage | `pages/sync/server_sync_page.dart` | 服务端同步配置 |
| WebDAVSyncPage | `pages/sync/webdav_sync_page.dart` | WebDAV 同步 |
| MdReaderTabPage | `pages/markdown_reader/md_reader_tab_page.dart` | Markdown 阅读器目录 |
| MdViewerPage | `pages/markdown_reader/md_viewer_page.dart` | Markdown 查看器 |
## 路由
`AppRouter``lib/utils/app_router.dart`)使用 `onGenerateRoute` 生成路由:
| 路由名 | 参数 | 目标页面 |
|--------|------|---------|
| `/movie-form` | `Movie?``{initialStatus}` | MovieFormPage |
| `/book-form` | `Book?``{initialStatus}` | BookFormPage |
| `/note-form` | `Note?` | NoteFormPage |
| `/movie-detail` | `Movie` | MovieDetailPage |
| `/book-detail` | `Book` | BookDetailPage |
| `/note-detail` | `Note` | NoteDetailPage |
| `/douban-webview` | `String` (URL) | DoubanWebViewPage |
## 过渡动画
所有路由使用 `SlideUpPageRoute``lib/utils/slide_up_page_route.dart`),实现从底部滑入的过渡效果。
[返回首页](Home.md)

View File

@@ -1,117 +0,0 @@
# 服务端 API
## 概述
服务端为 Python Flask 应用,位于 `server/` 目录。提供用户统计、数据同步和管理后台功能。
## 模块结构
| 文件 | 职责 |
|------|------|
| `app.py` | 入口,注册所有路由模块 |
| `config.py` | 配置JWT 密钥、数据库路径、备份目录)|
| `database.py` | 数据库初始化与管理SQLite|
| `auth.py` | 认证路由(激活码验证)|
| `admin_api.py` | 管理后台 API |
| `sync_api.py` | 同步 API文件上传/下载、心跳)|
| `data_api.py` | 数据 CRUD API影视/书籍/笔记/标签/图片)|
| `web_ui.py` | Web 管理界面 |
| `static/` | 静态资源 |
| `requirements.txt` | Python 依赖 |
## API 端点
### 认证
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/activate` | 校验激活码 |
请求体:`{"code": "激活码", "device_id": "设备标识"}`
响应:`{"valid": true, "expires_at": "...", "is_permanent": false}`
### 数据 CRUD
所有请求需在 body 中附带 `code` 字段。
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/data/movies` | 获取影视列表(支持 status/limit/offset|
| POST | `/api/data/movie/save` | 保存影视(新建/更新)|
| POST | `/api/data/movie/delete` | 删除影视 |
| POST | `/api/data/books` | 获取书籍列表 |
| POST | `/api/data/book/save` | 保存书籍 |
| POST | `/api/data/book/delete` | 删除书籍 |
| POST | `/api/data/notes` | 获取笔记列表 |
| POST | `/api/data/note/save` | 保存笔记 |
| POST | `/api/data/note/delete` | 删除笔记 |
| POST | `/api/data/movie_reviews` | 获取影评 |
| POST | `/api/data/movie_review/save` | 保存影评 |
| POST | `/api/data/movie_review/delete` | 删除影评 |
| POST | `/api/data/movie_posters` | 获取海报 |
| POST | `/api/data/movie_poster/save` | 保存海报 |
| POST | `/api/data/movie_poster/delete` | 删除海报 |
| POST | `/api/data/book_reviews` | 获取书评 |
| POST | `/api/data/book_review/save` | 保存书评 |
| POST | `/api/data/book_review/delete` | 删除书评 |
| POST | `/api/data/book_excerpts` | 获取摘抄 |
| POST | `/api/data/book_excerpt/save` | 保存摘抄 |
| POST | `/api/data/book_excerpt/delete` | 删除摘抄 |
| POST | `/api/data/tags` | 获取标签 |
| POST | `/api/data/tag/save` | 保存标签 |
| POST | `/api/data/tag/delete` | 删除标签 |
### 同步
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/heartbeat` | 设备心跳上报 |
| POST | `/api/sync/upload` | 上传数据库/图片文件 |
| POST | `/api/sync/download` | 下载数据库/图片文件 |
### 图片
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/data/image/upload` | 上传图片 |
| GET | `/api/data/image/{code}/{path}` | 获取图片 |
## 激活码机制
每个激活码对应一个独立的 SQLite 数据库:
```
server/backups/{CODE}/
├── database/
│ └── mooknote.db ← 该激活码的数据
├── images/ ← 图片文件
└── avatars/ ← 头像文件
```
激活码校验通过 `admin_api.py` 中的 `_verify_code()` 函数完成,支持有效期和永久有效两种模式。
## 服务端数据库
服务端自身使用 `stats.db` 存储设备信息和心跳日志:
| 表 | 说明 |
|----|------|
| `devices` | 设备注册device_hash, first_seen, last_seen|
| `heartbeat_logs` | 心跳日志device_hash, ip, device_type, device_name|
| `changelog` | 更新日志 |
每个激活码的数据存储在独立的 `backups/{code}/database/mooknote.db` 中,表结构与客户端一致。
## 启动
```bash
cd server
python app.py # 开发环境waitress
# 或
gunicorn -w 2 -b 0.0.0.0:27047 app:app # 生产环境
```
默认端口27047可通过环境变量 `PORT` 修改)。
[返回首页](Home.md)

View File

@@ -1,111 +0,0 @@
# 状态管理
## AppProvider
`AppProvider``lib/providers/app_provider.dart`)是应用唯一的 ChangeNotifier通过 `MultiProvider` 在根节点注入,所有页面通过 `context.watch<AppProvider>()``context.read<AppProvider>()` 访问。
## 管理的状态
### 数据列表
| 字段 | 类型 | 说明 |
|------|------|------|
| `_movies` | `List<Movie>` | 影视数据 |
| `_books` | `List<Book>` | 书籍数据 |
| `_notes` | `List<Note>` | 笔记数据 |
### UI 状态
| 字段 | 类型 | 说明 |
|------|------|------|
| `_mainTabIndex` | `int` | 主标签页0=观影, 1=阅读, 2=笔记)|
| `_bottomNavIndex` | `int` | 底部导航0=主页, 1=新增, 2=我的)|
| `_bottomNavVisible` | `bool` | 底部导航栏可见性 |
| `_themeMode` | `ThemeMode` | 主题模式 |
| `_movieStatusIndex` | `int` | 影视状态筛选0=已看, 1=想看, 2=在看)|
| `_bookStatusIndex` | `int` | 书籍状态筛选0=读完, 1=在读, 2=准备读)|
| `_drawerOpen` | `bool` | 侧边菜单状态 |
### DAO 实例
```dart
final MovieDao _movieDao = MovieDao();
final BookDao _bookDao = BookDao();
final NoteDao _noteDao = NoteDao();
final MovieReviewDao _reviewDao = MovieReviewDao();
final MoviePosterDao _posterDao = MoviePosterDao();
final BookReviewDao _bookReviewDao = BookReviewDao();
final BookExcerptDao _bookExcerptDao = BookExcerptDao();
final TagDao _tagDao = TagDao();
```
## 数据加载
### 全量加载
```dart
initDatabase() loadMovies() + loadBooks() + loadNotes()
```
根据 `_useRemote` 决定从本地 SQLite 或远程 API 加载。
### 分页加载
分页大小:`_pageSize = 20`
```dart
loadMoviesPaged({String? status, required int offset})
loadBooksPaged({String? status, required int offset})
loadNotesPaged({required int offset})
```
供列表页触底加载使用。
## CRUD 操作
所有 CRUD 方法都走双路径(本地/远程),并自动重新加载数据:
```dart
addMovie(Movie movie) : _movieDao.insertMovie / : ServerDataService.saveMovie loadMovies()
updateMovie(Movie movie) : _movieDao.updateMovie / : ServerDataService.saveMovie loadMovies()
removeMovie(String id) : _movieDao.deleteMovie / : ServerDataService.deleteMovie loadMovies()
```
Book、Note 同理。远程模式下还会自动上传关联图片。
## 子实体操作
影评、海报、书评、摘抄的 CRUD 方法不直接修改内存列表,而是按需查询:
```dart
getMovieReviews(movieId) / addMovieReview(review) / updateMovieReview(review) / removeMovieReview(id)
getMoviePosters(movieId) / addMoviePoster(poster) / removeMoviePoster(id)
getBookReviews(bookId) / addBookReview(review) / updateBookReview(review) / removeBookReview(id)
getBookExcerpts(bookId) / addBookExcerpt(excerpt) / updateBookExcerpt(excerpt) / removeBookExcerpt(id)
```
## 回收站
```dart
getDeletedMovies() / restoreMovie(id) / permanentDeleteMovie(id)
getDeletedBooks() / restoreBook(id) / permanentDeleteBook(id)
getDeletedNotes() / restoreNote(id) / permanentDeleteNote(id)
clearRecycleBin() // 清空所有已删除条目
```
永久删除时会同时清理关联图片目录。
## 标签管理
```dart
getTags(type) // 获取某类型标签
addTag(name, type) // 添加标签
renameTag(tagId, newName, type) // 重命名(级联更新关联条目)
deleteTag(tagId, type, {replacementName}) // 删除(可替换关联条目的标签名)
deleteTagOnly(tagId, type) // 仅删除标签本身,不影响条目
syncTagsFromData() // 从现有数据回填标签表
```
标签类型:`movie_genre``book_genre``note_tag`
[返回首页](Home.md)

View File

@@ -1,93 +0,0 @@
# 同步系统
MookNote 支持三种数据同步方式。
## 1. 服务端实时同步
通过激活码连接远程服务器,数据实时双向同步。
### 核心服务
#### ServerDataService (`lib/utils/sync/server_data_service.dart`)
远程 API 数据操作层,所有方法通过 HTTP POST 调用服务端接口:
```dart
// 影视
getMovies({status, limit, offset}) POST /api/data/movies
saveMovie(movie) POST /api/data/movie/save
deleteMovie(id) POST /api/data/movie/delete
// 书籍、笔记、影评、海报、书评、摘抄、标签同理
```
每个请求都附带 `code`(激活码)进行身份验证。
#### ServerSyncService (`lib/utils/sync/server_sync_service.dart`)
同步协调服务:
- `checkActivation()` — 校验激活码有效性
- `syncWithServer()` — 智能合并本地与服务端数据
- `downloadToLocal()` — 从服务端下载数据到本地(关闭同步时调用)
### 启动时校验流程
```
1. 检查 syncEnabled、syncServerUrl、syncActivationCode 是否已配置
2. 调用 /api/activate 校验激活码
3. 有效 → 更新有效期信息,继续同步模式
4. 无效/过期 → 调用 downloadToLocal() 下载数据 → 关闭同步开关
```
### 双模式切换
`AppProvider._useRemote` 控制:
```dart
bool get _useRemote {
return prefs.syncEnabled &&
prefs.syncServerUrl.isNotEmpty &&
prefs.syncActivationCode.isNotEmpty &&
ServerDataService.instance.isAvailable;
}
```
## 2. WebDAV 同步
通过 WebDAV 协议同步数据库文件和图片。
相关页面:`lib/pages/sync/webdav_sync_page.dart`
服务类:`lib/utils/sync/webdav_service.dart`
## 3. 自动本地备份
### AutoBackupService (`lib/utils/sync/auto_backup_service.dart`)
定时自动备份到设备下载目录:
- **间隔**5 分钟
- **保留数量**:最近 5 个备份文件
- **备份目录**`Download/mooknote/`Android`Documents/mooknote/`iOS
- **文件格式**`auto_backup_{yyyyMMdd_HHmmss}.zip`
### BackupService (`lib/utils/sync/backup_service.dart`)
手动备份与恢复服务,导出数据为 ZIP 文件(包含数据库和图片)。
相关页面:`lib/pages/sync/backup_page.dart`
## 同步相关配置
`UserPrefs` 中:
| 键 | 说明 |
|----|------|
| `syncServerUrl` | 服务器地址 |
| `syncActivationCode` | 激活码 |
| `syncExpiresAt` | 激活码有效期 |
| `syncIsPermanent` | 是否永久有效 |
| `syncEnabled` | 同步开关 |
| `syncLastEntryId` | 上次同步的 entry ID |
[返回首页](Home.md)

View File

@@ -1,75 +0,0 @@
# 主题与 UI
## AppTheme
`lib/utils/theme/app_theme.dart` — 极简主义黑白灰主题配置。
### 颜色体系
#### 中性色板
| 颜色 | 值 | 用途 |
|------|------|------|
| `_black` | `#1A1A1A` | 主要文字、主色 |
| `_darkGray` | `#333333` | 次要文字 |
| `_gray` | `#666666` | 辅助文字 |
| `_lightGray` | `#999999` | 占位文字 |
| `_lighterGray` | `#E5E5E5` | 分割线、边框 |
| `_offWhite` | `#F5F5F5` | 背景色 |
| `_white` | `#FFFFFF` | 主背景 |
#### 强调色
| 颜色 | 值 | 用途 |
|------|------|------|
| `accent` | `#0066FF` | 关键操作高亮 |
| `error` | `#DC2626` | 错误状态 |
#### 状态颜色
| 状态 | 颜色 | 说明 |
|------|------|------|
| `watched` / `readColor` | `#1A1A1A` | 已看/已读(纯黑)|
| `watching` / `readingColor` | `#666666` | 在看/在读(中灰)|
| `wantToWatch` / `wantToReadColor` | `#999999` | 想看/想读(浅灰)|
### Material 3 配置
```dart
ThemeData(
useMaterial3: true,
brightness: Brightness.light/dark,
scaffoldBackgroundColor: _white/_black,
colorScheme: ColorScheme.light/dark(...),
fontFamily: 'Inter',
)
```
提供 `lightTheme``darkTheme` 两套主题。
### 字体
- 字体族:`Inter`
- 字重:`w400`Regular`w500`Medium`w600`Semibold
## 共享 Widget
| Widget | 文件 | 说明 |
|--------|------|------|
| `MovieListItem` | `widgets/movie_list_item.dart` | 影视列表项 |
| `BookListItem` | `widgets/book_list_item.dart` | 书籍列表项 |
| `NoteListItem` | `widgets/note_list_item.dart` | 笔记列表项 |
| `MovieStatusBar` | `widgets/movie_status_bar.dart` | 影视状态筛选栏 |
| `BookStatusBar` | `widgets/book_status_bar.dart` | 书籍状态筛选栏 |
| `AnimatedStarRating` | `widgets/animated_star_rating.dart` | 动画星级评分 |
| `BottomNavBar` | `widgets/bottom_nav_bar.dart` | 底部导航栏 |
| `CustomDrawer` | `widgets/custom_drawer.dart` | 侧边抽屉菜单 |
| `FadeInLocalImage` | `widgets/fade_in_local_image.dart` | 本地图片渐入显示 |
| `AppRefreshIndicator` | `widgets/app_refresh_indicator.dart` | 自定义下拉刷新 |
| `ShimmerSkeleton` | `widgets/shimmer_skeleton.dart` | 骨架屏加载效果 |
## 系统 UI
`MyApp``main.dart` 中通过 `SystemChrome.setSystemUIOverlayStyle` 控制状态栏和导航栏样式,随主题模式自动切换亮/暗色。
[返回首页](Home.md)

View File

@@ -1,94 +0,0 @@
# 用户偏好
## UserPrefs
`lib/utils/user_prefs.dart` — SharedPreferences 单例包装器。
```dart
class UserPrefs {
static final UserPrefs _instance = UserPrefs._internal();
factory UserPrefs() => _instance;
}
```
使用前必须调用 `UserPrefs.init()`(在 `main.dart` 中完成)。
## 配置项列表
### 用户信息
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `nickname` | String | `'Mook'` | 昵称 |
| `motto` | String | `'好运不会眷顾一无所有之人。'` | 座右铭 |
| `avatarPath` | String? | `null` | 头像本地路径 |
### 应用设置
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `themeMode` | int | `0` | 0=跟随系统, 1=浅色, 2=深色 |
| `showExactReleaseDate` | bool | `true` | 上映日期显示到日/月 |
| `isFirstLaunch` | bool | `true` | 是否首次启动 |
### 主界面显示
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `hideBottomNavOnScroll` | bool | `true` | 滚动时隐藏底部导航 |
| `showMovieTab` | bool | `true` | 是否显示观影标签 |
| `showBookTab` | bool | `true` | 是否显示阅读标签 |
| `showNoteTab` | bool | `true` | 是否显示笔记标签 |
| `defaultMainTabIndex` | int | `0` | 默认启动标签0=影视, 1=阅读, 2=笔记)|
### 布局样式
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `noteLayoutStyle` | int | `0` | 笔记布局0=列表, 1=瀑布流, 2=时间线)|
| `movieLayoutStyle` | int | `0` | 影视布局0=海报网格, 1=列表)|
| `bookLayoutStyle` | int | `0` | 阅读布局0=封面网格, 1=列表)|
### Markdown 阅读器
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `lastMdFolder` | String? | `null` | 最近选择的目录 |
| `showEmptyDirs` | bool | `true` | 是否显示空目录 |
| `showImageOnlyDirs` | bool | `true` | 是否显示纯图片目录 |
### 应用图标
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `appIconName` | String | `'app_icon'` | 当前应用图标名称 |
### 用户统计
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `deviceId` | String | `''` | 匿名设备标识(首次启动自动生成)|
### 服务端同步
| 键 | 类型 | 默认值 | 说明 |
|----|------|--------|------|
| `syncServerUrl` | String | `''` | 服务器地址 |
| `syncActivationCode` | String | `''` | 激活码 |
| `syncExpiresAt` | String | `''` | 激活码有效期 |
| `syncIsPermanent` | bool | `false` | 是否永久有效 |
| `syncEnabled` | bool | `true` | 实时同步开关 |
| `syncLastEntryId` | int | `0` | 上次同步的 entry ID |
## 主题模式迁移
`UserPrefs.init()` 中包含旧版 `isDarkMode`(布尔值)到新版 `themeMode`(三态值)的自动迁移:
```dart
if (_prefs!.containsKey('isDarkMode') && !_prefs!.containsKey('themeMode')) {
final oldValue = _prefs!.getBool('isDarkMode') ?? false;
await _prefs!.setInt('themeMode', oldValue ? 2 : 0);
}
```
[返回首页](Home.md)