12.10 Tab 商品评论
实现步骤:
第 1 步:评论模型
json 数据格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| [ { "id": 12, "date_created": "2022-04-04T23:36:23", "date_created_gmt": "2022-04-04T15:36:23", "product_id": 13, "status": "approved", "reviewer": "ducafecat5", "reviewer_email": "ducafecat5@gmail.com", "review": "<p>001 - Nice album!</p>\n", "rating": 5, "verified": false, "reviewer_avatar_urls": { "24": "https://secure.gravatar.com/avatar/8b3a29ec6f524eed54bbf360e545fef8?s=24&d=mm&r=g", "48": "https://secure.gravatar.com/avatar/8b3a29ec6f524eed54bbf360e545fef8?s=48&d=mm&r=g", "96": "https://secure.gravatar.com/avatar/8b3a29ec6f524eed54bbf360e545fef8?s=96&d=mm&r=g" }, "_links": { "self": [ { "href": "https://wp.ducafecat.tech/wp-json/wc/v3/products/reviews/12" } ], "collection": [ { "href": "https://wp.ducafecat.tech/wp-json/wc/v3/products/reviews" } ], "up": [ { "href": "https://wp.ducafecat.tech/wp-json/wc/v3/products/13" } ] } } ]
|
删除 ReviewerAvatarUrls ,改成 Map
完整代码
lib/common/models/woo/review_model/review_model.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| import 'links.dart';
class ReviewModel { int? id; String? dateCreated; String? dateCreatedGmt; int? productId; String? status; String? reviewer; String? reviewerEmail; String? review; int? rating; bool? verified; Map? reviewerAvatarUrls; Links? links;
ReviewModel({ this.id, this.dateCreated, this.dateCreatedGmt, this.productId, this.status, this.reviewer, this.reviewerEmail, this.review, this.rating, this.verified, this.reviewerAvatarUrls, this.links, });
factory ReviewModel.fromJson(Map<String, dynamic> json) => ReviewModel( id: json['id'] as int?, dateCreated: json['date_created'] as String?, dateCreatedGmt: json['date_created_gmt'] as String?, productId: json['product_id'] as int?, status: json['status'] as String?, reviewer: json['reviewer'] as String?, reviewerEmail: json['reviewer_email'] as String?, review: json['review'] as String?, rating: json['rating'] as int?, verified: json['verified'] as bool?, reviewerAvatarUrls: json['reviewer_avatar_urls'] == null ? null : json['reviewer_avatar_urls'] as Map, links: json['_links'] == null ? null : Links.fromJson(json['_links'] as Map<String, dynamic>), );
Map<String, dynamic> toJson() => { 'id': id, 'date_created': dateCreated, 'date_created_gmt': dateCreatedGmt, 'product_id': productId, 'status': status, 'reviewer': reviewer, 'reviewer_email': reviewerEmail, 'review': review, 'rating': rating, 'verified': verified, 'reviewer_avatar_urls': reviewerAvatarUrls, '_links': links?.toJson(), }; }
|
第 2 步:评论 API
lib/common/models/request/product.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class ReviewsReq { final int? page; final int? prePage; final int? product;
ReviewsReq({ this.page, this.prePage, this.product, });
Map<String, dynamic> toJson() => { 'page': page ?? 1, 'pre_page': prePage ?? 10, 'product': product ?? 0, }; }
|
lib/common/api/product.dart
1 2 3 4 5 6 7 8 9 10 11 12 13
| static Future<List<ReviewModel>> reviews(ReviewsReq? req) async { var res = await WPHttpService.to.get( '/products/reviews', params: req?.toJson(), );
List<ReviewModel> reviews = []; for (var item in res.data) { reviews.add(ReviewModel.fromJson(item)); } return reviews; }
|
第 3 步:控制器
lib/pages/goods/product_details/controller.dart
1 2 3 4 5 6 7 8 9 10 11 12
| final RefreshController reviewsRefreshController = RefreshController( initialRefresh: true, );
List<ReviewModel> reviews = [];
List<String> reviewImages = [];
int _reviewsPage = 1;
final int _reviewsLimit = 20;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| _loadProduct() async { ...
reviews = await ProductApi.reviews(ReviewsReq( product: productId, ));
reviewImages.addAll([ "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/718Y%2BhJkMgL._AC_UY695_.jpg", "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/71n8Tg2ClZL._AC_UY695_.jpg", "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/819mEKajDML._AC_UY695_.jpg", "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/81J0UFuJHdL._AC_UY695_.jpg", "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/81M4BxGW4TL._AC_UY695_.jpg", "https://ducafecat.oss-cn-beijing.aliyuncs.com/bag/81s6OXEsZCL._AC_UY695_.jpg", ]);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| Future<bool> _loadReviews(bool isRefresh) async { var reviewsListTmp = await ProductApi.reviews(ReviewsReq( page: isRefresh ? 1 : _reviewsPage, prePage: _reviewsLimit, product: productId, ));
if (isRefresh) { _reviewsPage = 1; reviews.clear(); }
if (reviewsListTmp.isNotEmpty) { _reviewsPage++; reviews.addAll(reviewsListTmp); }
return reviewsListTmp.isEmpty; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void onReviewsRefresh() async { try { await _loadReviews(true);
reviewsRefreshController.refreshCompleted(); } catch (error) { reviewsRefreshController.refreshFailed(); } update(["product_reviews"]); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void onReviewsLoading() async { if (reviews.isNotEmpty) { try { var isEmpty = await _loadReviews(false);
if (isEmpty) { reviewsRefreshController.loadNoData(); } else { reviewsRefreshController.loadComplete(); } } catch (e) { reviewsRefreshController.loadFailed(); } } else { reviewsRefreshController.loadNoData(); } update(["product_reviews"]); }
|
1 2 3 4 5 6 7
| @override void onClose() { super.onClose(); ... reviewsRefreshController.dispose(); }
|
第 4 步:评论子视图
lib/pages/goods/product_details/widgets/tab_reviews.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| _buildListItem(ReviewModel item) { return <Widget>[ const ImageWidget.url( "https://ducafecat.oss-cn-beijing.aliyuncs.com/avatar/00258VC3ly1gty0r05zh2j60ut0u0tce02.jpg", width: 55, height: 55, ).paddingRight(AppSpace.listItem),
<Widget>[ TextWidget.title3( item.reviewer ?? "", ), TextWidget.body1( item.review?.clearHtml ?? "", ), _buildReviewImages(), ] .toColumn( crossAxisAlignment: CrossAxisAlignment.start, ) .expanded(), ].toRow( crossAxisAlignment: CrossAxisAlignment.start, ); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| Widget _buildReviewImages() { return <Widget>[ for (var i = 0; i < controller.reviewImages.length; i++) ImageWidget.url( Convert.aliImageResize(controller.reviewImages[i]), width: 45.w, height: 45.w, ) .paddingRight(AppSpace.listItem), ].toWrap(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @override Widget build(BuildContext context) { return GetBuilder<ProductDetailsController>( tag: tag, id: "product_reviews", builder: (_) { return SmartRefresher( controller: controller.reviewsRefreshController, enablePullUp: true, onRefresh: controller.onReviewsRefresh, onLoading: controller.onReviewsLoading, footer: const SmartRefresherFooterWidget(), child: ListView.separated( itemBuilder: (BuildContext context, int index) { var item = controller.reviews[index]; return _buildListItem(item); }, separatorBuilder: (BuildContext context, int index) { return SizedBox(height: AppSpace.listRow * 2); }, itemCount: controller.reviews.length, ), ); }, ); }
|
第 5 步:评论图片浏览
lib/pages/goods/product_details/controller.dart
1 2 3 4 5 6 7
| void onReviewsGalleryTap(int index) { Get.to(GalleryWidget( initialIndex: index, items: reviewImages, )); }
|
lib/pages/goods/product_details/widgets/tab_reviews.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Widget _buildReviewImages() { return <Widget>[ for (var i = 0; i < controller.reviewImages.length; i++) ImageWidget.url( Convert.aliImageResize(controller.reviewImages[i]), width: 45.w, height: 45.w, ) .onTap(() => controller.onReviewsGalleryTap(i)) .paddingRight(AppSpace.listItem), ].toWrap(); }
|
提交代码到 git