12.10 Tab 商品评论

12.10 Tab 商品评论

image-20220725135541972

实现步骤:


第 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

image-20220722123909582

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,
);
// reviews 评论列表
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(
// 刷新, 重置页数1
page: isRefresh ? 1 : _reviewsPage,
// 每页条数
prePage: _reviewsLimit,
// 商品id
product: productId,
));

// 更新数据
if (isRefresh) {
_reviewsPage = 1; // 重置页数1
reviews.clear(); // 清空数据
}

if (reviewsListTmp.isNotEmpty) {
_reviewsPage++; // 页数+1
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(
// item.reviewerAvatarUrls?["96"],
// 测试需要改成自定义头像
"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