Commit 075991052ccffde24b784e5c2b48abeecc4cf261
1 parent
4224b3f8
feat:商品列表请求&路由
Showing
12 changed files
with
155 additions
and
137 deletions
lib/common/request/apis.dart
lib/generated/json/product_entity.g.dart
| ... | ... | @@ -3,31 +3,96 @@ import 'package:wow_english/models/product_entity.dart'; |
| 3 | 3 | |
| 4 | 4 | ProductEntity $ProductEntityFromJson(Map<String, dynamic> json) { |
| 5 | 5 | final ProductEntity productEntity = ProductEntity(); |
| 6 | + final int? id = jsonConvert.convert<int>(json['id']); | |
| 7 | + if (id != null) { | |
| 8 | + productEntity.id = id; | |
| 9 | + } | |
| 6 | 10 | final String? name = jsonConvert.convert<String>(json['name']); |
| 7 | 11 | if (name != null) { |
| 8 | 12 | productEntity.name = name; |
| 9 | 13 | } |
| 14 | + final String? title = jsonConvert.convert<String>(json['title']); | |
| 15 | + if (title != null) { | |
| 16 | + productEntity.title = title; | |
| 17 | + } | |
| 10 | 18 | final double? price = jsonConvert.convert<double>(json['price']); |
| 11 | 19 | if (price != null) { |
| 12 | 20 | productEntity.price = price; |
| 13 | 21 | } |
| 22 | + final String? picUrl = jsonConvert.convert<String>(json['picUrl']); | |
| 23 | + if (picUrl != null) { | |
| 24 | + productEntity.picUrl = picUrl; | |
| 25 | + } | |
| 26 | + final String? bannerPicUrl = jsonConvert.convert<String>( | |
| 27 | + json['bannerPicUrl']); | |
| 28 | + if (bannerPicUrl != null) { | |
| 29 | + productEntity.bannerPicUrl = bannerPicUrl; | |
| 30 | + } | |
| 31 | + final String? detailPicUrl = jsonConvert.convert<String>( | |
| 32 | + json['detailPicUrl']); | |
| 33 | + if (detailPicUrl != null) { | |
| 34 | + productEntity.detailPicUrl = detailPicUrl; | |
| 35 | + } | |
| 36 | + final int? saleType = jsonConvert.convert<int>(json['saleType']); | |
| 37 | + if (saleType != null) { | |
| 38 | + productEntity.saleType = saleType; | |
| 39 | + } | |
| 40 | + final int? status = jsonConvert.convert<int>(json['status']); | |
| 41 | + if (status != null) { | |
| 42 | + productEntity.status = status; | |
| 43 | + } | |
| 44 | + final int? sortOrder = jsonConvert.convert<int>(json['sortOrder']); | |
| 45 | + if (sortOrder != null) { | |
| 46 | + productEntity.sortOrder = sortOrder; | |
| 47 | + } | |
| 48 | + final int? validityType = jsonConvert.convert<int>(json['validityType']); | |
| 49 | + if (validityType != null) { | |
| 50 | + productEntity.validityType = validityType; | |
| 51 | + } | |
| 14 | 52 | return productEntity; |
| 15 | 53 | } |
| 16 | 54 | |
| 17 | 55 | Map<String, dynamic> $ProductEntityToJson(ProductEntity entity) { |
| 18 | 56 | final Map<String, dynamic> data = <String, dynamic>{}; |
| 57 | + data['id'] = entity.id; | |
| 19 | 58 | data['name'] = entity.name; |
| 59 | + data['title'] = entity.title; | |
| 20 | 60 | data['price'] = entity.price; |
| 61 | + data['picUrl'] = entity.picUrl; | |
| 62 | + data['bannerPicUrl'] = entity.bannerPicUrl; | |
| 63 | + data['detailPicUrl'] = entity.detailPicUrl; | |
| 64 | + data['saleType'] = entity.saleType; | |
| 65 | + data['status'] = entity.status; | |
| 66 | + data['sortOrder'] = entity.sortOrder; | |
| 67 | + data['validityType'] = entity.validityType; | |
| 21 | 68 | return data; |
| 22 | 69 | } |
| 23 | 70 | |
| 24 | 71 | extension ProductEntityExtension on ProductEntity { |
| 25 | 72 | ProductEntity copyWith({ |
| 73 | + int? id, | |
| 26 | 74 | String? name, |
| 75 | + String? title, | |
| 27 | 76 | double? price, |
| 77 | + String? picUrl, | |
| 78 | + String? bannerPicUrl, | |
| 79 | + String? detailPicUrl, | |
| 80 | + int? saleType, | |
| 81 | + int? status, | |
| 82 | + int? sortOrder, | |
| 83 | + int? validityType, | |
| 28 | 84 | }) { |
| 29 | 85 | return ProductEntity() |
| 86 | + ..id = id ?? this.id | |
| 30 | 87 | ..name = name ?? this.name |
| 31 | - ..price = price ?? this.price; | |
| 88 | + ..title = title ?? this.title | |
| 89 | + ..price = price ?? this.price | |
| 90 | + ..picUrl = picUrl ?? this.picUrl | |
| 91 | + ..bannerPicUrl = bannerPicUrl ?? this.bannerPicUrl | |
| 92 | + ..detailPicUrl = detailPicUrl ?? this.detailPicUrl | |
| 93 | + ..saleType = saleType ?? this.saleType | |
| 94 | + ..status = status ?? this.status | |
| 95 | + ..sortOrder = sortOrder ?? this.sortOrder | |
| 96 | + ..validityType = validityType ?? this.validityType; | |
| 32 | 97 | } |
| 33 | 98 | } |
| 34 | 99 | \ No newline at end of file | ... | ... |
lib/models/product_entity.dart
| ... | ... | @@ -5,9 +5,30 @@ export 'package:wow_english/generated/json/product_entity.g.dart'; |
| 5 | 5 | |
| 6 | 6 | @JsonSerializable() |
| 7 | 7 | class ProductEntity { |
| 8 | - late String name; | |
| 9 | - late double price; | |
| 10 | - late String url; | |
| 8 | + | |
| 9 | + int? id; | |
| 10 | + | |
| 11 | + String? name; | |
| 12 | + | |
| 13 | + String? title; | |
| 14 | + | |
| 15 | + /// 售卖价格 | |
| 16 | + double? price; | |
| 17 | + /// 商品图片 | |
| 18 | + String? picUrl; | |
| 19 | + /// 商品banner图片 | |
| 20 | + String? bannerPicUrl; | |
| 21 | + /// 商品详情图片 | |
| 22 | + String? detailPicUrl; | |
| 23 | + /// 销售类型 | |
| 24 | + int? saleType; | |
| 25 | + /// 状态 | |
| 26 | + int? status; | |
| 27 | + /// 序号 | |
| 28 | + int? sortOrder; | |
| 29 | + /// 有效期类型 | |
| 30 | + int? validityType; | |
| 31 | + | |
| 11 | 32 | |
| 12 | 33 | ProductEntity(); |
| 13 | 34 | ... | ... |
lib/pages/home/widgets/home_tab_header_widget.dart
| ... | ... | @@ -104,14 +104,14 @@ class HomeTabHeaderWidget extends StatelessWidget { |
| 104 | 104 | } |
| 105 | 105 | }, |
| 106 | 106 | icon: Image.asset('listen'.assetPng)), |
| 107 | - // IconButton( | |
| 108 | - // onPressed: (){ | |
| 109 | - // if(actionTap != null) { | |
| 110 | - // actionTap!(HeaderActionType.shop); | |
| 111 | - // } | |
| 112 | - // }, | |
| 113 | - // icon: Image.asset('shop'.assetPng) | |
| 114 | - // ), | |
| 107 | + IconButton( | |
| 108 | + onPressed: (){ | |
| 109 | + if(actionTap != null) { | |
| 110 | + actionTap!(HeaderActionType.shop); | |
| 111 | + } | |
| 112 | + }, | |
| 113 | + icon: Image.asset('shop'.assetPng) | |
| 114 | + ), | |
| 115 | 115 | ScreenUtil().bottomBarHeight.horizontalSpace, |
| 116 | 116 | ], |
| 117 | 117 | ) | ... | ... |
lib/pages/shop/home/bloc/shop_home_bloc.dart
| 1 | 1 | |
| 2 | 2 | import 'package:bloc/bloc.dart'; |
| 3 | 3 | import 'package:meta/meta.dart'; |
| 4 | +import 'package:wow_english/common/request/dao/shop_dao.dart'; | |
| 5 | +import 'package:wow_english/models/product_entity.dart'; | |
| 6 | + | |
| 7 | +import '../../../../common/request/exception.dart'; | |
| 8 | +import '../../../../utils/loading.dart'; | |
| 9 | +import '../../../../utils/toast_util.dart'; | |
| 4 | 10 | |
| 5 | 11 | part 'shop_home_event.dart'; |
| 6 | 12 | part 'shop_home_state.dart'; |
| 7 | 13 | |
| 8 | 14 | class ShopHomeBloc extends Bloc<ShopHomeEvent, ShopHomeState> { |
| 15 | + | |
| 16 | + List<ProductEntity?> _productDatas = []; | |
| 17 | + | |
| 18 | + List<ProductEntity?> get productDatas => _productDatas; | |
| 19 | + | |
| 9 | 20 | ShopHomeBloc() : super(ShopHomeInitial()) { |
| 10 | 21 | on<ShopHomeEvent>((event, emit) { |
| 11 | 22 | // TODO: implement event handler |
| 12 | 23 | }); |
| 24 | + on<RequestDataEvent>(_requestData); | |
| 25 | + } | |
| 26 | + | |
| 27 | + void _requestData(RequestDataEvent event, Emitter<ShopHomeState> emitter) async { | |
| 28 | + try { | |
| 29 | + await loading(() async { | |
| 30 | + _productDatas = await ShopDao.productList() ?? []; | |
| 31 | + emitter(RequestListenDataState()); | |
| 32 | + }); | |
| 33 | + } catch (e) { | |
| 34 | + if (e is ApiException) { | |
| 35 | + showToast(e.message ?? '请求失败,请检查网络连接'); | |
| 36 | + } | |
| 37 | + } | |
| 13 | 38 | } |
| 14 | 39 | } | ... | ... |
lib/pages/shop/home/bloc/shop_home_event.dart
lib/pages/shop/home/bloc/shop_home_state.dart
lib/pages/shop/home/shop_home_page.dart
| ... | ... | @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; |
| 3 | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| 4 | 4 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 5 | 5 | import 'package:wow_english/common/widgets/we_app_bar.dart'; |
| 6 | -import 'package:wow_english/pages/shop/home/widgets/lesson_card_item.dart'; | |
| 6 | +import 'package:wow_english/pages/shop/home/widgets/product_item.dart'; | |
| 7 | 7 | import 'package:wow_english/route/route.dart'; |
| 8 | 8 | import 'package:wow_english/utils/toast_util.dart'; |
| 9 | 9 | |
| ... | ... | @@ -15,7 +15,7 @@ class ShopHomePage extends StatelessWidget { |
| 15 | 15 | @override |
| 16 | 16 | Widget build(BuildContext context) { |
| 17 | 17 | return BlocProvider( |
| 18 | - create: (context) => ShopHomeBloc(), | |
| 18 | + create: (context) => ShopHomeBloc()..add(RequestDataEvent()), | |
| 19 | 19 | child: _ShopHomeView(), |
| 20 | 20 | ); |
| 21 | 21 | } |
| ... | ... | @@ -30,7 +30,9 @@ class _ShopHomeView extends StatelessWidget { |
| 30 | 30 | ); |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | - Widget _shopHomeWidget() => BlocBuilder<ShopHomeBloc, ShopHomeState>(builder: (context, state) { | |
| 33 | + Widget _shopHomeWidget() => | |
| 34 | + BlocBuilder<ShopHomeBloc, ShopHomeState>(builder: (context, state) { | |
| 35 | + final bloc = BlocProvider.of<ShopHomeBloc>(context); | |
| 34 | 36 | return Scaffold( |
| 35 | 37 | appBar: WEAppBar( |
| 36 | 38 | actions: [ |
| ... | ... | @@ -62,7 +64,7 @@ class _ShopHomeView extends StatelessWidget { |
| 62 | 64 | child: Padding( |
| 63 | 65 | padding: EdgeInsets.symmetric(vertical: 25.h, horizontal: 25.w), |
| 64 | 66 | child: GridView.builder( |
| 65 | - itemCount: 4, | |
| 67 | + itemCount: bloc.productDatas.length, | |
| 66 | 68 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( |
| 67 | 69 | crossAxisCount: 2, |
| 68 | 70 | childAspectRatio: 2, |
| ... | ... | @@ -70,10 +72,10 @@ class _ShopHomeView extends StatelessWidget { |
| 70 | 72 | crossAxisSpacing: 4.5.w, |
| 71 | 73 | ), |
| 72 | 74 | itemBuilder: (BuildContext context, int index) { |
| 73 | - return LessonCardItem(onTap: () { | |
| 74 | - showToast('购买'); | |
| 75 | - pushNamed(AppRouteName.pay); | |
| 76 | - }); | |
| 75 | + final productEntity = bloc.productDatas[index]; | |
| 76 | + return ProductItem(onTap: () { | |
| 77 | + pushNamed(AppRouteName.pay, arguments: productEntity); | |
| 78 | + }, entity: productEntity); | |
| 77 | 79 | }), |
| 78 | 80 | ), |
| 79 | 81 | ), | ... | ... |
lib/pages/shop/home/widgets/lesson_card_item.dart deleted
| 1 | -import 'package:flutter/material.dart'; | |
| 2 | -import 'package:flutter_screenutil/flutter_screenutil.dart'; | |
| 3 | - | |
| 4 | -class LessonCardItem extends StatelessWidget { | |
| 5 | - const LessonCardItem({super.key, required this.onTap}); | |
| 6 | - | |
| 7 | - final Function() onTap; | |
| 8 | - | |
| 9 | - @override | |
| 10 | - Widget build(BuildContext context) { | |
| 11 | - return Container( | |
| 12 | - decoration: BoxDecoration( | |
| 13 | - borderRadius: BorderRadius.circular(10.r), | |
| 14 | - color: Colors.blue, | |
| 15 | - border: Border.all( | |
| 16 | - width: 1.0, | |
| 17 | - color: Colors.black | |
| 18 | - ) | |
| 19 | - // image: DecorationImage( | |
| 20 | - // image: AssetImage( | |
| 21 | - // ''.assetPng, | |
| 22 | - // ), | |
| 23 | - // fit: BoxFit.fill | |
| 24 | - // ) | |
| 25 | - ), | |
| 26 | - padding: EdgeInsets.symmetric(horizontal: 16.w,vertical: 16.h), | |
| 27 | - child: Row( | |
| 28 | - mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| 29 | - children: [ | |
| 30 | - Container( | |
| 31 | - width: 124.w, | |
| 32 | - decoration: BoxDecoration( | |
| 33 | - border: Border.all( | |
| 34 | - width: 1.0, | |
| 35 | - color: const Color(0xFF333333), | |
| 36 | - ), | |
| 37 | - image: const DecorationImage( | |
| 38 | - fit: BoxFit.fill, | |
| 39 | - image: NetworkImage('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Faa1c2213-820a-4223-8757-5f8cee318a28%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1688713226&t=192b18a613683bcdc5bd76f65c9ff032'), | |
| 40 | - ) | |
| 41 | - ), | |
| 42 | - ), | |
| 43 | - 21.5.horizontalSpace, | |
| 44 | - Expanded( | |
| 45 | - child: Column( | |
| 46 | - mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| 47 | - crossAxisAlignment: CrossAxisAlignment.end, | |
| 48 | - children: [ | |
| 49 | - Text( | |
| 50 | - 'Wow English 课程年卡', | |
| 51 | - softWrap: true, | |
| 52 | - textAlign: TextAlign.left, | |
| 53 | - style: TextStyle( | |
| 54 | - fontSize: 12.sp, | |
| 55 | - color: const Color(0xFF333333) | |
| 56 | - ), | |
| 57 | - ), | |
| 58 | - RichText( | |
| 59 | - text: TextSpan( | |
| 60 | - children:[ | |
| 61 | - TextSpan( | |
| 62 | - text: '¥', | |
| 63 | - style: TextStyle( | |
| 64 | - fontSize: 21.sp, | |
| 65 | - color: const Color(0xFFF51A1A), | |
| 66 | - ) | |
| 67 | - ), | |
| 68 | - TextSpan( | |
| 69 | - text: '998', | |
| 70 | - style: TextStyle( | |
| 71 | - fontSize: 40.sp, | |
| 72 | - color: const Color(0xFFF51A1A), | |
| 73 | - ), | |
| 74 | - ) | |
| 75 | - ] | |
| 76 | - ), | |
| 77 | - ), | |
| 78 | - GestureDetector( | |
| 79 | - onTap: () { | |
| 80 | - onTap(); | |
| 81 | - }, | |
| 82 | - child: Container( | |
| 83 | - decoration: BoxDecoration( | |
| 84 | - color: const Color(0xFFF5C51F), | |
| 85 | - borderRadius: BorderRadius.circular(5.r), | |
| 86 | - border: Border.all( | |
| 87 | - color: const Color(0xFF333333), | |
| 88 | - width: 1.0, | |
| 89 | - ) | |
| 90 | - ), | |
| 91 | - padding: EdgeInsets.symmetric( | |
| 92 | - vertical: 1.h, | |
| 93 | - horizontal: 26.5.w, | |
| 94 | - ), | |
| 95 | - child: Text( | |
| 96 | - '立即购买', | |
| 97 | - style: TextStyle( | |
| 98 | - fontSize: 10.sp, | |
| 99 | - color: const Color(0xFF333333) | |
| 100 | - ), | |
| 101 | - ), | |
| 102 | - ), | |
| 103 | - ) | |
| 104 | - ], | |
| 105 | - ), | |
| 106 | - ) | |
| 107 | - ], | |
| 108 | - ), | |
| 109 | - ); | |
| 110 | - } | |
| 111 | -} | |
| 112 | 0 | \ No newline at end of file |
lib/pages/shopping/bloc.dart
| ... | ... | @@ -16,7 +16,7 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
| 16 | 16 | PaymentChannel get curPaymentChannel => _curPaymentChannel; |
| 17 | 17 | |
| 18 | 18 | |
| 19 | - ShoppingBloc() : super(ShoppingState().init()) { | |
| 19 | + ShoppingBloc(this._productData) : super(ShoppingState().init()) { | |
| 20 | 20 | //页面初始化时刻 |
| 21 | 21 | on<InitEvent>(_init); |
| 22 | 22 | on<ChangePaymentChannelEvent>(_changePaymentChannel); | ... | ... |
lib/pages/shopping/view.dart
| ... | ... | @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; |
| 3 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 4 | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| 5 | 5 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 6 | +import 'package:wow_english/models/product_entity.dart'; | |
| 6 | 7 | |
| 7 | 8 | import '../../common/core/assets_const.dart'; |
| 8 | 9 | import '../../common/widgets/we_app_bar.dart'; |
| ... | ... | @@ -45,13 +46,15 @@ Widget buildRadioOption({ |
| 45 | 46 | } |
| 46 | 47 | |
| 47 | 48 | class ShoppingPage extends StatelessWidget { |
| 48 | - const ShoppingPage({super.key}); | |
| 49 | + const ShoppingPage({super.key, this.productEntity}); | |
| 50 | + | |
| 51 | + final ProductEntity? productEntity; | |
| 49 | 52 | |
| 50 | 53 | @override |
| 51 | 54 | Widget build(BuildContext context) { |
| 52 | 55 | return BlocProvider( |
| 53 | 56 | create: (BuildContext context) => |
| 54 | - ShoppingBloc() | |
| 57 | + ShoppingBloc(productEntity) | |
| 55 | 58 | ..add(InitEvent()), |
| 56 | 59 | child: _ShoppingView(), |
| 57 | 60 | ); |
| ... | ... | @@ -77,7 +80,7 @@ class _ShoppingView extends StatelessWidget { |
| 77 | 80 | crossAxisAlignment: CrossAxisAlignment.start, |
| 78 | 81 | children: [ |
| 79 | 82 | CachedNetworkImage( |
| 80 | - imageUrl: "${bloc.productData?.url}", | |
| 83 | + imageUrl: "${bloc.productData?.detailPicUrl}", | |
| 81 | 84 | imageBuilder: (context, imageProvider) => |
| 82 | 85 | Container( |
| 83 | 86 | decoration: BoxDecoration( |
| ... | ... | @@ -111,7 +114,7 @@ Widget _paymentWidget() => |
| 111 | 114 | return Column( |
| 112 | 115 | crossAxisAlignment: CrossAxisAlignment.start, |
| 113 | 116 | children: [ |
| 114 | - Text('套餐价格:${bloc.productData?.price ?? "199(记得删除)"}', | |
| 117 | + Text('套餐价格:${bloc.productData?.price ?? ''}', | |
| 115 | 118 | style: TextStyle( |
| 116 | 119 | color: const Color(0xFF333333), fontSize: 16.sp)), |
| 117 | 120 | const SizedBox(height: 15.0), | ... | ... |
lib/route/route.dart
| ... | ... | @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; |
| 2 | 2 | import 'package:flutter/material.dart'; |
| 3 | 3 | import 'package:wow_english/app/splash_page.dart'; |
| 4 | 4 | import 'package:wow_english/common/pages/wow_web_page.dart'; |
| 5 | +import 'package:wow_english/models/product_entity.dart'; | |
| 5 | 6 | import 'package:wow_english/pages/home/home_page.dart'; |
| 6 | 7 | import 'package:wow_english/pages/lessons/lesson_page.dart'; |
| 7 | 8 | import 'package:wow_english/pages/listen/listen_page.dart'; |
| ... | ... | @@ -26,6 +27,7 @@ import '../pages/reading/reading_page.dart'; |
| 26 | 27 | import '../pages/shopping/view.dart'; |
| 27 | 28 | import '../pages/user/setting/delete_account_page.dart'; |
| 28 | 29 | import '../pages/user/setting/reback_page.dart'; |
| 30 | +import '../utils/log_util.dart'; | |
| 29 | 31 | |
| 30 | 32 | class AppRouteName { |
| 31 | 33 | static const String splash = 'splash'; |
| ... | ... | @@ -104,7 +106,11 @@ class AppRouter { |
| 104 | 106 | case AppRouteName.shop: |
| 105 | 107 | return CupertinoPageRoute(builder: (_) => const ShopHomePage()); |
| 106 | 108 | case AppRouteName.pay: |
| 107 | - return CupertinoPageRoute(builder: (_) => const ShoppingPage()); | |
| 109 | + var productEntity = ProductEntity(); | |
| 110 | + if (settings.arguments != null && settings.arguments is ProductEntity) { | |
| 111 | + productEntity = settings.arguments as ProductEntity; | |
| 112 | + } | |
| 113 | + return CupertinoPageRoute(builder: (_) => ShoppingPage(productEntity: productEntity)); | |
| 108 | 114 | case AppRouteName.exLesson: |
| 109 | 115 | return CupertinoPageRoute(builder: (_) => const ExchangeLessonPage()); |
| 110 | 116 | case AppRouteName.exList: | ... | ... |