52 KiB
全面重构实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 实现 GetX 全局状态管理、组件 PageStandards 集成、路由守卫和组件复用
Architecture: 使用 GetX 进行状态管理,所有组件使用 PageStandards 统一样式,实现路由守卫保护需要认证的页面
Tech Stack: Flutter, GetX, PageStandards, Cupertino (iOS 风格)
文件结构
新增文件
lib/src/
├── controllers/
│ ├── base/
│ │ ├── base_controller.dart # 基础控制器
│ │ └── paged_controller.dart # 分页控制器
│ ├── home_controller.dart # 首页控制器
│ ├── cart_controller.dart # 购物车控制器
│ └── profile_controller.dart # 个人中心控制器
├── widgets/
│ ├── base/
│ │ ├── standard_button.dart # 标准按钮
│ │ ├── standard_text_field.dart # 标准输入框
│ │ ├── standard_card.dart # 标准卡片
│ │ └── standard_list_tile.dart # 标准列表项
│ ├── interactive/
│ │ ├── standard_dialog.dart # 标准对话框
│ │ ├── standard_bottom_sheet.dart # 标准底部弹窗
│ │ └── standard_picker.dart # 标准选择器
│ └── states/
│ ├── empty_state.dart # 空状态
│ └── error_state.dart # 错误状态
└── standards/
└── route_guard.dart # 路由守卫
修改文件
lib/
├── src/
│ ├── pages/
│ │ ├── home_page.dart # 使用 HomeController
│ │ ├── example_page.dart # 使用 PageStandards
│ │ └── theme_demo_page.dart # 使用控制器
│ ├── widgets/
│ │ ├── product_card.dart # 使用 PageStandards
│ │ ├── loading_indicator.dart # 使用 PageStandards
│ │ └── skeleton_loader.dart # 使用 PageStandards
│ └── standards/
│ └── app_pages.dart # 添加 authLevel
└── main.dart # 集成路由守卫
Task 1: 创建基础控制器
Files:
-
Create:
lib/src/controllers/base/base_controller.dart -
Step 1: 创建 BaseController
import 'package:get/get.dart';
import 'package:mom_kitchen/src/services/app_service.dart';
import 'package:mom_kitchen/src/utils/app_logger.dart';
abstract class BaseController extends GetxController {
final isLoading = false.obs;
final errorMessage = ''.obs;
Future<void> runWithLoading(Future<void> Function() action) async {
isLoading.value = true;
errorMessage.value = '';
try {
await action();
} catch (e) {
errorMessage.value = e.toString();
AppLogger.e('Controller error: $e');
} finally {
isLoading.value = false;
}
}
void clearError() {
errorMessage.value = '';
}
}
- Step 2: 创建 PagedController
Files:
- Create:
lib/src/controllers/base/paged_controller.dart
import 'package:get/get.dart';
import 'package:mom_kitchen/src/controllers/base/base_controller.dart';
abstract class PagedController<T> extends BaseController {
final items = <T>[].obs;
final currentPage = 1.obs;
final hasMore = true.obs;
final pageSize = 20;
Future<List<T>> fetchPage(int page);
Future<void> loadMore() async {
if (!hasMore.value || isLoading.value) return;
await runWithLoading(() async {
final newItems = await fetchPage(currentPage.value + 1);
if (newItems.isEmpty) {
hasMore.value = false;
} else {
items.addAll(newItems);
currentPage.value++;
}
});
}
Future<void> refresh() async {
currentPage.value = 1;
hasMore.value = true;
items.clear();
await loadMore();
}
void clear() {
items.clear();
currentPage.value = 1;
hasMore.value = true;
}
}
- Step 3: 提交基础控制器
git add lib/src/controllers/base/
git commit -m "feat: add base controllers for GetX state management"
Task 2: 创建首页控制器
Files:
-
Create:
lib/src/controllers/home_controller.dart -
Step 1: 创建 HomeController
import 'package:get/get.dart';
import 'package:mom_kitchen/src/controllers/base/base_controller.dart';
class ProductModel {
final String name;
final double price;
final String image;
final String category;
ProductModel({
required this.name,
required this.price,
required this.image,
required this.category,
});
factory ProductModel.fromMap(Map<String, dynamic> map) {
return ProductModel(
name: map['name'] ?? '',
price: (map['price'] as num?)?.toDouble() ?? 0.0,
image: map['image'] ?? '📦',
category: map['category'] ?? '',
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'price': price,
'image': image,
'category': category,
};
}
}
class HomeController extends BaseController {
final products = <ProductModel>[].obs;
final categories = <String>[].obs;
final selectedCategory = ''.obs;
final searchQuery = ''.obs;
@override
void onInit() {
super.onInit();
loadProducts();
}
Future<void> loadProducts() async {
await runWithLoading(() async {
await Future.delayed(const Duration(milliseconds: 500));
final mockProducts = [
ProductModel(name: 'Organic Apples', price: 4.99, image: '🍎', category: 'Fruits'),
ProductModel(name: 'Fresh Milk', price: 3.49, image: '🥛', category: 'Dairy'),
ProductModel(name: 'Whole Wheat Bread', price: 2.99, image: '🍞', category: 'Bakery'),
ProductModel(name: 'Free Range Eggs', price: 5.99, image: '🥚', category: 'Dairy'),
ProductModel(name: 'Organic Tomatoes', price: 3.99, image: '🍅', category: 'Vegetables'),
ProductModel(name: 'Fresh Salmon', price: 12.99, image: '🐟', category: 'Seafood'),
];
products.value = mockProducts;
final categorySet = <String>{};
for (var product in mockProducts) {
categorySet.add(product.category);
}
categories.value = categorySet.toList();
});
}
List<ProductModel> get filteredProducts {
var result = products.toList();
if (selectedCategory.value.isNotEmpty) {
result = result.where((p) => p.category == selectedCategory.value).toList();
}
if (searchQuery.value.isNotEmpty) {
result = result.where((p) =>
p.name.toLowerCase().contains(searchQuery.value.toLowerCase())
).toList();
}
return result;
}
void selectCategory(String category) {
selectedCategory.value = category;
}
void search(String query) {
searchQuery.value = query;
}
void clearFilters() {
selectedCategory.value = '';
searchQuery.value = '';
}
}
- Step 2: 提交首页控制器
git add lib/src/controllers/home_controller.dart
git commit -m "feat: add HomeController for state management"
Task 3: 创建购物车控制器
Files:
-
Create:
lib/src/controllers/cart_controller.dart -
Step 1: 创建 CartController
import 'package:get/get.dart';
import 'package:mom_kitchen/src/controllers/base/base_controller.dart';
import 'package:mom_kitchen/src/controllers/home_controller.dart';
class CartItem {
final ProductModel product;
int quantity;
CartItem({
required this.product,
this.quantity = 1,
});
double get totalPrice => product.price * quantity;
}
class CartController extends BaseController {
final cartItems = <CartItem>[].obs;
@override
void onInit() {
super.onInit();
}
void addProduct(ProductModel product) {
final index = cartItems.indexWhere((item) => item.product.name == product.name);
if (index >= 0) {
cartItems[index].quantity++;
cartItems.refresh();
} else {
cartItems.add(CartItem(product: product));
}
}
void removeProduct(String productName) {
cartItems.removeWhere((item) => item.product.name == productName);
}
void updateQuantity(String productName, int quantity) {
final index = cartItems.indexWhere((item) => item.product.name == productName);
if (index >= 0) {
if (quantity <= 0) {
cartItems.removeAt(index);
} else {
cartItems[index].quantity = quantity;
cartItems.refresh();
}
}
}
void incrementQuantity(String productName) {
final index = cartItems.indexWhere((item) => item.product.name == productName);
if (index >= 0) {
cartItems[index].quantity++;
cartItems.refresh();
}
}
void decrementQuantity(String productName) {
final index = cartItems.indexWhere((item) => item.product.name == productName);
if (index >= 0) {
if (cartItems[index].quantity > 1) {
cartItems[index].quantity--;
cartItems.refresh();
} else {
cartItems.removeAt(index);
}
}
}
void clearCart() {
cartItems.clear();
}
double get totalPrice {
return cartItems.fold(0.0, (sum, item) => sum + item.totalPrice);
}
int get totalItems {
return cartItems.fold(0, (sum, item) => sum + item.quantity);
}
bool isInCart(String productName) {
return cartItems.any((item) => item.product.name == productName);
}
int getQuantity(String productName) {
final index = cartItems.indexWhere((item) => item.product.name == productName);
return index >= 0 ? cartItems[index].quantity : 0;
}
}
- Step 2: 提交购物车控制器
git add lib/src/controllers/cart_controller.dart
git commit -m "feat: add CartController for cart state management"
Task 4: 创建个人中心控制器
Files:
-
Create:
lib/src/controllers/profile_controller.dart -
Step 1: 创建 ProfileController
import 'package:get/get.dart';
import 'package:mom_kitchen/src/controllers/base/base_controller.dart';
import 'package:mom_kitchen/src/services/app_service.dart';
class UserModel {
final String id;
final String name;
final String email;
final String? avatar;
UserModel({
required this.id,
required this.name,
required this.email,
this.avatar,
});
factory UserModel.empty() {
return UserModel(
id: '',
name: '',
email: '',
);
}
bool get isEmpty => id.isEmpty;
bool get isNotEmpty => id.isNotEmpty;
}
class ProfileController extends BaseController {
final user = Rx<UserModel?>(null);
final isLoggedIn = false.obs;
@override
void onInit() {
super.onInit();
checkLoginStatus();
}
Future<void> checkLoginStatus() async {
await runWithLoading(() async {
final storage = AppService.instance.storage;
final userId = await storage.getString('user_id');
if (userId != null && userId.isNotEmpty) {
final name = await storage.getString('user_name') ?? 'User';
final email = await storage.getString('user_email') ?? '';
user.value = UserModel(
id: userId,
name: name,
email: email,
);
isLoggedIn.value = true;
}
});
}
Future<void> login(String email, String password) async {
await runWithLoading(() async {
await Future.delayed(const Duration(seconds: 1));
final storage = AppService.instance.storage;
await storage.setString('user_id', 'user_123');
await storage.setString('user_name', 'Test User');
await storage.setString('user_email', email);
user.value = UserModel(
id: 'user_123',
name: 'Test User',
email: email,
);
isLoggedIn.value = true;
});
}
Future<void> logout() async {
await runWithLoading(() async {
final storage = AppService.instance.storage;
await storage.remove('user_id');
await storage.remove('user_name');
await storage.remove('user_email');
user.value = null;
isLoggedIn.value = false;
});
}
Future<void> updateProfile({String? name, String? avatar}) async {
if (user.value == null) return;
await runWithLoading(() async {
await Future.delayed(const Duration(milliseconds: 500));
final storage = AppService.instance.storage;
if (name != null) {
await storage.setString('user_name', name);
}
user.value = UserModel(
id: user.value!.id,
name: name ?? user.value!.name,
email: user.value!.email,
avatar: avatar ?? user.value!.avatar,
);
});
}
}
- Step 2: 提交个人中心控制器
git add lib/src/controllers/profile_controller.dart
git commit -m "feat: add ProfileController for user state management"
Task 5: 创建标准按钮组件
Files:
-
Create:
lib/src/widgets/base/standard_button.dart -
Step 1: 创建 StandardButton
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
enum StandardButtonType { primary, secondary, outline, text }
class StandardButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final StandardButtonType type;
final bool isLoading;
final IconData? icon;
final bool isFullWidth;
final double? width;
final double? height;
const StandardButton({
super.key,
required this.text,
this.onPressed,
this.type = StandardButtonType.primary,
this.isLoading = false,
this.icon,
this.isFullWidth = false,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: isLoading ? null : onPressed,
child: Container(
width: isFullWidth ? double.infinity : width,
height: height ?? standards.scaledHeight(48),
padding: standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
decoration: _buildDecoration(standards),
child: _buildChild(standards),
),
);
}
BoxDecoration _buildDecoration(PageStandards standards) {
switch (type) {
case StandardButtonType.primary:
return BoxDecoration(
color: standards.primaryColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.secondary:
return BoxDecoration(
color: standards.secondaryColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.outline:
return BoxDecoration(
border: Border.all(color: standards.primaryColor, width: 1.5),
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.text:
return const BoxDecoration();
}
}
Widget _buildChild(PageStandards standards) {
if (isLoading) {
return Center(
child: CupertinoActivityIndicator(color: _getTextColor(standards)),
);
}
return Row(
mainAxisSize: isFullWidth ? MainAxisSize.max : MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, color: _getTextColor(standards), size: standards.fontSize + 4),
SizedBox(width: standards.scaledWidth(8)),
],
Text(
text,
style: standards.textStyle.copyWith(
color: _getTextColor(standards),
fontWeight: FontWeight.w600,
),
),
],
);
}
Color _getTextColor(PageStandards standards) {
switch (type) {
case StandardButtonType.primary:
case StandardButtonType.secondary:
return CupertinoColors.white;
case StandardButtonType.outline:
case StandardButtonType.text:
return standards.primaryColor;
}
}
}
- Step 2: 提交标准按钮组件
git add lib/src/widgets/base/standard_button.dart
git commit -m "feat: add StandardButton component with PageStandards"
Task 6: 创建标准输入框组件
Files:
-
Create:
lib/src/widgets/base/standard_text_field.dart -
Step 1: 创建 StandardTextField
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardTextField extends StatelessWidget {
final String? placeholder;
final String? initialValue;
final ValueChanged<String>? onChanged;
final VoidCallback? onEditingComplete;
final ValueChanged<String>? onSubmitted;
final bool obscureText;
final TextInputType? keyboardType;
final TextEditingController? controller;
final FocusNode? focusNode;
final IconData? prefixIcon;
final IconData? suffixIcon;
final VoidCallback? onSuffixIconPressed;
final String? errorText;
final int? maxLines;
final bool enabled;
final bool autofocus;
const StandardTextField({
super.key,
this.placeholder,
this.initialValue,
this.onChanged,
this.onEditingComplete,
this.onSubmitted,
this.obscureText = false,
this.keyboardType,
this.controller,
this.focusNode,
this.prefixIcon,
this.suffixIcon,
this.onSuffixIconPressed,
this.errorText,
this.maxLines = 1,
this.enabled = true,
this.autofocus = false,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
border: Border.all(
color: errorText != null
? standards.secondaryColor
: standards.textColor.withOpacity(0.2),
),
),
child: Row(
children: [
if (prefixIcon != null)
Padding(
padding: EdgeInsets.only(left: standards.scaledWidth(12)),
child: Icon(
prefixIcon,
color: standards.textColor.withOpacity(0.5),
size: standards.fontSize + 4,
),
),
Expanded(
child: CupertinoTextField(
controller: controller,
focusNode: focusNode,
placeholder: placeholder,
placeholderStyle: TextStyle(
color: standards.textColor.withOpacity(0.5),
fontSize: standards.fontSize,
),
style: TextStyle(
color: standards.textColor,
fontSize: standards.fontSize,
),
onChanged: onChanged,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
obscureText: obscureText,
keyboardType: keyboardType,
maxLines: maxLines,
enabled: enabled,
autofocus: autofocus,
decoration: const BoxDecoration(),
padding: standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
if (suffixIcon != null)
GestureDetector(
onTap: onSuffixIconPressed,
child: Padding(
padding: EdgeInsets.only(right: standards.scaledWidth(12)),
child: Icon(
suffixIcon,
color: standards.textColor.withOpacity(0.5),
size: standards.fontSize + 4,
),
),
),
],
),
),
if (errorText != null) ...[
SizedBox(height: standards.scaledHeight(4)),
Padding(
padding: EdgeInsets.only(left: standards.scaledWidth(12)),
child: Text(
errorText!,
style: TextStyle(
color: standards.secondaryColor,
fontSize: standards.fontSize - 2,
),
),
),
],
],
);
}
}
- Step 2: 提交标准输入框组件
git add lib/src/widgets/base/standard_text_field.dart
git commit -m "feat: add StandardTextField component with PageStandards"
Task 7: 创建标准卡片组件
Files:
-
Create:
lib/src/widgets/base/standard_card.dart -
Step 1: 创建 StandardCard
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardCard extends StatelessWidget {
final Widget child;
final VoidCallback? onTap;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Color? backgroundColor;
final double? borderRadius;
final bool showShadow;
final bool showBorder;
final Color? borderColor;
const StandardCard({
super.key,
required this.child,
this.onTap,
this.padding,
this.margin,
this.backgroundColor,
this.borderRadius,
this.showShadow = true,
this.showBorder = false,
this.borderColor,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: onTap,
child: Container(
margin: margin ?? standards.scaledPadding(EdgeInsets.all(8)),
padding: padding ?? standards.scaledPadding(EdgeInsets.all(16)),
decoration: BoxDecoration(
color: backgroundColor ?? standards.backgroundColor,
borderRadius: BorderRadius.circular(
borderRadius ?? standards.scaledRadius(16),
),
boxShadow: showShadow
? [
BoxShadow(
color: standards.textColor.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
]
: null,
border: showBorder
? Border.all(
color: borderColor ?? standards.textColor.withOpacity(0.1),
)
: null,
),
child: child,
),
);
}
}
- Step 2: 提交标准卡片组件
git add lib/src/widgets/base/standard_card.dart
git commit -m "feat: add StandardCard component with PageStandards"
Task 8: 创建标准列表项组件
Files:
-
Create:
lib/src/widgets/base/standard_list_tile.dart -
Step 1: 创建 StandardListTile
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardListTile extends StatelessWidget {
final IconData? leadingIcon;
final Widget? leading;
final String title;
final String? subtitle;
final Widget? trailing;
final VoidCallback? onTap;
final bool showChevron;
final EdgeInsetsGeometry? padding;
const StandardListTile({
super.key,
this.leadingIcon,
this.leading,
required this.title,
this.subtitle,
this.trailing,
this.onTap,
this.showChevron = false,
this.padding,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
padding: padding ??
standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
child: Row(
children: [
if (leading != null) leading!,
if (leadingIcon != null)
Container(
width: standards.scaledWidth(40),
height: standards.scaledHeight(40),
decoration: BoxDecoration(
color: standards.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(standards.scaledRadius(10)),
),
child: Icon(
leadingIcon,
color: standards.primaryColor,
size: standards.fontSize + 8,
),
),
if (leadingIcon != null || leading != null)
SizedBox(width: standards.scaledWidth(12)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: standards.textStyle.copyWith(
fontWeight: FontWeight.w500,
),
),
if (subtitle != null) ...[
SizedBox(height: standards.scaledHeight(2)),
Text(
subtitle!,
style: standards.textStyle.copyWith(
color: standards.textColor.withOpacity(0.6),
fontSize: standards.fontSize - 2,
),
),
],
],
),
),
if (trailing != null) trailing!,
if (showChevron)
Icon(
CupertinoIcons.chevron_right,
color: standards.textColor.withOpacity(0.4),
size: standards.fontSize + 4,
),
],
),
),
);
}
}
- Step 2: 提交标准列表项组件
git add lib/src/widgets/base/standard_list_tile.dart
git commit -m "feat: add StandardListTile component with PageStandards"
Task 9: 创建空状态组件
Files:
-
Create:
lib/src/widgets/states/empty_state.dart -
Step 1: 创建 EmptyState
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
import 'package:mom_kitchen/src/widgets/base/standard_button.dart';
class EmptyState extends StatelessWidget {
final String? title;
final String? message;
final IconData? icon;
final String? emoji;
final String? buttonText;
final VoidCallback? onButtonPressed;
const EmptyState({
super.key,
this.title,
this.message,
this.icon,
this.emoji,
this.buttonText,
this.onButtonPressed,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
final l10n = standards.l10n;
return Center(
child: Padding(
padding: standards.scaledPadding(EdgeInsets.all(32)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (emoji != null)
Text(
emoji!,
style: TextStyle(fontSize: standards.scaledFontSize(64)),
)
else
Icon(
icon ?? CupertinoIcons.cube_box,
size: standards.scaledFontSize(64),
color: standards.textColor.withOpacity(0.4),
),
SizedBox(height: standards.scaledHeight(16)),
Text(
title ?? l10n.noData,
style: standards.textStyle.copyWith(
fontSize: standards.fontSize + 4,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
if (message != null) ...[
SizedBox(height: standards.scaledHeight(8)),
Text(
message!,
style: standards.textStyle.copyWith(
color: standards.textColor.withOpacity(0.6),
),
textAlign: TextAlign.center,
),
],
if (buttonText != null && onButtonPressed != null) ...[
SizedBox(height: standards.scaledHeight(24)),
StandardButton(
text: buttonText!,
onPressed: onButtonPressed,
type: StandardButtonType.primary,
),
],
],
),
),
);
}
}
- Step 2: 提交空状态组件
git add lib/src/widgets/states/empty_state.dart
git commit -m "feat: add EmptyState component with PageStandards"
Task 10: 创建错误状态组件
Files:
-
Create:
lib/src/widgets/states/error_state.dart -
Step 1: 创建 ErrorState
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
import 'package:mom_kitchen/src/widgets/base/standard_button.dart';
class ErrorState extends StatelessWidget {
final String message;
final VoidCallback? onRetry;
final String? retryText;
const ErrorState({
super.key,
required this.message,
this.onRetry,
this.retryText,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
final l10n = standards.l10n;
return Center(
child: Padding(
padding: standards.scaledPadding(EdgeInsets.all(32)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'⚠️',
style: TextStyle(fontSize: standards.scaledFontSize(64)),
),
SizedBox(height: standards.scaledHeight(16)),
Text(
message,
style: standards.textStyle,
textAlign: TextAlign.center,
),
if (onRetry != null) ...[
SizedBox(height: standards.scaledHeight(24)),
StandardButton(
text: retryText ?? l10n.retry,
onPressed: onRetry,
type: StandardButtonType.outline,
),
],
],
),
),
);
}
}
- Step 2: 提交错误状态组件
git add lib/src/widgets/states/error_state.dart
git commit -m "feat: add ErrorState component with PageStandards"
Task 11: 创建标准对话框组件
Files:
-
Create:
lib/src/widgets/interactive/standard_dialog.dart -
Step 1: 创建 StandardDialog
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardDialog extends StatelessWidget {
final String title;
final String? message;
final String? confirmText;
final String? cancelText;
final VoidCallback? onConfirm;
final VoidCallback? onCancel;
final bool isDestructive;
const StandardDialog({
super.key,
required this.title,
this.message,
this.confirmText,
this.cancelText,
this.onConfirm,
this.onCancel,
this.isDestructive = false,
});
static Future<bool?> show(
BuildContext context, {
required String title,
String? message,
String? confirmText,
String? cancelText,
bool isDestructive = false,
}) {
return showCupertinoDialog<bool>(
context: context,
builder: (context) => StandardDialog(
title: title,
message: message,
confirmText: confirmText,
cancelText: cancelText,
isDestructive: isDestructive,
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
final l10n = standards.l10n;
return CupertinoAlertDialog(
title: Text(title, style: standards.textStyle),
content: message != null
? Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(message!, style: standards.textStyle),
)
: null,
actions: [
if (cancelText != null || onCancel != null)
CupertinoDialogAction(
onPressed: () {
Navigator.pop(context, false);
onCancel?.call();
},
child: Text(cancelText ?? l10n.cancel),
),
CupertinoDialogAction(
isDestructiveAction: isDestructive,
onPressed: () {
Navigator.pop(context, true);
onConfirm?.call();
},
child: Text(confirmText ?? l10n.confirm),
),
],
);
}
}
- Step 2: 提交标准对话框组件
git add lib/src/widgets/interactive/standard_dialog.dart
git commit -m "feat: add StandardDialog component with PageStandards"
Task 12: 创建标准底部弹窗组件
Files:
-
Create:
lib/src/widgets/interactive/standard_bottom_sheet.dart -
Step 1: 创建 StandardBottomSheet
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardBottomSheet extends StatelessWidget {
final String? title;
final Widget child;
final double? height;
const StandardBottomSheet({
super.key,
this.title,
required this.child,
this.height,
});
static Future<T?> show<T>({
required BuildContext context,
String? title,
required Widget child,
double? height,
bool isScrollControlled = false,
}) {
return showModalBottomSheet<T>(
context: context,
isScrollControlled: isScrollControlled,
backgroundColor: Colors.transparent,
builder: (context) => StandardBottomSheet(
title: title,
child: child,
height: height,
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Container(
height: height,
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(standards.scaledRadius(20)),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.only(top: standards.scaledHeight(8)),
width: standards.scaledWidth(36),
height: standards.scaledHeight(4),
decoration: BoxDecoration(
color: standards.textColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(standards.scaledRadius(2)),
),
),
if (title != null)
Padding(
padding: standards.scaledPadding(EdgeInsets.all(16)),
child: Text(
title!,
style: standards.textStyle.copyWith(
fontSize: standards.fontSize + 4,
fontWeight: FontWeight.w600,
),
),
),
Flexible(child: child),
],
),
);
}
}
- Step 2: 提交标准底部弹窗组件
git add lib/src/widgets/interactive/standard_bottom_sheet.dart
git commit -m "feat: add StandardBottomSheet component with PageStandards"
Task 13: 创建标准选择器组件
Files:
-
Create:
lib/src/widgets/interactive/standard_picker.dart -
Step 1: 创建 StandardPicker
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardPickerItem<T> {
final T value;
final String label;
StandardPickerItem({
required this.value,
required this.label,
});
}
class StandardPicker<T> extends StatelessWidget {
final List<StandardPickerItem<T>> items;
final T? selectedValue;
final ValueChanged<T?>? onChanged;
final String? title;
const StandardPicker({
super.key,
required this.items,
this.selectedValue,
this.onChanged,
this.title,
});
static Future<T?> show<T>({
required BuildContext context,
required List<StandardPickerItem<T>> items,
T? selectedValue,
String? title,
}) async {
final standards = PageStandards.of(context);
return await showCupertinoModalPopup<T>(
context: context,
builder: (context) => Container(
height: standards.scaledHeight(300),
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(standards.scaledRadius(20)),
),
),
child: Column(
children: [
if (title != null)
Container(
padding: standards.scaledPadding(EdgeInsets.all(16)),
child: Text(
title!,
style: standards.textStyle.copyWith(
fontSize: standards.fontSize + 4,
fontWeight: FontWeight.w600,
),
),
),
Expanded(
child: CupertinoPicker(
itemExtent: standards.scaledHeight(40),
scrollController: FixedExtentScrollController(
initialItem: selectedValue != null
? items.indexWhere((e) => e.value == selectedValue)
: 0,
),
onSelectedItemChanged: (index) {
Navigator.pop(context, items[index].value);
},
children: items
.map((item) => Center(
child: Text(
item.label,
style: standards.textStyle,
),
))
.toList(),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Container(
height: standards.scaledHeight(200),
color: standards.backgroundColor,
child: CupertinoPicker(
itemExtent: standards.scaledHeight(40),
scrollController: FixedExtentScrollController(
initialItem: selectedValue != null
? items.indexWhere((e) => e.value == selectedValue)
: 0,
),
onSelectedItemChanged: (index) {
onChanged?.call(items[index].value);
},
children: items
.map((item) => Center(
child: Text(
item.label,
style: standards.textStyle,
),
))
.toList(),
),
);
}
}
- Step 2: 提交标准选择器组件
git add lib/src/widgets/interactive/standard_picker.dart
git commit -m "feat: add StandardPicker component with PageStandards"
Task 14: 创建路由守卫
Files:
-
Create:
lib/src/standards/route_guard.dart -
Step 1: 创建 RouteGuard
import 'package:get/get.dart';
import 'package:mom_kitchen/src/standards/app_pages.dart';
import 'package:mom_kitchen/src/controllers/profile_controller.dart';
enum AuthLevel { none, optional, required }
class RouteGuard {
static bool canAccess(String route, {String? userId}) {
final pageInfo = PageRegistry.getPage(route);
if (pageInfo == null) return false;
final authLevel = pageInfo.metadata?['authLevel'] as AuthLevel? ?? AuthLevel.none;
switch (authLevel) {
case AuthLevel.none:
return true;
case AuthLevel.optional:
return true;
case AuthLevel.required:
return userId != null && userId.isNotEmpty;
}
}
static String? getRedirectRoute(String route, {String? userId}) {
if (canAccess(route, userId: userId)) return null;
return '/login';
}
static Future<bool> checkAuth(String route) async {
final profileController = Get.find<ProfileController>();
await profileController.checkLoginStatus();
final userId = profileController.user.value?.id;
return canAccess(route, userId: userId);
}
static Future<String?> getAuthRedirect(String route) async {
final profileController = Get.find<ProfileController>();
await profileController.checkLoginStatus();
final userId = profileController.user.value?.id;
return getRedirectRoute(route, userId: userId);
}
}
- Step 2: 提交路由守卫
git add lib/src/standards/route_guard.dart
git commit -m "feat: add RouteGuard for authentication"
Task 15: 重构 LoadingIndicator
Files:
-
Modify:
lib/src/widgets/loading_indicator.dart -
Step 1: 读取现有 LoadingIndicator
Run: Read lib/src/widgets/loading_indicator.dart
- Step 2: 重构 LoadingIndicator 使用 PageStandards
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class LoadingIndicator extends StatelessWidget {
final double size;
final Color? color;
const LoadingIndicator({
super.key,
this.size = 32,
this.color,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Center(
child: CupertinoActivityIndicator(
radius: standards.scaledRadius(size / 2),
color: color ?? standards.primaryColor,
),
);
}
}
- Step 3: 提交重构
git add lib/src/widgets/loading_indicator.dart
git commit -m "refactor: update LoadingIndicator to use PageStandards"
Task 16: 重构 ProductCard
Files:
-
Modify:
lib/src/widgets/product_card.dart -
Step 1: 读取现有 ProductCard
Run: Read lib/src/widgets/product_card.dart
- Step 2: 重构 ProductCard 使用 PageStandards
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
import 'package:mom_kitchen/src/controllers/home_controller.dart';
class ProductCard extends StatelessWidget {
final ProductModel product;
final VoidCallback? onTap;
final VoidCallback? onAddToCart;
const ProductCard({
super.key,
required this.product,
this.onTap,
this.onAddToCart,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(16)),
boxShadow: [
BoxShadow(
color: standards.textColor.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImage(standards),
_buildInfo(standards),
],
),
),
);
}
Widget _buildImage(PageStandards standards) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: standards.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.vertical(
top: Radius.circular(standards.scaledRadius(16)),
),
),
child: Center(
child: Text(
product.image,
style: TextStyle(fontSize: standards.scaledFontSize(48)),
),
),
),
);
}
Widget _buildInfo(PageStandards standards) {
return Padding(
padding: standards.scaledPadding(EdgeInsets.all(12)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: standards.textStyle.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: standards.scaledHeight(4)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'\$${product.price.toStringAsFixed(2)}',
style: standards.primaryTextStyle.copyWith(
fontWeight: FontWeight.bold,
),
),
if (onAddToCart != null)
GestureDetector(
onTap: onAddToCart,
child: Container(
padding: standards.scaledPadding(EdgeInsets.all(6)),
decoration: BoxDecoration(
color: standards.primaryColor,
borderRadius: BorderRadius.circular(
standards.scaledRadius(8),
),
),
child: Icon(
CupertinoIcons.plus,
color: CupertinoColors.white,
size: standards.fontSize,
),
),
),
],
),
],
),
);
}
}
- Step 3: 提交重构
git add lib/src/widgets/product_card.dart
git commit -m "refactor: update ProductCard to use PageStandards"
Task 17: 重构 HomePage 使用 GetX
Files:
-
Modify:
lib/src/pages/home_page.dart -
Step 1: 读取现有 HomePage
Run: Read lib/src/pages/home_page.dart
- Step 2: 重构 HomePage 使用 GetX 控制器
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/controllers/home_controller.dart';
import 'package:mom_kitchen/src/controllers/cart_controller.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
import 'package:mom_kitchen/src/widgets/product_card.dart';
import 'package:mom_kitchen/src/widgets/states/empty_state.dart';
import 'package:mom_kitchen/src/widgets/states/error_state.dart';
import 'package:mom_kitchen/src/widgets/loading_indicator.dart';
import 'package:mom_kitchen/src/widgets/responsive_grid.dart';
import 'package:mom_kitchen/src/widgets/adaptive_scaffold.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final homeController = Get.put(HomeController());
final cartController = Get.put(CartController());
final standards = PageStandards.of(context);
return CupertinoPageScaffold(
backgroundColor: standards.backgroundColor,
child: SafeArea(
child: Obx(() {
if (homeController.isLoading.value) {
return const LoadingIndicator();
}
if (homeController.errorMessage.value.isNotEmpty) {
return ErrorState(
message: homeController.errorMessage.value,
onRetry: () => homeController.loadProducts(),
);
}
return Column(
children: [
_buildSearchBar(homeController, standards),
_buildCategoryFilter(homeController, standards),
Expanded(
child: homeController.filteredProducts.isEmpty
? const EmptyState(
emoji: '🔍',
title: 'No products found',
message: 'Try a different search or category',
)
: _buildProductGrid(
homeController,
cartController,
standards,
),
),
],
);
}),
),
);
}
Widget _buildSearchBar(HomeController controller, PageStandards standards) {
return Padding(
padding: standards.scaledPadding(EdgeInsets.all(16)),
child: Container(
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
border: Border.all(
color: standards.textColor.withOpacity(0.1),
),
),
child: CupertinoSearchTextField(
placeholder: 'Search products...',
style: TextStyle(color: standards.textColor),
placeholderStyle: TextStyle(
color: standards.textColor.withOpacity(0.5),
),
decoration: null,
onChanged: controller.search,
),
),
);
}
Widget _buildCategoryFilter(HomeController controller, PageStandards standards) {
return Obx(() => Container(
height: standards.scaledHeight(44),
padding: EdgeInsets.symmetric(horizontal: standards.scaledWidth(16)),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: controller.categories.length + 1,
itemBuilder: (context, index) {
final isSelected = index == 0
? controller.selectedCategory.value.isEmpty
: controller.selectedCategory.value ==
controller.categories[index - 1];
return Padding(
padding: EdgeInsets.only(right: standards.scaledWidth(8)),
child: GestureDetector(
onTap: () {
if (index == 0) {
controller.selectCategory('');
} else {
controller.selectCategory(controller.categories[index - 1]);
}
},
child: Container(
padding: standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
decoration: BoxDecoration(
color: isSelected
? standards.primaryColor
: standards.backgroundColor,
borderRadius: BorderRadius.circular(
standards.scaledRadius(20),
),
border: Border.all(
color: isSelected
? standards.primaryColor
: standards.textColor.withOpacity(0.2),
),
),
child: Text(
index == 0 ? 'All' : controller.categories[index - 1],
style: standards.textStyle.copyWith(
color: isSelected
? CupertinoColors.white
: standards.textColor,
fontWeight: FontWeight.w500,
),
),
),
),
);
},
),
));
}
Widget _buildProductGrid(
HomeController homeController,
CartController cartController,
PageStandards standards,
) {
return ResponsiveGrid(
crossAxisCount: 2,
crossAxisCountMedium: 3,
crossAxisCountLarge: 4,
childAspectRatio: 0.75,
spacing: 12,
runSpacing: 12,
children: homeController.filteredProducts
.map((product) => ProductCard(
product: product,
onAddToCart: () => cartController.addProduct(product),
))
.toList(),
);
}
}
- Step 3: 提交重构
git add lib/src/pages/home_page.dart
git commit -m "refactor: update HomePage to use GetX state management"
Task 18: 更新 CHANGELOG.md
Files:
-
Modify:
CHANGELOG.md -
Step 1: 读取 CHANGELOG.md
Run: Read CHANGELOG.md
- Step 2: 添加版本更新日志
在文件顶部添加:
## [1.3.0] - 2026-04-08
### Added
- GetX 全局状态管理系统
- BaseController 基础控制器
- PagedController 分页控制器
- HomeController 首页控制器
- CartController 购物车控制器
- ProfileController 个人中心控制器
- 标准组件库
- StandardButton 标准按钮
- StandardTextField 标准输入框
- StandardCard 标准卡片
- StandardListTile 标准列表项
- 状态组件
- EmptyState 空状态
- ErrorState 错误状态
- 交互组件
- StandardDialog 标准对话框
- StandardBottomSheet 标准底部弹窗
- StandardPicker 标准选择器
- RouteGuard 路由守卫系统
### Changed
- 重构 HomePage 使用 GetX 状态管理
- 重构 ProductCard 使用 PageStandards
- 重构 LoadingIndicator 使用 PageStandards
### Technical
- 统一状态管理为 GetX
- 所有组件集成 PageStandards
- 实现路由守卫认证机制
- Step 3: 提交 CHANGELOG
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG for v1.3.0"
验收清单
- 所有控制器正常工作
- 所有组件使用 PageStandards
- 路由守卫正常拦截
- HomePage 使用 GetX
- 代码分析无错误
- 深色模式正常
- 多语言正常
创建时间: 2026-04-08 作者: AI Assistant