Flutter for OpenHarmony《淘淘购物》 分类页:从静态 UI 到动态交互的全面升级

Flutter for OpenHarmony《淘淘购物》 分类页:从静态 UI 到动态交互的全面升级

在电商应用中,商品分类页 是用户发现和筛选商品的核心路径之一。一个设计精良的分类系统不仅能提升用户体验,还能显著提高转化率。本文将基于您提供的完整

Flutter 代码(import+pa.txt),对 CategoryScreen 进行深度剖析,重点讲解其 UI
结构、交互逻辑、数据驱动机制以及视觉动效
的实现细节,并对比基础版本与增强版本的关键差异。


完整效果展示



完整代码展示

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const ShoppingApp());
}

class ShoppingApp extends StatelessWidget {
  const ShoppingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '淘淘购物',
      theme: ThemeData(
        primaryColor: Colors.orange,
        scaffoldBackgroundColor: Colors.grey[100],
        useMaterial3: true,
      ),
      home: const MainScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

// 搜索结果页面
class SearchResultScreen extends StatelessWidget {
  final String query;

  const SearchResultScreen({super.key, required this.query});

  @override
  Widget build(BuildContext context) {
    // 过滤商品
    final filteredProducts = _allProducts.where((product) {
      return product.name.toLowerCase().contains(query.toLowerCase());
    }).toList();

    return Scaffold(
      appBar: AppBar(
        title: Text('搜索: $query'),
        backgroundColor: Colors.orange,
      ),
      body: filteredProducts.isEmpty
          ? const Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.search_off, size: 80, color: Colors.grey),
                  SizedBox(height: 20),
                  Text(
                    '未找到相关商品',
                    style: TextStyle(fontSize: 18, color: Colors.grey),
                  ),
                ],
              ),
            )
          : GridView.builder(
              padding: const EdgeInsets.all(10),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 0.75,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              itemCount: filteredProducts.length,
              itemBuilder: (context, index) {
                return ProductCard(product: filteredProducts[index]);
              },
            ),
    );
  }
}

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _currentIndex = 0;
  final List<Widget> _screens = [
    const HomeScreen(),
    const CategoryScreen(),
    const CartScreen(),
    const ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _screens[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.orange,
        unselectedItemColor: Colors.grey,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart),
            label: '购物车',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

// 首页
class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淘淘购物'),
        backgroundColor: Colors.orange,
        elevation: 0,
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {
              _showSearchDialog(context);
            },
          ),
          IconButton(
            icon: const Icon(Icons.message),
            onPressed: () {},
          ),
        ],
      ),
      body: ListView(
        children: [
          // 搜索框
          GestureDetector(
            onTap: () => _showSearchDialog(context),
            child: Container(
              padding: const EdgeInsets.all(10),
              color: Colors.orange,
              child: Container(
                padding:
                    const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Row(
                  children: const [
                    Icon(Icons.search, color: Colors.grey),
                    SizedBox(width: 10),
                    Text(
                      '搜索宝贝',
                      style: TextStyle(color: Colors.grey, fontSize: 16),
                    ),
                  ],
                ),
              ),
            ),
          ),

          // 轮播图区域
          Container(
            height: 150,
            margin: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: Colors.orange[300],
              borderRadius: BorderRadius.circular(10),
            ),
            child: const Center(
              child: Text(
                '轮播图区域',
                style: TextStyle(color: Colors.white, fontSize: 20),
              ),
            ),
          ),

          // 快捷入口
          Container(
            padding: const EdgeInsets.all(15),
            color: Colors.white,
            child: GridView.count(
              crossAxisCount: 5,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              children: const [
                QuickEntry(Icons.shopping_bag, '天猫'),
                QuickEntry(Icons.card_giftcard, '聚划算'),
                QuickEntry(Icons.local_offer, '优惠券'),
                QuickEntry(Icons.phone_android, '数码'),
                QuickEntry(Icons.style, '服饰'),
                QuickEntry(Icons.home, '家居'),
                QuickEntry(Icons.restaurant, '美食'),
                QuickEntry(Icons.flight, '旅行'),
                QuickEntry(Icons.sports_basketball, '运动'),
                QuickEntry(Icons.book, '图书'),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 推荐商品
          Container(
            color: Colors.white,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Padding(
                  padding: EdgeInsets.all(15),
                  child: Text(
                    '为你推荐',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                GridView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  padding: const EdgeInsets.all(10),
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    childAspectRatio: 0.75,
                    crossAxisSpacing: 10,
                    mainAxisSpacing: 10,
                  ),
                  itemCount: _recommendedProducts.length,
                  itemBuilder: (context, index) {
                    return ProductCard(product: _recommendedProducts[index]);
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // 显示搜索对话框
  void _showSearchDialog(BuildContext context) {
    final TextEditingController searchController = TextEditingController();

    showSearch(
      context: context,
      delegate: ProductSearchDelegate(),
    );
  }
}

// 搜索委托
class ProductSearchDelegate extends SearchDelegate<String> {
  @override
  List<Widget> buildActions(BuildContext context) {
    return [
      IconButton(
        icon: const Icon(Icons.clear),
        onPressed: () {
          query = '';
        },
      ),
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    return IconButton(
      icon: const Icon(Icons.arrow_back),
      onPressed: () {
        close(context, '');
      },
    );
  }

  @override
  Widget buildResults(BuildContext context) {
    final filteredProducts = _allProducts.where((product) {
      return product.name.toLowerCase().contains(query.toLowerCase());
    }).toList();

    return filteredProducts.isEmpty
        ? const Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.search_off, size: 80, color: Colors.grey),
                SizedBox(height: 20),
                Text(
                  '未找到相关商品',
                  style: TextStyle(fontSize: 18, color: Colors.grey),
                ),
              ],
            ),
          )
        : GridView.builder(
            padding: const EdgeInsets.all(10),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              childAspectRatio: 0.75,
              crossAxisSpacing: 10,
              mainAxisSpacing: 10,
            ),
            itemCount: filteredProducts.length,
            itemBuilder: (context, index) {
              return ProductCard(product: filteredProducts[index]);
            },
          );
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    final suggestionList = query.isEmpty
        ? _allProducts.take(6).toList()
        : _allProducts.where((product) {
            return product.name.toLowerCase().contains(query.toLowerCase());
          }).toList();

    return ListView.builder(
      itemCount: suggestionList.length,
      itemBuilder: (context, index) {
        final product = suggestionList[index];
        return ListTile(
          leading: ClipRRect(
            borderRadius: BorderRadius.circular(5),
            child: Image.network(
              product.imageUrl,
              width: 50,
              height: 50,
              fit: BoxFit.cover,
              errorBuilder: (context, error, stackTrace) {
                return Container(
                  width: 50,
                  height: 50,
                  decoration: BoxDecoration(
                    color: Colors.orange[100],
                    borderRadius: BorderRadius.circular(5),
                  ),
                  child: const Icon(Icons.image, color: Colors.orange),
                );
              },
            ),
          ),
          title: Text(product.name),
          subtitle: Text('¥${product.price.toStringAsFixed(2)}'),
          trailing: const Icon(Icons.arrow_forward_ios, size: 16),
          onTap: () {
            query = product.name;
            showResults(context);
          },
        );
      },
    );
  }
}

// 快捷入口组件
class QuickEntry extends StatelessWidget {
  final IconData icon;
  final String label;

  const QuickEntry(this.icon, this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icon, color: Colors.orange, size: 30),
        const SizedBox(height: 5),
        Text(label, style: const TextStyle(fontSize: 12)),
      ],
    );
  }
}

