-
Notifications
You must be signed in to change notification settings - Fork 0
Category: restore management page import flow (dry-run preview + details bottom sheet) #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,60 +1,184 @@ | ||||||
| import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import '../../models/category.dart'; | ||||||
| import '../../models/category_template.dart'; | ||||||
| import '../../providers/category_provider.dart'; | ||||||
| import '../../providers/ledger_provider.dart'; | ||||||
| import '../../services/api/category_service.dart'; | ||||||
| import '../../services/api/category_service_integrated.dart'; | ||||||
| import '../../widgets/bottom_sheets/import_details_sheet.dart'; | ||||||
|
|
||||||
| // 占位版:增强分类管理页面暂时下线以稳定测试。 | ||||||
| // 后续 PR 将恢复原完整交互(模板导入 / 拖拽排序 / 批量操作 / 转标签 / 统计等)。 | ||||||
| class CategoryManagementEnhancedPage extends StatelessWidget { | ||||||
| class CategoryManagementEnhancedPage extends ConsumerStatefulWidget { | ||||||
| const CategoryManagementEnhancedPage({super.key}); | ||||||
|
|
||||||
| @override | ||||||
| ConsumerState<CategoryManagementEnhancedPage> createState() => _CategoryManagementEnhancedPageState(); | ||||||
| } | ||||||
|
|
||||||
| class _CategoryManagementEnhancedPageState extends ConsumerState<CategoryManagementEnhancedPage> { | ||||||
| bool _busy = false; | ||||||
|
|
||||||
| @override | ||||||
| Widget build(BuildContext context) { | ||||||
| final cs = Theme.of(context).colorScheme; | ||||||
| return Scaffold( | ||||||
| appBar: AppBar(title: const Text('分类管理 (占位)')), | ||||||
| appBar: AppBar( | ||||||
| title: const Text('分类管理'), | ||||||
| actions: [ | ||||||
| IconButton( | ||||||
| tooltip: '从模板库导入', | ||||||
| icon: const Icon(Icons.library_add), | ||||||
| onPressed: _busy ? null : _showTemplateLibrary, | ||||||
| ), | ||||||
| ], | ||||||
| ), | ||||||
| body: Center( | ||||||
| child: ConstrainedBox( | ||||||
| constraints: const BoxConstraints(maxWidth: 440), | ||||||
| child: Column( | ||||||
| mainAxisAlignment: MainAxisAlignment.center, | ||||||
| children: [ | ||||||
| Icon(Icons.category_outlined, size: 72, color: cs.primary), | ||||||
| const SizedBox(height: 16), | ||||||
| const Text( | ||||||
| '增强版分类管理暂时下线', | ||||||
| style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), | ||||||
| ), | ||||||
| const SizedBox(height: 12), | ||||||
| Text( | ||||||
| '为稳定当前 PR 的测试环境,复杂分类增强功能已暂时移除。', | ||||||
| textAlign: TextAlign.center, | ||||||
| style: TextStyle(color: cs.onSurface.withOpacity(.72)), | ||||||
| ), | ||||||
| const SizedBox(height: 16), | ||||||
| FilledButton( | ||||||
| onPressed: () => showDialog( | ||||||
| context: context, | ||||||
| builder: (_) => AlertDialog( | ||||||
| title: const Text('提示'), | ||||||
| content: const Text('完整功能将于后续 PR 恢复'), | ||||||
| actions: [ | ||||||
| TextButton( | ||||||
| onPressed: () => Navigator.pop(context), | ||||||
| child: const Text('关闭'), | ||||||
| ) | ||||||
| child: _busy | ||||||
| ? const CircularProgressIndicator() | ||||||
| : const Text('分类管理(最小版):点击右上角导入模板') | ||||||
| ), | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| Future<void> _showTemplateLibrary() async { | ||||||
| final ledgerId = ref.read(currentLedgerProvider)?.id; | ||||||
| if (ledgerId == null) { | ||||||
| if (!mounted) return; | ||||||
| ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('无当前账本,无法导入模板'))); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| setState(() { _busy = true; }); | ||||||
| List<SystemCategoryTemplate> templates = []; | ||||||
| try { | ||||||
| templates = await CategoryServiceIntegrated().getAllTemplates(forceRefresh: true); | ||||||
| } catch (_) {} | ||||||
| if (!mounted) return; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using For more robust asynchronous operations, consider using a |
||||||
| setState(() { _busy = false; }); | ||||||
|
|
||||||
| final selected = <SystemCategoryTemplate>{}; | ||||||
| String conflict = 'skip'; // skip|rename|update | ||||||
| ImportResult? preview; | ||||||
|
|
||||||
| await showDialog<void>( | ||||||
| context: context, | ||||||
| builder: (ctx) { | ||||||
| return StatefulBuilder( | ||||||
| builder: (ctx, setLocal) => AlertDialog( | ||||||
| title: const Text('从模板库导入'), | ||||||
| content: SizedBox( | ||||||
| width: 480, | ||||||
| child: Column( | ||||||
| mainAxisSize: MainAxisSize.min, | ||||||
| children: [ | ||||||
| Row( | ||||||
| children: [ | ||||||
| const Text('冲突策略: '), | ||||||
| const SizedBox(width: 8), | ||||||
| DropdownButton<String>( | ||||||
| value: conflict, | ||||||
| items: const [ | ||||||
| DropdownMenuItem(value: 'skip', child: Text('跳过')), | ||||||
| DropdownMenuItem(value: 'rename', child: Text('重命名')), | ||||||
| DropdownMenuItem(value: 'update', child: Text('覆盖')), | ||||||
| ], | ||||||
| onChanged: (v) { if (v!=null) setLocal((){ conflict = v; }); }, | ||||||
|
||||||
| onChanged: (v) { if (v!=null) setLocal((){ conflict = v; }); }, | |
| onChanged: (v) { if (v != null) setLocal((){ conflict = v; }); }, |
Copilot
AI
Sep 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after 'setLocal' before the opening parenthesis. Should be formatted as 'setLocal(() {' for consistency with Dart formatting standards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded colors like Colors.orange and Colors.green are used here to indicate status. It's a better practice to use colors from the app's theme (e.g., Theme.of(context).colorScheme) to ensure UI consistency and support for different themes (like light and dark mode). For example, you could use Theme.of(context).colorScheme.primary for success and Theme.of(context).colorScheme.error for failures.
Copilot
AI
Sep 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after opening parenthesis. Should be formatted as 'setLocal(() { preview = res; });' for consistency with Dart formatting standards.
| setLocal((){ preview = res; }); | |
| setLocal(() { preview = res; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the '确认导入' (Confirm Import) button is pressed, the dialog is closed, and the import process begins. However, the main screen doesn't show a loading indicator because the _busy state is not updated. This can make the UI feel unresponsive during the import. It's better to provide visual feedback to the user that an operation is in progress.
onPressed: (selected.isEmpty) ? null : () async {
Navigator.pop(ctx);
setState(() { _busy = true; });
try {
final items = selected.map((t) => { 'template_id': t.id }).toList();
final result = await CategoryService().importTemplatesAdvanced(
ledgerId: ledgerId,
items: items,
onConflict: conflict,
);
if (!mounted) return;
await ref.read(userCategoriesProvider.notifier).refreshFromBackend(ledgerId: ledgerId);
await ImportDetailsSheet.show(context, result);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('导入失败: $e')));
} finally {
if (mounted) {
setState(() { _busy = false; });
}
}
},There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to create the items list from the selected templates is duplicated in the onPressed handlers for both the '预览' (Preview) and '确认导入' (Confirm Import) buttons (lines 142 and 162).
To improve maintainability and avoid redundancy, you can extract this logic. A good approach would be to define final items = selected.map((t) => { 'template_id': t.id }).toList(); at the beginning of the StatefulBuilder's builder method and then reuse the items variable in both handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
try-catchblock currently swallows errors silently. If fetching templates fails, the user will be presented with an empty list in the dialog without any explanation, which can be confusing. It's important to provide feedback to the user when an error occurs, for instance, by showing aSnackBar.