Commit 2879454a478c847cbaf6267adda145a29b1d48ac
1 parent
a4c3106a
feat:调通支付宝支付&游戏列表页
Showing
13 changed files
with
243 additions
and
82 deletions
assets/images/bg_frame_module_pic.png renamed to assets/images/bg_frame_module.png
54.1 KB
lib/common/request/apis.dart
... | ... | @@ -85,4 +85,11 @@ class Apis { |
85 | 85 | |
86 | 86 | /// 商品列表 |
87 | 87 | static const String productList = 'order/course/combo/list'; |
88 | + | |
89 | + /// 创建订单 | |
90 | + static const String createOrder = 'order/create/order'; | |
91 | + | |
92 | + /// 获取阿里支付token | |
93 | + static const String getAliPayToken = 'pay/alipay/token'; | |
94 | + | |
88 | 95 | } | ... | ... |
lib/common/request/dao/shop_dao.dart
0 → 100644
1 | +import '../../../models/product_entity.dart'; | |
2 | +import '../request_client.dart'; | |
3 | + | |
4 | +class ShopDao { | |
5 | + ///商品列表 | |
6 | + static Future productList() async { | |
7 | + return await requestClient.get<List<ProductEntity?>>(Apis.productList); | |
8 | + } | |
9 | + | |
10 | + ///创建订单 | |
11 | + static Future createOrder(ProductEntity productEntity) async { | |
12 | + return await requestClient | |
13 | + .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id}); | |
14 | + } | |
15 | + | |
16 | + ///获取ali支付订单信息 | |
17 | + static Future getAliPayToken(String orderNo) async { | |
18 | + return await requestClient | |
19 | + .post<Map<String, dynamic>>(Apis.getAliPayToken, data: {'orderNo': orderNo}); | |
20 | + } | |
21 | +} | ... | ... |
lib/pages/games/bloc.dart
1 | 1 | import 'package:bloc/bloc.dart'; |
2 | 2 | import 'package:flutter/cupertino.dart'; |
3 | 3 | import 'package:flutter/services.dart'; |
4 | +import 'package:wow_english/common/extension/string_extension.dart'; | |
4 | 5 | |
5 | 6 | import 'event.dart'; |
7 | +import 'game_entity.dart'; | |
6 | 8 | import 'state.dart'; |
7 | 9 | |
8 | 10 | class GamesBloc extends Bloc<GamesEvent, GamesState> { |
9 | 11 | |
10 | 12 | late MethodChannel _methodChannel; |
11 | 13 | |
14 | + //手动初始化4个GameEntity对象 | |
15 | + final List<GameEntity> _games = [ | |
16 | + GameEntity() | |
17 | + ..id = 1 | |
18 | + ..imageName = 'pic_module_game'.assetPng | |
19 | + ..name = '游戏1', | |
20 | + GameEntity() | |
21 | + ..id = 2 | |
22 | + ..imageName = 'pic_module_game'.assetPng | |
23 | + ..name = '游戏2', | |
24 | + GameEntity() | |
25 | + ..id = 3 | |
26 | + ..imageName = 'pic_module_game'.assetPng | |
27 | + ..name = '游戏3', | |
28 | + GameEntity() | |
29 | + ..id = 4 | |
30 | + ..imageName = 'pic_module_game'.assetPng | |
31 | + ..name = '游戏4' | |
32 | + ]; | |
33 | + | |
34 | + List<GameEntity> get listData => _games; | |
35 | + | |
12 | 36 | GamesBloc() : super(GamesState().init()) { |
13 | 37 | on<InitEvent>(_init); |
14 | 38 | on<GotoGamePageEvent>(_gotoGamePage); | ... | ... |
lib/pages/games/game_entity.dart
0 → 100644
lib/pages/games/view.dart
... | ... | @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; |
2 | 2 | import 'package:flutter_bloc/flutter_bloc.dart'; |
3 | 3 | |
4 | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
5 | -import 'package:wow_english/common/extension/string_extension.dart'; | |
6 | 5 | import 'package:wow_english/common/widgets/we_app_bar.dart'; |
7 | 6 | import 'package:wow_english/pages/games/state.dart'; |
8 | 7 | |
... | ... | @@ -26,12 +25,10 @@ class _GamesPageView extends StatelessWidget { |
26 | 25 | @override |
27 | 26 | Widget build(BuildContext context) { |
28 | 27 | return BlocListener<GamesBloc, GamesState>( |
29 | - listener: (context, state) { | |
30 | - | |
31 | - }, | |
28 | + listener: (context, state) {}, | |
32 | 29 | child: Scaffold( |
33 | 30 | appBar: const WEAppBar( |
34 | - titleText: '游戏列表', | |
31 | + titleText: '游戏专区', | |
35 | 32 | centerTitle: false, |
36 | 33 | ), |
37 | 34 | body: _gamesView(), |
... | ... | @@ -39,41 +36,39 @@ class _GamesPageView extends StatelessWidget { |
39 | 36 | ); |
40 | 37 | } |
41 | 38 | |
42 | - Widget _gamesView() => BlocBuilder<GamesBloc, GamesState>( | |
43 | - builder: (context, state) { | |
39 | + Widget _gamesView() => | |
40 | + BlocBuilder<GamesBloc, GamesState>(builder: (context, state) { | |
44 | 41 | final bloc = BlocProvider.of<GamesBloc>(context); |
45 | 42 | //屏幕中间横着放四张图片一行展示(尺寸120*200),每张图片下方有行文字 |
46 | - return GridView.builder( | |
47 | - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | |
48 | - crossAxisCount: 4, | |
49 | - crossAxisSpacing: 10, | |
50 | - mainAxisSpacing: 10, | |
51 | - childAspectRatio: 0.6, | |
52 | - ), | |
53 | - itemCount: 4, | |
54 | - itemBuilder: (BuildContext context, int index) { | |
55 | - // final entity = bloc.listData[index]; | |
56 | - return GestureDetector( | |
57 | - onTap: () { | |
58 | - bloc.add(GotoGamePageEvent(1)); | |
59 | - }, | |
60 | - child: Container( | |
61 | - decoration: BoxDecoration( | |
62 | - border: Border.all( | |
63 | - width: 1.0, | |
64 | - color: const Color(0xFF140C10), | |
65 | - ), | |
66 | - borderRadius: BorderRadius.circular(21), | |
67 | - ), | |
68 | - child: Column( | |
69 | - children: [ | |
70 | - Image.asset('pic_module_study'.assetPng, width: 120, height: 200), | |
71 | - Text('游戏名称', style: TextStyle(fontSize: 14.sp, color: const Color(0xFF140C10))) | |
72 | - ], | |
73 | - ), | |
43 | + return Container( | |
44 | + margin: EdgeInsets.symmetric(horizontal: 50.0.w), | |
45 | + child: GridView.builder( | |
46 | + padding: EdgeInsets.zero, | |
47 | + // physics: const NeverScrollableScrollPhysics(), | |
48 | + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | |
49 | + crossAxisCount: 3, | |
50 | + crossAxisSpacing: 10, | |
51 | + mainAxisSpacing: 10, | |
52 | + childAspectRatio: 0.6, | |
74 | 53 | ), |
75 | - ); | |
54 | + itemCount: bloc.listData.length, | |
55 | + itemBuilder: (BuildContext context, int index) { | |
56 | + final gameEntity = bloc.listData[index]; | |
57 | + return GestureDetector( | |
58 | + onTap: () { | |
59 | + bloc.add(GotoGamePageEvent(gameEntity.id)); | |
60 | + }, | |
61 | + child: Column( | |
62 | + children: [ | |
63 | + Image.asset(gameEntity.imageName, | |
64 | + width: 120, height: 200), | |
65 | + Text(gameEntity.name, | |
66 | + style: TextStyle( | |
67 | + fontSize: 14.sp, | |
68 | + color: const Color(0xFF140C10))) | |
69 | + ], | |
70 | + ), | |
71 | + ); | |
72 | + })); | |
76 | 73 | }); |
77 | - }); | |
78 | 74 | } |
79 | - | ... | ... |
lib/pages/moduleSelect/view.dart
... | ... | @@ -52,8 +52,18 @@ class _HomePageView extends StatelessWidget { |
52 | 52 | child: Column( |
53 | 53 | mainAxisAlignment: MainAxisAlignment.center, |
54 | 54 | children: [ |
55 | - Image.asset('pic_module_study'.assetPng, | |
56 | - width: 162.5.w, height: 203.5.h), | |
55 | + Stack( | |
56 | + alignment: AlignmentDirectional.center, | |
57 | + children: [ | |
58 | + Image.asset('bg_frame_module'.assetPng, | |
59 | + width: 162.5.w, height: 203.5.h), | |
60 | + Center( | |
61 | + child: Image.asset( | |
62 | + 'pic_module_study'.assetPng, | |
63 | + width: 140.5.w, | |
64 | + height: 172.h), | |
65 | + ) | |
66 | + ]), | |
57 | 67 | 10.verticalSpace, |
58 | 68 | Image.asset('label_module_study'.assetPng, |
59 | 69 | width: 124.w, height: 34.h), |
... | ... | @@ -69,8 +79,14 @@ class _HomePageView extends StatelessWidget { |
69 | 79 | child: Column( |
70 | 80 | mainAxisAlignment: MainAxisAlignment.center, |
71 | 81 | children: [ |
72 | - Image.asset('pic_module_game'.assetPng, | |
73 | - width: 162.5.w, height: 203.5.h), | |
82 | + Stack( | |
83 | + alignment: AlignmentDirectional.center, | |
84 | + children: [ | |
85 | + Image.asset('bg_frame_module'.assetPng, | |
86 | + width: 162.5.w, height: 203.5.h), | |
87 | + Image.asset('pic_module_game'.assetPng, | |
88 | + width: 140.5.w, height: 172.h) | |
89 | + ]), | |
74 | 90 | 10.verticalSpace, |
75 | 91 | Image.asset('label_module_game'.assetPng, |
76 | 92 | width: 124.w, height: 34.h), | ... | ... |
lib/pages/shop/home/widgets/product_item.dart
... | ... | @@ -31,37 +31,36 @@ class ProductItem extends StatelessWidget { |
31 | 31 | child: Row( |
32 | 32 | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
33 | 33 | children: [ |
34 | - // Container( | |
35 | - // width: 124.w, | |
36 | - // decoration: BoxDecoration( | |
37 | - // border: Border.all( | |
38 | - // width: 1.0, | |
39 | - // color: const Color(0xFF333333), | |
40 | - // ), | |
41 | - // image: const DecorationImage( | |
42 | - // | |
43 | - // image: NetworkImage(entity?.coverUrl??''), | |
44 | - // ) | |
45 | - // ), | |
46 | - // ), | |
47 | - CachedNetworkImage( | |
48 | - imageUrl: entity?.picUrl ?? '', | |
49 | - fit: BoxFit.fill, | |
50 | - imageBuilder: (context, imageProvider) => | |
51 | - Container( | |
52 | - decoration: BoxDecoration( | |
53 | - border: Border.all( | |
54 | - width: 1.0, | |
55 | - color: const Color(0xFF333333), | |
56 | - ), | |
57 | - borderRadius: BorderRadius.circular(5.0), | |
58 | - ), | |
59 | - ), | |
60 | - placeholder: (context, url) => CircularProgressIndicator(), | |
61 | - // errorWidget: (context, url, error) => const Icon(Icons.error), | |
62 | - height: 124.h, | |
34 | + Container( | |
63 | 35 | width: 124.w, |
36 | + decoration: BoxDecoration( | |
37 | + border: Border.all( | |
38 | + width: 1.0, | |
39 | + color: const Color(0xFF333333), | |
40 | + ), | |
41 | + image: DecorationImage( | |
42 | + image: NetworkImage(entity?.picUrl ?? ''), | |
43 | + ) | |
44 | + ), | |
64 | 45 | ), |
46 | + // CachedNetworkImage( | |
47 | + // imageUrl: entity?.picUrl ?? '', | |
48 | + // fit: BoxFit.fill, | |
49 | + // imageBuilder: (context, imageProvider) => | |
50 | + // Container( | |
51 | + // decoration: BoxDecoration( | |
52 | + // border: Border.all( | |
53 | + // width: 1.0, | |
54 | + // color: const Color(0xFF333333), | |
55 | + // ), | |
56 | + // borderRadius: BorderRadius.circular(5.0), | |
57 | + // ), | |
58 | + // ), | |
59 | + // placeholder: (context, url) => const CircularProgressIndicator(), | |
60 | + // // errorWidget: (context, url, error) => const Icon(Icons.error), | |
61 | + // height: 124.h, | |
62 | + // width: 124.w, | |
63 | + // ), | |
65 | 64 | 21.5.horizontalSpace, |
66 | 65 | Expanded( |
67 | 66 | child: Column( | ... | ... |
lib/pages/shopping/bloc.dart
1 | 1 | import 'package:bloc/bloc.dart'; |
2 | +import 'package:fluwx/fluwx.dart'; | |
3 | +import 'package:tobias/tobias.dart'; | |
4 | +import 'package:wow_english/generated/json/base/json_convert_content.dart'; | |
2 | 5 | import 'package:wow_english/models/product_entity.dart'; |
3 | 6 | |
7 | +import '../../common/request/dao/shop_dao.dart'; | |
8 | +import '../../common/request/exception.dart'; | |
9 | +import '../../utils/loading.dart'; | |
4 | 10 | import '../../utils/log_util.dart'; |
11 | +import '../../utils/toast_util.dart'; | |
5 | 12 | import 'event.dart'; |
6 | 13 | import 'state.dart'; |
7 | 14 | |
... | ... | @@ -20,6 +27,7 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
20 | 27 | //页面初始化时刻 |
21 | 28 | on<InitEvent>(_init); |
22 | 29 | on<ChangePaymentChannelEvent>(_changePaymentChannel); |
30 | + on<DoPayEvent>(_startPay); | |
23 | 31 | } |
24 | 32 | |
25 | 33 | void _init(InitEvent event, Emitter<ShoppingState> emit) async { |
... | ... | @@ -37,8 +45,67 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
37 | 45 | } |
38 | 46 | } |
39 | 47 | |
40 | - void _gotoPay() { | |
41 | - // 跳转到支付页面 | |
48 | + void _startPay(DoPayEvent event, | |
49 | + Emitter<ShoppingState> emitter) async { | |
50 | + Log.d("开始支付 ${event.productEntity} ${event.paymentChannel}"); | |
51 | + //如果event.productEntity为空,中断流程并toast提示 | |
52 | + if (event.productEntity == null) { | |
53 | + showToast("商品信息为空"); | |
54 | + return; | |
55 | + } | |
56 | + final productEntitySafely = event.productEntity!; | |
57 | + try { | |
58 | + await loading(() async { | |
59 | + final Map<String, dynamic> orderInfo = await ShopDao.createOrder(productEntitySafely); | |
60 | + Log.d("orderInfo $orderInfo"); | |
61 | + final String? orderNo = orderInfo.getOrNull("orderNo"); | |
62 | + if (orderNo == null) { | |
63 | + showToast("订单创建失败"); | |
64 | + return; | |
65 | + } | |
66 | + Log.d("orderNo $orderNo"); | |
67 | + | |
68 | + if (event.paymentChannel == PaymentChannel.wechatPay) { | |
69 | + Fluwx fluwx = Fluwx(); | |
70 | + fluwx.registerApi(appId: "wxd930ea5d5a228f5f", | |
71 | + universalLink: "https://app-api.wowenglish.com.cn/.well-known/apple-app-site-association"); | |
72 | + // fluwx.pay( | |
73 | + // which: Payment( | |
74 | + // appId: _orderInfo['appid'].toString(), | |
75 | + // partnerId: _orderInfo['partnerid'].toString(), | |
76 | + // prepayId: _orderInfo['prepayid'].toString(), | |
77 | + // packageValue: _orderInfo['package'].toString(), | |
78 | + // nonceStr: _orderInfo['noncestr'].toString(), | |
79 | + // timestamp: _orderInfo['timestamp'], | |
80 | + // sign: _orderInfo['sign'].toString(), | |
81 | + // )); | |
82 | + } else { | |
83 | + final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); | |
84 | + Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); | |
85 | + final String? aliPayInfo = aliPayOrderInfo.getOrNull("token"); | |
86 | + if (aliPayInfo == null) { | |
87 | + showToast("支付宝订单创建失败"); | |
88 | + return; | |
89 | + } | |
90 | + Log.d("aliPayInfo=$aliPayInfo"); | |
91 | + ///打印aliPayOrderInfo的type | |
92 | + Tobias tobias = Tobias(); | |
93 | + final Map aliPayResult = await tobias.pay(aliPayInfo); | |
94 | + Log.d("aliPayResult=$aliPayResult"); | |
95 | + // 判断resultStatus 为9000则代表支付成功 | |
96 | + if (aliPayResult.getOrNull("resultStatus") == "9000") { | |
97 | + showToast("支付成功"); | |
98 | + emit(PaySuccessState()); | |
99 | + } else { | |
100 | + showToast("支付失败"); | |
101 | + } | |
102 | + } | |
103 | + }); | |
104 | + } catch (e) { | |
105 | + if (e is ApiException) { | |
106 | + showToast(e.message ?? '请求失败,请检查网络连接'); | |
107 | + } | |
108 | + } | |
42 | 109 | } |
43 | 110 | } |
44 | 111 | ... | ... |
lib/pages/shopping/event.dart
1 | +import 'package:wow_english/models/product_entity.dart'; | |
2 | + | |
1 | 3 | import 'bloc.dart'; |
2 | 4 | |
3 | 5 | abstract class ShoppingEvent {} |
... | ... | @@ -8,4 +10,13 @@ class ChangePaymentChannelEvent extends ShoppingEvent { |
8 | 10 | final PaymentChannel paymentChannel; |
9 | 11 | |
10 | 12 | ChangePaymentChannelEvent(this.paymentChannel); |
13 | +} | |
14 | + | |
15 | +class DoPayEvent extends ShoppingEvent { | |
16 | + | |
17 | + final ProductEntity? productEntity; | |
18 | + | |
19 | + final PaymentChannel paymentChannel; | |
20 | + | |
21 | + DoPayEvent(this.productEntity, this.paymentChannel); | |
11 | 22 | } |
12 | 23 | \ No newline at end of file | ... | ... |
lib/pages/shopping/state.dart
... | ... | @@ -8,4 +8,6 @@ class ShoppingState { |
8 | 8 | } |
9 | 9 | } |
10 | 10 | |
11 | -class PaymentChannelChangeState extends ShoppingState {} | |
12 | 11 | \ No newline at end of file |
12 | +class PaymentChannelChangeState extends ShoppingState {} | |
13 | + | |
14 | +class PaySuccessState extends ShoppingState {} | |
13 | 15 | \ No newline at end of file | ... | ... |
lib/pages/shopping/view.dart
... | ... | @@ -8,6 +8,7 @@ import 'package:wow_english/models/product_entity.dart'; |
8 | 8 | import '../../common/core/assets_const.dart'; |
9 | 9 | import '../../common/widgets/we_app_bar.dart'; |
10 | 10 | import '../../utils/image_util.dart'; |
11 | +import '../../utils/log_util.dart'; | |
11 | 12 | import 'bloc.dart'; |
12 | 13 | import 'event.dart'; |
13 | 14 | import 'state.dart'; |
... | ... | @@ -66,13 +67,18 @@ class _ShoppingView extends StatelessWidget { |
66 | 67 | @override |
67 | 68 | Widget build(BuildContext context) { |
68 | 69 | final bloc = BlocProvider.of<ShoppingBloc>(context); |
70 | + // var title1 = bloc.productData?.name ?? ''; | |
69 | 71 | return BlocListener<ShoppingBloc, ShoppingState>( |
70 | 72 | listener: (context, state) { |
73 | + Log.d("wqf state=$state"); | |
74 | + if (state is PaySuccessState) { | |
75 | + Navigator.pop(context); | |
76 | + } | |
71 | 77 | }, |
72 | 78 | child: Scaffold( |
73 | 79 | appBar: const WEAppBar( |
74 | 80 | //标题传进来的 |
75 | - titleText: '支付', | |
81 | + titleText: "商品详情", | |
76 | 82 | ), |
77 | 83 | body: Container( |
78 | 84 | margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0), |
... | ... | @@ -80,7 +86,7 @@ class _ShoppingView extends StatelessWidget { |
80 | 86 | crossAxisAlignment: CrossAxisAlignment.start, |
81 | 87 | children: [ |
82 | 88 | CachedNetworkImage( |
83 | - imageUrl: "${bloc.productData?.detailPicUrl}", | |
89 | + imageUrl: bloc.productData?.detailPicUrl ?? '', | |
84 | 90 | imageBuilder: (context, imageProvider) => |
85 | 91 | Container( |
86 | 92 | decoration: BoxDecoration( |
... | ... | @@ -91,8 +97,7 @@ class _ShoppingView extends StatelessWidget { |
91 | 97 | borderRadius: BorderRadius.circular(5.0), |
92 | 98 | ), |
93 | 99 | ), |
94 | - placeholder: (context, url) => | |
95 | - const CircularProgressIndicator(), | |
100 | + placeholder: (context, url) => const CircularProgressIndicator(), | |
96 | 101 | // errorWidget: (context, url, error) => const Icon(Icons.error), |
97 | 102 | height: 210.0.h, |
98 | 103 | width: 210.0.w, |
... | ... | @@ -153,14 +158,15 @@ Widget _paymentWidget() => |
153 | 158 | text: PaymentChannel.aliPay.payChannelName, |
154 | 159 | groupValue: bloc.curPaymentChannel.payChannelType, |
155 | 160 | onChanged: (newValue) { |
156 | - bloc.add( | |
157 | - ChangePaymentChannelEvent(PaymentChannel.aliPay)); | |
161 | + bloc.add(ChangePaymentChannelEvent(PaymentChannel.aliPay)); | |
158 | 162 | }, |
159 | 163 | ), |
160 | 164 | const SizedBox(height: 15.0), |
161 | 165 | // 确认支付按钮 |
162 | 166 | InkWell( |
163 | 167 | onTap: () { |
168 | + Log.d('点击支付按钮 ${bloc.productData}'); | |
169 | + bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); | |
164 | 170 | }, |
165 | 171 | child: Image( |
166 | 172 | width: 125.w, | ... | ... |
pubspec.yaml
... | ... | @@ -66,7 +66,7 @@ dependencies: |
66 | 66 | # 拍照,从相册中选择 https://pub.flutter-io.cn/packages/image_picker |
67 | 67 | image_picker: ^0.8.7+5 |
68 | 68 | # 支付宝支付SDK https://pub.flutter-io.cn/packages/tobias |
69 | - tobias: ^3.1.0 | |
69 | + tobias: ^3.3.2 | |
70 | 70 | # 微信SDK相关 https://pub.flutter-io.cn/packages/fluwx |
71 | 71 | fluwx: ^4.5.5 |
72 | 72 | # json数据解析 https://pub.flutter-io.cn/packages/json_annotation |
... | ... | @@ -74,7 +74,9 @@ dependencies: |
74 | 74 | # double丢失精度问题 https://pub.dev/packages/decimal |
75 | 75 | decimal: ^2.3.2 |
76 | 76 | # 网络图片缓存 https://pub.flutter-io.cn/packages/cached_network_image |
77 | - cached_network_image: ^3.2.3 | |
77 | + cached_network_image: ^3.3.1 | |
78 | +# # 网络图片缓存 https://pub.dev/packages/extended_image | |
79 | +# extended_image: ^4.0.0 | |
78 | 80 | # 常用工具类(时间轴,倒计时等) https://pub.flutter-io.cn/packages/common_utils |
79 | 81 | common_utils: ^2.1.0 |
80 | 82 | # 获取设备信息 https://pub.flutter-io.cn/packages/device_info_plus | ... | ... |