标签支持分类

This commit is contained in:
DelLevin-Home
2026-08-12 20:19:09 +08:00
parent 43d8c01bb2
commit 98d8350693
4 changed files with 350 additions and 69 deletions

View File

@@ -81,7 +81,7 @@ class DatabaseHelper {
return await openDatabase(
path,
version: 41,
version: 42,
onCreate: _createDB,
onUpgrade: _onUpgrade,
);
@@ -420,6 +420,17 @@ class DatabaseHelper {
await db.execute('ALTER TABLE movies ADD COLUMN duration INTEGER DEFAULT 0');
}
}
if (oldVersion < 42) {
// 标签表添加父级分类字段(分级分类)
try {
final tagCols = await db.rawQuery('PRAGMA table_info(tags)');
if (!tagCols.any((col) => col['name'] == 'parent_id')) {
await db.execute("ALTER TABLE tags ADD COLUMN parent_id TEXT DEFAULT ''");
}
} catch (e) {
debugPrint('Migration v42 failed: $e');
}
}
}
Future<void> _upgradeBooksTableV26(Database db) async {
final columns = await db.rawQuery('PRAGMA table_info(books)');
@@ -924,6 +935,7 @@ class DatabaseHelper {
type TEXT NOT NULL,
created_at TEXT NOT NULL,
is_hidden INTEGER NOT NULL DEFAULT 0,
parent_id TEXT DEFAULT '',
UNIQUE(name, type)
)
''');

View File

@@ -33,19 +33,56 @@ class TagDao {
return results.isNotEmpty ? results.first : null;
});
/// 添加标签返回新标签ID
Future<String> addTag(String name, String type) => _wrap('addTag', () async {
/// 添加标签返回新标签ID。可选指定父级分类ID空串为顶级
Future<String> addTag(String name, String type, {String parentId = ''}) => _wrap('addTag', () async {
final db = await _dbHelper.database;
final id = 'tag_${DateTime.now().millisecondsSinceEpoch}';
await db.insert('tags', {
'id': id,
'name': name,
'type': type,
'parent_id': parentId,
'created_at': DateTime.now().toIso8601String(),
});
return id;
});
/// 设置标签的父级分类ID空串表示顶级。校验父级不能是自身或自己的后代防止成环
Future<bool> setTagParent(String tagId, String parentId) => _wrap('setTagParent', () async {
final db = await _dbHelper.database;
final tag = await getTagById(tagId);
if (tag == null) return false;
if (parentId.isNotEmpty) {
if (parentId == tagId) return false;
final type = tag['type'] as String;
final descendants = await _collectDescendantIds(db, tagId, type);
if (descendants.contains(parentId)) return false;
}
await db.update('tags', {'parent_id': parentId},
where: 'id = ?', whereArgs: [tagId]);
return true;
});
/// 收集某标签的所有后代ID集合BFS用于环校验
Future<Set<String>> _collectDescendantIds(Database db, String tagId, String type) async {
final result = <String>{};
final queue = <String>[tagId];
while (queue.isNotEmpty) {
final parent = queue.removeAt(0);
final children = await db.query('tags',
where: 'type = ? AND parent_id = ?', whereArgs: [type, parent]);
for (final child in children) {
final id = child['id'] as String;
if (result.add(id)) {
queue.add(id);
}
}
}
return result;
}
/// 重命名标签,同时级联更新所有关联条目(事务保护)
Future<bool> renameTag(String tagId, String newName) => _wrap('renameTag', () async {
final db = await _dbHelper.database;
@@ -114,6 +151,8 @@ class TagDao {
}
}
await txn.update('tags', {'parent_id': ''},
where: 'parent_id = ?', whereArgs: [tagId]);
await txn.delete('tags', where: 'id = ?', whereArgs: [tagId]);
});
});
@@ -121,7 +160,11 @@ class TagDao {
/// 仅删除标签本身,不级联影响已有条目(标签名保留在条目上)
Future<void> deleteTagOnly(String tagId) => _wrap('deleteTagOnly', () async {
final db = await _dbHelper.database;
await db.delete('tags', where: 'id = ?', whereArgs: [tagId]);
await db.transaction((txn) async {
await txn.update('tags', {'parent_id': ''},
where: 'parent_id = ?', whereArgs: [tagId]);
await txn.delete('tags', where: 'id = ?', whereArgs: [tagId]);
});
});
/// 切换标签的隐藏状态