You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Dart(Flutter)获取Supabase中带多图的产品数据?

解决Supabase关联查询产品及多图片的问题

数据库表结构

  • product表结构:
    product table
  • product_image表结构:
    product_image

第一步:修正ProductSup类定义

原类中productImage字段类型不符合需求,需改为List<ProductImage>,同时更新构造逻辑:

class ProductSup {
  final int id;
  final DateTime createAt;
  final String title;
  final int price;
  final int? previousPrice;
  final List<ProductImage> productImages; // 字段名更清晰,类型改为ProductImage列表

  ProductSup({
    required this.id,
    required this.createAt,
    required this.title,
    required this.price,
    this.previousPrice, // 可选参数无需required
    required this.productImages,
  });

  ProductSup.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        createAt = DateTime.parse(json['created_at']),
        title = json['title'],
        price = json['price'],
        previousPrice = json['previous_price'],
        productImages = (json['product_image'] as List<dynamic>)
            .map((imgJson) => ProductImage.fromJson(imgJson))
            .toList();
}

第二步:Supabase关联查询实现

利用Supabase嵌套查询能力,一次性获取产品及关联图片,同时按display_order排序图片:

import 'package:supabase_flutter/supabase_flutter.dart';

// 获取所有带关联图片的产品
Future<List<ProductSup>> fetchProductsWithImages() async {
  final supabase = Supabase.instance.client;

  // 执行关联查询,获取product全字段及关联的product_image全字段
  // 按display_order对图片排序,保证展示顺序正确
  final response = await supabase
      .from('product')
      .select('*, product_image(*)')
      .order('created_at', ascending: false) // 产品排序(可选)
      .order('display_order', foreignTable: 'product_image');

  // 解析JSON数组为ProductSup列表
  return (response as List<dynamic>)
      .map((productJson) => ProductSup.fromJson(productJson))
      .toList();
}

第三步:调用查询并在UI中使用

在Flutter页面中调用查询方法并展示数据:

class ProductsScreen extends StatefulWidget {
  @override
  State<ProductsScreen> createState() => _ProductsScreenState();
}

class _ProductsScreenState extends State<ProductsScreen> {
  List<ProductSup>? _products;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadProducts();
  }

  Future<void> _loadProducts() async {
    try {
      final products = await fetchProductsWithImages();
      setState(() {
        _products = products;
        _isLoading = false;
      });
    } catch (e) {
      print('获取产品失败: $e');
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const Center(child: CircularProgressIndicator());
    if (_products == null || _products!.isEmpty) return const Center(child: Text('暂无产品'));

    return ListView.builder(
      itemCount: _products!.length,
      itemBuilder: (context, index) {
        final product = _products![index];
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(product.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                const SizedBox(height: 8),
                Text('售价: ¥${product.price}', style: const TextStyle(color: Colors.red)),
                if (product.previousPrice != null)
                  Text('原价: ¥${product.previousPrice}', style: const TextStyle(decoration: TextDecoration.lineThrough, color: Colors.grey)),
                const SizedBox(height: 12),
                // 展示产品图片
                SizedBox(
                  height: 100,
                  child: ListView.builder(
                    scrollDirection: Axis.horizontal,
                    itemCount: product.productImages.length,
                    itemBuilder: (context, imgIndex) {
                      final img = product.productImages[imgIndex];
                      return Padding(
                        padding: const EdgeInsets.only(right: 8),
                        child: Image.network(img.imageUrl, width: 100, fit: BoxFit.cover),
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

关键注意点

  • Supabase的select('*, product_image(*)')依赖已建立的外键关联(你已设置多对一关系),无需额外配置即可自动关联数据。
  • 通过foreignTable参数指定对关联表的字段排序,确保图片按预设顺序展示。
  • 解析JSON时,需将product_image数组逐个转换为ProductImage实例。

内容的提问来源于stack exchange,提问作者Daniyal Dehghan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:49:50