feat:更新api模块

This commit is contained in:
2025-11-17 22:36:20 +08:00
parent 504ee073ce
commit 6bba9a488c
17 changed files with 478 additions and 221 deletions

View File

@@ -56,6 +56,74 @@ class _HomePage extends State<HomePage> {
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
Widget build(BuildContext context) {
return Scaffold(
@@ -77,6 +145,13 @@ class _HomePage extends State<HomePage> {
backgroundColor: Color(0xFFF5F5F5),
drawer: const SettingsDrawer(),
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(
navItems: bottomNavItems,
currentIndex: _currentIndex,

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.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/utils/sp_util.dart';
import 'package:food_hub_app/widgets/common/index.dart';

View File

@@ -1,6 +1,6 @@
import 'package:easy_refresh/easy_refresh.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/widgets/moment/card.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

View File

@@ -1,5 +1,5 @@
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/widgets/common/index.dart';

View File

@@ -1,205 +1,194 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.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/index.dart';
import 'package:food_hub_app/widgets/common/form.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 {
const RecordFormPage({super.key});
@override
State<RecordFormPage> createState() => _RecordFormPage();
State<RecordFormPage> createState() => _RecordFormPageState();
}
class _RecordFormPage extends State<RecordFormPage> {
class _RecordFormPageState extends State<RecordFormPage> {
final _formKey = GlobalKey<FormBuilderState>();
List<TextEditingController> _controller = [];
FormController _formController = FormController();
String _selected_1 = '';
String _selected_2 = '';
String? _initLocalData;
final _focusNode = FocusNode();
static const String _nameField = 'name';
static const String _dateField = 'date';
final ImagePicker _picker = ImagePicker();
XFile? _selectedImage;
/// 整个表单存放的数据
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();
}
File? imageUrl; // 改为单张图片变量
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
void _confirmClick(BuildContext context) {
if (_formKey.currentState?.saveAndValidate() ?? false) {
// showSuccessToast("新增记录成功");
print(_formData);
} else {
TDMessage.showMessage(
context: context,
visible: true,
icon: true,
content: "请先输入信息",
theme: MessageTheme.error,
duration: 3000,
);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
TDFormItem buildNameItem() {
return TDFormItem(
label: '菜谱名称',
name: 'name',
type: TDFormItemType.input,
labelWidth: 82.0,
showErrorMessage: true,
requiredMark: true,
child: TDInput(
leftContentSpace: 0,
inputDecoration: InputDecoration(
hintText: "请输入菜谱名称",
border: InputBorder.none,
hintStyle: TextStyle(color: TDTheme.of(context).grayColor6),
return Scaffold(
appBar: AppBar(
title: const Text('新增记录', style: TextStyle(color: Colors.white)),
backgroundColor: theme.primaryColor,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
backgroundColor: const Color(0xFFF5F5F5),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(5),
child: Card(
elevation: 0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: _buildFormBuilder(),
),
),
),
backgroundColor: Colors.white,
additionInfoColor: TDTheme.of(context).errorColor6,
showBottomDivider: false,
onChanged: (value) {
_formData['name'] = value;
},
),
);
}
TDFormItem buildDateItem() {
return TDFormItem(
label: '完成时间',
name: 'date',
labelWidth: 82.0,
type: TDFormItemType.dateTimePicker,
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],
);
Widget _buildFormBuilder() {
return FormBuilder(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFormLabel('菜谱名称', required: true),
const SizedBox(height: 8),
_buildNameTextField(),
const SizedBox(height: 10),
buildFormLabel('完成时间', required: true),
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;
},
);
}
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 _buildDateTextField() {
return FormBuilderTextField(
name: _dateField,
readOnly: true,
decoration: buildInputDecoration(
context: context,
hintText: '请选择完成时间',
prefixIcon: const Icon(
Icons.calendar_month,
size: 20,
color: Color(0xFF86909C),
),
),
onTap: () => _onSelectDate(),
validator: (value) {
if (value == null || value.isEmpty) {
return '请选择完成时间';
}
return null;
},
);
}
Widget buildFormBtnGroup() {
/// 图片上传区域(预览+上传按钮)
Widget _buildImageUploadArea() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageUrl != null)
_buildImagePreviewItem()
else
_buildImageUploadButton(),
],
);
}
Widget _buildImagePreviewItem() {
return Container(
decoration: BoxDecoration(color: TDTheme.of(context).whiteColor1),
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
Expanded(
child: TDButton(
text: '取消',
type: TDButtonType.fill,
theme: TDButtonTheme.light,
shape: TDButtonShape.rectangle,
onTap: () => Navigator.pop(context),
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),
),
);
},
),
),
SizedBox(width: 20),
Expanded(
child: TDButton(
text: '提交',
type: TDButtonType.fill,
theme: TDButtonTheme.primary,
shape: TDButtonShape.rectangle,
onTap: () => _confirmClick(context),
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
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('新增记录', style: TextStyle(color: Colors.white)),
backgroundColor: Theme.of(context).primaryColor,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context)
),
),
backgroundColor: Color(0xFFF5F5F5),
body: Padding(
padding: const EdgeInsets.all(10),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: SingleChildScrollView(
child: TDForm(
formController: _formController,
data: _formData,
rules: _validationRules,
formContentAlign: TextAlign.left,
formShowErrorMessage: true,
onSubmit: () => {},
items: [buildNameItem(), buildDateItem(), buildImageItem()],
btnGroup: [buildFormBtnGroup()],
void _showImagePreview() {
showDialog(
context: context,
barrierColor: Colors.black87, // 半透明黑色背景
builder:
(context) => Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
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)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.file(
imageUrl!,
fit: BoxFit.contain, // 保持图片比例
height: MediaQuery.of(context).size.height * 0.7, // 限制最大高度
),
),
),
),
),
);
}
/// 图片上传按钮
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');
}
}
}

View File

@@ -1,5 +1,5 @@
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/widgets/common/chart.dart';
import 'package:food_hub_app/widgets/stats/card.dart';