feat:增加dio网络请求

This commit is contained in:
2025-07-15 19:54:32 +08:00
parent 3f12b03648
commit c8f93f15a1
16 changed files with 570 additions and 126 deletions

16
lib/api/recipe.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'package:food_hub_app/models/recipe.dart';
import 'package:food_hub_app/utils/http_util.dart';
Future<Recipe> queryRecipeByIdApi(int id) {
return HttpUtil().get<Recipe>(
"/food/recipe/$id",
converter: (data) => Recipe.fromJson(data),
);
}
Future<List<Recipe>> queryRecipeByUserApi(int id) {
return HttpUtil().get<List<Recipe>>(
"/food/recipe/user/$id",
converter: (data) => data.map((item) => Recipe.fromJson(item)).toList()
);
}

View File

@@ -1,7 +1,7 @@
import 'package:food_hub_app/models/session.dart'; import 'package:food_hub_app/models/session.dart';
import 'package:food_hub_app/utils/HttpUtil.dart'; import 'package:food_hub_app/utils/http_util.dart';
Future<Session?> loginApi(String username, String password) { Future<Session> loginApi(String username, String password) {
return HttpUtil().post<Session>( return HttpUtil().post<Session>(
"/session", "/session",
queryParameters: {"username": username, "password": password}, queryParameters: {"username": username, "password": password},

View File

@@ -1,12 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:food_hub_app/utils/sp_util.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/recordForm.dart'; import 'package:food_hub_app/views/recordForm.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';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN')); FormBuilderLocalizations.delegate.load(const Locale('zh', 'CN'));
SPUtil.init();
runApp(const MyApp()); runApp(const MyApp());
} }

View File

@@ -1,30 +1,140 @@
class Recipe { import 'package:json_annotation/json_annotation.dart';
final String name;
final String category;
final String date;
final String imageUrl;
final int likeCount;
final int favoriteCount;
final int commentCount;
Recipe( part 'recipe.g.dart';
this.name,
this.category, /// 食材信息
this.date, @JsonSerializable()
this.imageUrl, class Material {
this.likeCount, String type;
this.favoriteCount, String name;
this.commentCount, String amount;
);
Material({
required this.type,
required this.name,
required this.amount,
});
factory Material.fromJson(Map<String, dynamic> json) =>
_$MaterialFromJson(json);
Map<String, dynamic> toJson() => _$MaterialToJson(this);
} }
class Record { /// 步骤信息
final String name; @JsonSerializable()
final String category; class Step {
final String date; int sort;
final String imageUrl; String content;
String imageUrl;
Record(this.name, this.category, this.date, this.imageUrl); Step({
required this.sort,
required this.content,
required this.imageUrl,
});
factory Step.fromJson(Map<String, dynamic> json) =>
_$StepFromJson(json);
Map<String, dynamic> toJson() => _$StepToJson(this);
}
/// 评论信息
@JsonSerializable()
class Comment {
int id;
String username;
String avatar;
String content;
String date;
Comment({
required this.id,
required this.username,
required this.avatar,
required this.content,
required this.date,
});
factory Comment.fromJson(Map<String, dynamic> json) =>
_$CommentFromJson(json);
Map<String, dynamic> toJson() => _$CommentToJson(this);
}
/// 成果信息
@JsonSerializable()
class Record {
int id;
String name;
String? category;
int person;
String date;
String imageUrl;
Record({
required this.id,
required this.name,
this.category,
required this.person,
required this.date,
required this.imageUrl,
});
factory Record.fromJson(Map<String, dynamic> json) =>
_$RecordFromJson(json);
Map<String, dynamic> toJson() => _$RecordToJson(this);
}
/// 菜谱信息
@JsonSerializable()
class Recipe {
int? id;
String name;
String category;
double recommendRate;
String? remark;
bool isShare;
int? userId;
String? username;
String? avatar;
List<Material> materialList;
List<Step> stepList;
List<Record> recordList;
List<int>? likeList;
int? likeCount;
List<int>? favouriteList;
int? favouriteCount;
List<Comment>? commentList;
int? commentCount;
Recipe({
this.id,
required this.name,
required this.category,
required this.recommendRate,
this.remark,
required this.isShare,
this.userId,
this.username,
this.avatar,
required this.materialList,
required this.stepList,
required this.recordList,
this.likeList,
this.likeCount,
this.favouriteList,
this.favouriteCount,
this.commentList,
this.commentCount,
});
factory Recipe.fromJson(Map<String, dynamic> json) =>
_$RecipeFromJson(json);
Map<String, dynamic> toJson() => _$RecipeToJson(this);
} }
enum ViewType { recipe, calendar, timeline } enum ViewType { recipe, calendar, timeline }

