19.1 订单列表页面

19.1 订单列表页面

img

实现步骤:


第 1 步:i18n

lib/common/i18n/locale_keys.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 订单页
static const orderListTitle = "order_list_title";
static const orderDetailsTitle = "order_details_title";
static const orderDetailsOrderID = "order_details_order_id";
static const orderDetailsBillFrom = "order_details_bill_from";
static const orderDetailsBillTo = "order_details_bill_to";
static const orderDetailsProduct = "order_details_product";
static const orderDetailsRateQty = "order_details_rate_qty";
static const orderDetailsAmount = "order_details_amount";
static const orderDetailsPaymentMethod = "order_details_payment_method";
static const orderDetailsBalance = "order_details_balance";
static const orderDetailsPaid = "order_details_paid";
static const orderDetailsTotal = "order_details_total";
static const orderDetailsShipping = "order_details_shipping";
static const orderDetailsDiscount = "order_details_discount";

lib/common/i18n/locales/locale_en.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 订单页
LocaleKeys.orderListTitle: 'Order List',
LocaleKeys.orderDetailsTitle: 'Order Detail',
LocaleKeys.orderDetailsOrderID: 'Order ID',
LocaleKeys.orderDetailsBillFrom: 'Bill From',
LocaleKeys.orderDetailsBillTo: 'Bill To',
LocaleKeys.orderDetailsProduct: 'Product',
LocaleKeys.orderDetailsRateQty: 'Rate & Qty',
LocaleKeys.orderDetailsAmount: 'Amount',
LocaleKeys.orderDetailsPaymentMethod: 'Payment Method',
LocaleKeys.orderDetailsBalance: 'Balance',
LocaleKeys.orderDetailsTotal: 'Total',
LocaleKeys.orderDetailsPaid: 'Paid',
LocaleKeys.orderDetailsShipping: 'Shipping',
LocaleKeys.orderDetailsDiscount: 'Discount',

lib/common/i18n/locales/locale_zh.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 订单页
LocaleKeys.orderListTitle: '订单列表',
LocaleKeys.orderDetailsTitle: '订单详情',
LocaleKeys.orderDetailsOrderID: '订单 ID',
LocaleKeys.orderDetailsBillFrom: '始发地',
LocaleKeys.orderDetailsBillTo: '目的地',
LocaleKeys.orderDetailsProduct: '商品',
LocaleKeys.orderDetailsRateQty: '单价 & 数量',
LocaleKeys.orderDetailsAmount: '小计',
LocaleKeys.orderDetailsPaymentMethod: '支付方式',
LocaleKeys.orderDetailsBalance: '结算',
LocaleKeys.orderDetailsTotal: '合计',
LocaleKeys.orderDetailsPaid: '支付',
LocaleKeys.orderDetailsShipping: '运费',
LocaleKeys.orderDetailsDiscount: '折扣',

第 2 步:api 接口

image-20220804224910584

lib/common/models/request/order.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 订单查询请求
class OrdersReq {
final int? page;
final int? prePage;

OrdersReq({
this.page,
this.prePage,
});

Map<String, dynamic> toJson() => {
'page': page ?? 1,
'pre_page': prePage ?? 10,
};
}

lib/common/api/order.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 订单列表
static Future<List<OrderModel>> orders(OrdersReq req) async {
var res = await WPHttpService.to.get(
'/orders',
params: req.toJson(),
);

List<OrderModel> orders = [];
for (var item in res.data) {
orders.add(OrderModel.fromJson(item));
}
return orders;
}

