feat(category): 新增分类功能并重构相关屏幕
- 新增 CategoryScreen 屏幕用于显示分类- 新增 ItemCategory 模型类用于分类数据管理 - 新增 ItemCategoryRepository 用于分类数据持久化 - 新增 item_category_table 创建分类表结构 -重构 HomeScreen 底部导航栏,增加分类选项 - 重命名相关屏幕文件,统一命名规范 - 调整 ItemScreen 以适应新增的分类功能- 更新 SQLiteHelper 以支持分类表创建和默认分类插入 - 删除 StatisticsScreen 屏幕
This commit is contained in:
parent
b7d1cdc62e
commit
b2217ae2be
13
lib/database/item_category_table.dart
Normal file
13
lib/database/item_category_table.dart
Normal 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 ('默认分类', '系统默认分类');
|
||||||
|
''';
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
54
lib/models/item_category_model.dart
Normal file
54
lib/models/item_category_model.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
57
lib/repository/item_category_repository.dart
Normal file
57
lib/repository/item_category_repository.dart
Normal 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],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
lib/screens/category_screens/category_screen.dart
Normal file
42
lib/screens/category_screens/category_screen.dart
Normal 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))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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: '我的',
|
||||||
|
|||||||
@ -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}');
|
||||||
@ -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)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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)),
|
||||||
|
|||||||
@ -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('这里将显示统计信息'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user