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