数据结构

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
[
{
"id": 84,
"parent_id": 0,
"status": "pending",
"currency": "CNY",
"version": "6.4.1",
"prices_include_tax": false,
"date_created": "2022-05-12T10:47:36",
"date_modified": "2022-05-12T10:47:37",
"discount_total": "0.00",
"discount_tax": "0.00",
"shipping_total": "0.00",
"shipping_tax": "0.00",
"cart_tax": "0.00",
"total": "18.00",
"total_tax": "0.00",
"customer_id": 6,
"order_key": "wc_order_6oxAAGxxXIyvt",
"billing": {
"first_name": "ducafe",
"last_name": "cat5",
"company": "ducafecat",
"address_1": "changping 001",
"address_2": "cp 001",
"city": "beijing",
"state": "CN2",
"postcode": "100101",
"country": "CN",
"email": "ducafecat5@gmail.com",
"phone": "110"
},
"shipping": {
"first_name": "ducafe",
"last_name": "cat5",
"company": "ducafecat",
"address_1": "sh 9001-1012",
"address_2": "sh 9002",
"city": "Shang hai",
"state": "CN10",
"postcode": "200126",
"country": "CN",
"phone": "1232"
},
"payment_method": "",
"payment_method_title": "",
"transaction_id": "",
"customer_ip_address": "",
"customer_user_agent": "",
"created_via": "rest-api",
"customer_note": "",
"date_completed": null,
"date_paid": null,
"cart_hash": "",
"number": "84",
"meta_data": [],
"line_items": [
{
"id": 52,
"name": "Beanie",
"product_id": 15,
"variation_id": 0,
"quantity": 1,
"tax_class": "",
"subtotal": "18.00",
"subtotal_tax": "0.00",
"total": "18.00",
"total_tax": "0.00",
"taxes": [],
"meta_data": [],
"sku": "woo-beanie",
"price": 18,
"parent_name": null,
"product": {
"id": 15,
"name": "Beanie",
"slug": "beanie",
"permalink": "https://wp.ducafecat.tech/product/beanie/",
"date_created": "2022-03-31T23:19:37",
"date_created_gmt": "2022-03-31T15:19:37",
"date_modified": "2022-04-18T23:50:20",
"date_modified_gmt": "2022-04-18T15:50:20",
"type": "simple",
"status": "publish",
"featured": true,
"catalog_visibility": "visible",
"description": "<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>n",
"short_description": "<p>This is a simple product.</p>n",
"sku": "woo-beanie",
"price": "18",
"regular_price": "20",
"sale_price": "18",
"date_on_sale_from": null,
"date_on_sale_from_gmt": null,
"date_on_sale_to": null,
"date_on_sale_to_gmt": null,
"on_sale": true,
"purchasable": true,
"total_sales": 0,
"virtual": false,
"downloadable": false,
"downloads": [],
"download_limit": 0,
"download_expiry": 0,
"external_url": "",
"button_text": "",
"tax_status": "taxable",
"tax_class": "",
"manage_stock": false,
"stock_quantity": null,
"backorders": "no",
"backorders_allowed": false,
"backordered": false,
"low_stock_amount": null,
"sold_individually": false,
"weight": "1",
"dimensions": {
"length": "12",
"width": "12",
"height": "3"
},
"shipping_required": true,
"shipping_taxable": true,
"shipping_class": "china_shipping",
"shipping_class_id": 41,
"reviews_allowed": true,
"average_rating": "0.00",
"rating_count": 0,
"upsell_ids": [],
"cross_sell_ids": [],
"parent_id": 0,
"purchase_note": "",
"categories": [
{
"id": 34,
"name": "Bag",
"slug": "bag"
},
{
"id": 29,
"name": "Man",
"slug": "man"
},
{
"id": 15,
"name": "More",
"slug": "more"
},
{
"id": 30,
"name": "Woman",
"slug": "woman"
}
],
"tags": [
{
"id": 42,
"name": "beanie",
"slug": "beanie"
}
],
"images": [
{
"id": 44,
"date_created": "2022-04-01T07:20:01",
"date_created_gmt": "2022-03-31T15:20:01",
"date_modified": "2022-04-01T07:20:01",
"date_modified_gmt": "2022-03-31T15:20:01",
"src": "https://ducafecat.oss-cn-beijing.aliyuncs.com/wp-content/uploads/2022/03/beanie-2.jpg",
"name": "beanie-2.jpg",
"alt": ""
}
],
"attributes": [
{
"id": 1,
"name": "Color",
"position": 0,
"visible": true,
"variation": false,
"options": [
"Green",
"Purple"
]
},
{
"id": 2,
"name": "Size",
"position": 1,
"visible": true,
"variation": false,
"options": [
"XL",
"2XL",
"3XL"
]
}
],
"default_attributes": [],
"variations": [],
"grouped_products": [],
"menu_order": 0,
"price_html": "<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&yen;</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&yen;</span>18.00</bdi></span></ins>",
"related_ids": [
12,
13,
14,
11
],
"meta_data": [
{
"id": 89,
"key": "_original_id",
"value": "48"
},
{
"id": 479,
"key": "_wpcom_is_markdown",
"value": "1"
}
],
"stock_status": "instock",
"has_options": false,
"_links": {
"self": [
{
"href": "https://wp.ducafecat.tech/wp-json/wc/v3/products/15"
}
],
"collection": [
{
"href": "https://wp.ducafecat.tech/wp-json/wc/v3/products"
}
]
}
}
}
],
"tax_lines": [],
"shipping_lines": [
{
"id": 53,
"method_title": "免费配送",
"method_id": "free_shipping",
"instance_id": "",
"total": "0.00",
"total_tax": "0.00",
"taxes": [],
"meta_data": []
}
],
"fee_lines": [],
"coupon_lines": [],
"refunds": [],
"payment_url": "https://wp.ducafecat.tech/checkout/order-pay/84/?pay_for_order=true&key=wc_order_6oxAAGxxXIyvt",
"date_created_gmt": "2022-05-12T02:47:36",
"date_modified_gmt": "2022-05-12T02:47:37",
"date_completed_gmt": null,
"date_paid_gmt": null,
"currency_symbol": "¥",
"_links": {
"self": [
{
"href": "https://wp.ducafecat.tech/wp-json/wc/v3/orders/84"
}
],
"collection": [
{
"href": "https://wp.ducafecat.tech/wp-json/wc/v3/orders"
}
],
"customer": [
{
"href": "https://wp.ducafecat.tech/wp-json/wc/v3/customers/6"
}
]
}
}
]