// 分类页
class CategoryScreen extends StatefulWidget {
  const CategoryScreen({super.key});

  @override
  State<CategoryScreen> createState() => _CategoryScreenState();
}

class _CategoryScreenState extends State<CategoryScreen> {
  int _selectedCategoryIndex = 0;

  // 定义子分类对应的图片URL
  final Map<String, String> _categoryImages = {
    // 手机数码
    '手机': 'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=200',
    '平板': 'https://images.unsplash.com/photo-1544244015-0df4b3ffc6b0?w=200',
    '耳机': 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=200',
    '充电宝': 'https://images.unsplash.com/photo-1609091839311-d5365f9ff1c5?w=200',
    '数据线': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=200',
    '保护壳': 'https://images.unsplash.com/photo-1601784551446-20c9e07cdbdb?w=200',
    '智能手表':
        'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=200',
    '蓝牙音箱':
        'https://images.unsplash.com/photo-1589492477829-5e65395b66cc?w=200',
    '路由器': 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?w=200',
    '数码相机':
        'https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?w=200',
    '游戏手柄':
        'https://images.unsplash.com/photo-1592840496694-26d035b52b48?w=200',
    '对讲机': 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=200',
    '行车记录仪':
        'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=200',
    '电子烟': 'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=200',
    '智能门锁':
        'https://images.unsplash.com/photo-1589492477829-5e65395b66cc?w=200',
    '无人机': 'https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?w=200',
    'VR眼镜':
        'https://images.unsplash.com/photo-1592840496694-26d035b52b48?w=200',
    // 家用电器
    '冰箱': 'https://images.unsplash.com/photo-1584568694244-14fbdf83bd30?w=200',
    '洗衣机': 'https://images.unsplash.com/photo-1626806819282-2c1dc01a5e0c?w=200',
    '空调': 'https://images.unsplash.com/photo-1578632292335-df3abbb0d586?w=200',
    '电视': 'https://images.unsplash.com/photo-1593359677879-a4bb92f829d1?w=200',
    '微波炉': 'https://images.unsplash.com/photo-1586953208448-b95a79798f07?w=200',
    '电饭煲': 'https://images.unsplash.com/photo-1584990347449-a2d4e2b40c95?w=200',
    '热水器': 'https://images.unsplash.com/photo-1584622650111-993a426fbf0a?w=200',
    '烤箱': 'https://images.unsplash.com/photo-1558346490-a72e53ae2d4f?w=200',
    '吸尘器': 'https://images.unsplash.com/photo-1558317374-067fb5f30001?w=200',
    '豆浆机': 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=200',
    '电压力锅':
        'https://images.unsplash.com/photo-1584990347449-a2d4e2b40c95?w=200',
    '扫地机器人': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=200',
    '净水器': 'https://images.unsplash.com/photo-1548839140-29a749e1cf4d?w=200',
    '空气净化器': 'https://images.unsplash.com/photo-1558346490-a72e53ae2d4f?w=200',
    // 电脑办公
    '笔记本': 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=200',
    '台式机': 'https://images.unsplash.com/photo-1587202372775-e229f172b9d7?w=200',
    '键盘': 'https://images.unsplash.com/photo-1587829741301-dc798b91a603?w=200',
    '鼠标': 'https://images.unsplash.com/photo-1527864550417-7fd91fc51a46?w=200',
    '显示器': 'https://images.unsplash.com/photo-1527443224154-c4a3942d3acf?w=200',
    '音响': 'https://images.unsplash.com/photo-1589492477829-5e65395b66cc?w=200',
    '打印机': 'https://images.unsplash.com/photo-1565437837960-0e65c5b4382c?w=200',
    'U盘': 'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=200',
    '移动硬盘': 'https://images.unsplash.com/photo-1563770095-39d468f95c34?w=200',
    '交换机': 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?w=200',
    '投影仪': 'https://images.unsplash.com/photo-1478720568477-152d9b164e26?w=200',
    '扫描仪': 'https://images.unsplash.com/photo-1558346490-a72e53ae2d4f?w=200',
    '摄像头': 'https://images.unsplash.com/photo-1585659722983-3a675dabf23d?w=200',
    // 家居家装
    '沙发': 'https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=200',
    '床': 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?w=200',
    '衣柜': 'https://images.unsplash.com/photo-1558997519-83ea9252edf8?w=200',
    '餐桌': 'https://images.unsplash.com/photo-1617806118233-18e1de247200?w=200',
    '椅子': 'https://images.unsplash.com/photo-1592078615290-033ee584e267?w=200',
    '书桌': 'https://images.unsplash.com/photo-1518455027359-f3f8164ba6bd?w=200',
    '茶几': 'https://images.unsplash.com/photo-1533090481720-856c6e3c1fdc?w=200',
    '电视柜': 'https://images.unsplash.com/photo-1558997519-83ea9252edf8?w=200',
    '鞋柜': 'https://images.unsplash.com/photo-1584990347449-a2d4e2b40c95?w=200',
    '床垫': 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?w=200',
    '窗帘': 'https://images.unsplash.com/photo-1617806118233-18e1de247200?w=200',
    '地毯': 'https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=200',
    '灯具': 'https://images.unsplash.com/photo-1524484485831-a92ffc0de03f?w=200',
    '装饰画': 'https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=200',
    // 服装服饰
    '男装': 'https://images.unsplash.com/photo-1594938298603-c8148c4dae35?w=200',
    '女装': 'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=200',
    '童装': 'https://images.unsplash.com/photo-1503919545889-aef636e10ad4?w=200',
    '内衣': 'https://images.unsplash.com/photo-1582552938357-32b906df40cb?w=200',
    '运动服': 'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=200',
    '牛仔裤': 'https://images.unsplash.com/photo-1542272454315-4c01d7abdf4a?w=200',
    'T恤': 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=200',
    '衬衫': 'https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=200',
    '连衣裙': 'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=200',
    '羽绒服': 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?w=200',
    '夹克': 'https://images.unsplash.com/photo-1551028719-00167b16eac5?w=200',
    '卫衣': 'https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=200',
    '毛衣': 'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=200',
    '西装': 'https://images.unsplash.com/photo-1593032465175-481ac7f401a0?w=200',
    // 鞋靴箱包
    '运动鞋': 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=200',
    '皮鞋': 'https://images.unsplash.com/photo-1614252369475-531eba835eb1?w=200',
    '靴子': 'https://images.unsplash.com/photo-1608256246200-53e635b5b65f?w=200',
    '凉鞋': 'https://images.unsplash.com/photo-1543163521-1bf539c55dd2?w=200',
    '拖鞋': 'https://images.unsplash.com/photo-1617806118233-18e1de247200?w=200',
    '休闲鞋': 'https://images.unsplash.com/photo-1549298916-b41d501d3772?w=200',
    '高跟鞋': 'https://images.unsplash.com/photo-1543163521-1bf539c55dd2?w=200',
    '帆布鞋': 'https://images.unsplash.com/photo-1460353581641-37baddab0fa2?w=200',
    '登山鞋': 'https://images.unsplash.com/photo-1606107557195-0e29a4b5b4aa?w=200',
    '篮球鞋': 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=200',
    '足球鞋': 'https://images.unsplash.com/photo-1489987707025-afc232f7ea0f?w=200',
    '跑鞋': 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=200',
    '板鞋': 'https://images.unsplash.com/photo-1460353581641-37baddab0fa2?w=200',
    '马丁靴': 'https://images.unsplash.com/photo-1608256246200-53e635b5b65f?w=200',
    // 运动户外
    '篮球': 'https://images.unsplash.com/photo-1535248344320-4a1b93f8e85f?w=200',
    '足球': 'https://images.unsplash.com/photo-1519861531473-9200262188bf?w=200',
    '羽毛球': 'https://images.unsplash.com/photo-1622279457486-62dcc4a431d6?w=200',
    '网球': 'https://images.unsplash.com/photo-1622279457486-62dcc4a431d6?w=200',
    '乒乓球': 'https://images.unsplash.com/photo-1535248344320-4a1b93f8e85f?w=200',
    '游泳': 'https://images.unsplash.com/photo-1530549387789-4c1017266635?w=200',
    '健身': 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=200',
    '瑜伽': 'https://images.unsplash.com/photo-1544367563-12123d896889?w=200',
    '跑步': 'https://images.unsplash.com/photo-1552674605-db6ffd4facb5?w=200',
    '骑行': 'https://images.unsplash.com/photo-1485965120184-e224f7a1db69?w=200',
    '登山': 'https://images.unsplash.com/photo-1551632811-561732d1e306?w=200',
    '滑雪': 'https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=200',
    '钓鱼': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=200',
    '滑板': 'https://images.unsplash.com/photo-1460353581641-37baddab0fa2?w=200',
    // 美妆个护
    '护肤': 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=200',
    '彩妆': 'https://images.unsplash.com/photo-1596462502278-27bfdc403348?w=200',
    '香水': 'https://images.unsplash.com/photo-1541643600914-78b084683601?w=200',
    '面膜': 'https://images.unsplash.com/photo-1596755389377-c760f65e806e?w=200',
    '洗护': 'https://images.unsplash.com/photo-1598440947619-2c35fc9aa908?w=200',
    '美发': 'https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?w=200',
    '美甲': 'https://images.unsplash.com/photo-1604654894610-df63bc536371?w=200',
    '美容仪': 'https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=200',
    '口腔护理':
        'https://images.unsplash.com/photo-1606811841689-23dfddce3e95?w=200',
    '身体护理': 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=200',
    '眼部护理':
        'https://images.unsplash.com/photo-1576426863848-c21f53c60b19?w=200',
    '防晒': 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=200',
    '脱毛': 'https://images.unsplash.com/photo-1598440947619-2c35fc9aa908?w=200',
    '足部护理':
        'https://images.unsplash.com/photo-1516975080664-ed2fc6a32937?w=200',
    // 食品生鲜
    '水果': 'https://images.unsplash.com/photo-1619566636858-adf3ef46400b?w=200',
    '蔬菜': 'https://images.unsplash.com/photo-1540420773420-3366772f4999?w=200',
    '肉类': 'https://images.unsplash.com/photo-1544025162-d76694265947?w=200',
    '海鲜': 'https://images.unsplash.com/photo-1565680018434-b513d5e5fd47?w=200',
    '零食': 'https://images.unsplash.com/photo-1563729784474-d77dbb933a9e?w=200',
    '饮料': 'https://images.unsplash.com/photo-1544145945-f90425340c7e?w=200',
    '粮油': 'https://images.unsplash.com/photo-1586201375761-83865001e31c?w=200',
    '调味品': 'https://images.unsplash.com/photo-1596040033229-a9821ebd058d?w=200',
    '速食': 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=200',
    '酒水': 'https://images.unsplash.com/photo-1569529465841-dfecdab7503b?w=200',
    '茶叶': 'https://images.unsplash.com/photo-1556679343-c7306c1976bc?w=200',
    '进口食品':
        'https://images.unsplash.com/photo-1563729784474-d77dbb933a9e?w=200',
    '地方特产':
        'https://images.unsplash.com/photo-1604908176997-125f25cc6f3d?w=200',
    '生鲜': 'https://images.unsplash.com/photo-1565680018434-b513d5e5fd47?w=200',
  };

