From 97e7a6f44b6b2902bc3a3d4fac4a9cb27f34daa7 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Sun, 23 Nov 2025 23:19:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E6=9C=8B=E5=8F=8B?= =?UTF-8?q?=E5=9C=88=E8=A1=A8=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config/app_config.dart | 3 +- lib/main.dart | 2 + lib/provider/food_provider.dart | 62 +++++++++++++++++++++- lib/views/login.dart | 19 +++++-- lib/views/moment_form.dart | 92 ++++++++++++++++++--------------- lib/views/record_form.dart | 7 +++ lib/views/stats.dart | 21 +++----- lib/widgets/common/form.dart | 7 ++- lib/widgets/recipe/card.dart | 33 ++++++++---- lib/widgets/stats/card.dart | 54 ------------------- 10 files changed, 169 insertions(+), 131 deletions(-) delete mode 100644 lib/widgets/stats/card.dart diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index fc6f97f..4af06f3 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -5,7 +5,8 @@ class AppConfig { // http://14.103.235.151:81/food-service // static const String baseApiUrl = "http://14.103.235.151:81/food-service"; // static const String baseApiUrl = "http://192.168.1.3:8100"; - static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api"; + // static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api"; + static const String baseApiUrl = "http://192.168.1.4:8083"; // static const String baseApiUrl = "http://192.168.1.103:8083"; static const String rustfsIp = '14.103.235.151'; static const String rustfsFileUrl = 'http://14.103.235.151:9100'; diff --git a/lib/main.dart b/lib/main.dart index 2534914..81207e1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:food_hub_app/provider/food_provider.dart'; import 'package:food_hub_app/views/home.dart'; import 'package:food_hub_app/views/login.dart'; +import 'package:food_hub_app/views/moment.dart'; import 'package:food_hub_app/views/moment_form.dart'; import 'package:food_hub_app/views/record_form.dart'; import 'package:food_hub_app/views/recipe_detail.dart'; @@ -64,6 +65,7 @@ class MyApp extends StatelessWidget { '/recordForm': (context) => RecordFormPage(), '/recipeDetail': (context) => RecipeDetailPage(), '/momentForm': (context) => MomentFormPage(), + '/moment': (context) => MomentPage() }, ); } diff --git a/lib/provider/food_provider.dart b/lib/provider/food_provider.dart index 1205762..f7f2b43 100644 --- a/lib/provider/food_provider.dart +++ b/lib/provider/food_provider.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_common/models/common_model.dart'; -import 'package:flutter_common/utils/toast_util.dart'; import 'package:food_hub_app/apis/moment.dart'; import 'package:food_hub_app/apis/recipe.dart'; import 'package:food_hub_app/apis/stats.dart'; @@ -43,7 +42,7 @@ class FoodProvider with ChangeNotifier { List momentList = []; int currentPage = 1; - final int pageSize = 3; + final int pageSize = 5; bool hasMore = true; void resetRecordForm() { @@ -63,6 +62,21 @@ class FoodProvider with ChangeNotifier { notifyListeners(); } + void updateMomentFormItem({String? content}) { + if (content != null) momentFormItem.content = content; + notifyListeners(); + } + + void addMomentFormImage(List imageUrls) { + momentFormItem.imageList.addAll(imageUrls); + notifyListeners(); + } + + void removeMomentFormImage(int index) { + momentFormItem.imageList.removeAt(index); + notifyListeners(); + } + void resetMomentForm() { momentFormItem = Moment.getEmpty(); notifyListeners(); @@ -301,4 +315,48 @@ class FoodProvider with ChangeNotifier { Future refreshMomentList() async { await queryMomentByPage(isRefresh: true); } + + Future handleMoment() async { + if (isLoading) return true; + + try { + isLoading = true; + error = null; + notifyListeners(); + + if (isEditing) { + await updateMomentApi(momentFormItem.id!, momentFormItem); + } else { + await addMomentApi(momentFormItem); + } + return true; + } catch (e) { + error = '处理数据失败: $e'; + debugPrint('处理数据失败: $e'); + return false; + } finally { + isLoading = false; + notifyListeners(); + } + } + + Future deleteMoment() async { + if (isLoading) return true; + + try { + isLoading = true; + error = null; + notifyListeners(); + + await deleteMomentApi(momentFormItem.id!); + return true; + } catch (e) { + error = '处理数据失败: $e'; + debugPrint('处理数据失败: $e'); + return false; + } finally { + isLoading = false; + notifyListeners(); + } + } } diff --git a/lib/views/login.dart b/lib/views/login.dart index db56db4..21334cc 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_common/utils/sp_utils.dart'; import 'package:flutter_common/utils/toast_util.dart'; +import 'package:flutter_common/widget/loading_widget.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:food_hub_app/apis/session.dart'; import 'package:food_hub_app/models/session.dart'; @@ -70,12 +71,20 @@ class _LoginPage extends State { // 表单校验通过才会继续执行 if ((_formKey.currentState as FormState).validate()) { (_formKey.currentState as FormState).save(); - Session session = await loginApi(_username, _password); - ToastUtil.success('登录成功'); - handleRememberState(); - handleTokenState(session.saToken.tokenValue); - Navigator.pushNamed(context, '/home'); + try { + LoadingDialog.show(context, message: '登录中'); + Session session = await loginApi(_username, _password); + ToastUtil.success('登录成功'); + handleRememberState(); + handleTokenState(session.saToken.tokenValue); + + LoadingDialog.hide(context); + Navigator.pushNamed(context, '/home'); + } catch (e) { + LoadingDialog.hide(context); + ToastUtil.error(e.toString()); + } } setState(() { diff --git a/lib/views/moment_form.dart b/lib/views/moment_form.dart index 2ddfc52..562b227 100644 --- a/lib/views/moment_form.dart +++ b/lib/views/moment_form.dart @@ -4,6 +4,8 @@ import 'package:flutter_common/utils/log_utils.dart'; import 'package:flutter_common/utils/minio_utils.dart'; import 'package:flutter_common/utils/toast_util.dart'; import 'package:flutter_common/widget/common_widget.dart'; +import 'package:flutter_common/widget/dialog_widget.dart'; +import 'package:flutter_common/widget/loading_widget.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/provider/food_provider.dart'; @@ -48,15 +50,9 @@ class _MomentFormPageState extends State { ); }, onChanged: (value) { - setState(() { - provider.momentFormItem.content = value!; - }); + provider.updateMomentFormItem(content: value); }, - decoration: buildInputDecoration( - context: context, - hintText: '请输入朋友圈内容', - prefixIcon: Icons.article, - ), + decoration: buildInputDecoration(context: context, hintText: '请输入朋友圈内容'), validator: (value) { if (value == null || value.isEmpty) { return '请输入朋友圈内容'; @@ -81,7 +77,7 @@ class _MomentFormPageState extends State { crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8, - childAspectRatio: 1, + childAspectRatio: 4/3, ), itemCount: totalItems, itemBuilder: (context, index) { @@ -103,9 +99,7 @@ class _MomentFormPageState extends State { ); } - Widget _buildFormBuilder() { - final provider = context.watch(); - + Widget _buildFormBuilder(FoodProvider provider) { return FormBuilder( key: _formKey, child: Column( @@ -116,7 +110,7 @@ class _MomentFormPageState extends State { _buildContentField(provider), const SizedBox(height: 8), - buildFormLabel(context: context, text: '上传图片', isRequired: true), + buildFormLabel(context: context, text: '上传图片', isRequired: false), const SizedBox(height: 8), _buildImageField(provider), const SizedBox(height: 8), @@ -152,47 +146,59 @@ class _MomentFormPageState extends State { // 限制选择数量 final filesToUpload = result.files.take(remainingCount).toList(); - // 显示加载提示 - // showLoadingToast('正在上传图片...'); + LoadingDialog.show(context, message: '上传中'); - try { - final newImageUrls = []; + final newImageUrls = []; - // 逐个上传图片 - for (final file in filesToUpload) { - final fileName = await MinIOHelper().uploadFile( - bucketName: AppConfig.bucketName, - file: file, - ); - logger.i('图片名称:$fileName'); - newImageUrls.add(fileName); - } - - // 更新图片列表 - setState(() { - provider.momentFormItem.imageList.addAll(newImageUrls); - }); - - ToastUtil.success('图片上传成功'); - } catch (e) { - ToastUtil.error('图片上传失败'); + // 逐个上传图片 + for (final file in filesToUpload) { + final fileName = await MinIOHelper().uploadFile( + bucketName: AppConfig.bucketName, + file: file, + ); + logger.i('图片名称:$fileName'); + newImageUrls.add(fileName); } + + // 更新图片列表 + LoadingDialog.hide(context); + provider.addMomentFormImage(newImageUrls); } } /// 移除单张图片 void _removeImage(FoodProvider provider, int index) { - setState(() { - provider.momentFormItem.imageList.removeAt(index); - }); + provider.removeMomentFormImage(index); } /// 提交表单 - void _submitForm(FoodProvider provider) { + void _submitForm(FoodProvider provider) async{ if (_formKey.currentState?.saveAndValidate() ?? false) { - // 这里处理表单提交逻辑 - // print('提交内容: ${_formKey.currentState?.value[_contentField]}'); - // print('图片列表: ${provider.recordFormItem.imageUrls}'); + LoadingDialog.show(context, message: '提交中'); + final result = await provider.handleMoment(); + if (result == true) { + await provider.refreshMomentList(); + LoadingDialog.hide(context); + + if (provider.isEditing) { + showSuccessTip(context, '更新朋友圈成功'); + } else { + showSuccessTip(context, '新增朋友圈成功'); + } + + Future.delayed(Duration(milliseconds: 1500), () { + if (mounted) { + Navigator.pop(context); + } + }); + } else { + LoadingDialog.hide(context); + if (provider.isEditing) { + showErrorTip(context, '更新朋友圈失败'); + } else { + showErrorTip(context, '新增朋友圈失败'); + } + } } } @@ -216,7 +222,7 @@ class _MomentFormPageState extends State { body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(10), - child: CommonCard(child: _buildFormBuilder()), + child: CommonCard(child: _buildFormBuilder(provider)), ), ), ); diff --git a/lib/views/record_form.dart b/lib/views/record_form.dart index 6491a4b..23147c8 100644 --- a/lib/views/record_form.dart +++ b/lib/views/record_form.dart @@ -294,6 +294,13 @@ class _RecordFormPageState extends State { Navigator.pop(context); } }); + } else { + LoadingDialog.hide(context); + if (provider.isEditing) { + showErrorTip(context, '更新记录失败'); + } else { + showErrorTip(context, '新增记录失败'); + } } } } diff --git a/lib/views/stats.dart b/lib/views/stats.dart index 4f785d8..57ef760 100644 --- a/lib/views/stats.dart +++ b/lib/views/stats.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_common/widget/chart.dart'; import 'package:flutter_common/widget/common_widget.dart'; import 'package:food_hub_app/provider/food_provider.dart'; -import 'package:food_hub_app/widgets/stats/card.dart'; import 'package:provider/provider.dart'; class StatsPage extends StatefulWidget { @@ -35,32 +34,28 @@ class _StatsPage extends State { mainAxisSpacing: 8, childAspectRatio: 2, children: [ - StatisticCard( + StatsCard( icon: Icons.restaurant_menu, - color: Colors.blue, title: '菜谱总数', - value: provider.summaryStats.recipeCount, + value: provider.summaryStats.recipeCount.toString(), unit: '个', ), - StatisticCard( + StatsCard( icon: Icons.grid_view_rounded, - color: Colors.green, title: '菜谱类别', - value: provider.summaryStats.categoryCount, + value: provider.summaryStats.categoryCount.toString(), unit: '种', ), - StatisticCard( + StatsCard( icon: Icons.flag, - color: Colors.orange, title: '做菜次数', - value: provider.summaryStats.workCount, + value: provider.summaryStats.workCount.toString(), unit: '个', ), - StatisticCard( + StatsCard( icon: Icons.show_chart, - color: Colors.red, title: '平均次数', - value: double.parse(provider.averageRecordCount.toStringAsFixed(2)), + value: provider.averageRecordCount.toStringAsFixed(2), unit: '次/月', ), ], diff --git a/lib/widgets/common/form.dart b/lib/widgets/common/form.dart index 477abe6..9f63c30 100644 --- a/lib/widgets/common/form.dart +++ b/lib/widgets/common/form.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; InputDecoration buildInputDecoration({ required BuildContext context, required String hintText, - required IconData prefixIcon, + IconData? prefixIcon, }) { final colors = Theme.of(context).colorScheme; @@ -27,7 +27,10 @@ InputDecoration buildInputDecoration({ ), contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red), - prefixIcon: Icon(prefixIcon, size: 20, color: Color(0xFF86909C)), + prefixIcon: + prefixIcon != null + ? Icon(prefixIcon, size: 20, color: Color(0xFF86909C)) + : null, prefixIconConstraints: const BoxConstraints(minWidth: 40), isDense: true, ); diff --git a/lib/widgets/recipe/card.dart b/lib/widgets/recipe/card.dart index 958af1c..b67e3cf 100644 --- a/lib/widgets/recipe/card.dart +++ b/lib/widgets/recipe/card.dart @@ -24,25 +24,36 @@ class RecipeCard extends StatelessWidget { children: [ CommonImage(imageUrls: imageUrls, index: 0), SizedBox(height: 8), - GestureDetector( - onTap: () => navigatorToRecipeDetail(context), - child: _buildRecipeContent(), - ), + _buildRecipeContent(context), ], ), ); } - Widget _buildRecipeContent() { + Widget _buildRecipeContent(BuildContext context) { + final colors = Theme.of(context).colorScheme; + return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - recipe.name, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - maxLines: 1, - overflow: TextOverflow.ellipsis, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + recipe.name, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + InkWell( + onTap: () => navigatorToRecipeDetail(context), + child: Icon( + Icons.arrow_circle_right_sharp, + color: colors.secondary, + ), + ), + ], ), SizedBox(height: 5), Row( @@ -66,7 +77,7 @@ class RecipeCard extends StatelessWidget { color: Color(0xFF20B2AA), ), ], - ) + ), ], ); } diff --git a/lib/widgets/stats/card.dart b/lib/widgets/stats/card.dart deleted file mode 100644 index 7236995..0000000 --- a/lib/widgets/stats/card.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_common/widget/common_widget.dart'; - -class StatisticCard extends StatelessWidget { - final IconData icon; - final Color color; - final String title; - final num value; - final String unit; - - const StatisticCard({ - super.key, - required this.icon, - required this.color, - required this.title, - required this.value, - required this.unit, - }); - - @override - Widget build(BuildContext context) { - return CommonCard( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 40, - height: 40, - child: Icon(icon, color: color, size: 40), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(title, style: TextStyle(fontSize: 20, color: color)), - const SizedBox(height: 4), - Row( - children: [ - Text( - value.toString(), - style: TextStyle(fontSize: 20, color: color), - ), - const SizedBox(width: 5), - Text(unit, style: TextStyle(fontSize: 20, color: color)), - ], - ), - ], - ), - ], - ), - ); - } -}