Files
kitchen/docs/superpowers/plans/2026-04-08-refactor-implementation.md

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