  // 定义每个主分类对应的子分类
  final Map<int, List<String>> _categorySubCategories = {
    0: [
      '手机',
      '平板',
      '耳机',
      '充电宝',
      '数据线',
      '保护壳',
      '智能手表',
      '蓝牙音箱',
      '路由器',
      '数码相机',
      '游戏手柄',
      '智能门锁',
      '无人机',
      'VR眼镜'
    ],
    1: [
      '手机',
      '平板',
      '耳机',
      '充电宝',
      '数据线',
      '保护壳',
      '智能手表',
      '蓝牙音箱',
      '路由器',
      '数码相机',
      '游戏手柄',
      '对讲机',
      '行车记录仪',
      '电子烟'
    ],
    2: [
      '冰箱',
      '洗衣机',
      '空调',
      '电视',
      '微波炉',
      '电饭煲',
      '热水器',
      '烤箱',
      '吸尘器',
      '豆浆机',
      '电压力锅',
      '扫地机器人',
      '净水器',
      '空气净化器'
    ],
    3: [
      '笔记本',
      '台式机',
      '键盘',
      '鼠标',
      '显示器',
      '音响',
      '打印机',
      'U盘',
      '移动硬盘',
      '路由器',
      '交换机',
      '投影仪',
      '扫描仪',
      '摄像头'
    ],
    4: [
      '沙发',
      '床',
      '衣柜',
      '餐桌',
      '椅子',
      '书桌',
      '茶几',
      '电视柜',
      '鞋柜',
      '床垫',
      '窗帘',
      '地毯',
      '灯具',
      '装饰画'
    ],
    5: [
      '男装',
      '女装',
      '童装',
      '内衣',
      '运动服',
      '牛仔裤',
      'T恤',
      '衬衫',
      '连衣裙',
      '羽绒服',
      '夹克',
      '卫衣',
      '毛衣',
      '西装'
    ],
    6: [
      '运动鞋',
      '皮鞋',
      '靴子',
      '凉鞋',
      '拖鞋',
      '休闲鞋',
      '高跟鞋',
      '帆布鞋',
      '登山鞋',
      '篮球鞋',
      '足球鞋',
      '跑鞋',
      '板鞋',
      '马丁靴'
    ],
    7: [
      '篮球',
      '足球',
      '羽毛球',
      '网球',
      '乒乓球',
      '游泳',
      '健身',
      '瑜伽',
      '跑步',
      '骑行',
      '登山',
      '滑雪',
      '钓鱼',
      '滑板'
    ],
    8: [
      '护肤',
      '彩妆',
      '香水',
      '面膜',
      '洗护',
      '美发',
      '美甲',
      '美容仪',
      '口腔护理',
      '身体护理',
      '眼部护理',
      '防晒',
      '脱毛',
      '足部护理'
    ],
    9: [
      '水果',
      '蔬菜',
      '肉类',
      '海鲜',
      '零食',
      '饮料',
      '粮油',
      '调味品',
      '速食',
      '酒水',
      '茶叶',
      '进口食品',
      '地方特产',
      '生鲜'
    ],
  };

