feat:更新api模块
This commit is contained in:
@@ -34,9 +34,9 @@ Future<bool> addMomentCommentApi(int id, String content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addMomentLikeApi(int id) {
|
Future<bool> addMomentLikeApi(int id) {
|
||||||
return HttpUtil().post<bool>("/moment/$id/like");
|
return HttpUtil().post<bool>("/food/moment/$id/like");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteMomentLikeApi(int id) {
|
Future<bool> deleteMomentLikeApi(int id) {
|
||||||
return HttpUtil().delete<bool>("/moment/$id/like");
|
return HttpUtil().delete<bool>("/food/moment/$id/like");
|
||||||
}
|
}
|
||||||
@@ -10,15 +10,15 @@ Future<RecipeDetail> queryRecipeByIdApi(int id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addRecipeApi(Recipe recipe) {
|
Future<bool> addRecipeApi(Recipe recipe) {
|
||||||
return HttpUtil().post<bool>("/food/food/recipe", data: recipe);
|
return HttpUtil().post<bool>("/food/recipe", data: recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateRecipeApi(int id, Recipe recipe) {
|
Future<bool> updateRecipeApi(int id, Recipe recipe) {
|
||||||
return HttpUtil().put<bool>("/food/food/recipe/$id", data: recipe);
|
return HttpUtil().put<bool>("/food/recipe/$id", data: recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteRecipeApi(int id) {
|
Future<bool> deleteRecipeApi(int id) {
|
||||||
return HttpUtil().delete<bool>("/food/food/recipe/$id");
|
return HttpUtil().delete<bool>("/food/recipe/$id");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Recipe>> queryRecipeByUserApi(int id) {
|
Future<List<Recipe>> queryRecipeByUserApi(int id) {
|
||||||
@@ -3,6 +3,7 @@ class AppConfig {
|
|||||||
// 网络配置
|
// 网络配置
|
||||||
// http://192.168.1.3:8100
|
// http://192.168.1.3:8100
|
||||||
// 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://172.29.101.108:8100";
|
// static const String baseApiUrl = "http://192.168.1.3:8100";
|
||||||
|
static const String baseApiUrl = "https://cxx0822.iepose.cn/food-api";
|
||||||
}
|
}
|
||||||
@@ -106,20 +106,10 @@ List<StatelessWidget> homeActions(BuildContext context) {
|
|||||||
// 搜索功能
|
// 搜索功能
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
// IconButton(
|
||||||
icon: Icon(Icons.add, color: Colors.white),
|
// icon: Icon(Icons.add, color: Colors.white),
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(context, '/recordForm');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Builder(
|
|
||||||
// builder: (BuildContext context) {
|
|
||||||
// return IconButton(
|
|
||||||
// icon: Icon(Icons.settings, color: Colors.white),
|
|
||||||
// onPressed: () {
|
// onPressed: () {
|
||||||
// Scaffold.of(context).openEndDrawer();
|
// Navigator.pushNamed(context, '/recordForm');
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -56,6 +56,74 @@ class _HomePage extends State<HomePage> {
|
|||||||
|
|
||||||
List<Widget> get tabPages => navItems.map((item) => item.page).toList();
|
List<Widget> get tabPages => navItems.map((item) => item.page).toList();
|
||||||
|
|
||||||
|
// 显示底部弹窗
|
||||||
|
void _showBottomSheet() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context) => Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'请选择操作',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.book, color: Theme.of(context).primaryColor),
|
||||||
|
title: const Text('新增菜谱'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_handleAddRecipe();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.note_add, color: Theme.of(context).primaryColor),
|
||||||
|
title: const Text('新增记录'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pushNamed(context, "/recordForm");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.group, color: Theme.of(context).primaryColor),
|
||||||
|
title: const Text('发布朋友圈'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_handleAddRecord();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理添加菜谱
|
||||||
|
void _handleAddRecipe() {
|
||||||
|
// 这里添加跳转或处理添加菜谱的逻辑
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('添加菜谱功能')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理添加记录
|
||||||
|
void _handleAddRecord() {
|
||||||
|
// 这里添加跳转或处理添加记录的逻辑
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('添加记录功能')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -77,6 +145,13 @@ class _HomePage extends State<HomePage> {
|
|||||||
backgroundColor: Color(0xFFF5F5F5),
|
backgroundColor: Color(0xFFF5F5F5),
|
||||||
drawer: const SettingsDrawer(),
|
drawer: const SettingsDrawer(),
|
||||||
body: tabPages[_currentIndex],
|
body: tabPages[_currentIndex],
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
onPressed: _showBottomSheet,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
child: const Icon(Icons.add, color: Colors.white, size: 30),
|
||||||
|
),
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
bottomNavigationBar: NavBar(
|
bottomNavigationBar: NavBar(
|
||||||
navItems: bottomNavItems,
|
navItems: bottomNavItems,
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:food_hub_app/api/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';
|
||||||
import 'package:food_hub_app/utils/sp_util.dart';
|
import 'package:food_hub_app/utils/sp_util.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:easy_refresh/easy_refresh.dart';
|
import 'package:easy_refresh/easy_refresh.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/moment.dart';
|
import 'package:food_hub_app/apis/moment.dart';
|
||||||
import 'package:food_hub_app/models/moment.dart';
|
import 'package:food_hub_app/models/moment.dart';
|
||||||
import 'package:food_hub_app/widgets/moment/card.dart';
|
import 'package:food_hub_app/widgets/moment/card.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,205 +1,194 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:food_hub_app/utils/index.dart';
|
import 'package:food_hub_app/widgets/common/form.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
class RecordFormPage extends StatefulWidget {
|
class RecordFormPage extends StatefulWidget {
|
||||||
const RecordFormPage({super.key});
|
const RecordFormPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecordFormPage> createState() => _RecordFormPage();
|
State<RecordFormPage> createState() => _RecordFormPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecordFormPage extends State<RecordFormPage> {
|
class _RecordFormPageState extends State<RecordFormPage> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
final _focusNode = FocusNode();
|
||||||
List<TextEditingController> _controller = [];
|
static const String _nameField = 'name';
|
||||||
FormController _formController = FormController();
|
static const String _dateField = 'date';
|
||||||
|
|
||||||
String _selected_1 = '';
|
|
||||||
String _selected_2 = '';
|
|
||||||
String? _initLocalData;
|
|
||||||
|
|
||||||
final ImagePicker _picker = ImagePicker();
|
final ImagePicker _picker = ImagePicker();
|
||||||
XFile? _selectedImage;
|
File? imageUrl; // 改为单张图片变量
|
||||||
|
|
||||||
/// 整个表单存放的数据
|
|
||||||
Map<String, dynamic> _formData = {"name": '', "date": '', "photo": ''};
|
|
||||||
|
|
||||||
/// 定义整个校验规则
|
|
||||||
final Map<String, TDFormValidation> _validationRules = {
|
|
||||||
'name': TDFormValidation(
|
|
||||||
validate: (value) => value == null || value.isEmpty ? 'empty' : null,
|
|
||||||
errorMessage: '输入不能为空',
|
|
||||||
type: TDFormItemType.input,
|
|
||||||
),
|
|
||||||
"birth": TDFormValidation(
|
|
||||||
validate: (value) => value == null || value.isEmpty ? 'empty' : null,
|
|
||||||
errorMessage: '不能为空',
|
|
||||||
type: TDFormItemType.dateTimePicker,
|
|
||||||
),
|
|
||||||
"photo": TDFormValidation(
|
|
||||||
validate: (value) => value == null || value.isEmpty ? 'empty' : null,
|
|
||||||
errorMessage: '不能为空',
|
|
||||||
type: TDFormItemType.upLoadImg,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
List<TDUploadFile> files = [];
|
|
||||||
|
|
||||||
List<TDUploadFile> _onValueChanged(
|
|
||||||
List<TDUploadFile> fileList,
|
|
||||||
List<TDUploadFile> value,
|
|
||||||
TDUploadType event,
|
|
||||||
) {
|
|
||||||
switch (event) {
|
|
||||||
case TDUploadType.add:
|
|
||||||
fileList.addAll(value);
|
|
||||||
break;
|
|
||||||
case TDUploadType.remove:
|
|
||||||
fileList.removeWhere((element) => element.key == value[0].key);
|
|
||||||
break;
|
|
||||||
case TDUploadType.replace:
|
|
||||||
final firstReplaceFile = value.first;
|
|
||||||
final index = fileList.indexWhere(
|
|
||||||
(file) => file.key == firstReplaceFile.key,
|
|
||||||
);
|
|
||||||
if (index != -1) {
|
|
||||||
fileList[index] = firstReplaceFile;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return fileList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _confirmClick(BuildContext context) {
|
@override
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
Widget build(BuildContext context) {
|
||||||
// showSuccessToast("新增记录成功");
|
final theme = Theme.of(context);
|
||||||
print(_formData);
|
|
||||||
} else {
|
|
||||||
TDMessage.showMessage(
|
|
||||||
context: context,
|
|
||||||
visible: true,
|
|
||||||
icon: true,
|
|
||||||
content: "请先输入信息",
|
|
||||||
theme: MessageTheme.error,
|
|
||||||
duration: 3000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TDFormItem buildNameItem() {
|
return Scaffold(
|
||||||
return TDFormItem(
|
appBar: AppBar(
|
||||||
label: '菜谱名称',
|
title: const Text('新增记录', style: TextStyle(color: Colors.white)),
|
||||||
name: 'name',
|
backgroundColor: theme.primaryColor,
|
||||||
type: TDFormItemType.input,
|
leading: IconButton(
|
||||||
labelWidth: 82.0,
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
showErrorMessage: true,
|
onPressed: () => Navigator.pop(context),
|
||||||
requiredMark: true,
|
|
||||||
child: TDInput(
|
|
||||||
leftContentSpace: 0,
|
|
||||||
inputDecoration: InputDecoration(
|
|
||||||
hintText: "请输入菜谱名称",
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintStyle: TextStyle(color: TDTheme.of(context).grayColor6),
|
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.white,
|
|
||||||
additionInfoColor: TDTheme.of(context).errorColor6,
|
|
||||||
showBottomDivider: false,
|
|
||||||
onChanged: (value) {
|
|
||||||
_formData['name'] = value;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
backgroundColor: const Color(0xFFF5F5F5),
|
||||||
}
|
body: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
TDFormItem buildDateItem() {
|
padding: const EdgeInsets.all(5),
|
||||||
return TDFormItem(
|
child: Card(
|
||||||
label: '完成时间',
|
elevation: 0,
|
||||||
name: 'date',
|
color: Colors.white,
|
||||||
labelWidth: 82.0,
|
shape: RoundedRectangleBorder(
|
||||||
type: TDFormItemType.dateTimePicker,
|
borderRadius: BorderRadius.circular(10),
|
||||||
contentAlign: TextAlign.left,
|
|
||||||
tipAlign: TextAlign.left,
|
|
||||||
hintText: '请选择完成时间',
|
|
||||||
select: _formData['date'],
|
|
||||||
selectFn: (BuildContext context) {
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
TDPicker.showDatePicker(
|
|
||||||
context,
|
|
||||||
title: '选择时间',
|
|
||||||
onConfirm: (selected) {
|
|
||||||
setState(() {
|
|
||||||
_formData['date'] = parseDatePickerSelected(selected);
|
|
||||||
});
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
dateStart: [2000, 01, 01],
|
|
||||||
dateEnd: [2100, 12, 31],
|
|
||||||
initialDate: [now.year, now.month, now.day],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TDFormItem buildImageItem() {
|
|
||||||
return TDFormItem(
|
|
||||||
label: '上传图片',
|
|
||||||
name: 'photo',
|
|
||||||
labelWidth: 82.0,
|
|
||||||
type: TDFormItemType.upLoadImg,
|
|
||||||
child: TDUpload(
|
|
||||||
files: files,
|
|
||||||
onError: print,
|
|
||||||
onValidate: print,
|
|
||||||
onChange: ((imgList, type) {
|
|
||||||
files = _onValueChanged(files ?? [], imgList, type);
|
|
||||||
List imgs = files.map((e) => e.remotePath ?? e.assetPath).toList();
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildFormBtnGroup() {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(color: TDTheme.of(context).whiteColor1),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Row(
|
child: _buildFormBuilder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFormBuilder() {
|
||||||
|
return FormBuilder(
|
||||||
|
key: _formKey,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
buildFormLabel('菜谱名称', required: true),
|
||||||
child: TDButton(
|
const SizedBox(height: 8),
|
||||||
text: '取消',
|
_buildNameTextField(),
|
||||||
type: TDButtonType.fill,
|
const SizedBox(height: 10),
|
||||||
theme: TDButtonTheme.light,
|
|
||||||
shape: TDButtonShape.rectangle,
|
buildFormLabel('完成时间', required: true),
|
||||||
onTap: () => Navigator.pop(context),
|
const SizedBox(height: 8),
|
||||||
|
_buildDateTextField(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
buildFormLabel('上传图片', required: true),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildImageUploadArea(),
|
||||||
|
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
buildFormButtonGroup(context: context, onConfirm: () => _submitForm),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 菜谱名称输入框
|
||||||
|
Widget _buildNameTextField() {
|
||||||
|
return FormBuilderTextField(
|
||||||
|
name: _nameField,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
decoration: buildInputDecoration(context: context, hintText: '请输入菜谱名称'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '请输入菜谱名称';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 完成时间选择框
|
||||||
|
Widget _buildDateTextField() {
|
||||||
|
return FormBuilderTextField(
|
||||||
|
name: _dateField,
|
||||||
|
readOnly: true,
|
||||||
|
decoration: buildInputDecoration(
|
||||||
|
context: context,
|
||||||
|
hintText: '请选择完成时间',
|
||||||
|
prefixIcon: const Icon(
|
||||||
|
Icons.calendar_month,
|
||||||
|
size: 20,
|
||||||
|
color: Color(0xFF86909C),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
onTap: () => _onSelectDate(),
|
||||||
Expanded(
|
validator: (value) {
|
||||||
child: TDButton(
|
if (value == null || value.isEmpty) {
|
||||||
text: '提交',
|
return '请选择完成时间';
|
||||||
type: TDButtonType.fill,
|
}
|
||||||
theme: TDButtonTheme.primary,
|
return null;
|
||||||
shape: TDButtonShape.rectangle,
|
},
|
||||||
onTap: () => _confirmClick(context),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 图片上传区域(预览+上传按钮)
|
||||||
|
Widget _buildImageUploadArea() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (imageUrl != null)
|
||||||
|
_buildImagePreviewItem()
|
||||||
|
else
|
||||||
|
_buildImageUploadButton(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImagePreviewItem() {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => _showImagePreview(),
|
||||||
|
child: Image(
|
||||||
|
image: FileImage(imageUrl!),
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Container(
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
color: Colors.grey[100],
|
||||||
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Positioned(
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: _removeImage,
|
||||||
|
child: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 2,
|
||||||
|
spreadRadius: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.close, color: Colors.white, size: 16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -208,36 +197,137 @@ class _RecordFormPage extends State<RecordFormPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _showImagePreview() {
|
||||||
Widget build(BuildContext context) {
|
showDialog(
|
||||||
return Scaffold(
|
context: context,
|
||||||
appBar: AppBar(
|
barrierColor: Colors.black87, // 半透明黑色背景
|
||||||
title: Text('新增记录', style: TextStyle(color: Colors.white)),
|
builder:
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
(context) => Dialog(
|
||||||
leading: IconButton(
|
backgroundColor: Colors.transparent,
|
||||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
elevation: 0,
|
||||||
onPressed: () => Navigator.pop(context)
|
insetPadding: const EdgeInsets.all(16),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.pop(context), // 点击空白处关闭
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
// 预览图添加轻微阴影
|
||||||
|
boxShadow: [BoxShadow(color: Colors.black38, blurRadius: 10)],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
backgroundColor: Color(0xFFF5F5F5),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: SingleChildScrollView(
|
child: Image.file(
|
||||||
child: TDForm(
|
imageUrl!,
|
||||||
formController: _formController,
|
fit: BoxFit.contain, // 保持图片比例
|
||||||
data: _formData,
|
height: MediaQuery.of(context).size.height * 0.7, // 限制最大高度
|
||||||
rules: _validationRules,
|
),
|
||||||
formContentAlign: TextAlign.left,
|
|
||||||
formShowErrorMessage: true,
|
|
||||||
onSubmit: () => {},
|
|
||||||
items: [buildNameItem(), buildDateItem(), buildImageItem()],
|
|
||||||
btnGroup: [buildFormBtnGroup()],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 图片上传按钮
|
||||||
|
Widget _buildImageUploadButton() {
|
||||||
|
return InkWell(
|
||||||
|
onTap: _pickImage,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
width: 96,
|
||||||
|
height: 96,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF2F3F5),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: const Color(0xFFDCDFE6), width: 1),
|
||||||
|
),
|
||||||
|
child: const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.add, color: Color(0xFF86909C), size: 24),
|
||||||
|
SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
'添加图片',
|
||||||
|
style: TextStyle(color: Color(0xFF86909C), fontSize: 13),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 选择日期
|
||||||
|
Future<void> _onSelectDate() async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: DateTime.now(),
|
||||||
|
firstDate: DateTime(2000),
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
builder:
|
||||||
|
(context, child) => Theme(
|
||||||
|
data: ThemeData.light().copyWith(
|
||||||
|
primaryColor: Theme.of(context).primaryColor,
|
||||||
|
colorScheme: ColorScheme.light(
|
||||||
|
primary: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
buttonTheme: const ButtonThemeData(
|
||||||
|
textTheme: ButtonTextTheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked != null) {
|
||||||
|
_formKey.currentState?.fields[_dateField]?.didChange(
|
||||||
|
DateFormat('yyyy-MM-dd').format(picked),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 选择图片(限制单张)
|
||||||
|
Future<void> _pickImage() async {
|
||||||
|
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (image != null) {
|
||||||
|
setState(() {
|
||||||
|
imageUrl = File(image.path); // 直接覆盖现有图片
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除图片
|
||||||
|
void _removeImage() {
|
||||||
|
setState(() {
|
||||||
|
imageUrl = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 提交表单
|
||||||
|
void _submitForm() {
|
||||||
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
|
if (imageUrl == null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('请上传图片'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final formData = {
|
||||||
|
..._formKey.currentState!.value,
|
||||||
|
'imageUrl': imageUrl?.path, // 单张图片路径
|
||||||
|
};
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('提交成功!'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print('表单数据: $formData');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/stats.dart';
|
import 'package:food_hub_app/apis/stats.dart';
|
||||||
import 'package:food_hub_app/models/stats.dart';
|
import 'package:food_hub_app/models/stats.dart';
|
||||||
import 'package:food_hub_app/widgets/common/chart.dart';
|
import 'package:food_hub_app/widgets/common/chart.dart';
|
||||||
import 'package:food_hub_app/widgets/stats/card.dart';
|
import 'package:food_hub_app/widgets/stats/card.dart';
|
||||||
|
|||||||
102
lib/widgets/common/form.dart
Normal file
102
lib/widgets/common/form.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// 通用输入框样式
|
||||||
|
InputDecoration buildInputDecoration({
|
||||||
|
required BuildContext context,
|
||||||
|
required String hintText,
|
||||||
|
Widget? prefixIcon,
|
||||||
|
}) {
|
||||||
|
return InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: Color(0xFF999999),
|
||||||
|
fontSize: 15,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.white,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 1.5),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: Colors.red, width: 1.5),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
|
||||||
|
errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red),
|
||||||
|
prefixIcon: prefixIcon,
|
||||||
|
prefixIconConstraints: const BoxConstraints(minWidth: 40),
|
||||||
|
isDense: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildFormLabel(String text, {bool required = false}) {
|
||||||
|
return RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF1D2129),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontFamily: 'CustomFont',
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
if (required)
|
||||||
|
const TextSpan(
|
||||||
|
text: ' *',
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 表单底部按钮组组件
|
||||||
|
Widget buildFormButtonGroup({
|
||||||
|
required BuildContext context,
|
||||||
|
required VoidCallback onConfirm,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _buildCancelButton(context)),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(child: _buildSubmitButton(context, onConfirm)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取消按钮
|
||||||
|
Widget _buildCancelButton(BuildContext context) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context), // 直接使用传入的context返回
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: const Color(0xFF4E5969),
|
||||||
|
side: const BorderSide(color: Color(0xFFDCDFE6)),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: const Text('取消'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 提交按钮
|
||||||
|
Widget _buildSubmitButton(BuildContext context, VoidCallback onConfirm) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: onConfirm,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor, // 使用传入的context获取主题
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: const Text('提交', style: TextStyle(color: Colors.white)),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/utils/date_util.dart';
|
import 'package:food_hub_app/utils/date_util.dart';
|
||||||
import 'package:food_hub_app/utils/index.dart';
|
import 'package:food_hub_app/utils/index.dart';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/card.dart';
|
import 'package:food_hub_app/widgets/recipe/card.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/api/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/config/app_config.dart';
|
import 'package:food_hub_app/config/app_config.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/common/image.dart';
|
import 'package:food_hub_app/widgets/common/image.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
|
||||||
import 'package:food_hub_app/widgets/common/year_selector.dart';
|
import 'package:food_hub_app/widgets/common/year_selector.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
import 'package:timelines_plus/timelines_plus.dart';
|
import 'package:timelines_plus/timelines_plus.dart';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace {
|
|||||||
///
|
///
|
||||||
/// Redefined in case the developer's machine has a Windows SDK older than
|
/// Redefined in case the developer's machine has a Windows SDK older than
|
||||||
/// version 10.0.22000.0.
|
/// version 10.0.22000.0.
|
||||||
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
/// See: https://docs.microsoft.com/windows/win32/apis/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user