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:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user