  List<String> get _currentSubCategories {
    return _categorySubCategories[_selectedCategoryIndex] ?? _subCategories;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('商品分类'),
        backgroundColor: Colors.orange,
      ),
      body: Row(
        children: [
          // 左侧分类列表
          Container(
            width: 100,
            color: Colors.grey[200],
            child: ListView.builder(
              itemCount: _categories.length,
              itemBuilder: (context, index) {
                final isSelected = index == _selectedCategoryIndex;
                return AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  padding: const EdgeInsets.symmetric(vertical: 20),
                  decoration: BoxDecoration(
                    color: isSelected ? Colors.white : Colors.transparent,
                    border: isSelected
                        ? const Border(
                            left: BorderSide(color: Colors.orange, width: 3))
                        : null,
                  ),
                  child: InkWell(
                    onTap: () {
                      setState(() {
                        _selectedCategoryIndex = index;
                      });
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: Text('已选择 ${_categories[index]}'),
                          duration: const Duration(milliseconds: 800),
                          behavior: SnackBarBehavior.floating,
                        ),
                      );
                    },
                    splashColor: Colors.orange.withOpacity(0.1),
                    highlightColor: Colors.orange.withOpacity(0.05),
                    child: Center(
                      child: Text(
                        _categories[index],
                        style: TextStyle(
                          color: isSelected ? Colors.orange : Colors.black87,
                          fontWeight:
                              isSelected ? FontWeight.bold : FontWeight.normal,
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
          // 右侧商品列表
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(10),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 0.8,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              itemCount: _currentSubCategories.length,
              itemBuilder: (context, index) {
                return SubCategoryCard(
                  category: _currentSubCategories[index],
                  imageUrl: _categoryImages[_currentSubCategories[index]] ??
                      'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=200',
                  onTap: () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('查看 ${_currentSubCategories[index]}'),
                        duration: const Duration(milliseconds: 800),
                        behavior: SnackBarBehavior.floating,
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// 子分类卡片
class SubCategoryCard extends StatefulWidget {
  final String category;
  final String imageUrl;
  final VoidCallback onTap;

  const SubCategoryCard({
    super.key,
    required this.category,
    required this.imageUrl,
    required this.onTap,
  });

  @override
  State<SubCategoryCard> createState() => _SubCategoryCardState();
}

class _SubCategoryCardState extends State<SubCategoryCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;
  bool _isPressed = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 100),
      vsync: this,
    );
    _scaleAnimation = Tween<double>(begin: 1.0, end: 0.92).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) {
        setState(() {
          _isPressed = true;
        });
        _animationController.forward();
      },
      onTapUp: (_) {
        setState(() {
          _isPressed = false;
        });
        _animationController.reverse();
        widget.onTap();
      },
      onTapCancel: () {
        setState(() {
          _isPressed = false;
        });
        _animationController.reverse();
      },
      child: ScaleTransition(
        scale: _scaleAnimation,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          padding: const EdgeInsets.all(8),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(10),
            border: Border.all(
              color: _isPressed ? Colors.orange : Colors.transparent,
              width: 2,
            ),
            boxShadow: _isPressed
                ? [
                    BoxShadow(
                      color: Colors.orange.withOpacity(0.3),
                      blurRadius: 8,
                      offset: const Offset(0, 2),
                    ),
                  ]
                : [
                    BoxShadow(
                      color: Colors.grey.withOpacity(0.1),
                      blurRadius: 3,
                      offset: const Offset(0, 1),
                    ),
                  ],
          ),
          child: Column(
            children: [
              Container(
                width: 50,
                height: 50,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  border: _isPressed
                      ? Border.all(color: Colors.orange, width: 2)
                      : null,
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(10),
                  child: Image.network(
                    widget.imageUrl,
                    fit: BoxFit.cover,
                    errorBuilder: (context, error, stackTrace) {
                      return Container(
                        width: 50,
                        height: 50,
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: _isPressed
                                ? [Colors.orange, Colors.orange[700]!]
                                : [Colors.orange[100]!, Colors.orange[200]!],
                            begin: Alignment.topLeft,
                            end: Alignment.bottomRight,
                          ),
                          borderRadius: BorderRadius.circular(10),
                        ),
                        child: Icon(
                          Icons.image,
                          color: _isPressed ? Colors.white : Colors.orange,
                          size: _isPressed ? 28 : 24,
                        ),
                      );
                    },
                    loadingBuilder: (context, child, loadingProgress) {
                      if (loadingProgress == null) return child;
                      return Container(
                        width: 50,
                        height: 50,
                        decoration: BoxDecoration(
                          color: Colors.orange[100],
                          borderRadius: BorderRadius.circular(10),
                        ),
                        child: const CircularProgressIndicator(
                          strokeWidth: 2,
                          color: Colors.orange,
                        ),
                      );
                    },
                  ),
                ),
              ),
              const SizedBox(height: 8),
              Text(
                widget.category,
                style: TextStyle(
                  fontSize: 12,
                  color: _isPressed ? Colors.orange : Colors.black87,
                  fontWeight: _isPressed ? FontWeight.bold : FontWeight.normal,
                ),
                textAlign: TextAlign.center,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 购物车页
class CartScreen extends StatefulWidget {
  const CartScreen({super.key});

  @override
  State<CartScreen> createState() => _CartScreenState();
}

class _CartScreenState extends State<CartScreen> {
  double _totalPrice = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
        backgroundColor: Colors.orange,
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _cartItems.length,
              itemBuilder: (context, index) {
                return CartItemCard(
                  item: _cartItems[index],
                  onQuantityChanged: (quantity) {
                    setState(() {
                      _totalPrice = _calculateTotal();
                    });
                  },
                );
              },
            ),
          ),
          // 底部结算栏
          Container(
            padding: const EdgeInsets.all(15),
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.3),
                  blurRadius: 5,
                  offset: const Offset(0, -2),
                ),
              ],
            ),
            child: Row(
              children: [
                const Text(
                  '合计: ',
                  style: TextStyle(fontSize: 16),
                ),
                Text(
                  '¥$_totalPrice',
                  style: const TextStyle(
                    fontSize: 24,
                    color: Colors.orange,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const Spacer(),
                ElevatedButton(
                  onPressed: () {},
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.orange,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 40,
                      vertical: 12,
                    ),
                  ),
                  child: const Text('结算', style: TextStyle(fontSize: 16)),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  double _calculateTotal() {
    return _cartItems.fold(
        0.0, (sum, item) => sum + item.price * item.quantity);
  }
}

// 我的页面
class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('我的淘淘'),
        backgroundColor: Colors.orange,
      ),
      body: ListView(
        children: [
          // 用户信息
          Container(
            padding: const EdgeInsets.all(20),
            color: Colors.orange,
            child: Column(
              children: [
                Container(
                  width: 80,
                  height: 80,
                  decoration: const BoxDecoration(
                    color: Colors.white,
                    shape: BoxShape.circle,
                  ),
                  child:
                      const Icon(Icons.person, size: 50, color: Colors.orange),
                ),
                const SizedBox(height: 10),
                const Text(
                  '用户昵称',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 5),
                Container(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.white),
                    borderRadius: BorderRadius.circular(15),
                  ),
                  child: const Text(
                    '登录/注册',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 订单状态
          Container(
            padding: const EdgeInsets.all(15),
            color: Colors.white,
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text(
                      '我的订单',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Row(
                      children: const [
                        Text('全部订单', style: TextStyle(color: Colors.grey)),
                        Icon(Icons.chevron_right, color: Colors.grey),
                      ],
                    ),
                  ],
                ),
                const SizedBox(height: 15),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: const [
                    OrderStatusIcon(Icons.payment, '待付款'),
                    OrderStatusIcon(Icons.inventory, '待发货'),
                    OrderStatusIcon(Icons.local_shipping, '待收货'),
                    OrderStatusIcon(Icons.star, '待评价'),
                    OrderStatusIcon(Icons.replay, '退款/售后'),
                  ],
                ),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 功能列表
          Container(
            color: Colors.white,
            child: Column(
              children: const [
                ListTile(
                  leading: Icon(Icons.favorite_border, color: Colors.orange),
                  title: Text('我的收藏'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.location_on, color: Colors.orange),
                  title: Text('收货地址'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.help_outline, color: Colors.orange),
                  title: Text('联系客服'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.settings, color: Colors.orange),
                  title: Text('设置'),
                  trailing: Icon(Icons.chevron_right),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 订单状态图标
class OrderStatusIcon extends StatelessWidget {
  final IconData icon;
  final String label;

  const OrderStatusIcon(this.icon, this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icon, color: Colors.orange, size: 28),
        const SizedBox(height: 5),
        Text(label, style: const TextStyle(fontSize: 12)),
      ],
    );
  }
}

// 商品卡片
class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({super.key, required this.product});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.2),
            blurRadius: 5,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: ClipRRect(
              borderRadius:
                  const BorderRadius.vertical(top: Radius.circular(10)),
              child: Image.network(
                product.imageUrl,
                fit: BoxFit.cover,
                width: double.infinity,
                height: double.infinity,
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    color: Colors.orange[100],
                    child: const Center(
                      child: Icon(Icons.image, color: Colors.orange, size: 50),
                    ),
                  );
                },
                loadingBuilder: (context, child, loadingProgress) {
                  if (loadingProgress == null) return child;
                  return Container(
                    color: Colors.orange[100],
                    child: const Center(
                      child: CircularProgressIndicator(color: Colors.orange),
                    ),
                  );
                },
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: const TextStyle(fontSize: 14),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 5),
                Row(
                  children: [
                    Text(
                      '¥${product.price.toStringAsFixed(2)}',
                      style: const TextStyle(
                        color: Colors.orange,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(width: 5),
                    Text(
                      '${product.sales}人付款',
                      style: const TextStyle(fontSize: 12, color: Colors.grey),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 购物车商品卡片
class CartItemCard extends StatefulWidget {
  final CartItem item;
  final Function(int) onQuantityChanged;

  const CartItemCard({
    super.key,
    required this.item,
    required this.onQuantityChanged,
  });

  @override
  State<CartItemCard> createState() => _CartItemCardState();
}

class _CartItemCardState extends State<CartItemCard> {
  bool _isChecked = true;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(10),
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.2),
            blurRadius: 5,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Row(
        children: [
          Checkbox(
            value: _isChecked,
            onChanged: (value) {
              setState(() {
                _isChecked = value ?? false;
              });
            },
          ),
          ClipRRect(
            borderRadius: BorderRadius.circular(5),
            child: Image.network(
              widget.item.imageUrl,
              width: 80,
              height: 80,
              fit: BoxFit.cover,
              errorBuilder: (context, error, stackTrace) {
                return Container(
                  width: 80,
                  height: 80,
                  decoration: BoxDecoration(
                    color: Colors.orange[100],
                    borderRadius: BorderRadius.circular(5),
                  ),
                  child: const Icon(Icons.image, color: Colors.orange),
                );
              },
              loadingBuilder: (context, child, loadingProgress) {
                if (loadingProgress == null) return child;
                return Container(
                  width: 80,
                  height: 80,
                  decoration: BoxDecoration(
                    color: Colors.orange[100],
                    borderRadius: BorderRadius.circular(5),
                  ),
                  child: const CircularProgressIndicator(strokeWidth: 2),
                );
              },
            ),
          ),
          const SizedBox(width: 10),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  widget.item.name,
                  style: const TextStyle(fontSize: 14),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 10),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      '¥${widget.item.price.toStringAsFixed(2)}',
                      style: const TextStyle(
                        color: Colors.orange,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Row(
                      children: [
                        IconButton(
                          icon: const Icon(Icons.remove, size: 18),
                          onPressed: () {
                            if (widget.item.quantity > 1) {
                              widget.item.quantity--;
                              widget.onQuantityChanged(widget.item.quantity);
                            }
                          },
                        ),
                        Text('${widget.item.quantity}'),
                        IconButton(
                          icon: const Icon(Icons.add, size: 18),
                          onPressed: () {
                            widget.item.quantity++;
                            widget.onQuantityChanged(widget.item.quantity);
                          },
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 数据模型
class Product {
  final String name;
  final double price;
  final int sales;
  final String imageUrl;

  Product({
    required this.name,
    required this.price,
    required this.sales,
    required this.imageUrl,
  });
}

class CartItem {
  final String name;
  final double price;
  final String imageUrl;
  int quantity;

  CartItem({
    required this.name,
    required this.price,
    required this.imageUrl,
    this.quantity = 1,
  });
}

// 所有商品数据(用于搜索)
final List<Product> _allProducts = [
  Product(
      name: '新款智能手机',
      price: 2999.00,
      sales: 1000,
      imageUrl:
          'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=400'),
  Product(
      name: '无线蓝牙耳机',
      price: 199.00,
      sales: 5000,
      imageUrl:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400'),
  Product(
      name: '智能手表',
      price: 899.00,
      sales: 2000,
      imageUrl:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=400'),
  Product(
      name: '运动鞋',
      price: 399.00,
      sales: 3000,
      imageUrl:
          'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400'),
  Product(
      name: '男士休闲裤',
      price: 159.00,
      sales: 1500,
      imageUrl:
          'https://images.unsplash.com/photo-1624378439575-d8705ad7ae80?w=400'),
  Product(
      name: '女士连衣裙',
      price: 299.00,
      sales: 2500,
      imageUrl:
          'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=400'),
  Product(
      name: '笔记本电脑',
      price: 5999.00,
      sales: 800,
      imageUrl:
          'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=400'),
  Product(
      name: '平板电脑',
      price: 3299.00,
      sales: 1200,
      imageUrl:
          'https://images.unsplash.com/photo-1544244015-0df4b3ffc6b0?w=400'),
  Product(
      name: '数码相机',
      price: 4599.00,
      sales: 500,
      imageUrl:
          'https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?w=400'),
  Product(
      name: '游戏手柄',
      price: 299.00,
      sales: 1800,
      imageUrl:
          'https://images.unsplash.com/photo-1592840496694-26d035b52b48?w=400'),
  Product(
      name: '智能音箱',
      price: 499.00,
      sales: 2200,
      imageUrl:
          'https://images.unsplash.com/photo-1589492477829-5e65395b66cc?w=400'),
  Product(
      name: '无线鼠标',
      price: 99.00,
      sales: 4000,
      imageUrl:
          'https://images.unsplash.com/photo-1527864550417-7fd91fc51a46?w=400'),
  Product(
      name: '机械键盘',
      price: 399.00,
      sales: 1500,
      imageUrl:
          'https://images.unsplash.com/photo-1587829741301-dc798b91a603?w=400'),
  Product(
      name: '显示器',
      price: 1299.00,
      sales: 900,
      imageUrl:
          'https://images.unsplash.com/photo-1527443224154-c4a3942d3acf?w=400'),
  Product(
      name: '路由器',
      price: 199.00,
      sales: 2500,
      imageUrl:
          'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?w=400'),
  Product(
      name: '充电宝',
      price: 129.00,
      sales: 6000,
      imageUrl:
          'https://images.unsplash.com/photo-1609091839311-d5365f9ff1c5?w=400'),
  Product(
      name: '数据线',
      price: 29.00,
      sales: 8000,
      imageUrl:
          'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400'),
  Product(
      name: '手机壳',
      price: 39.00,
      sales: 7000,
      imageUrl:
          'https://images.unsplash.com/photo-1601784551446-20c9e07cdbdb?w=400'),
  Product(
      name: '钢化膜',
      price: 19.00,
      sales: 9000,
      imageUrl:
          'https://images.unsplash.com/photo-1598327105666-5b89351aff4e?w=400'),
  Product(
      name: '耳机支架',
      price: 49.00,
      sales: 3000,
      imageUrl:
          'https://images.unsplash.com/photo-1613040809024-b4ef7ba99bc3?w=400'),
];

// 推荐商品
final List<Product> _recommendedProducts = _allProducts.take(6).toList();

final List<String> _categories = [
  '热门推荐',
  '手机数码',
  '家用电器',
  '电脑办公',
  '家居家装',
  '服装服饰',
  '鞋靴箱包',
  '运动户外',
  '美妆个护',
  '食品生鲜',
];

final List<String> _subCategories = [
  '手机',
  '平板',
  '耳机',
  '充电宝',
  '数据线',
  '保护壳',
  '笔记本',
  '键盘',
  '鼠标',
  '显示器',
  '音响',
  '耳机',
  '冰箱',
  '洗衣机',
  '空调',
  '电视',
  '微波炉',
  '电饭煲',
];

final List<CartItem> _cartItems = [
  CartItem(
      name: '新款智能手机',
      price: 2999.00,
      imageUrl:
          'https://images.unsplash.com/photo-1511707171634-5f897ff02aa9?w=400',
      quantity: 1),
  CartItem(
      name: '无线蓝牙耳机',
      price: 199.00,
      imageUrl:
          'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400',
      quantity: 2),
  CartItem(
      name: '智能手表',
      price: 899.00,
      imageUrl:
          'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=400',
      quantity: 1),
];

一、核心架构:双栏布局的信息高效组织

分类页采用经典的 "左导航 + 右内容" 双栏布局,这是处理多级分类信息的最佳实践。

dart 复制代码
body: Row(
  children: [
    // 左侧:主分类列表
    Container(width: 100, child: ListView.builder(...)),
    // 右侧:子分类网格
    Expanded(child: GridView.builder(...)),
  ],
)

左侧固定宽度 (100px):确保主分类标签清晰可读,不随屏幕尺寸过度拉伸。

  • 右侧自适应 (Expanded):充分利用剩余空间展示子分类,适配不同屏幕。
  • Row 布局 :天然支持水平分割,比 Column + Flexible 更直观。

📐 设计优势:用户一眼即可建立"主类目 -> 子类目"的心智模型,操作路径极短。


二、数据驱动:结构化分类体系的构建

分类页的强大之处在于其背后精心设计的数据结构。

2.1 主分类列表 (_categories)

dart 复制代码
final List<String> _categories = [
  '热门推荐', '手机数码', '家用电器', '电脑办公',
  '家居家装', '服装服饰', '鞋靴箱包', '运动户外',
  '美妆个护', '食品生鲜',
];
  • 共 10 个一级类目,覆盖主流电商品类。
  • "热门推荐"作为默认入口,引导新用户探索。

2.2 动态子分类映射 (_categorySubCategories)

dart 复制代码
final Map<int, List<String>> _categorySubCategories = {
  0: ['手机','平板','耳机', ...], // 热门推荐
  1: ['手机','平板','耳机', ...], // 手机数码
  2: ['冰箱','洗衣机','空调', ...], // 家用电器
  // ... 其他8个类目
};
  • Key 为 _categories 的索引,Value 为对应的子分类列表。
  • 每个主类目下有 14 个精准子类目,例如"手机数码"包含"行车记录仪"、"电子烟"等细分项。

2.3 图片资源映射 (_categoryImages)

dart 复制代码
final Map<String, String> _categoryImages = {
  '手机': 'https://images.unsplash.com/...?w=200',
  '平板': 'https://images.unsplash.com/...?w=200',
  // ... 覆盖所有子分类
};
  • 为每个子分类提供专属图片 URL,极大提升视觉吸引力。
  • 使用 Unsplash 高质量免费图库,保证演示效果。

💡 关键方法get _currentSubCategories 动态返回当前选中主类目下的子分类列表。

dart 复制代码
List<String> get _currentSubCategories {
  return _categorySubCategories[_selectedCategoryIndex] ?? _subCategories;
}

三、交互逻辑:状态管理与用户反馈

增强版分类页的核心升级在于 完整的交互闭环

3.1 选中状态管理

dart 复制代码
int _selectedCategoryIndex = 0; // 默认选中第一个

// 左侧列表项点击事件
onTap: () {
  setState(() {
    _selectedCategoryIndex = index; // 更新状态
  });
  // 显示 SnackBar 提示
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('已选择 ${_categories[index]}'))
  );
}
  • setState 触发 UI 重建,右侧子分类列表自动更新。
  • SnackBar 提供即时操作反馈,符合 Material Design 规范。

3.2 视觉状态同步

左侧列表项根据 _selectedCategoryIndex 动态调整样式:

dart 复制代码
final isSelected = index == _selectedCategoryIndex;
decoration: BoxDecoration(
  color: isSelected ? Colors.white : Colors.transparent,
  border: isSelected 
      ? Border(left: BorderSide(color: Colors.orange, width: 3))
      : null,
),
child: Text(
  _categories[index],
  style: TextStyle(
    color: isSelected ? Colors.orange : Colors.black87,
    fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
  ),
)
  • 选中项:白色背景 + 左侧橙色边框 + 橙色粗体文字。
  • 未选中项:透明背景 + 黑色常规文字。
  • 视觉焦点明确,避免用户迷失。

四、视觉呈现:SubCategoryCard 的精致设计

右侧的子分类不再是简单的图标+文字,而是功能丰富的可交互卡片。

4.1 卡片结构

dart 复制代码
class SubCategoryCard extends StatefulWidget {
  final String category;
  final String imageUrl;
  final VoidCallback onTap;
}
  • 接收子分类名称、图片 URL 和点击回调,高度解耦。

4.2 核心特性:按压动效与状态反馈

通过 AnimationControllerGestureDetector 实现拟物化交互:

dart 复制代码
// 按压时缩放
onTapDown: (_) {
  _isPressed = true;
  _animationController.forward(); // 启动缩小动画
},
onTapUp: (_) {
  _isPressed = false;
  _animationController.reverse();
  widget.onTap(); // 触发外部回调
}
视觉变化细节:
状态 图片边框 文字颜色 阴影效果 整体缩放
常态 黑色 轻微灰色阴影 1.0x
按压 橙色边框 橙色粗体 橙色发光阴影 0.92x

用户体验价值:这种微交互让用户感知到"按钮被按下",提供物理世界的操作反馈,极大提升操作信心和愉悦感。

4.3 图片加载的健壮性处理

dart 复制代码
Image.network(
  widget.imageUrl,
  errorBuilder: (context, error, stackTrace) {
    // 加载失败:显示渐变色背景 + 相机图标
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: _isPressed ? [Colors.orange, ...] : [...])
      ),
      child: Icon(Icons.image, color: _isPressed ? Colors.white : Colors.orange),
    );
  },
  loadingBuilder: (context, child, loadingProgress) {
    // 加载中:显示圆形进度条
    if (loadingProgress == null) return child;
    return CircularProgressIndicator(color: Colors.orange);
  },
)
  • 优雅降级:网络问题或图片失效时,不显示空白,而是有意义的占位符。
  • 加载指示:用户明确知道内容正在加载,减少等待焦虑。

五、功能扩展:点击事件的预留接口

虽然当前点击子分类仅弹出 SnackBar,但代码已为未来功能做好准备:

dart 复制代码
// 在 CategoryScreen 中
onTap: () {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('查看 ${_currentSubCategories[index]}'))
  );
  // TODO: 此处可跳转到该子分类的商品列表页
  // Navigator.push(context, MaterialPageRoute(builder: (_) => ProductListScreen(category: _currentSubCategories[index])));
}
  • 清晰的 TODO 注释:指明了下一步开发方向。
  • 低耦合设计SubCategoryCard 通过 onTap 回调将控制权交还给父组件,便于灵活扩展。

六、总结:从"能用"到"好用"的进化

对比基础版本的静态分类页,增强版实现了质的飞跃:

维度 基础版本 增强版本
数据 硬编码少量子分类 结构化映射,覆盖140+子类目
交互 无状态,不可点击 完整选中状态管理 + SnackBar反馈
视觉 静态图标+文字 按压动效 + 图片加载状态管理
扩展性 难以维护 模块化组件,预留跳转接口

🏁 最终呈现效果

用户进入分类页,左侧高亮"热门推荐",右侧展示14个带精美图片的子分类卡片。点击任意主类目(如"美妆个护"),左侧高亮切换,右侧平滑过渡到对应的14个美妆子类目(护肤、彩妆、香水...)。点击任一子分类卡片,卡片产生按压反馈,并提示"查看 [子类目名]"。

这套实现不仅满足了当前需求,更为后续接入真实商品数据、实现无限滚动、添加搜索过滤等功能打下了坚实的基础。它完美诠释了 Flutter 如何通过 声明式 UI + 精细的状态管理 + 丰富的动效,构建出媲美原生的复杂应用界面。

🌐 加入社区

欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持:

👉 开源鸿蒙跨平台开发者社区


技术因分享而进步,生态因共建而繁荣

------ 晚霞的不甘 · 与您共赴鸿蒙跨平台开发之旅

相关推荐
沛沛老爹2 小时前
Web开发者实战:多模态Agent技能开发——语音交互与合成技能集成指南
java·开发语言·前端·人工智能·交互·skills
kirk_wang2 小时前
Flutter艺术探索-Flutter列表性能优化:ListView.builder与itemExtent
flutter·移动开发·flutter教程·移动开发教程
Whisper_Sy10 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 网络状态实现
android·java·开发语言·javascript·网络·flutter·php
ujainu11 小时前
Flutter + OpenHarmony 网格布局:GridView 与 SliverGrid 在鸿蒙设备内容展示中的应用
android·flutter·组件
雨季66612 小时前
基于设备特征的响应式 UI 构建:Flutter for OpenHarmony 中的智能布局实践
javascript·flutter·ui
挂机且五杀12 小时前
为什么在React地图组件里,memo 不是优化,而是生存?
前端·react.js·前端框架
利刃大大13 小时前
【Vue】组件生命周期 && 组件生命周期钩子
前端·javascript·vue.js·前端框架
ujainu13 小时前
Flutter + OpenHarmony 弹出反馈:SnackBar、SnackBarAction 与 ScaffoldMessenger 的轻量提示规范
flutter·组件
鸣弦artha16 小时前
Flutter框架跨平台鸿蒙开发——GridView数据绑定实战
flutter·华为·harmonyos