feat:增加网络请求
This commit is contained in:
10
lib/api/session.dart
Normal file
10
lib/api/session.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:food_hub_app/models/session.dart';
|
||||
import 'package:food_hub_app/utils/HttpUtil.dart';
|
||||
|
||||
Future<Session?> loginApi(String username, String password) {
|
||||
return HttpUtil().post<Session>(
|
||||
"/session",
|
||||
queryParameters: {"username": username, "password": password},
|
||||
converter: (data) => Session.fromJson(data),
|
||||
);
|
||||
}
|
||||
80
lib/models/session.dart
Normal file
80
lib/models/session.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'session.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class SaTokenInfo {
|
||||
String? tokenName;
|
||||
String? tokenValue;
|
||||
bool? isLogin;
|
||||
dynamic loginId;
|
||||
String? loginType;
|
||||
int? tokenTimeout;
|
||||
int? sessionTimeout;
|
||||
int? tokenSessionTimeout;
|
||||
int? tokenActiveTimeout;
|
||||
String? loginDeviceType;
|
||||
String? tag;
|
||||
|
||||
SaTokenInfo({
|
||||
this.tokenName,
|
||||
this.tokenValue,
|
||||
this.isLogin,
|
||||
this.loginId,
|
||||
this.loginType,
|
||||
this.tokenTimeout,
|
||||
this.sessionTimeout,
|
||||
this.tokenSessionTimeout,
|
||||
this.tokenActiveTimeout,
|
||||
this.loginDeviceType,
|
||||
this.tag,
|
||||
});
|
||||
|
||||
factory SaTokenInfo.fromJson(Map<String, dynamic> json) => _$SaTokenInfoFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$SaTokenInfoToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class User {
|
||||
String? username;
|
||||
int? gender;
|
||||
String? phoneNumber;
|
||||
String? email;
|
||||
DateTime? birthDate;
|
||||
String? avatar;
|
||||
List<String>? area;
|
||||
String? address;
|
||||
String? job;
|
||||
List<String>? tags;
|
||||
String? description;
|
||||
String? id;
|
||||
|
||||
User({
|
||||
this.id,
|
||||
this.username,
|
||||
this.gender,
|
||||
this.phoneNumber,
|
||||
this.email,
|
||||
this.birthDate,
|
||||
this.avatar,
|
||||
this.area,
|
||||
this.address,
|
||||
this.job,
|
||||
this.tags,
|
||||
this.description,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$UserToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Session {
|
||||
SaTokenInfo? saToken;
|
||||
User? userInfo;
|
||||
|
||||
Session({this.saToken, this.userInfo});
|
||||
|
||||
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||
}
|
||||
85
lib/models/session.g.dart
Normal file
85
lib/models/session.g.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'session.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SaTokenInfo _$SaTokenInfoFromJson(Map<String, dynamic> json) => SaTokenInfo(
|
||||
tokenName: json['tokenName'] as String?,
|
||||
tokenValue: json['tokenValue'] as String?,
|
||||
isLogin: json['isLogin'] as bool?,
|
||||
loginId: json['loginId'],
|
||||
loginType: json['loginType'] as String?,
|
||||
tokenTimeout: (json['tokenTimeout'] as num?)?.toInt(),
|
||||
sessionTimeout: (json['sessionTimeout'] as num?)?.toInt(),
|
||||
tokenSessionTimeout: (json['tokenSessionTimeout'] as num?)?.toInt(),
|
||||
tokenActiveTimeout: (json['tokenActiveTimeout'] as num?)?.toInt(),
|
||||
loginDeviceType: json['loginDeviceType'] as String?,
|
||||
tag: json['tag'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SaTokenInfoToJson(SaTokenInfo instance) =>
|
||||
<String, dynamic>{
|
||||
'tokenName': instance.tokenName,
|
||||
'tokenValue': instance.tokenValue,
|
||||
'isLogin': instance.isLogin,
|
||||
'loginId': instance.loginId,
|
||||
'loginType': instance.loginType,
|
||||
'tokenTimeout': instance.tokenTimeout,
|
||||
'sessionTimeout': instance.sessionTimeout,
|
||||
'tokenSessionTimeout': instance.tokenSessionTimeout,
|
||||
'tokenActiveTimeout': instance.tokenActiveTimeout,
|
||||
'loginDeviceType': instance.loginDeviceType,
|
||||
'tag': instance.tag,
|
||||
};
|
||||
|
||||
User _$UserFromJson(Map<String, dynamic> json) => User(
|
||||
id: json['id'] as String?,
|
||||
username: json['username'] as String?,
|
||||
gender: (json['gender'] as num?)?.toInt(),
|
||||
phoneNumber: json['phoneNumber'] as String?,
|
||||
email: json['email'] as String?,
|
||||
birthDate:
|
||||
json['birthDate'] == null
|
||||
? null
|
||||
: DateTime.parse(json['birthDate'] as String),
|
||||
avatar: json['avatar'] as String?,
|
||||
area: (json['area'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
address: json['address'] as String?,
|
||||
job: json['job'] as String?,
|
||||
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
description: json['description'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
|
||||
'username': instance.username,
|
||||
'gender': instance.gender,
|
||||
'phoneNumber': instance.phoneNumber,
|
||||
'email': instance.email,
|
||||
'birthDate': instance.birthDate?.toIso8601String(),
|
||||
'avatar': instance.avatar,
|
||||
'area': instance.area,
|
||||
'address': instance.address,
|
||||
'job': instance.job,
|
||||
'tags': instance.tags,
|
||||
'description': instance.description,
|
||||
'id': instance.id,
|
||||
};
|
||||
|
||||
Session _$SessionFromJson(Map<String, dynamic> json) => Session(
|
||||
saToken:
|
||||
json['saToken'] == null
|
||||
? 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>{
|
||||
'saToken': instance.saToken,
|
||||
'userInfo': instance.userInfo,
|
||||
};
|
||||
222
lib/utils/HttpUtil.dart
Normal file
222
lib/utils/HttpUtil.dart
Normal file
@@ -0,0 +1,222 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:food_hub_app/widgets/common/index.dart';
|
||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||
|
||||
class HttpUtil {
|
||||
static final HttpUtil _instance = HttpUtil._internal();
|
||||
|
||||
factory HttpUtil() => _instance;
|
||||
|
||||
late Dio _dio;
|
||||
String baseUrl = "http://172.29.101.108:8100/";
|
||||
|
||||
// 请求头配置
|
||||
Map<String, dynamic> headers = {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
// 超时时间
|
||||
final int timeout = 5;
|
||||
|
||||
HttpUtil._internal() {
|
||||
// 初始化Dio实例
|
||||
BaseOptions options = BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: Duration(seconds: timeout),
|
||||
receiveTimeout: Duration(seconds: timeout),
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
_dio = Dio(options);
|
||||
|
||||
// 添加请求拦截器
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
print("请求URL: ${options.uri}");
|
||||
if (options.data != null) {
|
||||
print("请求参数: ${options.data}");
|
||||
}
|
||||
// 可以在此添加token等操作
|
||||
// options.headers['Authorization'] = 'Bearer your_token';
|
||||
return handler.next(options);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// 添加响应拦截器
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onResponse: (response, handler) {
|
||||
print("响应状态码: ${response.statusCode}");
|
||||
print("响应数据: ${response.data}");
|
||||
return handler.next(response);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// 添加错误拦截器
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onError: (DioException e, handler) {
|
||||
if (e.response != null) {
|
||||
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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 基础请求方法(处理所有类型的请求)
|
||||
Future<T?> _request<T>(
|
||||
String path, {
|
||||
required String method,
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(dynamic data)? converter,
|
||||
}) async {
|
||||
try {
|
||||
Response response = await _dio.request(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(method: method),
|
||||
);
|
||||
|
||||
// 处理响应数据
|
||||
if (converter != null) {
|
||||
return converter(response.data);
|
||||
}
|
||||
|
||||
// 如果没有转换器且T是dynamic,直接返回原始数据
|
||||
if (T == dynamic) {
|
||||
return response.data as T;
|
||||
}
|
||||
|
||||
// 没有转换器时尝试直接返回(可能不安全,建议提供转换器)
|
||||
return response.data as T?;
|
||||
} catch (e) {
|
||||
_handleError(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// GET请求
|
||||
Future<T?> get<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(dynamic data)? converter,
|
||||
}) => _request(
|
||||
path,
|
||||
method: "GET",
|
||||
queryParameters: queryParameters,
|
||||
converter: converter,
|
||||
);
|
||||
|
||||
// POST请求
|
||||
Future<T?> post<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(dynamic data)? converter,
|
||||
}) => _request(
|
||||
path,
|
||||
method: "POST",
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
converter: converter,
|
||||
);
|
||||
|
||||
// PUT请求
|
||||
Future<T?> put<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(dynamic data)? converter,
|
||||
}) => _request(
|
||||
path,
|
||||
method: "PUT",
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
converter: converter,
|
||||
);
|
||||
|
||||
// DELETE请求
|
||||
Future<T?> delete<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(dynamic data)? converter,
|
||||
}) => _request(
|
||||
path,
|
||||
method: "DELETE",
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
converter: converter,
|
||||
);
|
||||
|
||||
// 文件上传
|
||||
Future<dynamic> upload(
|
||||
String path,
|
||||
String filePath, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) async {
|
||||
try {
|
||||
FormData formData = FormData.fromMap({
|
||||
'file': await MultipartFile.fromFile(filePath),
|
||||
});
|
||||
|
||||
Response response = await _dio.post(
|
||||
path,
|
||||
data: formData,
|
||||
queryParameters: queryParameters,
|
||||
);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
_handleError(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
void _handleError(dynamic error) {
|
||||
if (error is DioException) {
|
||||
switch (error.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
print("连接超时");
|
||||
break;
|
||||
case DioExceptionType.sendTimeout:
|
||||
print("发送超时");
|
||||
break;
|
||||
case DioExceptionType.receiveTimeout:
|
||||
print("接收超时");
|
||||
break;
|
||||
case DioExceptionType.cancel:
|
||||
print("请求取消");
|
||||
break;
|
||||
case DioExceptionType.badCertificate:
|
||||
print("证书错误");
|
||||
case DioExceptionType.badResponse:
|
||||
print("错误响应");
|
||||
case DioExceptionType.connectionError:
|
||||
print("连接错误");
|
||||
case DioExceptionType.unknown:
|
||||
print("未知错误");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
print("未知错误: $error");
|
||||
}
|
||||
}
|
||||
}
|
||||
3063
lib/views/data.dart
Normal file
3063
lib/views/data.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +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/widgets/common/index.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||
@@ -23,8 +24,10 @@ class _LoginPage extends State<LoginPage> {
|
||||
{"title": "wechat", "icon": Icons.wechat},
|
||||
];
|
||||
|
||||
void loginClick(BuildContext context) {
|
||||
Navigator.pushNamed(context, '/home');
|
||||
void loginClick(BuildContext context) async {
|
||||
await loginApi("Cxx", "1232");
|
||||
|
||||
// Navigator.pushNamed(context, '/home');
|
||||
return;
|
||||
|
||||
// 表单校验通过才会继续执行
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:graphic/graphic.dart';
|
||||
|
||||
import 'data.dart';
|
||||
|
||||
class StatsPage extends StatefulWidget {
|
||||
const StatsPage({super.key});
|
||||
@@ -8,27 +10,91 @@ class StatsPage extends StatefulWidget {
|
||||
State<StatsPage> createState() => _StatsPage();
|
||||
}
|
||||
|
||||
class _StatsPage extends State<StatsPage>{
|
||||
class _StatsPage extends State<StatsPage> {
|
||||
var _smooth = false;
|
||||
var _stepped = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 月度做菜次数数据
|
||||
final List<FlSpot> spots = [
|
||||
FlSpot(0, 13), FlSpot(1, 32), FlSpot(2, 121),
|
||||
FlSpot(3, 31), FlSpot(4, 34), FlSpot(5, 45)
|
||||
];
|
||||
|
||||
// 月份标签
|
||||
final List<String> months = [
|
||||
'1月', '2月', '3月', '4月', '5月', '6月'
|
||||
];
|
||||
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: spots,
|
||||
),
|
||||
],
|
||||
return SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
width: 350,
|
||||
height: 300,
|
||||
child: Chart(
|
||||
data: basicData,
|
||||
variables: {
|
||||
'genre': Variable(
|
||||
accessor: (Map map) => map['genre'] as String,
|
||||
),
|
||||
'sold': Variable(accessor: (Map map) => map['sold'] as num),
|
||||
},
|
||||
marks: [
|
||||
IntervalMark(
|
||||
label: LabelEncode(
|
||||
encoder: (tuple) => Label(tuple['sold'].toString()),
|
||||
),
|
||||
elevation: ElevationEncode(
|
||||
value: 0,
|
||||
updaters: {
|
||||
'tap': {true: (_) => 5},
|
||||
},
|
||||
),
|
||||
color: ColorEncode(
|
||||
value: Defaults.primaryColor,
|
||||
updaters: {
|
||||
'tap': {false: (color) => color.withAlpha(100)},
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
axes: [Defaults.horizontalAxis, Defaults.verticalAxis],
|
||||
selections: {'tap': PointSelection(dim: Dim.x)},
|
||||
tooltip: TooltipGuide(),
|
||||
crosshair: CrosshairGuide(),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 40, 20, 5),
|
||||
child: const Text(
|
||||
'Transposed Bar Chart',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
width: 350,
|
||||
height: 300,
|
||||
child: Chart(
|
||||
data: basicData,
|
||||
variables: {
|
||||
'genre': Variable(
|
||||
accessor: (Map map) => map['genre'] as String,
|
||||
),
|
||||
'sold': Variable(accessor: (Map map) => map['sold'] as num),
|
||||
},
|
||||
transforms: [Proportion(variable: 'sold', as: 'percent')],
|
||||
marks: [
|
||||
IntervalMark(
|
||||
position: Varset('percent') / Varset('genre'),
|
||||
label: LabelEncode(
|
||||
encoder: (tuple) => Label(tuple['sold'].toString()),
|
||||
),
|
||||
color: ColorEncode(
|
||||
variable: 'genre',
|
||||
values: Defaults.colors10,
|
||||
),
|
||||
modifiers: [StackModifier()],
|
||||
),
|
||||
],
|
||||
coord: PolarCoord(transposed: true, dimCount: 1, dimFill: 1.05),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
// import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
Widget formLabelText({required String labelText, bool isRequired = false}) {
|
||||
@@ -61,14 +62,14 @@ Text buttonText({required String text}) {
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// void showErrorToast(String message) {
|
||||
// Fluttertoast.showToast(
|
||||
// msg: message,
|
||||
// toastLength: Toast.LENGTH_SHORT,
|
||||
// gravity: ToastGravity.CENTER,
|
||||
// timeInSecForIosWeb: 1,
|
||||
// backgroundColor: Colors.red,
|
||||
// textColor: Colors.white,
|
||||
// fontSize: 16.0,
|
||||
// );
|
||||
// }
|
||||
void showErrorToast(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
timeInSecForIosWeb: 1,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user