Files
App.K12.Student.Miniapp_Flu…/lib/pages/demo/dio_demo.dart
T
2026-09-04 15:22:20 +08:00

215 lines
7.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)),
),
],
),
),
);
}
}