refactor(item): 重构添加物品页面
- 将 AddItemScreen 中的各个字段提取为独立的 Widget - 新增 CategoryDropdown、DatePickerField、DescriptionField 等组件 - 优化 Item 模型,使用 ItemIsUse 枚举替代字符串表示是否使用 - 在数据库中添加 price 字段- 重构表单提交逻辑,使用新的组件进行数据采集
This commit is contained in:
parent
083b4e506a
commit
faf439087a
@ -5,6 +5,7 @@ CREATE TABLE items (
|
|||||||
category_id INTEGER,
|
category_id INTEGER,
|
||||||
location_id INTEGER,
|
location_id INTEGER,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
|
price REAL,
|
||||||
purchase_date TEXT,
|
purchase_date TEXT,
|
||||||
is_in_use TEXT DEFAULT 'no',
|
is_in_use TEXT DEFAULT 'no',
|
||||||
status TEXT DEFAULT 'normal',
|
status TEXT DEFAULT 'normal',
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:item_tracker/screens/item_screens/add_item_screen.dart';
|
||||||
|
|
||||||
class Item {
|
class Item {
|
||||||
int? id;
|
int? id;
|
||||||
// 名称
|
// 名称
|
||||||
@ -8,10 +10,12 @@ class Item {
|
|||||||
final int? locationId;
|
final int? locationId;
|
||||||
// 描述
|
// 描述
|
||||||
final String? description;
|
final String? description;
|
||||||
|
// 价格
|
||||||
|
final double? price;
|
||||||
// 购买日期
|
// 购买日期
|
||||||
final DateTime? purchaseDate;
|
final DateTime? purchaseDate;
|
||||||
// 是否使用
|
// 是否使用
|
||||||
final String? isInUse;
|
final ItemIsUse? isInUse;
|
||||||
// 数据状态 -normal -deleted
|
// 数据状态 -normal -deleted
|
||||||
final String? status;
|
final String? status;
|
||||||
// 创建时间
|
// 创建时间
|
||||||
@ -25,8 +29,9 @@ class Item {
|
|||||||
this.categoryId,
|
this.categoryId,
|
||||||
this.locationId,
|
this.locationId,
|
||||||
this.description,
|
this.description,
|
||||||
|
this.price,
|
||||||
this.purchaseDate,
|
this.purchaseDate,
|
||||||
this.isInUse = 'no',
|
this.isInUse = ItemIsUse.no,
|
||||||
this.status = 'normal',
|
this.status = 'normal',
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
@ -38,8 +43,9 @@ class Item {
|
|||||||
'category_id': categoryId,
|
'category_id': categoryId,
|
||||||
'location_id': locationId,
|
'location_id': locationId,
|
||||||
'description': description,
|
'description': description,
|
||||||
|
'price': price,
|
||||||
'purchase_date': purchaseDate,
|
'purchase_date': purchaseDate,
|
||||||
'is_in_use': isInUse,
|
'is_in_use': isInUse?.toInt(),
|
||||||
'status': status,
|
'status': status,
|
||||||
'created_at': createdAt,
|
'created_at': createdAt,
|
||||||
'updated_at': updatedAt,
|
'updated_at': updatedAt,
|
||||||
@ -53,8 +59,11 @@ class Item {
|
|||||||
categoryId: map['category_id'],
|
categoryId: map['category_id'],
|
||||||
locationId: map['location_id'],
|
locationId: map['location_id'],
|
||||||
description: map['description'],
|
description: map['description'],
|
||||||
|
price: map['price'],
|
||||||
purchaseDate: map['purchase_date'],
|
purchaseDate: map['purchase_date'],
|
||||||
isInUse: map['is_in_use'],
|
isInUse: map['is_in_use'] != null
|
||||||
|
? ItemIsUseX.fromInt(int.parse(map['is_in_use'].toString()))
|
||||||
|
: ItemIsUse.no,
|
||||||
status: map['status'],
|
status: map['status'],
|
||||||
createdAt: map['created_at'],
|
createdAt: map['created_at'],
|
||||||
updatedAt: map['updated_at'],
|
updatedAt: map['updated_at'],
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:item_tracker/screens/item_screens/widgets/category_dropdown.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/date_picker_field.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/description_field.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/item_is_use_selector.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/location_input_field.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/name_input_field.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/price_input_field.dart';
|
||||||
|
import 'package:item_tracker/screens/item_screens/widgets/submit_button.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:item_tracker/models/item_model.dart';
|
import 'package:item_tracker/models/item_model.dart';
|
||||||
import 'package:item_tracker/provider/item_provider.dart';
|
import 'package:item_tracker/provider/item_provider.dart';
|
||||||
@ -11,40 +18,49 @@ enum ItemIsUse {
|
|||||||
no,
|
no,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ItemIsUseX on ItemIsUse {
|
||||||
|
int toInt() {
|
||||||
|
switch (this) {
|
||||||
|
case ItemIsUse.yes:
|
||||||
|
return 1;
|
||||||
|
case ItemIsUse.no:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ItemIsUse? fromInt(int? value) {
|
||||||
|
switch (value) {
|
||||||
|
case 1:
|
||||||
|
return ItemIsUse.yes;
|
||||||
|
case 0:
|
||||||
|
return ItemIsUse.no;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AddItemScreen extends StatefulWidget {
|
class AddItemScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_FromTestRouteSate createState() => _FromTestRouteSate();
|
_FromTestRouteSate createState() => _FromTestRouteSate();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FromTestRouteSate extends State<AddItemScreen> {
|
class _FromTestRouteSate extends State<AddItemScreen> {
|
||||||
TextEditingController _nameController = TextEditingController();
|
String _name = '';
|
||||||
TextEditingController _descriptionController = TextEditingController();
|
String _description = '';
|
||||||
|
String _location = '';
|
||||||
|
String _selected = '';
|
||||||
|
double? _price;
|
||||||
|
DateTime? _selectedDate;
|
||||||
String? _selectedCategory; // 当前选中的分类
|
String? _selectedCategory; // 当前选中的分类
|
||||||
TextEditingController _locationController = TextEditingController();
|
|
||||||
ItemIsUse? _itemIsUse = ItemIsUse.yes;
|
ItemIsUse? _itemIsUse = ItemIsUse.yes;
|
||||||
TextEditingController _priceController = TextEditingController();
|
|
||||||
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// 添加自定义分类列表
|
// 添加自定义分类列表
|
||||||
List<String> _categories = ['A', 'B', 'C', 'D']; // 自定义分类
|
List<String> _categories = ['A', 'B', 'C', 'D']; // 自定义分类
|
||||||
|
|
||||||
// 更新选中的分类
|
|
||||||
void _updateSelectedCategory(String? category) {
|
|
||||||
setState(() {
|
|
||||||
_selectedCategory = category;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var date = selectedDate;
|
|
||||||
|
|
||||||
void setGroupValue(ItemIsUse? itemIsUse) {
|
|
||||||
setState(() {
|
|
||||||
_itemIsUse = itemIsUse;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('新增物品'),
|
title: Text('新增物品'),
|
||||||
@ -56,174 +72,69 @@ class _FromTestRouteSate extends State<AddItemScreen> {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextFormField(
|
NameInputField(initialValue: _name, onChanged: (v) => _name = v),
|
||||||
autofocus: true,
|
|
||||||
controller: _nameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "名称",
|
|
||||||
hintText: "请输入物品名称",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLength: 20,
|
|
||||||
validator: (v) {
|
|
||||||
if (v == null || v.trim().isEmpty) {
|
|
||||||
print('名称不能为空');
|
|
||||||
return "物品名称不能为空";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 16.0),
|
||||||
TextField(
|
DescriptionField(onChanged: (v) => _description = v),
|
||||||
controller: _descriptionController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "物品描述",
|
|
||||||
hintText: "请输入物品描述",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 4,
|
|
||||||
maxLength: 200,
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 16.0),
|
||||||
DropdownButtonFormField<String>(
|
CategoryDropdown(
|
||||||
value: _selectedCategory,
|
categories: _categories,
|
||||||
items: _categories.map((category) {
|
selectedCategory: _selectedCategory,
|
||||||
return DropdownMenuItem<String>(
|
onChanged: (value) {
|
||||||
value: category,
|
|
||||||
child: Text(category),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: _updateSelectedCategory,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey[100],
|
|
||||||
labelText: '请选择分类',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
|
||||||
TextFormField(
|
|
||||||
controller: _locationController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "物品位置",
|
|
||||||
hintText: "请输入物品位置",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
|
||||||
Text(
|
|
||||||
date == null
|
|
||||||
? '请选择日期'
|
|
||||||
: '已选择日期: ${date.year}年${date.month}月${date.day}日',
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
icon: Icon(Icons.calendar_today, color: Colors.white),
|
|
||||||
onPressed: () async {
|
|
||||||
var pickedDate = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
|
||||||
initialDate: DateTime.now(),
|
|
||||||
firstDate: DateTime(2015, 8),
|
|
||||||
lastDate: DateTime(2101),
|
|
||||||
);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedDate = pickedDate;
|
_selectedCategory = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
label: Text('选择日期', style: TextStyle(color: Colors.white)),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 16.0),
|
||||||
Wrap(
|
LocationInputField(onChanged: (v) => _location = v),
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
spacing: 12, // 每个元素之间水平间距
|
|
||||||
runSpacing: 8, // 换行后垂直间距
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'请选择是否使用:',
|
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Radio<ItemIsUse>(
|
|
||||||
value: ItemIsUse.yes,
|
|
||||||
groupValue: _itemIsUse,
|
|
||||||
onChanged: setGroupValue,
|
|
||||||
),
|
|
||||||
Text('是'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Radio<ItemIsUse>(
|
|
||||||
value: ItemIsUse.no,
|
|
||||||
groupValue: _itemIsUse,
|
|
||||||
onChanged: setGroupValue,
|
|
||||||
),
|
|
||||||
Text('否'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 16.0),
|
||||||
TextField(
|
DatePickerField(
|
||||||
controller: _priceController,
|
selectedDate: _selectedDate,
|
||||||
keyboardType: TextInputType.number,
|
onDateSelected: (date) => setState(() {
|
||||||
inputFormatters: [
|
_selectedDate = date;
|
||||||
FilteringTextInputFormatter.allow(
|
})),
|
||||||
RegExp(r'^\d*\.?\d{0,2}$'),
|
SizedBox(height: 16.0),
|
||||||
),
|
ItemIsUseSelector(
|
||||||
],
|
selected: _itemIsUse,
|
||||||
decoration: InputDecoration(
|
onChanged: (v) => setState(() {
|
||||||
labelText: "物品价格",
|
_itemIsUse = v;
|
||||||
hintText: "请输入物品价格",
|
})),
|
||||||
border: OutlineInputBorder(),
|
SizedBox(height: 16.0),
|
||||||
),
|
PriceInputField(
|
||||||
|
value: _price,
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
_price = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.0),
|
SizedBox(height: 28.0),
|
||||||
ElevatedButton(
|
SubmitButton(onPressed: () {
|
||||||
child: Padding(
|
if (_formKey.currentState!.validate()) {
|
||||||
padding: const EdgeInsets.all(16.0),
|
final newItem = Item(
|
||||||
child: Text("提交"),
|
name: _name,
|
||||||
),
|
description: _description,
|
||||||
onPressed: () {
|
purchaseDate: _selectedDate,
|
||||||
if (_formKey.currentState!.validate()) {
|
isInUse: _itemIsUse,
|
||||||
final newItem = Item(
|
price: _price,
|
||||||
name: _nameController.text,
|
);
|
||||||
description: _descriptionController.text,
|
|
||||||
purchaseDate: selectedDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
elevation: 4.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
33
lib/screens/item_screens/widgets/category_dropdown.dart
Normal file
33
lib/screens/item_screens/widgets/category_dropdown.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CategoryDropdown extends StatelessWidget {
|
||||||
|
final List<String> categories;
|
||||||
|
final String? selectedCategory;
|
||||||
|
final ValueChanged<String?> onChanged;
|
||||||
|
|
||||||
|
CategoryDropdown({
|
||||||
|
required this.categories,
|
||||||
|
required this.selectedCategory,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DropdownButtonFormField<String>(
|
||||||
|
value: selectedCategory,
|
||||||
|
items: categories.map((category) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: category,
|
||||||
|
child: Text(category),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: onChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey[100],
|
||||||
|
labelText: '请选择分类',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
lib/screens/item_screens/widgets/date_picker_field.dart
Normal file
45
lib/screens/item_screens/widgets/date_picker_field.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DatePickerField extends StatelessWidget {
|
||||||
|
final DateTime? selectedDate;
|
||||||
|
final ValueChanged<DateTime?> onDateSelected;
|
||||||
|
const DatePickerField({
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.onDateSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
selectedDate == null
|
||||||
|
? '请选择日期'
|
||||||
|
: '已选择日期: ${selectedDate!.year}年${selectedDate!.month}月${selectedDate!.day}日',
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: Icon(Icons.calendar_today, color: Colors.white),
|
||||||
|
label: Text('选择日期'),
|
||||||
|
onPressed: () async {
|
||||||
|
final pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||||||
|
initialDate: DateTime.now(),
|
||||||
|
firstDate: DateTime(2015, 8),
|
||||||
|
lastDate: DateTime(2101),
|
||||||
|
);
|
||||||
|
if (pickedDate == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDateSelected(pickedDate);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
lib/screens/item_screens/widgets/description_field.dart
Normal file
21
lib/screens/item_screens/widgets/description_field.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DescriptionField extends StatelessWidget {
|
||||||
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
|
const DescriptionField({required this.onChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "物品描述",
|
||||||
|
hintText: "请输入物品描述",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 4,
|
||||||
|
maxLength: 200,
|
||||||
|
onChanged: onChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
lib/screens/item_screens/widgets/item_is_use_selector.dart
Normal file
45
lib/screens/item_screens/widgets/item_is_use_selector.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../add_item_screen.dart';
|
||||||
|
|
||||||
|
class ItemIsUseSelector extends StatelessWidget {
|
||||||
|
final ItemIsUse? selected;
|
||||||
|
final ValueChanged<ItemIsUse?> onChanged;
|
||||||
|
|
||||||
|
const ItemIsUseSelector({required this.selected, required this.onChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'请选择是否使用:',
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Radio<ItemIsUse>(
|
||||||
|
value: ItemIsUse.yes,
|
||||||
|
groupValue: selected,
|
||||||
|
onChanged: onChanged),
|
||||||
|
Text('是'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Radio<ItemIsUse>(
|
||||||
|
value: ItemIsUse.no,
|
||||||
|
groupValue: selected,
|
||||||
|
onChanged: onChanged),
|
||||||
|
Text('否'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/screens/item_screens/widgets/location_input_field.dart
Normal file
19
lib/screens/item_screens/widgets/location_input_field.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LocationInputField extends StatelessWidget {
|
||||||
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
|
const LocationInputField({required this.onChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextFormField(
|
||||||
|
onChanged: onChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "物品位置",
|
||||||
|
hintText: "请输入物品位置",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lib/screens/item_screens/widgets/name_input_field.dart
Normal file
28
lib/screens/item_screens/widgets/name_input_field.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NameInputField extends StatelessWidget {
|
||||||
|
final String initialValue;
|
||||||
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
|
const NameInputField({required this.initialValue, required this.onChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextFormField(
|
||||||
|
initialValue: initialValue,
|
||||||
|
onChanged: onChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '名称',
|
||||||
|
hintText: '请输入名称',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLength: 20,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return '名称不能为空';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
lib/screens/item_screens/widgets/price_input_field.dart
Normal file
37
lib/screens/item_screens/widgets/price_input_field.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class PriceInputField extends StatelessWidget {
|
||||||
|
final double? value;
|
||||||
|
final ValueChanged<double?> onChanged;
|
||||||
|
|
||||||
|
const PriceInputField({
|
||||||
|
Key? key,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = TextEditingController(
|
||||||
|
text: value != null ? value.toString() : '',
|
||||||
|
);
|
||||||
|
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "物品价格",
|
||||||
|
hintText: "请输入物品价格",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (text) {
|
||||||
|
final parsed = double.tryParse(text);
|
||||||
|
onChanged(parsed);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
lib/screens/item_screens/widgets/submit_button.dart
Normal file
21
lib/screens/item_screens/widgets/submit_button.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SubmitButton extends StatelessWidget {
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
const SubmitButton({required this.onPressed});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text('提交'),
|
||||||
|
),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
elevation: 4,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user