diff --git a/client/lib/screens/public/public_product_screen.dart b/client/lib/screens/public/public_product_screen.dart index 732e782..805040e 100644 --- a/client/lib/screens/public/public_product_screen.dart +++ b/client/lib/screens/public/public_product_screen.dart @@ -115,6 +115,13 @@ class _PublicProductScreenState extends State { final brand = d['brand'] as String? ?? ''; final unit = d['unit'] as String? ?? ''; final description = d['description'] as String? ?? ''; + final descriptionTitle = d['description_title'] as String? ?? '商品介绍'; + final descriptionKeywords = (d['description_keywords'] as List?) + ?.map((e) => e as String) + .toList() ?? const []; + final origin = d['origin'] as String? ?? ''; + final shelfLife = d['shelf_life'] as String? ?? '无限期(适饮)'; + final storage = d['storage'] as String? ?? '阴凉干燥、避光保存'; final shop = d['shop'] as Map?; final batch = d['batch'] as Map?; final quickSpecs = _parseQuickSpecs(spec, batch?['production_date'] as String?); @@ -151,9 +158,13 @@ class _PublicProductScreenState extends State { SliverToBoxAdapter( child: _QuickSpecs(items: quickSpecs), ), - // 5. Description — always shown; falls back to editorial mock text + // 5. Description — always shown; body/keywords come from backend (三级回退) SliverToBoxAdapter( - child: _DescriptionSection(description: description), + child: _DescriptionSection( + title: descriptionTitle, + body: description, + keywords: descriptionKeywords, + ), ), // 6. Params — full table SliverToBoxAdapter( @@ -164,6 +175,9 @@ class _PublicProductScreenState extends State { series: series, unit: unit, productionDate: productionDate, + origin: origin, + shelfLife: shelfLife, + storage: storage, ), ), // 7. Authenticity card (batch + stamp) @@ -813,22 +827,17 @@ class _QuickSpecs extends StatelessWidget { // ── Description Section ─────────────────────────────────────────────── class _DescriptionSection extends StatelessWidget { - final String description; - const _DescriptionSection({required this.description}); - - static const _mockBody = - '飞天牌茅台酒采用本地优质红高粱与小麦为原料,承袭历经数百年沉淀的酱香酿造工艺,' - '经一年一个生产周期、两次投料、九次蒸煮、八次发酵、七次取酒,' - '于陶坛中陈藏至少五年,方得装瓶。色泽微黄透明,开瓶酱香盈室,' - '入口绵柔不烈,咽下后舌底生津,余香萦绕,是中国酱香型白酒之典范。'; - - static const _mockKeywords = ['酱香典范', '陈藏五年', '回味悠长', '国家地理标志保护产品']; + final String title; + final String body; + final List keywords; + const _DescriptionSection({ + required this.title, + required this.body, + required this.keywords, + }); @override Widget build(BuildContext context) { - final body = description.isNotEmpty ? description : _mockBody; - final keywords = description.isNotEmpty ? _extractKeywords(description) : _mockKeywords; - return Container( padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), decoration: const BoxDecoration( @@ -838,7 +847,7 @@ class _DescriptionSection extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const _SectionHeader(title: '商品介绍'), + _SectionHeader(title: title), const SizedBox(height: 14), Text( body, @@ -865,10 +874,6 @@ class _DescriptionSection extends StatelessWidget { ); } - List _extractKeywords(String text) { - final matches = RegExp(r'【([^】]+)】').allMatches(text); - return matches.map((m) => m.group(1)!).toList(); - } } // ── Authenticity Card ───────────────────────────────────────────────── @@ -1062,6 +1067,9 @@ class _StampPainter extends CustomPainter { class _ParamsCard extends StatelessWidget { final String publicId, spec, brand, series, unit; final String? productionDate; + final String origin; // 空串表示无产地数据,不显示该行 + final String shelfLife; // 后端已回填默认值 + final String storage; // 后端已回填默认值 const _ParamsCard({ required this.publicId, required this.spec, @@ -1069,6 +1077,9 @@ class _ParamsCard extends StatelessWidget { required this.series, required this.unit, this.productionDate, + this.origin = '', + this.shelfLife = '无限期(适饮)', + this.storage = '阴凉干燥、避光保存', }); @override @@ -1086,10 +1097,10 @@ class _ParamsCard extends StatelessWidget { if (degM != null) (label: '酒精度', value: '${degM.group(1)!}% vol', mono: false), if (mlM != null) (label: '净含量', value: '${mlM.group(1)!} ml', mono: false), if (spec.isNotEmpty) (label: '规格', value: spec, mono: false), - (label: '产地', value: '贵州省仁怀市茅台镇', mono: false), - (label: '生产日期', value: productionDate ?? '2024-01-01', mono: false), - (label: '保质期', value: '无限期(适饮)', mono: false), - (label: '储存方式', value: '阴凉干燥、避光保存', mono: false), + if (origin.isNotEmpty) (label: '产地', value: origin, mono: false), + if (productionDate != null) (label: '生产日期', value: productionDate!, mono: false), + (label: '保质期', value: shelfLife, mono: false), + (label: '储存方式', value: storage, mono: false), ]; if (rows.isEmpty) return const SizedBox.shrink();