flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+214
View File
@@ -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)),
),
],
),
),
);
}
}