feat:增加朋友圈表单功能

This commit is contained in:
2025-11-23 23:19:17 +08:00
parent 245edd9d27
commit 97e7a6f44b
10 changed files with 169 additions and 131 deletions

View File

@@ -5,7 +5,8 @@ class AppConfig {
// http://14.103.235.151:81/food-service // 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://14.103.235.151:81/food-service";
// static const String baseApiUrl = "http://192.168.1.3:8100"; // 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 baseApiUrl = "http://192.168.1.103:8083";
static const String rustfsIp = '14.103.235.151'; static const String rustfsIp = '14.103.235.151';
static const String rustfsFileUrl = 'http://14.103.235.151:9100'; static const String rustfsFileUrl = 'http://14.103.235.151:9100';

View File

@@ -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/provider/food_provider.dart';
import 'package:food_hub_app/views/home.dart'; import 'package:food_hub_app/views/home.dart';
import 'package:food_hub_app/views/login.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/moment_form.dart';
import 'package:food_hub_app/views/record_form.dart'; import 'package:food_hub_app/views/record_form.dart';
import 'package:food_hub_app/views/recipe_detail.dart'; import 'package:food_hub_app/views/recipe_detail.dart';
@@ -64,6 +65,7 @@ class MyApp extends StatelessWidget {
'/recordForm': (context) => RecordFormPage(), '/recordForm': (context) => RecordFormPage(),
'/recipeDetail': (context) => RecipeDetailPage(), '/recipeDetail': (context) => RecipeDetailPage(),
'/momentForm': (context) => MomentFormPage(), '/momentForm': (context) => MomentFormPage(),
'/moment': (context) => MomentPage()
}, },
); );
} }

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/models/common_model.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/moment.dart';
import 'package:food_hub_app/apis/recipe.dart'; import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/apis/stats.dart'; import 'package:food_hub_app/apis/stats.dart';
@@ -43,7 +42,7 @@ class FoodProvider with ChangeNotifier {
List<Moment> momentList = []; List<Moment> momentList = [];
int currentPage = 1; int currentPage = 1;
final int pageSize = 3; final int pageSize = 5;
bool hasMore = true; bool hasMore = true;
void resetRecordForm() { void resetRecordForm() {
@@ -63,6 +62,21 @@ class FoodProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateMomentFormItem({String? content}) {
if (content != null) momentFormItem.content = content;
notifyListeners();
}
void addMomentFormImage(List<String> imageUrls) {
momentFormItem.imageList.addAll(imageUrls);
notifyListeners();
}
void removeMomentFormImage(int index) {
momentFormItem.imageList.removeAt(index);
notifyListeners();
}
void resetMomentForm() { void resetMomentForm() {
momentFormItem = Moment.getEmpty(); momentFormItem = Moment.getEmpty();
notifyListeners(); notifyListeners();
@@ -301,4 +315,48 @@ class FoodProvider with ChangeNotifier {
Future<void> refreshMomentList() async { Future<void> refreshMomentList() async {
await queryMomentByPage(isRefresh: true); await queryMomentByPage(isRefresh: true);
} }
Future<bool> 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<bool> 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();
}
}
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/utils/sp_utils.dart'; import 'package:flutter_common/utils/sp_utils.dart';
import 'package:flutter_common/utils/toast_util.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:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/apis/session.dart'; import 'package:food_hub_app/apis/session.dart';
import 'package:food_hub_app/models/session.dart'; import 'package:food_hub_app/models/session.dart';
@@ -70,12 +71,20 @@ class _LoginPage extends State<LoginPage> {
// 表单校验通过才会继续执行 // 表单校验通过才会继续执行
if ((_formKey.currentState as FormState).validate()) { if ((_formKey.currentState as FormState).validate()) {
(_formKey.currentState as FormState).save(); (_formKey.currentState as FormState).save();
Session session = await loginApi(_username, _password);
ToastUtil.success('登录成功');
handleRememberState(); try {
handleTokenState(session.saToken.tokenValue); LoadingDialog.show(context, message: '登录中');
Navigator.pushNamed(context, '/home'); 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(() { setState(() {

View File

@@ -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/minio_utils.dart';
import 'package:flutter_common/utils/toast_util.dart'; import 'package:flutter_common/utils/toast_util.dart';
import 'package:flutter_common/widget/common_widget.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:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/config/app_config.dart';
import 'package:food_hub_app/provider/food_provider.dart'; import 'package:food_hub_app/provider/food_provider.dart';
@@ -48,15 +50,9 @@ class _MomentFormPageState extends State<MomentFormPage> {
); );
}, },
onChanged: (value) { onChanged: (value) {
setState(() { provider.updateMomentFormItem(content: value);
provider.momentFormItem.content = value!;
});
}, },
decoration: buildInputDecoration( decoration: buildInputDecoration(context: context, hintText: '请输入朋友圈内容'),
context: context,
hintText: '请输入朋友圈内容',
prefixIcon: Icons.article,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return '请输入朋友圈内容'; return '请输入朋友圈内容';
@@ -81,7 +77,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
crossAxisCount: 3, crossAxisCount: 3,
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 1, childAspectRatio: 4/3,
), ),
itemCount: totalItems, itemCount: totalItems,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@@ -103,9 +99,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
); );
} }
Widget _buildFormBuilder() { Widget _buildFormBuilder(FoodProvider provider) {
final provider = context.watch<FoodProvider>();
return FormBuilder( return FormBuilder(
key: _formKey, key: _formKey,
child: Column( child: Column(
@@ -116,7 +110,7 @@ class _MomentFormPageState extends State<MomentFormPage> {
_buildContentField(provider), _buildContentField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormLabel(context: context, text: '上传图片', isRequired: true), buildFormLabel(context: context, text: '上传图片', isRequired: false),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildImageField(provider), _buildImageField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -152,47 +146,59 @@ class _MomentFormPageState extends State<MomentFormPage> {
// 限制选择数量 // 限制选择数量
final filesToUpload = result.files.take(remainingCount).toList(); final filesToUpload = result.files.take(remainingCount).toList();
// 显示加载提示 LoadingDialog.show(context, message: '上传中');
// showLoadingToast('正在上传图片...');
try { final newImageUrls = <String>[];
final newImageUrls = <String>[];
// 逐个上传图片 // 逐个上传图片
for (final file in filesToUpload) { for (final file in filesToUpload) {
final fileName = await MinIOHelper().uploadFile( final fileName = await MinIOHelper().uploadFile(
bucketName: AppConfig.bucketName, bucketName: AppConfig.bucketName,
file: file, file: file,
); );
logger.i('图片名称:$fileName'); logger.i('图片名称:$fileName');
newImageUrls.add(fileName); newImageUrls.add(fileName);
}
// 更新图片列表
setState(() {
provider.momentFormItem.imageList.addAll(newImageUrls);
});
ToastUtil.success('图片上传成功');
} catch (e) {
ToastUtil.error('图片上传失败');
} }
// 更新图片列表
LoadingDialog.hide(context);
provider.addMomentFormImage(newImageUrls);
} }
} }
/// 移除单张图片 /// 移除单张图片
void _removeImage(FoodProvider provider, int index) { void _removeImage(FoodProvider provider, int index) {
setState(() { provider.removeMomentFormImage(index);
provider.momentFormItem.imageList.removeAt(index);
});
} }
/// 提交表单 /// 提交表单
void _submitForm(FoodProvider provider) { void _submitForm(FoodProvider provider) async{
if (_formKey.currentState?.saveAndValidate() ?? false) { if (_formKey.currentState?.saveAndValidate() ?? false) {
// 这里处理表单提交逻辑 LoadingDialog.show(context, message: '提交中');
// print('提交内容: ${_formKey.currentState?.value[_contentField]}'); final result = await provider.handleMoment();
// print('图片列表: ${provider.recordFormItem.imageUrls}'); 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<MomentFormPage> {
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: CommonCard(child: _buildFormBuilder()), child: CommonCard(child: _buildFormBuilder(provider)),
), ),
), ),
); );

View File

@@ -294,6 +294,13 @@ class _RecordFormPageState extends State<RecordFormPage> {
Navigator.pop(context); Navigator.pop(context);
} }
}); });
} else {
LoadingDialog.hide(context);
if (provider.isEditing) {
showErrorTip(context, '更新记录失败');
} else {
showErrorTip(context, '新增记录失败');
}
} }
} }
} }

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_common/widget/chart.dart'; import 'package:flutter_common/widget/chart.dart';
import 'package:flutter_common/widget/common_widget.dart'; import 'package:flutter_common/widget/common_widget.dart';
import 'package:food_hub_app/provider/food_provider.dart'; import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/widgets/stats/card.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class StatsPage extends StatefulWidget { class StatsPage extends StatefulWidget {
@@ -35,32 +34,28 @@ class _StatsPage extends State<StatsPage> {
mainAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 2, childAspectRatio: 2,
children: [ children: [
StatisticCard( StatsCard(
icon: Icons.restaurant_menu, icon: Icons.restaurant_menu,
color: Colors.blue,
title: '菜谱总数', title: '菜谱总数',
value: provider.summaryStats.recipeCount, value: provider.summaryStats.recipeCount.toString(),
unit: '', unit: '',
), ),
StatisticCard( StatsCard(
icon: Icons.grid_view_rounded, icon: Icons.grid_view_rounded,
color: Colors.green,
title: '菜谱类别', title: '菜谱类别',
value: provider.summaryStats.categoryCount, value: provider.summaryStats.categoryCount.toString(),
unit: '', unit: '',
), ),
StatisticCard( StatsCard(
icon: Icons.flag, icon: Icons.flag,
color: Colors.orange,
title: '做菜次数', title: '做菜次数',
value: provider.summaryStats.workCount, value: provider.summaryStats.workCount.toString(),
unit: '', unit: '',
), ),
StatisticCard( StatsCard(
icon: Icons.show_chart, icon: Icons.show_chart,
color: Colors.red,
title: '平均次数', title: '平均次数',
value: double.parse(provider.averageRecordCount.toStringAsFixed(2)), value: provider.averageRecordCount.toStringAsFixed(2),
unit: '次/月', unit: '次/月',
), ),
], ],

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
InputDecoration buildInputDecoration({ InputDecoration buildInputDecoration({
required BuildContext context, required BuildContext context,
required String hintText, required String hintText,
required IconData prefixIcon, IconData? prefixIcon,
}) { }) {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
@@ -27,7 +27,10 @@ InputDecoration buildInputDecoration({
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red), 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), prefixIconConstraints: const BoxConstraints(minWidth: 40),
isDense: true, isDense: true,
); );

View File

@@ -24,25 +24,36 @@ class RecipeCard extends StatelessWidget {
children: [ children: [
CommonImage(imageUrls: imageUrls, index: 0), CommonImage(imageUrls: imageUrls, index: 0),
SizedBox(height: 8), SizedBox(height: 8),
GestureDetector( _buildRecipeContent(context),
onTap: () => navigatorToRecipeDetail(context),
child: _buildRecipeContent(),
),
], ],
), ),
); );
} }
Widget _buildRecipeContent() { Widget _buildRecipeContent(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Row(
recipe.name, mainAxisAlignment: MainAxisAlignment.spaceBetween,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), children: [
maxLines: 1, Text(
overflow: TextOverflow.ellipsis, 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), SizedBox(height: 5),
Row( Row(
@@ -66,7 +77,7 @@ class RecipeCard extends StatelessWidget {
color: Color(0xFF20B2AA), color: Color(0xFF20B2AA),
), ),
], ],
) ),
], ],
); );
} }

View File

@@ -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)),
],
),
],
),
],
),
);
}
}