feat:增加菜谱后端接口
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/utils/http_util.dart';
|
import 'package:food_hub_app/utils/http_util.dart';
|
||||||
|
import 'package:food_hub_app/utils/index.dart';
|
||||||
|
|
||||||
Future<Recipe> queryRecipeByIdApi(int id) {
|
Future<Recipe> queryRecipeByIdApi(int id) {
|
||||||
return HttpUtil().get<Recipe>(
|
return HttpUtil().get<Recipe>(
|
||||||
@@ -8,9 +9,36 @@ Future<Recipe> queryRecipeByIdApi(int id) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> addRecipeApi(Recipe recipe) {
|
||||||
|
return HttpUtil().post<bool>("/food/food/recipe", data: recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updateRecipeApi(int id, Recipe recipe) {
|
||||||
|
return HttpUtil().put<bool>("/food/food/recipe/$id", data: recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteRecipeApi(int id) {
|
||||||
|
return HttpUtil().delete<bool>("/food/food/recipe/$id");
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Recipe>> queryRecipeByUserApi(int id) {
|
Future<List<Recipe>> queryRecipeByUserApi(int id) {
|
||||||
return HttpUtil().get<List<Recipe>>(
|
return HttpUtil().get<List<Recipe>>(
|
||||||
"/food/recipe/user/$id",
|
"/food/recipe/user/$id",
|
||||||
converter: (data) => data.map((item) => Recipe.fromJson(item)).toList()
|
converter: (data) => convertListResponse<Recipe>(data, Recipe.fromJson),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Recipe>> queryRecipeUserFavouriteApi() {
|
||||||
|
return HttpUtil().get<List<Recipe>>(
|
||||||
|
"/food/recipe/user/favourite",
|
||||||
|
converter: (data) => convertListResponse<Recipe>(data, Recipe.fromJson),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Recipe>> queryRecipeApi(RecipeQuery recipeQuery) {
|
||||||
|
return HttpUtil().get<List<Recipe>>(
|
||||||
|
"/food/recipe",
|
||||||
|
queryParameters: recipeQuery.toJson(),
|
||||||
|
converter: (data) => convertListResponse<Recipe>(data, Recipe.fromJson),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
|
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
|
||||||
SPUtil.init();
|
await SPUtil.init();
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ class Material {
|
|||||||
String name;
|
String name;
|
||||||
String amount;
|
String amount;
|
||||||
|
|
||||||
Material({
|
Material({required this.type, required this.name, required this.amount});
|
||||||
required this.type,
|
|
||||||
required this.name,
|
|
||||||
required this.amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Material.fromJson(Map<String, dynamic> json) =>
|
factory Material.fromJson(Map<String, dynamic> json) =>
|
||||||
_$MaterialFromJson(json);
|
_$MaterialFromJson(json);
|
||||||
@@ -28,14 +24,9 @@ class Step {
|
|||||||
String content;
|
String content;
|
||||||
String imageUrl;
|
String imageUrl;
|
||||||
|
|
||||||
Step({
|
Step({required this.sort, required this.content, required this.imageUrl});
|
||||||
required this.sort,
|
|
||||||
required this.content,
|
|
||||||
required this.imageUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Step.fromJson(Map<String, dynamic> json) =>
|
factory Step.fromJson(Map<String, dynamic> json) => _$StepFromJson(json);
|
||||||
_$StepFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$StepToJson(this);
|
Map<String, dynamic> toJson() => _$StepToJson(this);
|
||||||
}
|
}
|
||||||
@@ -66,28 +57,40 @@ class Comment {
|
|||||||
/// 成果信息
|
/// 成果信息
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Record {
|
class Record {
|
||||||
int id;
|
int? id;
|
||||||
String name;
|
String name;
|
||||||
String? category;
|
String category;
|
||||||
int person;
|
int person;
|
||||||
String date;
|
String date;
|
||||||
String imageUrl;
|
String imageUrl;
|
||||||
|
|
||||||
Record({
|
Record({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
this.category,
|
required this.category,
|
||||||
required this.person,
|
required this.person,
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Record.fromJson(Map<String, dynamic> json) =>
|
factory Record.fromJson(Map<String, dynamic> json) => _$RecordFromJson(json);
|
||||||
_$RecordFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$RecordToJson(this);
|
Map<String, dynamic> toJson() => _$RecordToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class RecipeQuery {
|
||||||
|
String category;
|
||||||
|
|
||||||
|
RecipeQuery({
|
||||||
|
required this.category
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RecipeQuery.fromJson(Map<String, dynamic> json) => _$RecipeQueryFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$RecipeQueryToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
/// 菜谱信息
|
/// 菜谱信息
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Recipe {
|
class Recipe {
|
||||||
@@ -100,8 +103,8 @@ class Recipe {
|
|||||||
int? userId;
|
int? userId;
|
||||||
String? username;
|
String? username;
|
||||||
String? avatar;
|
String? avatar;
|
||||||
List<Material> materialList;
|
List<Material>? materialList;
|
||||||
List<Step> stepList;
|
List<Step>? stepList;
|
||||||
List<Record> recordList;
|
List<Record> recordList;
|
||||||
List<int>? likeList;
|
List<int>? likeList;
|
||||||
int? likeCount;
|
int? likeCount;
|
||||||
@@ -120,8 +123,8 @@ class Recipe {
|
|||||||
this.userId,
|
this.userId,
|
||||||
this.username,
|
this.username,
|
||||||
this.avatar,
|
this.avatar,
|
||||||
required this.materialList,
|
this.materialList,
|
||||||
required this.stepList,
|
this.stepList,
|
||||||
required this.recordList,
|
required this.recordList,
|
||||||
this.likeList,
|
this.likeList,
|
||||||
this.likeCount,
|
this.likeCount,
|
||||||
@@ -131,8 +134,7 @@ class Recipe {
|
|||||||
this.commentCount,
|
this.commentCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Recipe.fromJson(Map<String, dynamic> json) =>
|
factory Recipe.fromJson(Map<String, dynamic> json) => _$RecipeFromJson(json);
|
||||||
_$RecipeFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$RecipeToJson(this);
|
Map<String, dynamic> toJson() => _$RecipeToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
Record _$RecordFromJson(Map<String, dynamic> json) => Record(
|
Record _$RecordFromJson(Map<String, dynamic> json) => Record(
|
||||||
id: (json['id'] as num).toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
category: json['category'] as String?,
|
category: json['category'] as String,
|
||||||
person: (json['person'] as num).toInt(),
|
person: (json['person'] as num).toInt(),
|
||||||
date: json['date'] as String,
|
date: json['date'] as String,
|
||||||
imageUrl: json['imageUrl'] as String,
|
imageUrl: json['imageUrl'] as String,
|
||||||
@@ -64,6 +64,12 @@ Map<String, dynamic> _$RecordToJson(Record instance) => <String, dynamic>{
|
|||||||
'imageUrl': instance.imageUrl,
|
'imageUrl': instance.imageUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RecipeQuery _$RecipeQueryFromJson(Map<String, dynamic> json) =>
|
||||||
|
RecipeQuery(category: json['category'] as String);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$RecipeQueryToJson(RecipeQuery instance) =>
|
||||||
|
<String, dynamic>{'category': instance.category};
|
||||||
|
|
||||||
Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
|
Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
@@ -75,12 +81,12 @@ Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
|
|||||||
username: json['username'] as String?,
|
username: json['username'] as String?,
|
||||||
avatar: json['avatar'] as String?,
|
avatar: json['avatar'] as String?,
|
||||||
materialList:
|
materialList:
|
||||||
(json['materialList'] as List<dynamic>)
|
(json['materialList'] as List<dynamic>?)
|
||||||
.map((e) => Material.fromJson(e as Map<String, dynamic>))
|
?.map((e) => Material.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
stepList:
|
stepList:
|
||||||
(json['stepList'] as List<dynamic>)
|
(json['stepList'] as List<dynamic>?)
|
||||||
.map((e) => Step.fromJson(e as Map<String, dynamic>))
|
?.map((e) => Step.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
recordList:
|
recordList:
|
||||||
(json['recordList'] as List<dynamic>)
|
(json['recordList'] as List<dynamic>)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:dio/dio.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';
|
||||||
|
|
||||||
|
import 'log_util.dart';
|
||||||
|
|
||||||
class HttpUtil {
|
class HttpUtil {
|
||||||
static final HttpUtil _instance = HttpUtil._internal();
|
static final HttpUtil _instance = HttpUtil._internal();
|
||||||
|
|
||||||
@@ -34,9 +36,9 @@ class HttpUtil {
|
|||||||
_dio.interceptors.add(
|
_dio.interceptors.add(
|
||||||
InterceptorsWrapper(
|
InterceptorsWrapper(
|
||||||
onRequest: (options, handler) {
|
onRequest: (options, handler) {
|
||||||
print("请求URL: ${options.uri}");
|
logger.d("请求URL: ${options.uri}");
|
||||||
if (options.data != null) {
|
if (options.data != null) {
|
||||||
print("请求参数: ${options.data}");
|
logger.d("请求参数: ${options.data}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SPUtil.getString('token').isNotEmpty) {
|
if (SPUtil.getString('token').isNotEmpty) {
|
||||||
@@ -51,8 +53,8 @@ class HttpUtil {
|
|||||||
_dio.interceptors.add(
|
_dio.interceptors.add(
|
||||||
InterceptorsWrapper(
|
InterceptorsWrapper(
|
||||||
onResponse: (response, handler) {
|
onResponse: (response, handler) {
|
||||||
print("响应状态码: ${response.statusCode}");
|
logger.d("响应状态码: ${response.statusCode}");
|
||||||
print("响应数据: ${response.data}");
|
logger.d("响应数据: ${response.data}");
|
||||||
return handler.next(response);
|
return handler.next(response);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,3 +15,11 @@ String formatDateTime(DateTime dateTime, [String format = 'yyyy-MM-dd']) {
|
|||||||
// return formatter.format(dateTime);
|
// return formatter.format(dateTime);
|
||||||
return Intl.withLocale('zh_CN', () => formatter.format(dateTime));
|
return Intl.withLocale('zh_CN', () => formatter.format(dateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 通用列表转换函数
|
||||||
|
List<T> convertListResponse<T>(dynamic data, T Function(Map<String, dynamic>) fromJson) {
|
||||||
|
if (data is List) {
|
||||||
|
return data.map((item) => fromJson(item as Map<String, dynamic>)).toList();
|
||||||
|
}
|
||||||
|
throw FormatException('Expected a list of items for conversion, but got ${data.runtimeType}');
|
||||||
|
}
|
||||||
12
lib/utils/log_util.dart
Normal file
12
lib/utils/log_util.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
// 全局日志实例,在整个项目中共享
|
||||||
|
final Logger logger = Logger(
|
||||||
|
printer: PrettyPrinter(
|
||||||
|
methodCount: 1,
|
||||||
|
colors: true,
|
||||||
|
dateTimeFormat: DateTimeFormat.dateAndTime,
|
||||||
|
),
|
||||||
|
// 可选:配置输出到文件(需配合文件操作库)
|
||||||
|
// output: FileOutput(file: File('logs/app.log')),
|
||||||
|
);
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
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/recipe.dart';
|
|
||||||
import 'package:food_hub_app/api/session.dart';
|
import 'package:food_hub_app/api/session.dart';
|
||||||
import 'package:food_hub_app/models/recipe.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';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
@@ -20,7 +17,8 @@ class LoginPage extends StatefulWidget {
|
|||||||
class _LoginPage extends State<LoginPage> {
|
class _LoginPage extends State<LoginPage> {
|
||||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
final GlobalKey _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
late String _username, _password;
|
String _username = "", _password = "";
|
||||||
|
bool _isRemember = false;
|
||||||
bool _isObscure = true;
|
bool _isObscure = true;
|
||||||
Color _eyeColor = Colors.grey;
|
Color _eyeColor = Colors.grey;
|
||||||
final List _loginMethod = [
|
final List _loginMethod = [
|
||||||
@@ -29,39 +27,51 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
{"title": "wechat", "icon": Icons.wechat},
|
{"title": "wechat", "icon": Icons.wechat},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadRememberState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRememberState() async {
|
||||||
|
bool? isRemember = await SPUtil.getBool('isRemember');
|
||||||
|
if (isRemember) {
|
||||||
|
setState(() {
|
||||||
|
_isRemember = true;
|
||||||
|
_username = SPUtil.getString('username');
|
||||||
|
_password = SPUtil.getString('password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRememberState() {
|
||||||
|
if (_isRemember) {
|
||||||
|
SPUtil.set('isRemember', true);
|
||||||
|
SPUtil.set('username', _username);
|
||||||
|
SPUtil.set('password', _password);
|
||||||
|
} else {
|
||||||
|
SPUtil.set('isRemember', false);
|
||||||
|
SPUtil.remove('username');
|
||||||
|
SPUtil.remove('password');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTokenState(String token) {
|
||||||
|
SPUtil.set('token', token);
|
||||||
|
}
|
||||||
|
|
||||||
void loginClick(BuildContext context) async {
|
void loginClick(BuildContext context) async {
|
||||||
SPUtil.clear();
|
|
||||||
Session session = await loginApi("Cxx0822", "19940822Cxx");
|
|
||||||
SPUtil.set('token', session.saToken.tokenValue);
|
|
||||||
|
|
||||||
Recipe recipe = await queryRecipeByIdApi(614660173221957);
|
|
||||||
List<Recipe> recipeList = await queryRecipeByUserApi(691110779121733);
|
|
||||||
// SPUtil.set("token", session?.saToken?.tokenValue);
|
|
||||||
|
|
||||||
// Navigator.pushNamed(context, '/home');
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 表单校验通过才会继续执行
|
// 表单校验通过才会继续执行
|
||||||
if ((_formKey.currentState as FormState).validate()) {
|
if ((_formKey.currentState as FormState).validate()) {
|
||||||
(_formKey.currentState as FormState).save();
|
(_formKey.currentState as FormState).save();
|
||||||
TDMessage.showMessage(
|
Session session = await loginApi(_username, _password);
|
||||||
context: context,
|
showSuccessToast('登录成功');
|
||||||
visible: true,
|
|
||||||
icon: true,
|
handleRememberState();
|
||||||
content: "登录成功",
|
handleTokenState(session.saToken.tokenValue);
|
||||||
theme: MessageTheme.success,
|
|
||||||
duration: 3000,
|
|
||||||
);
|
|
||||||
Navigator.pushNamed(context, '/home');
|
Navigator.pushNamed(context, '/home');
|
||||||
} else {
|
} else {
|
||||||
TDMessage.showMessage(
|
showErrorToast('请先输入信息');
|
||||||
context: context,
|
|
||||||
visible: true,
|
|
||||||
icon: true,
|
|
||||||
content: "请先输入信息",
|
|
||||||
theme: MessageTheme.error,
|
|
||||||
duration: 3000,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +145,8 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: 'username',
|
name: 'username',
|
||||||
|
initialValue: _username,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
decoration: formInputDecoration(
|
decoration: formInputDecoration(
|
||||||
hintText: "请输入账号",
|
hintText: "请输入账号",
|
||||||
prefixIcon: Icons.person,
|
prefixIcon: Icons.person,
|
||||||
@@ -154,8 +166,9 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: 'password',
|
name: 'password',
|
||||||
|
initialValue: _password,
|
||||||
|
keyboardType: TextInputType.visiblePassword,
|
||||||
obscureText: _isObscure,
|
obscureText: _isObscure,
|
||||||
// 是否显示文字
|
|
||||||
onSaved: (v) => _password = v!,
|
onSaved: (v) => _password = v!,
|
||||||
validator: FormBuilderValidators.required(),
|
validator: FormBuilderValidators.required(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -168,7 +181,6 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(Icons.remove_red_eye, color: _eyeColor),
|
icon: Icon(Icons.remove_red_eye, color: _eyeColor),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState()
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isObscure = !_isObscure;
|
_isObscure = !_isObscure;
|
||||||
_eyeColor =
|
_eyeColor =
|
||||||
@@ -188,9 +200,11 @@ class _LoginPage extends State<LoginPage> {
|
|||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: false,
|
value: _isRemember,
|
||||||
onChanged: (bool? value) {
|
onChanged: (bool? value) {
|
||||||
print("记住密码: $value");
|
setState(() {
|
||||||
|
_isRemember = value ?? false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Text('记住密码', style: TextStyle(color: Colors.grey, fontSize: 14)),
|
const Text('记住密码', style: TextStyle(color: Colors.grey, fontSize: 14)),
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:food_hub_app/api/recipe.dart';
|
||||||
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/recipe_calendar.dart';
|
import 'package:food_hub_app/widgets/recipe/recipe_calendar.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/recipe_list.dart';
|
import 'package:food_hub_app/widgets/recipe/recipe_list.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/recipe_timeline.dart';
|
import 'package:food_hub_app/widgets/recipe/recipe_timeline.dart';
|
||||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
import '../models/recipe.dart';
|
|
||||||
|
|
||||||
class RecordPage extends StatefulWidget {
|
class RecordPage extends StatefulWidget {
|
||||||
const RecordPage({super.key});
|
const RecordPage({super.key});
|
||||||
@@ -15,33 +16,62 @@ class RecordPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _RecordPageState extends State<RecordPage>
|
class _RecordPageState extends State<RecordPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
final List<Recipe> recipeList = [];
|
List<Recipe> _recipeList = [];
|
||||||
|
|
||||||
final List<Record> recordList = [];
|
final List<Record> _recordList = [];
|
||||||
|
|
||||||
late final TabController _tabController = TabController(
|
late final TabController _tabController = TabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义标签列表
|
|
||||||
final List<TDTab> tabs = [
|
final List<TDTab> tabs = [
|
||||||
const TDTab(text: '菜谱', icon: Icon(Icons.book)),
|
const TDTab(text: '菜谱', icon: Icon(Icons.book)),
|
||||||
const TDTab(text: '日历', icon: Icon(Icons.calendar_month)),
|
const TDTab(text: '日历', icon: Icon(Icons.calendar_month)),
|
||||||
const TDTab(text: '时间轴', icon: Icon(Icons.timeline)),
|
const TDTab(text: '时间轴', icon: Icon(Icons.timeline)),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getRecipeList();
|
||||||
|
_tabController.addListener(_handleTabChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.removeListener(_handleTabChange);
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTabChange() {
|
||||||
|
if (_tabController.indexIsChanging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据当前选中的索引执行对应接口请求
|
||||||
|
switch (_tabController.index) {
|
||||||
|
case 0:
|
||||||
|
_getRecipeList();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
print("2");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
print("3");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getRecipeList() async {
|
||||||
|
final result = await queryRecipeApi(RecipeQuery(category: ""));
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_recipeList = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -60,9 +90,9 @@ class _RecordPageState extends State<RecordPage>
|
|||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
RecipeList(recipeList: recipeList),
|
RecipeList(recipeList: _recipeList),
|
||||||
RecipeCalendar(),
|
RecipeCalendar(),
|
||||||
RecipeTimeline(recordList: recordList),
|
RecipeTimeline(recordList: _recordList),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -50,18 +50,18 @@ Text buttonText({required String text}) {
|
|||||||
return Text(text, style: TextStyle(color: Colors.white));
|
return Text(text, style: TextStyle(color: Colors.white));
|
||||||
}
|
}
|
||||||
|
|
||||||
// void showSuccessToast(String message) {
|
void showSuccessToast(String message) {
|
||||||
// Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
// msg: message,
|
msg: message,
|
||||||
// toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
// gravity: ToastGravity.CENTER,
|
gravity: ToastGravity.CENTER,
|
||||||
// timeInSecForIosWeb: 1,
|
timeInSecForIosWeb: 1,
|
||||||
// backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
// textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
// fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
void showErrorToast(String message) {
|
void showErrorToast(String message) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: message,
|
msg: message,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
@@ -18,7 +17,7 @@ class RecipeCard extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Image.network(
|
Image.network(
|
||||||
recipe.recordList[0].imageUrl,
|
'http://172.29.101.108:8100/${recipe.recordList[0].imageUrl}',
|
||||||
height: 200,
|
height: 200,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
@@ -38,104 +37,108 @@ class RecipeCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Column(
|
child: _buildRecipeContent(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
recipe.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
maxLines: 1, // 限制单行,避免挤压按钮
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit, size: 14),
|
|
||||||
color: Colors.white,
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.all(
|
|
||||||
Colors.green,
|
|
||||||
),
|
|
||||||
shape: WidgetStateProperty.all(CircleBorder()),
|
|
||||||
minimumSize: WidgetStateProperty.all(Size(20, 20)),
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.book, size: 14),
|
|
||||||
color: Colors.white,
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.all(
|
|
||||||
Colors.green,
|
|
||||||
),
|
|
||||||
shape: WidgetStateProperty.all(CircleBorder()),
|
|
||||||
minimumSize: WidgetStateProperty.all(Size(20, 20)),
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.food_bank,
|
|
||||||
text: recipe.category,
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.thumb_up_alt_outlined,
|
|
||||||
text: recipe.likeCount.toString(),
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.star_outline,
|
|
||||||
text: recipe.favouriteCount.toString(),
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.comment_outlined,
|
|
||||||
text: recipe.commentCount.toString(),
|
|
||||||
color: Colors.deepOrange,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildIconText(
|
|
||||||
icon: Icons.date_range,
|
|
||||||
text: recipe.recordList[0].date,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRecipeContent() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
recipe.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1, // 限制单行,避免挤压按钮
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit, size: 14),
|
||||||
|
color: Colors.white,
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStateProperty.all(
|
||||||
|
Colors.green,
|
||||||
|
),
|
||||||
|
shape: WidgetStateProperty.all(CircleBorder()),
|
||||||
|
minimumSize: WidgetStateProperty.all(Size(20, 20)),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.book, size: 14),
|
||||||
|
color: Colors.white,
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStateProperty.all(
|
||||||
|
Colors.green,
|
||||||
|
),
|
||||||
|
shape: WidgetStateProperty.all(CircleBorder()),
|
||||||
|
minimumSize: WidgetStateProperty.all(Size(20, 20)),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildIconText(
|
||||||
|
icon: Icons.food_bank,
|
||||||
|
text: recipe.category,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_buildIconText(
|
||||||
|
icon: Icons.thumb_up_alt_outlined,
|
||||||
|
text: recipe.likeCount.toString(),
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_buildIconText(
|
||||||
|
icon: Icons.star_outline,
|
||||||
|
text: recipe.favouriteCount.toString(),
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_buildIconText(
|
||||||
|
icon: Icons.comment_outlined,
|
||||||
|
text: recipe.commentCount.toString(),
|
||||||
|
color: Colors.deepOrange,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildIconText(
|
||||||
|
icon: Icons.date_range,
|
||||||
|
text: recipe.recordList[0].date,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 通用图标文本组件
|
// 通用图标文本组件
|
||||||
Widget _buildIconText({
|
Widget _buildIconText({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
import 'package:food_hub_app/widgets/recipe/recipe_card.dart';
|
import 'package:food_hub_app/widgets/recipe/recipe_card.dart';
|
||||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||||
|
|
||||||
class RecipeList extends StatelessWidget {
|
class RecipeList extends StatelessWidget {
|
||||||
final List<Recipe> recipeList;
|
final List<Recipe> recipeList;
|
||||||
@@ -9,14 +10,21 @@ class RecipeList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListView.separated(
|
if (recipeList.isEmpty) {
|
||||||
itemCount: recipeList.length,
|
return const TDEmpty(
|
||||||
itemBuilder: (context, index) {
|
type: TDEmptyType.plain,
|
||||||
return RecipeCard(recipe: recipeList[index]);
|
emptyText: '暂无数据',
|
||||||
},
|
);
|
||||||
separatorBuilder: (context, index) {
|
} else {
|
||||||
return const SizedBox(height: 10);
|
return ListView.separated(
|
||||||
},
|
itemCount: recipeList.length,
|
||||||
);
|
itemBuilder: (context, index) {
|
||||||
|
return RecipeCard(recipe: recipeList[index]);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) {
|
||||||
|
return const SizedBox(height: 10);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,6 +541,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: logger
|
||||||
|
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ dependencies:
|
|||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
fluttertoast: ^8.2.0
|
fluttertoast: ^8.2.0
|
||||||
shared_preferences: ^2.3.0
|
shared_preferences: ^2.3.0
|
||||||
|
logger: ^2.6.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
tdesign_flutter_adaptation: 3.16.0
|
tdesign_flutter_adaptation: 3.16.0
|
||||||
|
|||||||
Reference in New Issue
Block a user