flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import 'package:new_learn/core/network/dio_client.dart';
|
||||
import 'package:new_learn/core/network/post_api.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// Dio 网络请求 Demo 页
|
||||
///
|
||||
/// 一、演示内容:
|
||||
/// 1. GET 单条:PostApi.fetchPost(1) → 加载中/成功/失败三种 UI;
|
||||
/// 2. GET 列表:PostApi.fetchPosts() → 展示 `List<PostModel>`;
|
||||
/// 3. POST 新建:PostApi.createPost() → 展示服务器回执;
|
||||
/// 4. 错误兜底:请求不存在的路径,演示统一错误提示。
|
||||
///
|
||||
/// 二、页面写法要点:
|
||||
/// 1. 请求期间 _loading=true 显示 CircularProgressIndicator,
|
||||
/// 按钮禁用,防止重复点击;
|
||||
/// 2. try/catch 只 catch ApiException —— 封装层已把 DioException
|
||||
/// 翻译成中文提示,页面不用关心底层细节;
|
||||
/// 3. 所有 await 之后 setState 前必须 if (!mounted) return;
|
||||
/// (用户可能已切走页面,context 已失效)。
|
||||
/// ============================================================
|
||||
class DioDemoPage extends StatefulWidget {
|
||||
const DioDemoPage({super.key});
|
||||
|
||||
@override
|
||||
State<DioDemoPage> createState() => _DioDemoPageState();
|
||||
}
|
||||
|
||||
class _DioDemoPageState extends State<DioDemoPage> {
|
||||
bool _loading = false; // 请求中
|
||||
String? _error; // 错误提示
|
||||
String _resultTitle = '点击上方按钮发起请求'; // 结果区标题
|
||||
String _resultBody = ''; // 结果区内容
|
||||
|
||||
/// 统一请求入口:loading → 请求 → 成功/失败 → 结束
|
||||
Future<void> _run(Future<void> Function() action) async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
await action();
|
||||
} on ApiException catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = '请求失败:${e.message}');
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = '未知异常:$e');
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// GET 单条帖子
|
||||
Future<void> _fetchOne() async {
|
||||
await _run(() async {
|
||||
final post = await PostApi.fetchPost(1);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_resultTitle = 'GET /posts/1 成功';
|
||||
_resultBody = 'id: ${post.id}\nuserId: ${post.userId}\n'
|
||||
'title: ${post.title}\nbody: ${post.body}';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// GET 帖子列表(前 5 条)
|
||||
Future<void> _fetchList() async {
|
||||
await _run(() async {
|
||||
final posts = await PostApi.fetchPosts(userId: 1);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_resultTitle = 'GET /posts?userId=1 成功,共 ${posts.length} 条';
|
||||
_resultBody = posts
|
||||
.take(5)
|
||||
.map((p) => '#${p.id} ${p.title}')
|
||||
.join('\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// POST 新建帖子
|
||||
Future<void> _create() async {
|
||||
await _run(() async {
|
||||
final post = await PostApi.createPost(
|
||||
title: 'Flutter 学习 Demo',
|
||||
body: 'Dio 封装 POST 请求测试',
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_resultTitle = 'POST /posts 成功(模拟创建)';
|
||||
_resultBody = 'id: ${post.id}\nuserId: ${post.userId}\n'
|
||||
'title: ${post.title}\nbody: ${post.body}';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// 错误演示:请求不存在的接口
|
||||
Future<void> _fetchError() async {
|
||||
await _run(() async {
|
||||
await DioClient.instance.get('/not-exist-path');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Dio 网络请求封装 Demo')),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 三个操作按钮
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _fetchOne,
|
||||
icon: const Icon(Icons.south_west),
|
||||
label: const Text('GET 单条'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _fetchList,
|
||||
icon: const Icon(Icons.list),
|
||||
label: const Text('GET 列表'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _create,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('POST 新建'),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loading ? null : _fetchError,
|
||||
icon: const Icon(Icons.warning_amber),
|
||||
label: const Text('故意报错'),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// 加载中
|
||||
if (_loading)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40.h),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
|
||||
// 错误提示
|
||||
if (_error != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
padding: EdgeInsets.all(12.w),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFDECEA),
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: Text(
|
||||
_error!,
|
||||
style: const TextStyle(color: Color(0xFFD32F2F)),
|
||||
),
|
||||
),
|
||||
|
||||
// 结果展示
|
||||
SizedBox(height: 12.h),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(color: const Color(0xFFE5E7EB)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_resultTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF333333),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
SelectableText(
|
||||
_resultBody.isEmpty ? '(暂无数据)' : _resultBody,
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
height: 1.6,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// 日志提示
|
||||
Text(
|
||||
'💡 请求/响应日志在控制台打印(debugPrint),'
|
||||
'可在 DioClient 拦截器里查看完整 URL。',
|
||||
style: TextStyle(fontSize: 12.sp, color: Color(0xFF999999)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user