一键导入
flutter-api-integration
Flutter HTTP API 集成规范。Invoke when: 1) 添加新的 API 接口 2) 处理网络请求 3) 实现数据缓存 4) 处理请求错误
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Flutter HTTP API 集成规范。Invoke when: 1) 添加新的 API 接口 2) 处理网络请求 3) 实现数据缓存 4) 处理请求错误
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Create or update minimal AGENTS.md files in the repository root and nested module directories using progressive disclosure. Works across heterogeneous projects without assuming any fixed agent folder structure.
Code review excellence guidelines and best practices. Invoke when reviewing code, ensuring code quality, identifying bugs, improving performance, and maintaining code standards.
Complete guide for using drift database library in Dart applications (CLI, server-side, non-Flutter). Use when building Dart apps that need local SQLite database storage or PostgreSQL connection with type-safe queries, reactive streams, migrations, and efficient CRUD operations. Includes setup with sqlite3 package, PostgreSQL support with drift_postgres, connection pooling, and server-side patterns.
Build adaptive and responsive Flutter UIs that work beautifully across all platforms and screen sizes. Use when creating Flutter apps that need to adapt layouts based on screen size, support multiple platforms including mobile tablet desktop and web, handle different input devices like touch mouse and keyboard, implement responsive navigation patterns, optimize for large screens and foldables, or use Capability and Policy patterns for platform-specific behavior.
Comprehensive guide for implementing animations in Flutter. Use when adding motion and visual effects to Flutter apps: implicit animations (AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder), explicit animations (AnimationController, Tween, AnimatedWidget/AnimatedBuilder), hero animations (shared element transitions), staggered animations (sequential/overlapping), and physics-based animations. Includes workflow for choosing the right animation type, implementation patterns, and best practices for performance and user experience.
Comprehensive guide for architecting Flutter applications following MVVM pattern and best practices with feature-first project organization. Use when working with Flutter projects to structure code properly, implement clean architecture layers (UI, Data, Domain), apply recommended design patterns, and organize projects using feature-first approach for scalable, maintainable apps.
| name | flutter-api-integration |
| description | Flutter HTTP API 集成规范。Invoke when: 1) 添加新的 API 接口 2) 处理网络请求 3) 实现数据缓存 4) 处理请求错误 |
lib/http/
├── request.dart # 主要请求类
├── requestConfig.dart # 请求配置
├── http_method.dart # HTTP 方法枚举
├── dioResponse.dart # 响应封装
├── cacheInterceptor.dart # 缓存拦截器
├── errorInterceptor.dart # 错误拦截器
├── responseInterceptor.dart # 响应拦截器
├── tokenInterceptors.dart # Token 拦截器
├── print_log_interceptor.dart # 日志拦截器
└── whitelistPaths.dart # 白名单路径
import 'package:flutter_app/http/request.dart';
class VideoApi {
// GET 请求
static Future<VideoPageEntity> getVideoList({
int page = 1,
int size = 20,
int? categoryId,
}) async {
final response = await Request.get(
'/video/list',
params: {
'page': page,
'size': size,
if (categoryId != null) 'categoryId': categoryId,
},
);
return VideoPageEntity.fromJson(response.data);
}
// POST 请求
static Future<bool> addFavorite(int videoId) async {
final response = await Request.post(
'/video/favorite',
data: {'videoId': videoId},
);
return response.code == 200;
}
// 上传文件
static Future<String> uploadAvatar(File file) async {
final response = await Request.upload(
'/upload/avatar',
file: file,
filename: 'avatar.jpg',
);
return response.data['url'];
}
}
class DioResponse<T> {
final int code;
final String message;
final T data;
final bool success;
DioResponse({
required this.code,
required this.message,
required this.data,
required this.success,
});
factory DioResponse.fromJson(
Map<String, dynamic> json,
T Function(dynamic) fromJsonT,
) {
return DioResponse(
code: json['code'],
message: json['message'],
data: fromJsonT(json['data']),
success: json['code'] == 200,
);
}
}
class ApiException implements Exception {
final int code;
final String message;
ApiException(this.code, this.message);
@override
String toString() => 'ApiException: $code - $message';
}
// 使用 try-catch
try {
final video = await VideoApi.getVideoDetail(id);
} on ApiException catch (e) {
if (e.code == 401) {
// 未登录,跳转到登录页
Get.toNamed('/login');
} else {
// 显示错误提示
Fluttertoast.showToast(msg: e.message);
}
} catch (e) {
// 未知错误
Fluttertoast.showToast(msg: '网络错误,请稍后重试');
}
import 'package:flutter_app/generated/json/base/json_convert_content.dart';
@JsonConvert()
class VideoEntity {
int? id;
String? title;
String? cover;
String? url;
int? duration;
int? viewCount;
VideoEntity({
this.id,
this.title,
this.cover,
this.url,
this.duration,
this.viewCount,
});
VideoEntity.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
cover = json['cover'];
url = json['url'];
duration = json['duration'];
viewCount = json['viewCount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {};
data['id'] = id;
data['title'] = title;
data['cover'] = cover;
data['url'] = url;
data['duration'] = duration;
data['viewCount'] = viewCount;
return data;
}
}
@JsonConvert()
class VideoPageEntity {
List<VideoEntity>? list;
int? total;
int? page;
int? size;
bool? hasMore;
VideoPageEntity({
this.list,
this.total,
this.page,
this.size,
this.hasMore,
});
VideoPageEntity.fromJson(Map<String, dynamic> json) {
if (json['list'] != null) {
list = (json['list'] as List)
.map((e) => VideoEntity.fromJson(e))
.toList();
}
total = json['total'];
page = json['page'];
size = json['size'];
hasMore = json['hasMore'];
}
}
class TokenInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
// 白名单路径不需要 Token
if (WhitelistPaths.contains(options.path)) {
handler.next(options);
return;
}
final token = await TokenDatabaseHelper().getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
// Token 过期,清除登录状态
EventBus().emit('logout');
}
handler.next(err);
}
}
class CacheInterceptor extends Interceptor {
final Map<String, Response> _cache = {};
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// GET 请求且带有 cache=true 参数时启用缓存
if (options.method == 'GET' && options.extra['cache'] == true) {
final cached = _cache[options.uri.toString()];
if (cached != null) {
handler.resolve(cached);
return;
}
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.requestOptions.extra['cache'] == true) {
_cache[response.requestOptions.uri.toString()] = response;
}
handler.next(response);
}
}
class VideoListPage extends StatefulWidget {
@override
State<VideoListPage> createState() => _VideoListPageState();
}
class _VideoListPageState extends State<VideoListPage> {
List<VideoEntity> _videos = [];
bool _isLoading = false;
bool _hasMore = true;
int _page = 1;
Future<void> _loadData({bool refresh = false}) async {
if (_isLoading) return;
setState(() => _isLoading = true);
try {
if (refresh) _page = 1;
final result = await VideoApi.getVideoList(page: _page);
setState(() {
if (refresh) {
_videos = result.list ?? [];
} else {
_videos.addAll(result.list ?? []);
}
_hasMore = result.hasMore ?? false;
_page++;
});
} catch (e) {
Fluttertoast.showToast(msg: '加载失败');
} finally {
setState(() => _isLoading = false);
}
}
@override
void initState() {
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () => _loadData(refresh: true),
child: ListView.builder(
itemCount: _videos.length,
itemBuilder: (context, index) => VideoItem(video: _videos[index]),
),
);
}
}
class VideoDetailPage extends StatelessWidget {
final int videoId;
VideoDetailPage({required this.videoId});
@override
Widget build(BuildContext context) {
return FutureBuilder<VideoDetailEntity>(
future: VideoApi.getVideoDetail(videoId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return LoadingWidget();
}
if (snapshot.hasError) {
return ErrorWidget(message: '加载失败');
}
final video = snapshot.data!;
return VideoPlayerWidget(url: video.url!);
},
);
}
}