feat:"初始化仓库,并更新基础代码。"

This commit is contained in:
2026-01-14 09:25:52 +08:00
commit 9a91c40a97
140 changed files with 6513 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:uuid/uuid.dart';
import '../models/transaction_model.dart';
import '../providers/expense_provider.dart';
class AddTransactionScreen extends StatefulWidget {
const AddTransactionScreen({super.key});
@override
State<AddTransactionScreen> createState() => _AddTransactionScreenState();
}
class _AddTransactionScreenState extends State<AddTransactionScreen> {
final _formKey = GlobalKey<FormState>();
final _amountController = TextEditingController();
final _noteController = TextEditingController();
DateTime _selectedDate = DateTime.now();
String _transactionType = 'expense';
int? _selectedCategoryId;
@override
Widget build(BuildContext context) {
final provider = Provider.of<ExpenseProvider>(context);
// Flatten categories with hierarchy logic for display
final List<DropdownMenuItem<int>> categoryItems = [];
final rootCategories = provider.rootCategories.where((c) => c.type == _transactionType).toList();
for (var root in rootCategories) {
// Add Root
categoryItems.add(
DropdownMenuItem<int>(
value: root.id,
child: Row(
children: [
Icon(_getIconData(root.icon), color: Color(root.color), size: 20),
const SizedBox(width: 8),
Text(root.name, style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
),
);
// Add Children
final children = provider.getSubCategories(root.id!);
for (var child in children) {
categoryItems.add(
DropdownMenuItem<int>(
value: child.id,
child: Row(
children: [
const SizedBox(width: 24), // Indent
Icon(_getIconData(child.icon), color: Color(child.color), size: 16),
const SizedBox(width: 8),
Text(child.name),
],
),
),
);
}
}
return Scaffold(
appBar: AppBar(
title: const Text('记一笔'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
// Type Selection
RadioGroup<String>(
groupValue: _transactionType,
onChanged: (value) {
setState(() {
_transactionType = value!;
_selectedCategoryId = null;
});
},
child: Row(
children: [
Expanded(
child: RadioListTile<String>(
title: const Text('支出'),
value: 'expense',
),
),
Expanded(
child: RadioListTile<String>(
title: const Text('收入'),
value: 'income',
),
),
],
),
),
// Amount
TextFormField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: '金额',
prefixText: '¥ ',
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入金额';
}
if (double.tryParse(value) == null) {
return '请输入有效的数字';
}
return null;
},
),
const SizedBox(height: 16),
// Category
DropdownButtonFormField<int>(
key: ValueKey(_selectedCategoryId),
initialValue: _selectedCategoryId,
decoration: const InputDecoration(labelText: '分类'),
items: categoryItems,
onChanged: (value) {
setState(() {
_selectedCategoryId = value;
});
},
validator: (value) {
if (value == null) {
return '请选择分类';
}
return null;
},
),
const SizedBox(height: 16),
// Date Picker
Row(
children: [
Text('日期: ${DateFormat('yyyy-MM-dd').format(_selectedDate)}'),
const Spacer(),
TextButton(
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime(2100),
helpText: '选择日期',
);
if (pickedDate != null) {
setState(() {
_selectedDate = pickedDate;
});
}
},
child: const Text('选择日期'),
),
],
),
const SizedBox(height: 16),
// Note
TextFormField(
controller: _noteController,
decoration: const InputDecoration(labelText: '备注'),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: _saveTransaction,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50),
),
child: const Text('保存', style: TextStyle(fontSize: 18)),
),
],
),
),
),
);
}
void _saveTransaction() {
if (_formKey.currentState!.validate()) {
final newTransaction = TransactionModel(
id: const Uuid().v4(),
amount: double.parse(_amountController.text),
type: _transactionType,
categoryId: _selectedCategoryId!,
date: _selectedDate,
note: _noteController.text,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
Provider.of<ExpenseProvider>(context, listen: false).addTransaction(newTransaction);
Navigator.of(context).pop();
}
}
IconData _getIconData(String iconName) {
switch (iconName) {
case 'restaurant': return Icons.restaurant;
case 'directions_bus': return Icons.directions_bus;
case 'shopping_cart': return Icons.shopping_cart;
case 'movie': return Icons.movie;
case 'local_hospital': return Icons.local_hospital;
case 'attach_money': return Icons.attach_money;
case 'trending_up': return Icons.trending_up;
case 'breakfast_dining': return Icons.breakfast_dining;
case 'lunch_dining': return Icons.lunch_dining;
case 'dinner_dining': return Icons.dinner_dining;
case 'subway': return Icons.subway;
case 'local_taxi': return Icons.local_taxi;
default: return Icons.category;
}
}
}