如何用Dart(Flutter)获取Supabase中带多图的产品数据?
解决Supabase关联查询产品及多图片的问题
数据库表结构
- product表结构:

- 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
相关产品推荐
相关产品推荐

