feat(client): 公开商品页改读真实数据,移除茅台 mock 硬编码

- _DescriptionSection: 接受 title/body/keywords 三参数,移除 _mockBody/_mockKeywords 和茅台专属文案
- _ParamsCard: 新增 origin/shelfLife/storage 参数;产地空时不显示,生产日期无时不显示,保质期/储存方式用后端返回值(含默认话术)
- 数据提取段:新增 origin/shelfLife/storage/descriptionTitle/descriptionKeywords 从 API 响应读取
- 删除未使用的 _extractKeywords 方法

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-08 06:58:28 +08:00
parent ba127826b2
commit 45b6ec832b
@@ -115,6 +115,13 @@ class _PublicProductScreenState extends State<PublicProductScreen> {
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<dynamic>?)
?.map((e) => e as String)
.toList() ?? const <String>[];
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<String, dynamic>?;
final batch = d['batch'] as Map<String, dynamic>?;
final quickSpecs = _parseQuickSpecs(spec, batch?['production_date'] as String?);
@@ -151,9 +158,13 @@ class _PublicProductScreenState extends State<PublicProductScreen> {
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<PublicProductScreen> {
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<String> 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<String> _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();