feat(category): 新增分类功能并重构相关屏幕

- 新增 CategoryScreen 屏幕用于显示分类- 新增 ItemCategory 模型类用于分类数据管理
- 新增 ItemCategoryRepository 用于分类数据持久化
- 新增 item_category_table 创建分类表结构
-重构 HomeScreen 底部导航栏,增加分类选项
- 重命名相关屏幕文件,统一命名规范
- 调整 ItemScreen 以适应新增的分类功能- 更新 SQLiteHelper 以支持分类表创建和默认分类插入
- 删除 StatisticsScreen 屏幕
This commit is contained in:
LingandRX 2025-05-07 22:52:20 +08:00
parent b7d1cdc62e
commit b2217ae2be
12 changed files with 214 additions and 54 deletions

View File

@ -0,0 +1,13 @@
const String createItemCategoryTable = '''
CREATE TABLE item_category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
''';
const String insertDefaultCategory = '''
INSERT INTO item_category (name, description) VALUES ('默认分类', '系统默认分类');
''';

View File

@ -3,6 +3,8 @@ import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'item_category_table.dart';
class DatabaseHelper { class DatabaseHelper {
// //
static final _databaseName = "my_database.db"; static final _databaseName = "my_database.db";
@ -35,5 +37,7 @@ class DatabaseHelper {
Future<void> _onCreate(Database db, int version) async { Future<void> _onCreate(Database db, int version) async {
await db.execute(createItemTable); await db.execute(createItemTable);
await db.execute(createItemCategoryTable);
await db.execute(insertDefaultCategory);
} }
} }

View File

@ -0,0 +1,54 @@
class ItemCategory {
final int? id;
final String name;
final String? description;
final String? createdAt;
final String? updatedAt;
ItemCategory({
this.id,
required this.name,
this.description,
this.createdAt,
this.updatedAt,
});
// fromMap map -> Dart
factory ItemCategory.fromMap(Map<String, dynamic> map) {
return ItemCategory(
id: map['id'] as int?,
name: map['name'] as String,
description: map['description'] as String?,
createdAt: map['created_at'] as String?,
updatedAt: map['updated_at'] as String?,
);
}
// toMap Dart -> map
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'description': description,
'created_at': createdAt,
'updated_at': updatedAt,
};
}
// copyWith 便
ItemCategory copyWith({
int? id,
String? name,
String? description,
String? createdAt,
String? updatedAt,
}) {
return ItemCategory(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@ -1,4 +1,4 @@
import 'package:item_tracker/screens/item_screens/add_item_screen.dart'; import 'package:item_tracker/screens/item_screens/item_screen.dart';
class Item { class Item {
int? id; int? id;

View File

@ -0,0 +1,57 @@
import 'package:sqflite/sqflite.dart';
import '../models/item_category_model.dart';
class ItemCategoryRepository {
final Database db;
ItemCategoryRepository(this.db);
// (Create)
Future<int> insert(ItemCategory category) async {
return await db.insert(
'item_category',
category.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// (Read All)
Future<List<ItemCategory>> getAll() async {
final List<Map<String, dynamic>> maps = await db.query('item_category');
return maps.map((map) => ItemCategory.fromMap(map)).toList();
}
// (Read One by id)
Future<ItemCategory?> getById(int id) async {
final List<Map<String, dynamic>> maps = await db.query(
'item_category',
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
if (maps.isNotEmpty) {
return ItemCategory.fromMap(maps.first);
}
return null;
}
// (Update)
Future<int> update(ItemCategory category) async {
return await db.update(
'item_category',
category.toMap(),
where: 'id = ?',
whereArgs: [category.id],
);
}
// (Delete)
Future<int> delete(int id) async {
return await db.delete(
'item_category',
where: 'id = ?',
whereArgs: [id],
);
}
}

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
class CategoryScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
const items = 20;
return Scaffold(
appBar: AppBar(
title: Text('分类'),
),
body: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(
items,
(index) => ItemWidget(text: 'Item $index'),
),
),
),
);
},
),
);
}
}
class ItemWidget extends StatelessWidget {
const ItemWidget({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Card(child: SizedBox(height: 100, child: Center(child: Text(text))));
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:item_tracker/screens/item_list_screen.dart'; import 'package:item_tracker/screens/category_screens/category_screen.dart';
import 'package:item_tracker/screens/statistics_screen.dart'; import 'package:item_tracker/screens/item_screens/item_list_screen.dart';
import 'package:item_tracker/screens/my_screen.dart'; // import 'package:item_tracker/screens/my_screen.dart'; //
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
@ -12,7 +12,7 @@ class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0; int _selectedIndex = 0;
static List<Widget> _widgetOptions = <Widget>[ static List<Widget> _widgetOptions = <Widget>[
ItemListScreen(), ItemListScreen(),
StatisticsScreen(), CategoryScreen(),
MyScreen(), MyScreen(),
]; ];
@ -37,6 +37,10 @@ class _HomeScreenState extends State<HomeScreen> {
icon: Icon(Icons.add_business_sharp), icon: Icon(Icons.add_business_sharp),
label: '首页', label: '首页',
), ),
BottomNavigationBarItem(
icon: Icon(Icons.add_business_sharp),
label: '分类',
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person), icon: Icon(Icons.person),
label: '我的', label: '我的',

View File

@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:item_tracker/provider/item_provider.dart'; import 'package:item_tracker/provider/item_provider.dart';
import 'package:item_tracker/screens/item_screens/add_item_screen.dart'; import 'package:item_tracker/screens/item_screens/item_screen.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'item_screens/detail_item_screen.dart';
class ItemListScreen extends StatefulWidget { class ItemListScreen extends StatefulWidget {
@override @override
_ItemListScreenState createState() => _ItemListScreenState(); _ItemListScreenState createState() => _ItemListScreenState();
@ -52,7 +50,7 @@ class _ItemListScreenState extends State<ItemListScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AddItemScreen(itemToEdit: item), builder: (context) => ItemScreen(itemToEdit: item, ),
), ),
); );
}, },
@ -63,7 +61,7 @@ class _ItemListScreenState extends State<ItemListScreen> {
onPressed: () async { onPressed: () async {
final shouldRefresh = await Navigator.push( final shouldRefresh = await Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => AddItemScreen()), MaterialPageRoute(builder: (context) => ItemScreen()),
); );
print('shouldRefresh${shouldRefresh}'); print('shouldRefresh${shouldRefresh}');

View File

@ -40,18 +40,16 @@ extension ItemIsUseX on ItemIsUse {
} }
} }
class AddItemScreen extends StatefulWidget { class ItemScreen extends StatefulWidget {
final Item? itemToEdit; final Item? itemToEdit;
const AddItemScreen({Key? key, this.itemToEdit}) : super(key: key); const ItemScreen({Key? key, this.itemToEdit}) : super(key: key);
@override @override
_FromTestRouteSate createState() => _FromTestRouteSate(); _FromTestRouteSate createState() => _FromTestRouteSate();
} }
class _FromTestRouteSate extends State<AddItemScreen> { class _FromTestRouteSate extends State<ItemScreen> {
String _name = ''; String _name = '';
String _description = ''; String _description = '';
String _location = ''; String _location = '';
@ -126,32 +124,34 @@ class _FromTestRouteSate extends State<AddItemScreen> {
}, },
), ),
SizedBox(height: 28.0), SizedBox(height: 28.0),
SubmitButton(onPressed: () { SubmitButton(
if (_formKey.currentState!.validate()) { onPressed: () {
final newItem = Item( if (_formKey.currentState!.validate()) {
name: _name, final newItem = Item(
description: _description, name: _name,
purchaseDate: _selectedDate, description: _description,
isInUse: _itemIsUse, purchaseDate: _selectedDate,
price: _price, isInUse: _itemIsUse,
); price: _price,
);
print(newItem.toMap()); print(newItem.toMap());
// ItemProvider // ItemProvider
Provider.of<ItemProvider>(context, listen: false) Provider.of<ItemProvider>(context, listen: false)
.addItem(newItem); .addItem(newItem);
// //
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('提交成功'), content: Text('提交成功'),
duration: Duration(seconds: 1), duration: Duration(seconds: 1),
)); ));
// Navigator.pop // Navigator.pop
Navigator.pop(context, true); Navigator.pop(context, true);
} }
}) },
isEdit: widget.itemToEdit != null)
], ],
), ),
); );

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../add_item_screen.dart'; import '../item_screen.dart';
class ItemIsUseSelector extends StatelessWidget { class ItemIsUseSelector extends StatelessWidget {
final ItemIsUse? selected; final ItemIsUse? selected;

View File

@ -2,15 +2,18 @@ import 'package:flutter/material.dart';
class SubmitButton extends StatelessWidget { class SubmitButton extends StatelessWidget {
final VoidCallback onPressed; final VoidCallback onPressed;
const SubmitButton({required this.onPressed}); final bool isEdit;
const SubmitButton({required this.onPressed, this.isEdit = false});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final buttonText = isEdit ? '保存' : '提交';
return ElevatedButton( return ElevatedButton(
onPressed: onPressed, onPressed: onPressed,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Text('提交'), child: Text('${buttonText}'),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),

View File

@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
class StatisticsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('统计信息'),
),
body: Center(
child: Text('这里将显示统计信息'),
),
);
}
}