epub阅读+关联书籍

This commit is contained in:
DelLevin-Home
2026-06-27 18:13:34 +08:00
parent d54e33e4cf
commit bcff4f6c01
15 changed files with 1143 additions and 124 deletions

View File

@@ -39,7 +39,7 @@ class DatabaseHelper {
return await openDatabase(
path,
version: 23,
version: 24,
onCreate: _createDB,
onUpgrade: _onUpgrade,
);
@@ -194,6 +194,13 @@ class DatabaseHelper {
'CREATE INDEX IF NOT EXISTS idx_book_annotations_type ON book_annotations(book_id, type)',
);
}
if (oldVersion < 24) {
// reader_books 添加 book_id 列(关联 books 表)
final cols = await db.rawQuery('PRAGMA table_info(reader_books)');
if (!cols.any((col) => col['name'] == 'book_id')) {
await db.execute("ALTER TABLE reader_books ADD COLUMN book_id TEXT DEFAULT ''");
}
}
}
/// 升级books表到V11添加ISBN和出版时间字段
@@ -696,6 +703,7 @@ class DatabaseHelper {
file_extension TEXT NOT NULL DEFAULT 'epub',
last_read_cfi TEXT DEFAULT '',
reading_percentage REAL DEFAULT 0.0,
book_id TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
is_deleted INTEGER DEFAULT 0

View File

@@ -11,15 +11,10 @@ class EpubParser {
Future<EpubBookInfo?> parseFromFile(String filePath,
{String? fileName}) async {
try {
debugPrint('[EpubParser] 开始解析: $filePath');
final bytes = await File(filePath).readAsBytes();
debugPrint('[EpubParser] 读取 ${bytes.length} 字节');
final archive = ZipDecoder().decodeBytes(bytes);
debugPrint('[EpubParser] ZIP 解码成功, ${archive.files.length} 个文件');
return _parseFromArchive(archive, fileName: fileName);
} catch (e, stack) {
debugPrint('[EpubParser] 解析失败: $e');
debugPrint('[EpubParser] $stack');
} catch (e) {
return null;
}
}

View File

@@ -72,6 +72,42 @@ class ReaderDao {
);
}
// ─── 关联 books 表 ─────────────────────────────────────────────
/// 关联 EPUB 到 books 表中的书籍
Future<int> linkToBook(String readerBookId, String bookId) async {
final db = await _db.database;
return db.update(
'reader_books',
{'book_id': bookId, 'updated_at': DateTime.now().toIso8601String()},
where: 'id = ?',
whereArgs: [readerBookId],
);
}
/// 取消关联
Future<int> unlinkBook(String readerBookId) async {
final db = await _db.database;
return db.update(
'reader_books',
{'book_id': '', 'updated_at': DateTime.now().toIso8601String()},
where: 'id = ?',
whereArgs: [readerBookId],
);
}
/// 通过 books.id 查找关联的 reader_book
Future<Map<String, dynamic>?> getReaderBookByBookId(String bookId) async {
final db = await _db.database;
final results = await db.query(
'reader_books',
where: 'book_id = ? AND is_deleted = 0',
whereArgs: [bookId],
limit: 1,
);
return results.isNotEmpty ? results.first : null;
}
// ─── book_annotations ─────────────────────────────────────────────
/// 获取某本书的所有批注

View File

@@ -75,9 +75,9 @@ String generateSkeletonHtml(
</head>
<body>
<div id="frame-container">
<iframe id="frame-prev" sandbox="allow-same-origin" scrolling="no" style="z-index: 1; opacity: 0;"></iframe>
<iframe id="frame-curr" sandbox="allow-same-origin" scrolling="no" style="z-index: 2; opacity: 1;"></iframe>
<iframe id="frame-next" sandbox="allow-same-origin" scrolling="no" style="z-index: 1; opacity: 0;"></iframe>
<iframe id="frame-prev" sandbox="allow-same-origin allow-scripts" scrolling="no" style="z-index: 1; opacity: 0;"></iframe>
<iframe id="frame-curr" sandbox="allow-same-origin allow-scripts" scrolling="no" style="z-index: 2; opacity: 1;"></iframe>
<iframe id="frame-next" sandbox="allow-same-origin allow-scripts" scrolling="no" style="z-index: 1; opacity: 0;"></iframe>
</div>
</body>
</html>

View File

@@ -207,4 +207,8 @@ class UserPrefs {
/// EPUB 阅读器字体大小
double get epubFontSize => prefs.getDouble('epubFontSize') ?? 18.0;
Future<bool> setEpubFontSize(double value) => prefs.setDouble('epubFontSize', value);
/// EPUB 书架视图模式: 0=宽松, 1=紧凑
int get epubViewMode => prefs.getInt('epubViewMode') ?? 0;
Future<bool> setEpubViewMode(int value) => prefs.setInt('epubViewMode', value);
}