From afa1a0bcd23f7ca3b8d880ebf237d2866db845e8 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 9 Jun 2026 22:50:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(client):=20=E5=95=86=E5=93=81=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E5=B1=95=E7=A4=BA=E6=89=80=E6=9C=89=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E8=A1=A5=E5=85=A8=E5=88=86=E7=B1=BB/?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E9=A2=84=E8=AD=A6/=E5=A4=87=E6=B3=A8/?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AD=97=E6=AE=B5/=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- client/lib/models/product.dart | 12 +++- .../products/product_detail_screen.dart | 55 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/client/lib/models/product.dart b/client/lib/models/product.dart index e9313ed..39600ca 100644 --- a/client/lib/models/product.dart +++ b/client/lib/models/product.dart @@ -18,10 +18,14 @@ class Product { final String? remark; final Map? customFields; final List images; - // 产地/保质期/储存方式名称(Detail 接口 Preload 后填充) + // 产地/保质期/储存方式/分类名称(Detail 接口 Preload 后填充) final String? originName; final String? shelfLifeName; final String? storageName; + final String? categoryName; + // 描述文档(关联的字典记录) + final String? descriptionDocTitle; + final String? descriptionDocContent; const Product({ required this.id, @@ -44,6 +48,9 @@ class Product { this.originName, this.shelfLifeName, this.storageName, + this.categoryName, + this.descriptionDocTitle, + this.descriptionDocContent, }); factory Product.fromJson(Map json) => Product( @@ -78,6 +85,9 @@ class Product { originName: (json['origin'] as Map?)?['name'] as String?, shelfLifeName: (json['shelf_life'] as Map?)?['name'] as String?, storageName: (json['storage'] as Map?)?['name'] as String?, + categoryName: (json['category'] as Map?)?['name'] as String?, + descriptionDocTitle: (json['description_doc'] as Map?)?['title'] as String?, + descriptionDocContent: (json['description_doc'] as Map?)?['content'] as String?, ); Map toJson() => { diff --git a/client/lib/screens/products/product_detail_screen.dart b/client/lib/screens/products/product_detail_screen.dart index a878fc9..eb1bfb4 100644 --- a/client/lib/screens/products/product_detail_screen.dart +++ b/client/lib/screens/products/product_detail_screen.dart @@ -202,6 +202,10 @@ class _ProductDetailScreenState extends ConsumerState { categoryId: p.categoryId, brand: p.brand, purchasePrice: p.purchasePrice, salePrice: p.salePrice, minStock: p.minStock, description: p.description, remark: p.remark, customFields: p.customFields, images: images, + originName: p.originName, shelfLifeName: p.shelfLifeName, + storageName: p.storageName, categoryName: p.categoryName, + descriptionDocTitle: p.descriptionDocTitle, + descriptionDocContent: p.descriptionDocContent, ); }); } @@ -275,6 +279,10 @@ class _ProductDetailScreenState extends ConsumerState { ], const SizedBox(height: 20), _buildDescSection(), + if (p.descriptionDocTitle?.isNotEmpty ?? false) ...[ + const SizedBox(height: 20), + _buildDescriptionDocSection(p), + ], const SizedBox(height: 20), _buildPublicLinkSection(p), ], @@ -353,7 +361,11 @@ class _ProductDetailScreenState extends ConsumerState { SizedBox(width: 200, child: row('规格', p.spec?.isEmpty ?? true ? '-' : p.spec!)), SizedBox(width: 200, child: row('品牌', p.brand?.isEmpty ?? true ? '-' : p.brand!)), SizedBox(width: 200, child: row('单位', p.unit.isEmpty ? '-' : p.unit)), - SizedBox(width: 200, child: row('进价', p.purchasePrice != null ? '¥${p.purchasePrice!.toStringAsFixed(2)}' : '-')), + if (p.categoryName?.isNotEmpty ?? false) + SizedBox(width: 200, child: row('分类', p.categoryName!)), + SizedBox(width: 200, child: row('进价', p.purchasePrice != null && p.purchasePrice! > 0 ? '¥${p.purchasePrice!.toStringAsFixed(2)}' : '-')), + if (p.minStock != null && p.minStock! > 0) + SizedBox(width: 200, child: row('库存预警', '${p.minStock}')), if (p.originName?.isNotEmpty ?? false) SizedBox(width: 200, child: row('产地', p.originName!)), if (p.shelfLifeName?.isNotEmpty ?? false) @@ -362,6 +374,47 @@ class _ProductDetailScreenState extends ConsumerState { SizedBox(width: 200, child: row('储存方式', p.storageName!)), if (p.barcode?.isNotEmpty ?? false) SizedBox(width: 200, child: row('条码', p.barcode!)), + if (p.remark?.isNotEmpty ?? false) + SizedBox(width: 400, child: row('备注', p.remark!)), + ...?_buildCustomFieldRows(p, row), + ], + ), + ), + ); + } + + List? _buildCustomFieldRows( + Product p, Widget Function(String, String) row) { + final fields = p.customFields; + if (fields == null || fields.isEmpty) return null; + return fields.entries + .where((e) => e.value != null && e.value.toString().isNotEmpty) + .map((e) => + SizedBox(width: 200, child: row(e.key, e.value.toString()))) + .toList(); + } + + Widget _buildDescriptionDocSection(Product p) { + return Card( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + side: BorderSide(color: AppTheme.border, width: 0.5), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(p.descriptionDocTitle!, + style: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w600)), + if (p.descriptionDocContent?.isNotEmpty ?? false) ...[ + const SizedBox(height: 10), + Text(p.descriptionDocContent!, + style: const TextStyle( + fontSize: 13, color: AppTheme.textSecondary, height: 1.6)), + ], ], ), ),