第 3 步:控制器

lib/pages/my/order_list/controller.dart

1
2
3
4
5
6
7
8
9
10
// 刷新控制器
final RefreshController refreshController = RefreshController(
initialRefresh: true,
);
// 列表
List<OrderModel> items = [];
// 页码
int _page = 1;
// 页尺寸
final int _limit = 20;
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
// 拉取数据
// isRefresh 是否是刷新
Future<bool> _loadSearch(bool isRefresh) async {
// 拉取数据
var result = await OrderApi.orders(OrdersReq(
// 刷新, 重置页数1
page: isRefresh ? 1 : _page,
// 每页条数
prePage: _limit,
));

// 下拉刷新
if (isRefresh) {
_page = 1; // 重置页数1
items.clear(); // 清空数据
}

// 有数据
if (result.isNotEmpty) {
// 页数+1
_page++;

// 添加数据
items.addAll(result);
}

// 是否空
return result.isEmpty;
}
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 onLoading() async {
if (items.isNotEmpty) {
try {
// 拉取数据是否为空
var isEmpty = await _loadSearch(false);

if (isEmpty) {
// 设置无数据
refreshController.loadNoData();
} else {
// 加载完成
refreshController.loadComplete();
}
} catch (e) {
// 加载失败
refreshController.loadFailed();
}
} else {
// 设置无数据
refreshController.loadNoData();
}
update(["order_list"]);
}
1
2
3
4
5
6
7
8
9
10
11
// 下拉刷新
void onRefresh() async {
try {
await _loadSearch(true);
refreshController.refreshCompleted();
} catch (error) {
// 刷新失败
refreshController.refreshFailed();
}
update(["order_list"]);
}
1
2
3
4
5
6
@override
void onClose() {
super.onClose();
// 刷新控制器释放
refreshController.dispose();
}

上面都是标准翻页控制代码,大家可以做成代码模板,直接复用

1
2
// 订单详情
void onOrderItemTap(OrderModel order) {}

先留空后面要用上

第 4 步:数据行组件

image-20220804231839952

lib/pages/my/order_list/widgets/list_item.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
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_woo_commerce_getx_learn/common/index.dart';
import 'package:get/get.dart';

/// 订单列表项
class BuildOrderListItem extends StatelessWidget {
const BuildOrderListItem({Key? key, required this.item}) : super(key: key);

final OrderModel item;

// 标签
Widget _buildTag(String tag) {
return ButtonWidget.primary(
tag,
).tight(
width: 100.w,
height: 32.w,
);
}

// 主视图
Widget _buildView() {
return <Widget>[
// id tag
<Widget>[
// 编号
TextWidget.body1("# ${item.id}").expanded(),

// 标签tag
_buildTag(item.status ?? "-"),
].toRow().paddingBottom(AppSpace.listRow),

// orderKey date
<Widget>[
// 订单号
TextWidget.body1("${item.orderKey}").expanded(),

// 日期
TextWidget.body2("${item.dateCreated}".dateFormatOfyyyyMMdd),
].toRow(),
].toColumn().paddingAll(AppSpace.card).card();
}

@override
Widget build(BuildContext context) {
return _buildView();
}
}

dateFormatOfyyyyMMdd 是我们的自定义扩展

第 5 步:视图

lib/pages/my/order_list/view.dart

ListView.separated 方式构建列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 列表
Widget _buildList() {
return ListView.separated(
itemBuilder: (context, index) {
OrderModel item = controller.items[index];
return BuildOrderListItem(item: item)
.onTap(() => controller.onOrderItemTap(item));
},
separatorBuilder: (context, index) {
return SizedBox(height: AppSpace.listRow * 2);
},
itemCount: controller.items.length,
);
}
1
2
3
4
// 主视图
Widget _buildView() {
return _buildList();
}

刷新控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@override
Widget build(BuildContext context) {
return GetBuilder<OrderListController>(
init: OrderListController(),
id: "order_list",
builder: (_) {
return Scaffold(
appBar: mainAppBarWidget(titleString: LocaleKeys.orderListTitle.tr),
body: SafeArea(
child: SmartRefresher(
controller: controller.refreshController, // 刷新控制器
enablePullUp: true, // 启用上拉加载
onRefresh: controller.onRefresh, // 下拉刷新回调
onLoading: controller.onLoading, // 上拉加载回调
footer: const SmartRefresherFooterWidget(), // 底部加载更多
child: _buildView(),
).paddingAll(AppSpace.page),
),
);
},
);
}

提交代码到 git