125
lib/models/recipe.g.dart Normal file
View File

@@ -0,0 +1,125 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'recipe.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Material _$MaterialFromJson(Map<String, dynamic> json) => Material(
type: json['type'] as String,
name: json['name'] as String,
amount: json['amount'] as String,
);
Map<String, dynamic> _$MaterialToJson(Material instance) => <String, dynamic>{
'type': instance.type,
'name': instance.name,
'amount': instance.amount,
};
Step _$StepFromJson(Map<String, dynamic> json) => Step(
sort: (json['sort'] as num).toInt(),
content: json['content'] as String,
imageUrl: json['imageUrl'] as String,
);
Map<String, dynamic> _$StepToJson(Step instance) => <String, dynamic>{
'sort': instance.sort,
'content': instance.content,
'imageUrl': instance.imageUrl,
};
Comment _$CommentFromJson(Map<String, dynamic> json) => Comment(
id: (json['id'] as num).toInt(),
username: json['username'] as String,
avatar: json['avatar'] as String,
content: json['content'] as String,
date: json['date'] as String,
);
Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username,
'avatar': instance.avatar,
'content': instance.content,
'date': instance.date,
};
Record _$RecordFromJson(Map<String, dynamic> json) => Record(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
category: json['category'] as String?,
person: (json['person'] as num).toInt(),
date: json['date'] as String,
imageUrl: json['imageUrl'] as String,
);
Map<String, dynamic> _$RecordToJson(Record instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'category': instance.category,
'person': instance.person,
'date': instance.date,
'imageUrl': instance.imageUrl,
};
Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String,
category: json['category'] as String,
recommendRate: (json['recommendRate'] as num).toDouble(),
remark: json['remark'] as String?,
isShare: json['isShare'] as bool,
userId: (json['userId'] as num?)?.toInt(),
username: json['username'] as String?,
avatar: json['avatar'] as String?,
materialList:
(json['materialList'] as List<dynamic>)
.map((e) => Material.fromJson(e as Map<String, dynamic>))
.toList(),
stepList:
(json['stepList'] as List<dynamic>)
.map((e) => Step.fromJson(e as Map<String, dynamic>))
.toList(),
recordList:
(json['recordList'] as List<dynamic>)
.map((e) => Record.fromJson(e as Map<String, dynamic>))
.toList(),
likeList:
(json['likeList'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
likeCount: (json['likeCount'] as num?)?.toInt(),
favouriteList:
(json['favouriteList'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
favouriteCount: (json['favouriteCount'] as num?)?.toInt(),
commentList:
(json['commentList'] as List<dynamic>?)
?.map((e) => Comment.fromJson(e as Map<String, dynamic>))
.toList(),
commentCount: (json['commentCount'] as num?)?.toInt(),
);
Map<String, dynamic> _$RecipeToJson(Recipe instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'category': instance.category,
'recommendRate': instance.recommendRate,
'remark': instance.remark,
'isShare': instance.isShare,
'userId': instance.userId,
'username': instance.username,
'avatar': instance.avatar,
'materialList': instance.materialList,
'stepList': instance.stepList,
'recordList': instance.recordList,
'likeList': instance.likeList,
'likeCount': instance.likeCount,
'favouriteList': instance.favouriteList,
'favouriteCount': instance.favouriteCount,
'commentList': instance.commentList,
'commentCount': instance.commentCount,
};

View File

@@ -4,29 +4,29 @@ part 'session.g.dart';
@JsonSerializable() @JsonSerializable()
class SaTokenInfo { class SaTokenInfo {
String? tokenName; String tokenName;
String? tokenValue; String tokenValue;
bool? isLogin; bool isLogin;
dynamic loginId; dynamic loginId;
String? loginType; String loginType;
int? tokenTimeout; int tokenTimeout;
int? sessionTimeout; int sessionTimeout;
int? tokenSessionTimeout; int tokenSessionTimeout;
int? tokenActiveTimeout; int tokenActiveTimeout;
String? loginDeviceType; String loginDeviceType;
String? tag; String? tag;
SaTokenInfo({ SaTokenInfo({
this.tokenName, required this.tokenName,
this.tokenValue, required this.tokenValue,
this.isLogin, required this.isLogin,
this.loginId, required this.loginId,
this.loginType, required this.loginType,
this.tokenTimeout, required this.tokenTimeout,
this.sessionTimeout, required this.sessionTimeout,
this.tokenSessionTimeout, required this.tokenSessionTimeout,
this.tokenActiveTimeout, required this.tokenActiveTimeout,
this.loginDeviceType, required this.loginDeviceType,
this.tag, this.tag,
}); });
@@ -36,6 +36,7 @@ class SaTokenInfo {
@JsonSerializable() @JsonSerializable()
class User { class User {
int? id;
String? username; String? username;
int? gender; int? gender;
String? phoneNumber; String? phoneNumber;
@@ -47,7 +48,6 @@ class User {
String? job; String? job;
List<String>? tags; List<String>? tags;
String? description; String? description;
String? id;
User({ User({
this.id, this.id,
@@ -70,10 +70,10 @@ class User {
@JsonSerializable() @JsonSerializable()
class Session { class Session {
SaTokenInfo? saToken; SaTokenInfo saToken;
User? userInfo; User userInfo;
Session({this.saToken, this.userInfo}); Session({required this.saToken, required this.userInfo});
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json); factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
Map<String, dynamic> toJson() => _$SessionToJson(this); Map<String, dynamic> toJson() => _$SessionToJson(this);

View File

@@ -7,16 +7,16 @@ part of 'session.dart';
// ************************************************************************** // **************************************************************************
SaTokenInfo _$SaTokenInfoFromJson(Map<String, dynamic> json) => SaTokenInfo( SaTokenInfo _$SaTokenInfoFromJson(Map<String, dynamic> json) => SaTokenInfo(
tokenName: json['tokenName'] as String?, tokenName: json['tokenName'] as String,
tokenValue: json['tokenValue'] as String?, tokenValue: json['tokenValue'] as String,
isLogin: json['isLogin'] as bool?, isLogin: json['isLogin'] as bool,
loginId: json['loginId'], loginId: json['loginId'],
loginType: json['loginType'] as String?, loginType: json['loginType'] as String,
tokenTimeout: (json['tokenTimeout'] as num?)?.toInt(), tokenTimeout: (json['tokenTimeout'] as num).toInt(),
sessionTimeout: (json['sessionTimeout'] as num?)?.toInt(), sessionTimeout: (json['sessionTimeout'] as num).toInt(),
tokenSessionTimeout: (json['tokenSessionTimeout'] as num?)?.toInt(), tokenSessionTimeout: (json['tokenSessionTimeout'] as num).toInt(),
tokenActiveTimeout: (json['tokenActiveTimeout'] as num?)?.toInt(), tokenActiveTimeout: (json['tokenActiveTimeout'] as num).toInt(),
loginDeviceType: json['loginDeviceType'] as String?, loginDeviceType: json['loginDeviceType'] as String,
tag: json['tag'] as String?, tag: json['tag'] as String?,
); );
@@ -36,7 +36,7 @@ Map<String, dynamic> _$SaTokenInfoToJson(SaTokenInfo instance) =>
}; };
User _$UserFromJson(Map<String, dynamic> json) => User( User _$UserFromJson(Map<String, dynamic> json) => User(
id: json['id'] as String?, id: (json['id'] as num?)?.toInt(),
username: json['username'] as String?, username: json['username'] as String?,
gender: (json['gender'] as num?)?.toInt(), gender: (json['gender'] as num?)?.toInt(),
phoneNumber: json['phoneNumber'] as String?, phoneNumber: json['phoneNumber'] as String?,
@@ -54,6 +54,7 @@ User _$UserFromJson(Map<String, dynamic> json) => User(
); );
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username, 'username': instance.username,
'gender': instance.gender, 'gender': instance.gender,
'phoneNumber': instance.phoneNumber, 'phoneNumber': instance.phoneNumber,
@@ -65,18 +66,11 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'job': instance.job, 'job': instance.job,
'tags': instance.tags, 'tags': instance.tags,
'description': instance.description, 'description': instance.description,
'id': instance.id,
}; };
Session _$SessionFromJson(Map<String, dynamic> json) => Session( Session _$SessionFromJson(Map<String, dynamic> json) => Session(
saToken: saToken: SaTokenInfo.fromJson(json['saToken'] as Map<String, dynamic>),
json['saToken'] == null userInfo: User.fromJson(json['userInfo'] as Map<String, dynamic>),
? null
: SaTokenInfo.fromJson(json['saToken'] as Map<String, dynamic>),
userInfo:
json['userInfo'] == null
? null
: User.fromJson(json['userInfo'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$SessionToJson(Session instance) => <String, dynamic>{ Map<String, dynamic> _$SessionToJson(Session instance) => <String, dynamic>{

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.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:tdesign_flutter/tdesign_flutter.dart';
class HttpUtil { class HttpUtil {
static final HttpUtil _instance = HttpUtil._internal(); static final HttpUtil _instance = HttpUtil._internal();
@@ -38,8 +38,10 @@ class HttpUtil {
if (options.data != null) { if (options.data != null) {
print("请求参数: ${options.data}"); print("请求参数: ${options.data}");
} }
// token等操作
// options.headers['Authorization'] = 'Bearer your_token'; if (SPUtil.getString('token').isNotEmpty) {
options.headers['satoken'] = SPUtil.getString('token');
}
return handler.next(options); return handler.next(options);
}, },
), ),
@@ -60,18 +62,7 @@ class HttpUtil {
_dio.interceptors.add( _dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onError: (DioException e, handler) { onError: (DioException e, handler) {
if (e.response != null) { _handleError(e);
final responseData = e.response?.data;
if (responseData is Map<String, dynamic>) {
// JSON错误格式
print(responseData['message']?.toString());
showErrorToast('错误');
} else {
// JSON格式错误
print('网络错误: ${e.message}');
}
}
return handler.next(e); return handler.next(e);
}, },
), ),
@@ -79,7 +70,7 @@ class HttpUtil {
} }
// //
Future<T?> _request<T>( Future<T> _request<T>(
String path, { String path, {
required String method, required String method,
dynamic data, dynamic data,
@@ -99,21 +90,15 @@ class HttpUtil {
return converter(response.data); return converter(response.data);
} }
// T是dynamic
if (T == dynamic) {
return response.data as T;
}
// //
return response.data as T?; return response.data;
} catch (e) { } catch (e) {
_handleError(e);
rethrow; rethrow;
} }
} }
// GET请求 // GET请求
Future<T?> get<T>( Future<T> get<T>(
String path, { String path, {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
T Function(dynamic data)? converter, T Function(dynamic data)? converter,
@@ -125,7 +110,7 @@ class HttpUtil {
); );
// POST请求 // POST请求
Future<T?> post<T>( Future<T> post<T>(
String path, { String path, {
dynamic data, dynamic data,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@@ -139,7 +124,7 @@ class HttpUtil {
); );
// PUT请求 // PUT请求
Future<T?> put<T>( Future<T> put<T>(
String path, { String path, {
dynamic data, dynamic data,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@@ -153,7 +138,7 @@ class HttpUtil {
); );
// DELETE请求 // DELETE请求
Future<T?> delete<T>( Future<T> delete<T>(
String path, { String path, {
dynamic data, dynamic data,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@@ -191,32 +176,68 @@ class HttpUtil {
// //
void _handleError(dynamic error) { void _handleError(dynamic error) {
String errorMessage = '未知错误';
if (error is DioException) { if (error is DioException) {
switch (error.type) { switch (error.type) {
case DioExceptionType.connectionTimeout: case DioExceptionType.connectionTimeout:
print("连接超时"); errorMessage = '连接超时,请检查网络连接';
break; break;
case DioExceptionType.sendTimeout: case DioExceptionType.sendTimeout:
print("发送超时"); errorMessage = '发送超时,请检查网络连接';
break; break;
case DioExceptionType.receiveTimeout: case DioExceptionType.receiveTimeout:
print("接收超时"); errorMessage = '接收超时,请检查网络连接';
break; break;
case DioExceptionType.cancel: case DioExceptionType.cancel:
print("请求取消"); errorMessage = '请求取消';
break; break;
case DioExceptionType.badCertificate: case DioExceptionType.badCertificate:
print("证书错误"); errorMessage = '证书验证失败';
break;
case DioExceptionType.badResponse: case DioExceptionType.badResponse:
print("错误响应"); // HTTP响应错误4xx, 5xx
final statusCode = error.response?.statusCode ?? 0;
final responseData = error.response?.data;
if (responseData is Map<String, dynamic> && responseData.containsKey('message')) {
//
errorMessage = responseData['message']?.toString() ?? '未知错误';
} else {
errorMessage = _getHttpErrorMessage(statusCode);
}
break;
case DioExceptionType.connectionError: case DioExceptionType.connectionError:
print("连接错误"); errorMessage = '网络连接错误,请检查网络设置';
break;
case DioExceptionType.unknown: case DioExceptionType.unknown:
print("未知错误"); if (error.error != null) {
errorMessage = '未知错误: ${error.error.toString()}';
}
errorMessage = '发生未知错误';
break; break;
} }
} else { } else {
print("未知错误: $error"); errorMessage = '非Dio错误: $error';
}
print(errorMessage);
showErrorToast(errorMessage);
}
String _getHttpErrorMessage(int statusCode) {
// HTTP状态码返回对应的错误消息
switch (statusCode) {
case 400: return '错误请求,请检查参数';
case 401: return '未授权,请登录';
case 403: return '禁止访问,权限不足';
case 404: return '资源不存在';
case 405: return '方法不允许';
case 408: return '请求超时';
case 500: return '服务器内部错误';
case 502: return '网关错误';
case 503: return '服务不可用';
case 504: return '网关超时';
default: return 'HTTP错误: $statusCode';
} }
} }
} }

61
lib/utils/sp_util.dart Normal file
View File

@@ -0,0 +1,61 @@
import 'package:shared_preferences/shared_preferences.dart';
class SPUtil {
static late SharedPreferences _prefs;
/// 初始化
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
/// 存储数据
static Future<bool> set(String key, dynamic value) {
if (value is String) return _prefs.setString(key, value);
if (value is int) return _prefs.setInt(key, value);
if (value is bool) return _prefs.setBool(key, value);
if (value is double) return _prefs.setDouble(key, value);
if (value is List<String>) return _prefs.setStringList(key, value);
throw ArgumentError('Unsupported value type: ${value.runtimeType}');
}
/// 获取数据
static dynamic get(String key, [dynamic defaultValue]) {
if (!_prefs.containsKey(key)) return defaultValue;
return _prefs.get(key) ?? defaultValue;
}
/// 获取字符串
static String getString(String key, [String defaultValue = '']) {
return _prefs.getString(key) ?? defaultValue;
}
/// 获取布尔值
static bool getBool(String key, [bool defaultValue = false]) {
return _prefs.getBool(key) ?? defaultValue;
}
/// 获取整数
static int getInt(String key, [int defaultValue = 0]) {
return _prefs.getInt(key) ?? defaultValue;
}
/// 获取浮点数
static double getDouble(String key, [double defaultValue = 0.0]) {
return _prefs.getDouble(key) ?? defaultValue;
}
/// 获取字符串列表
static List<String> getStringList(String key, [List<String> defaultValue = const []]) {
return _prefs.getStringList(key) ?? defaultValue;
}
/// 删除数据
static Future<bool> remove(String key) {
return _prefs.remove(key);
}
/// 清空所有数据
static Future<bool> clear() {
return _prefs.clear();
}
}

View File

@@ -1,8 +1,13 @@
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/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 {
@@ -25,7 +30,13 @@ class _LoginPage extends State<LoginPage> {
]; ];
void loginClick(BuildContext context) async { void loginClick(BuildContext context) async {
await loginApi("Cxx", "1232"); 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'); // Navigator.pushNamed(context, '/home');
return; return;

View File

@@ -15,15 +15,9 @@ class RecordPage extends StatefulWidget {
class _RecordPageState extends State<RecordPage> class _RecordPageState extends State<RecordPage>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
final List<Recipe> recipeList = [ final List<Recipe> recipeList = [];
Recipe("韭菜炒鸡蛋", "家常菜", "2025-05-04", "https://picsum.photos/200", 10, 2, 4),
Recipe("红烧肉", "家常菜", "2025-05-05", "https://picsum.photos/200", 12, 4, 7),
];
final List<Record> recordList = [ final List<Record> recordList = [];
Record("韭菜炒鸡蛋", "家常菜", "2025-05-04", "https://picsum.photos/200"),
Record("红烧肉", "家常菜", "2025-05-05", "https://picsum.photos/200"),
];
late final TabController _tabController = TabController( late final TabController _tabController = TabController(
length: 3, length: 3,
@@ -68,7 +62,7 @@ class _RecordPageState extends State<RecordPage>
children: [ children: [
RecipeList(recipeList: recipeList), RecipeList(recipeList: recipeList),
RecipeCalendar(), RecipeCalendar(),
RecipeTimeline(recipeList: recipeList), RecipeTimeline(recordList: recordList),
], ],
), ),
), ),

View File

@@ -18,7 +18,7 @@ class RecipeCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.network( Image.network(
recipe.imageUrl, recipe.recordList[0].imageUrl,
height: 200, height: 200,
width: double.infinity, width: double.infinity,
fit: BoxFit.contain, fit: BoxFit.contain,
@@ -106,7 +106,7 @@ class RecipeCard extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
_buildIconText( _buildIconText(
icon: Icons.star_outline, icon: Icons.star_outline,
text: recipe.favoriteCount.toString(), text: recipe.favouriteCount.toString(),
color: Colors.blue, color: Colors.blue,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -121,7 +121,7 @@ class RecipeCard extends StatelessWidget {
children: [ children: [
_buildIconText( _buildIconText(
icon: Icons.date_range, icon: Icons.date_range,
text: recipe.date, text: recipe.recordList[0].date,
color: Colors.red, color: Colors.red,
), ),
], ],

View File

@@ -3,23 +3,23 @@ import 'package:food_hub_app/models/recipe.dart';
import 'package:timelines_plus/timelines_plus.dart'; import 'package:timelines_plus/timelines_plus.dart';
class RecipeTimeline extends StatelessWidget { class RecipeTimeline extends StatelessWidget {
final List<Recipe> recipeList; final List<Record> recordList;
const RecipeTimeline({super.key, required this.recipeList}); const RecipeTimeline({super.key, required this.recordList});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Timeline.tileBuilder( return Timeline.tileBuilder(
theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0), theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0),
builder: TimelineTileBuilder.connected( builder: TimelineTileBuilder.connected(
itemCount: recipeList.length, itemCount: recordList.length,
connectorBuilder: connectorBuilder:
(context, index, type) => Connector.solidLine(thickness: 2), (context, index, type) => Connector.solidLine(thickness: 2),
indicatorBuilder: (context, index) { indicatorBuilder: (context, index) {
return Indicator.dot(size: 12.0); return Indicator.dot(size: 12.0);
}, },
contentsBuilder: (context, index) { contentsBuilder: (context, index) {
return TimelineCard(recipe: recipeList[index]); return TimelineCard(record: recordList[index]);
}, },
), ),
); );
@@ -27,9 +27,9 @@ class RecipeTimeline extends StatelessWidget {
} }
class TimelineCard extends StatelessWidget { class TimelineCard extends StatelessWidget {
final Recipe recipe; final Record record;
const TimelineCard({super.key, required this.recipe}); const TimelineCard({super.key, required this.record});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -41,7 +41,7 @@ class TimelineCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
recipe.date, record.date,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
Card( Card(
@@ -51,10 +51,10 @@ class TimelineCard extends StatelessWidget {
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: Column( child: Column(
children: [ children: [
Text(recipe.name, style: TextStyle(fontSize: 16)), Text(record.name, style: TextStyle(fontSize: 16)),
const SizedBox(height: 10), const SizedBox(height: 10),
Image.network( Image.network(
recipe.imageUrl, record.imageUrl,
width: double.infinity, width: double.infinity,
height: 250, height: 250,
fit: BoxFit.contain, fit: BoxFit.contain,

View File

@@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation import Foundation
import file_selector_macos import file_selector_macos
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@@ -217,6 +217,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -605,6 +613,38 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -637,6 +677,62 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.5.0" version: "1.5.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -834,6 +930,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@@ -45,6 +45,7 @@ dependencies:
tdesign_flutter: ^0.2.3 tdesign_flutter: ^0.2.3
json_annotation: ^4.9.0 json_annotation: ^4.9.0
fluttertoast: ^8.2.0 fluttertoast: ^8.2.0
shared_preferences: ^2.3.0
dependency_overrides: dependency_overrides:
tdesign_flutter_adaptation: 3.16.0 tdesign_flutter_adaptation: 3.16.0
@@ -60,6 +61,7 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
# flutter pub run build_runner build
build_runner: ^2.4.5 build_runner: ^2.4.5
json_serializable: ^6.7.1 json_serializable: ^6.7.1