From 8058a77bd1984bfabfd643646c15285a7108e69a Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Mon, 7 Jul 2025 22:55:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=8B=86=E5=88=86=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 21 +++++- lib/profile.dart | 115 +-------------------------------- lib/views/login.dart | 99 +++++++++++++++------------- lib/views/recordForm.dart | 118 ++++++++++++++++++++++++++++++---- lib/widgets/common/index.dart | 73 +++++++++++++++++++++ pubspec.lock | 23 ++++++- pubspec.yaml | 4 +- 7 files changed, 281 insertions(+), 172 deletions(-) create mode 100644 lib/widgets/common/index.dart diff --git a/lib/main.dart b/lib/main.dart index a7feff8..e808c1e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:food_hub_app/profile.dart'; import 'package:food_hub_app/record.dart'; import 'package:food_hub_app/views/login.dart'; @@ -19,7 +20,25 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(fontFamily: 'CustomFont', primaryColor: Colors.green), - locale: const Locale('zh', 'CN'), + supportedLocales: const [ + Locale('en', 'US'), // 英语 + Locale('zh', 'CN'), // 中文 + ], + // 本地化代理配置 + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + // 根据系统语言自动选择Locale + localeResolutionCallback: (locale, supportedLocales) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return supportedLocales.first; + }, home: LoginPage(), routes: { '/home': (context) => MainPage(), diff --git a/lib/profile.dart b/lib/profile.dart index c801faf..a011369 100644 --- a/lib/profile.dart +++ b/lib/profile.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:form_builder_validators/form_builder_validators.dart'; -import 'package:intl/intl.dart'; class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @@ -10,117 +7,9 @@ class ProfilePage extends StatefulWidget { State createState() => _ProfilePage(); } -// class _ProfilePage extends State{ -// @override -// Widget build(BuildContext context) { -// return const Center(child: Text("个人主页")); -// } -// } - -class _ProfilePage extends State { - final _formKey = GlobalKey(); - bool autoValidate = true; - bool readOnly = false; - bool showSegmentedControl = true; - final _genderOptions = ['Male', 'Female', 'Other']; - +class _ProfilePage extends State{ @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - children: [ - FormBuilder( - key: _formKey, - // enabled: false, - autovalidateMode: AutovalidateMode.onUserInteraction, - initialValue: {'date': DateTime.now(), 'accept_terms': false}, - skipDisabled: true, - child: Column( - children: [ - const SizedBox(height: 15), - FormBuilderTextField( - name: 'name', - decoration: InputDecoration( - labelText: '姓名', - hintText: '请输入姓名', - filled: true, - fillColor: Colors.white, - floatingLabelBehavior: FloatingLabelBehavior.always, - // 始终显示标签 - border: OutlineInputBorder(), - ), - // onChanged: (val) => print(val), - validator: FormBuilderValidators.compose([ - FormBuilderValidators.required(), - FormBuilderValidators.minLength(3), - FormBuilderValidators.maxLength(70), - ]), - keyboardType: TextInputType.name, - ), - const SizedBox(height: 15), - FormBuilderDateTimePicker( - name: 'date', - decoration: InputDecoration( - labelText: '时间', - hintText: '请选择时间', - filled: true, - fillColor: Colors.white, - border: OutlineInputBorder(), - ), - inputType: InputType.both, - initialDate: DateTime.now(), - firstDate: DateTime(1900), - lastDate: DateTime(2100), - format: DateFormat('yyyy-MM-dd HH:mm:ss'), - // enabled: _formKey.currentState?.fields['date']?.enabled ?? true, - ), - ], - ), - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () { - if (_formKey.currentState?.saveAndValidate() ?? false) { - print(_formKey.currentState?.value); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Processing Data')), - ); - } else { - print(_formKey.currentState?.value); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Validation failed')), - ); - } - }, - child: const Text( - 'Submit', - style: TextStyle(color: Colors.green), - ), - ), - ), - const SizedBox(width: 20), - Expanded( - child: OutlinedButton( - onPressed: () { - _formKey.currentState?.reset(); - }, - // color: Colors.red, - child: const Text( - 'Reset', - style: TextStyle(color: Colors.red), - ), - ), - ), - ], - ), - ], - ), - ), - ); + return const Center(child: Text("个人主页")); } } diff --git a/lib/views/login.dart b/lib/views/login.dart index 49a566c..6f3ad5b 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; class LoginPage extends StatefulWidget { @@ -20,6 +21,16 @@ class _LoginPage extends State { {"title": "wechat", "icon": Icons.wechat}, ]; + void loginClick(BuildContext context) { + // 表单校验通过才会继续执行 + if ((_formKey.currentState as FormState).validate()) { + (_formKey.currentState as FormState).save(); + Navigator.pushNamed(context, '/home'); + } else { + showErrorToast("请先输入信息"); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -86,50 +97,56 @@ class _LoginPage extends State { } Widget buildUsernameTextField() { - return FormBuilderTextField( - name: 'username', - decoration: InputDecoration( - labelText: '账号', - prefixIcon: Icon(Icons.person), - hintText: "请输入账号", - border: OutlineInputBorder(), - floatingLabelBehavior: FloatingLabelBehavior.always, - filled: true, - fillColor: Colors.white, - ), - validator: FormBuilderValidators.required(), - onSaved: (v) => _username = v!, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + formLabelText(labelText: "账号:"), + const SizedBox(height: 5), + FormBuilderTextField( + name: 'username', + decoration: formInputDecoration(hintText: "请输入账号", prefixIcon: Icons.person), + validator: FormBuilderValidators.required(), + onSaved: (v) => _username = v!, + ), + ], ); } Widget buildPasswordTextField(BuildContext context) { - return FormBuilderTextField( - name: 'password', - obscureText: _isObscure, // 是否显示文字 - onSaved: (v) => _password = v!, - validator: FormBuilderValidators.required(), - decoration: InputDecoration( - labelText: "密码", - prefixIcon: Icon(Icons.lock), - hintText: "请输入密码", - border: OutlineInputBorder(), - filled: true, - fillColor: Colors.white, - floatingLabelBehavior: FloatingLabelBehavior.always, - suffixIcon: IconButton( - icon: Icon(Icons.remove_red_eye, color: _eyeColor), - onPressed: () { - // 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState() - setState(() { - _isObscure = !_isObscure; - _eyeColor = + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + formLabelText(labelText: "密码:"), + const SizedBox(height: 5), + FormBuilderTextField( + name: 'password', + obscureText: _isObscure, + // 是否显示文字 + onSaved: (v) => _password = v!, + validator: FormBuilderValidators.required(), + decoration: InputDecoration( + prefixIcon: Icon(Icons.lock), + hintText: "请输入密码", + hintStyle: TextStyle(color: Colors.grey), + border: OutlineInputBorder(), + filled: true, + fillColor: Colors.white, + suffixIcon: IconButton( + icon: Icon(Icons.remove_red_eye, color: _eyeColor), + onPressed: () { + // 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState() + setState(() { + _isObscure = !_isObscure; + _eyeColor = (_isObscure ? Colors.grey : Theme.of(context).iconTheme.color)!; - }); - }, - ), - ), + }); + }, + ), + ), + ) + ], ); } @@ -178,13 +195,7 @@ class _LoginPage extends State { '登录', style: TextStyle(fontSize: 24, color: Colors.white), ), - onPressed: () { - // 表单校验通过才会继续执行 - if ((_formKey.currentState as FormState).validate()) { - (_formKey.currentState as FormState).save(); - Navigator.pushNamed(context, '/home'); - } - }, + onPressed: () => loginClick(context), ), ), ); diff --git a/lib/views/recordForm.dart b/lib/views/recordForm.dart index c63b7c3..e5d06cc 100644 --- a/lib/views/recordForm.dart +++ b/lib/views/recordForm.dart @@ -1,23 +1,117 @@ import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:food_hub_app/widgets/common/index.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:intl/intl.dart'; -class RecordFormPage extends StatelessWidget { +class RecordFormPage extends StatefulWidget { const RecordFormPage({super.key}); + // @override + // Widget build(BuildContext context) { + // return Scaffold( + // appBar: AppBar(title: Text('新页面'), backgroundColor: Colors.white), + // backgroundColor: Color(0xFFF5F5F5), + // body: Center( + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text('这是新页面'), + // ElevatedButton( + // onPressed: () => Navigator.pop(context), + // child: Text('返回'), + // ), + // ], + // ), + // ), + // ); + // } + + @override + State createState() => _RecordFormPage(); +} + +class _RecordFormPage extends State { + final _formKey = GlobalKey(); + + void confirmClick(BuildContext context) { + if (_formKey.currentState?.saveAndValidate() ?? false) { + showSuccessToast("新增记录成功"); + } else { + showErrorToast("请先提交信息"); + } + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text('新页面'), backgroundColor: Colors.white), + appBar: AppBar(title: Text('新增记录'), backgroundColor: Colors.white), backgroundColor: Color(0xFFF5F5F5), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('这是新页面'), - ElevatedButton( - onPressed: () => Navigator.pop(context), - child: Text('返回'), - ), - ], + body: Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + children: [ + FormBuilder( + key: _formKey, + initialValue: {'date': DateTime.now(), 'accept_terms': false}, + skipDisabled: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + formLabelText(labelText: '菜谱名称'), + const SizedBox(height: 5), + FormBuilderTextField( + name: 'name', + decoration: formInputDecoration(hintText: '请输入或选择菜谱名称'), + // onChanged: (val) => print(val), + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(), + FormBuilderValidators.minLength(3), + FormBuilderValidators.maxLength(70), + ]), + keyboardType: TextInputType.name, + ), + const SizedBox(height: 10), + formLabelText(labelText: '完成时间'), + const SizedBox(height: 5), + FormBuilderDateTimePicker( + name: 'date', + decoration: formInputDecoration( + hintText: '请选择日期', + prefixIcon: Icons.date_range, + ), + inputType: InputType.date, + firstDate: DateTime(1900), + lastDate: DateTime(2100), + format: DateFormat('yyyy-MM-dd'), + // enabled: _formKey.currentState?.fields['date']?.enabled ?? true, + ), + ], + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: ElevatedButton( + style: primaryButtonStyle(), + onPressed: () => confirmClick(context), + child: buttonText(text: '确认'), + ), + ), + const SizedBox(width: 20), + Expanded( + child: ElevatedButton( + style: cancelButtonStyle(), + onPressed: () => Navigator.pop(context), + child: buttonText(text: '取消'), + ), + ), + ], + ), + ], + ), ), ), ); diff --git a/lib/widgets/common/index.dart b/lib/widgets/common/index.dart new file mode 100644 index 0000000..de8ba47 --- /dev/null +++ b/lib/widgets/common/index.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +Widget formLabelText({required String labelText}) { + return Text.rich( + TextSpan( + children: [ + TextSpan(text: '* ', style: TextStyle(color: Colors.red)), + TextSpan(text: labelText), + ], + ), + ); +} + +InputDecoration formInputDecoration({ + required String hintText, // 使用命名参数并标记为必填 + IconData? prefixIcon, +}) { + return InputDecoration( + hintText: hintText, + hintStyle: TextStyle(color: Colors.grey), + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder(), + prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null, + ); +} + +ButtonStyle primaryButtonStyle() { + return ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.green), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + ); +} + +ButtonStyle cancelButtonStyle() { + return ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.grey), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + ); +} + +Text buttonText({required String text}) { + return Text(text, style: TextStyle(color: Colors.white)); +} + +void showSuccessToast(String message) { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.green, + textColor: Colors.white, + fontSize: 16.0, + ); +} + +void showErrorToast(String message) { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.red, + textColor: Colors.white, + fontSize: 16.0, + ); +} diff --git a/pubspec.lock b/pubspec.lock index 92a4e98..8df39fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -103,7 +103,7 @@ packages: source: hosted version: "5.0.0" flutter_localizations: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" @@ -112,6 +112,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.2.12" form_builder_validators: dependency: "direct main" description: @@ -285,6 +298,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index a491646..d2748a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,8 @@ environment: dependencies: flutter: sdk: flutter - + flutter_localizations: + sdk: flutter # The following adds the Cupertino Icons fonts to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 @@ -41,6 +42,7 @@ dependencies: flutter_date_pickers: ^0.4.3 flutter_form_builder: ^10.0.0 form_builder_validators: ^11.1.2 + fluttertoast: ^8.2.2 intl: ^0.19.0 dev_dependencies: