diff --git a/lib/components/my_qrcode.dart b/lib/components/my_qrcode.dart index c9710e4..e48cbb2 100644 --- a/lib/components/my_qrcode.dart +++ b/lib/components/my_qrcode.dart @@ -14,7 +14,12 @@ import 'package:loopin/utils/scan_code_type.dart'; import 'package:pretty_qr_code/pretty_qr_code.dart'; class MyQrcode extends StatelessWidget { - MyQrcode({super.key}); + MyQrcode({ + super.key, + this.prefix, + }); + + final String? prefix; final controller = Get.find(); @@ -22,12 +27,14 @@ class MyQrcode extends StatelessWidget { final GlobalKey _qrKey = GlobalKey(); /// 将 Widget 渲染为 Uint8List - Future capturePng() async { + Future capturePng(BuildContext context) async { try { final boundary = _qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return null; - final image = await boundary.toImage(pixelRatio: ui.window.devicePixelRatio); + // final image = await boundary.toImage(pixelRatio: ui.window.devicePixelRatio); + final pixelRatio = View.of(context).devicePixelRatio; + final image = await boundary.toImage(pixelRatio: pixelRatio); final byteData = await image.toByteData(format: ui.ImageByteFormat.png); return byteData?.buffer.asUint8List(); } catch (e) { @@ -39,7 +46,7 @@ class MyQrcode extends StatelessWidget { /// 保存二维码到相册 Future saveQrToGallery() async { logger.w('长安了'); - final pngBytes = await capturePng(); + final pngBytes = await capturePng(Get.context!); if (pngBytes == null) return; final result = await ImageGallerySaverPlus.saveImage( @@ -67,6 +74,8 @@ class MyQrcode extends StatelessWidget { face = CachedNetworkImageProvider(faceUrl); } + final data = '${prefix ?? QrTypeCode.hym}$userID'; + return GestureDetector( onLongPress: () { showModalBottomSheet( @@ -104,7 +113,7 @@ class MyQrcode extends StatelessWidget { key: _qrKey, child: PrettyQrView.data( errorCorrectLevel: QrErrorCorrectLevel.H, // 高容错 - data: '${QrTypeCode.hym}$userID', + data: data, // 二维码内容 decoration: PrettyQrDecoration( background: Colors.transparent, shape: const PrettyQrShape.custom( diff --git a/lib/pages/chat/chat_no_friend.dart b/lib/pages/chat/chat_no_friend.dart index 95776d8..8e64daf 100644 --- a/lib/pages/chat/chat_no_friend.dart +++ b/lib/pages/chat/chat_no_friend.dart @@ -1713,26 +1713,54 @@ class _ChatNoFriendState extends State with SingleTickerProviderSt ), onPressed: () async { // 回关 - final res = await ImService.instance.followUser(userIDList: [arguments.value.userID!]); - if (res.success) { + final ctl = Get.find(); + + final chatRes = await ImService.instance.followUser(userIDList: [arguments.value.userID!]); + if (chatRes.success) { controller.isFriend.value = true; controller.followType.value = 3; - if (arguments.value.conversationGroupList?.isNotEmpty == true) { - //把会话数据从陌生人分组移除 - final ctl = Get.find(); - ctl.removeNoFriend(conversationID: arguments.value.conversationID); - ctl.updateNoFriendMenu(); + final res = await ImService.instance.getConversation(conversationID: 'c2c_${arguments.value.userID}'); + if (res.success) { + V2TimConversation conversation = res.data; + if (conversation.conversationGroupList?.isNotEmpty ?? false) { + await ImService.instance.deleteConversationsFromGroup( + groupName: conversation.conversationGroupList!.first!, + conversationIDList: [conversation.conversationID], + ); + await ctl.updateNoFriendMenu(); + // 跳转到chat地址,销毁当前页面 + Get.offAllNamed( + '/chat', + arguments: arguments.value, + predicate: (route) { + // 清理栈,只保留 `/` + return route.settings.name == '/'; + }, + ); + } } - // 跳转到chat地址,销毁当前页面 - Get.offAllNamed( - '/chat', - arguments: arguments.value, - predicate: (route) { - // 清理栈,只保留 `/` - return route.settings.name == '/'; - }, - ); } + // 回关 + // final res = await ImService.instance.followUser(userIDList: [arguments.value.userID!]); + // if (res.success) { + // controller.isFriend.value = true; + // controller.followType.value = 3; + // if (arguments.value.conversationGroupList?.isNotEmpty == true) { + // //把会话数据从陌生人分组移除 + // final ctl = Get.find(); + // await ctl.removeNoFriend(conversationID: arguments.value.conversationID); + // ctl.updateNoFriendMenu(); + // // 跳转到chat地址,销毁当前页面 + // Get.offAllNamed( + // '/chat', + // arguments: arguments.value, + // predicate: (route) { + // // 清理栈,只保留 `/` + // return route.settings.name == '/'; + // }, + // ); + // } + // } }, child: const Text('回关', style: TextStyle(color: Colors.white)), ), diff --git a/lib/pages/chat/menu/add_friend.dart b/lib/pages/chat/menu/add_friend.dart index 9ab4201..205d09f 100644 --- a/lib/pages/chat/menu/add_friend.dart +++ b/lib/pages/chat/menu/add_friend.dart @@ -107,11 +107,15 @@ class _AddFriendState extends State { suffixIcon: TextButton( child: Text('搜索'), onPressed: () { - // 点击按钮时触发的逻辑 + // 点击按钮搜索 final value = txtcontroller.text.trim(); if (value.isNotEmpty) { // 跳转搜索页 focusNode.unfocus(); + Get.toNamed( + '/search-result', + arguments: {'searchWords': value, 'tab': 2}, + ); } }, ), @@ -124,8 +128,12 @@ class _AddFriendState extends State { ), onSubmitted: (value) { if (value.trim().isNotEmpty) { - focusNode.unfocus(); // 跳转搜索页 + focusNode.unfocus(); + Get.toNamed( + '/search-result', + arguments: {'searchWords': value, 'tab': 2}, + ); } }, ), @@ -155,6 +163,7 @@ class _AddFriendState extends State { SizedBox(height: 20), + // 我的二维码 MyQrcode(), ], ), diff --git a/lib/pages/goods/detail.dart b/lib/pages/goods/detail.dart index e4b5d47..0c9b7d0 100644 --- a/lib/pages/goods/detail.dart +++ b/lib/pages/goods/detail.dart @@ -42,7 +42,6 @@ class _GoodsState extends State { double scrollOffset = 0; // 分享列表 List shareList = [ - {'icon': 'assets/images/share-wx.png', 'label': '好友'}, {'icon': 'assets/images/share-wx.png', 'label': '微信'}, {'icon': 'assets/images/share-pyq.png', 'label': '朋友圈'}, ]; @@ -105,11 +104,12 @@ class _GoodsState extends State { } void handleShareClick(int index) { - final description = shopObj['describe']; // 商品描述 - if (index == 1) { + logger.w(shopObj); + final description = shopObj['describe'] ?? '未上传商品描述'; // 商品描述 + if (index == 0) { // 好友 Wxsdk.shareToFriend(title: '快看看我分享的商品', description: description, webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}'); - } else if (index == 2) { + } else if (index == 1) { // 朋友圈 Wxsdk.shareToTimeline(title: '快看看我分享的商品', webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}'); } @@ -178,7 +178,9 @@ class _GoodsState extends State { padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), itemBuilder: (context, index) { return GestureDetector( - onTap: () => handleShareClick(index), + onTap: () { + handleShareClick(index); + }, child: Container( width: 64, margin: EdgeInsets.symmetric(horizontal: 8.0), diff --git a/lib/pages/my/all_function.dart b/lib/pages/my/all_function.dart index ee1a9f0..f3f902a 100644 --- a/lib/pages/my/all_function.dart +++ b/lib/pages/my/all_function.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:loopin/IM/controller/im_user_info_controller.dart'; +import 'package:loopin/components/my_qrcode.dart'; +import 'package:loopin/pages/my/merchant/balance/balance.dart'; +import 'package:loopin/pages/my/merchant/balance/controller.dart'; +import 'package:loopin/utils/scan_code_type.dart'; class AllFunctionsPage extends StatefulWidget { const AllFunctionsPage({super.key}); @@ -29,6 +33,7 @@ class _AllFunctionsPageState extends State { ] }, ]; + final ImUserInfoController controller = Get.find(); @override Widget build(BuildContext context) { @@ -106,7 +111,12 @@ class _AllFunctionsPageState extends State { itemCount: (section['items'] as List).length, itemBuilder: (context, index) { final item = (section['items'] as List)[index]; - return _buildFunctionItem(item); + final role = controller.role.value; + if (item['id'] == 'home_promo' && role != 4) { + return SizedBox(); + } else { + return _buildFunctionItem(item); + } }, ), @@ -137,7 +147,7 @@ class _AllFunctionsPageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( + SizedBox( width: 36, height: 36, child: Center( @@ -176,18 +186,25 @@ class _AllFunctionsPageState extends State { Get.toNamed('/myOrder'); break; case 'home_balance': - showLogoutDialog(context); + // 钱包 + Get.put(BalanceController()); + Get.to(() => Balance()); + // showLogoutDialog(context); break; case 'home_withdraw': Get.toNamed('/vloger'); break; case 'home_promo': + // 推广码 + Get.to(() => MyQrcode( + prefix: QrTypeCode.tgm, + )); break; - case 'more_seller_order': - Get.toNamed('/sellerOrder'); + case 'more_seller_order': + Get.toNamed('/sellerOrder'); break; case 'more_seller_income': - Get.toNamed('/merchant/income'); + Get.toNamed('/merchant/income'); break; } } @@ -211,10 +228,10 @@ class _AllFunctionsPageState extends State { }, child: const Text('取消', style: TextStyle(color: Colors.black54)), ), - TextButton(onPressed: ()=>{}, child: const Text('退出登录', style: TextStyle(color: Colors.red))), + TextButton(onPressed: () => {}, child: const Text('退出登录', style: TextStyle(color: Colors.red))), ], ); }, ); } -} \ No newline at end of file +} diff --git a/lib/pages/my/index.dart b/lib/pages/my/index.dart index 9abd1da..2bd9526 100644 --- a/lib/pages/my/index.dart +++ b/lib/pages/my/index.dart @@ -5,15 +5,18 @@ import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart'; import 'package:loopin/IM/controller/im_user_info_controller.dart'; import 'package:loopin/IM/im_service.dart'; -import 'package:loopin/api/video_api.dart'; import 'package:loopin/api/common_api.dart'; -import 'package:loopin/utils/index.dart'; +import 'package:loopin/api/video_api.dart'; import 'package:loopin/components/custom_sticky_header.dart'; import 'package:loopin/components/my_confirm.dart'; +import 'package:loopin/components/my_qrcode.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/only_down_scroll_physics.dart'; import 'package:loopin/controller/video_module_controller.dart'; import 'package:loopin/service/http.dart'; +import 'package:loopin/styles/index.dart'; +import 'package:loopin/utils/index.dart'; +import 'package:loopin/utils/scan_code_type.dart'; import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart'; import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart'; @@ -131,17 +134,14 @@ class MyPageState extends State with SingleTickerProviderStateMixin { super.dispose(); } - // 获取用户的所有视频的点赞数量 + // 获取用户的所有视频的点赞数量 void getUserLikesCount() async { try { final resData = await Http.get(CommonApi.accountInfo); - if(resData != null && resData['code'] == 200){ - vlogLikeCount = resData['data']['vlogLikeCount']??0; - } - } catch (e) { - + if (resData != null && resData['code'] == 200) { + vlogLikeCount = resData['data']['vlogLikeCount'] ?? 0; } - + } catch (e) {} } // 添加控制子列表滚动的方法 @@ -263,121 +263,20 @@ class MyPageState extends State with SingleTickerProviderStateMixin { } } + // 删除当前用户发布的视频 // 删除当前用户发布的视频 void deletePersonalVideo(videoId) async { - logger.i('删除视频${videoId}'); + logger.i('删除视频$videoId'); try { - final res = await Http.post('${VideoApi.deleteVideo}?id=${videoId}', data: {}); - logger.i('删除成功响应${res}'); - if(res != null && res['code'] == 200){ + final res = await Http.post('${VideoApi.deleteVideo}?id=$videoId', data: {}); + logger.i('删除成功响应$res'); + if (res != null && res['code'] == 200) { MyDialog.toast('删除成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); loadData(0); } - } catch (e) { - logger.e('删除视频失败: $e'); - } + } catch (e) {} } - // 显示删除确认对话框 - void showDeleteConfirmDialog(String videoId) { - Get.dialog( - Dialog( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 图标 - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.delete_outline, - color: Colors.red, - size: 30, - ), - ), - SizedBox(height: 16), - - // 标题 - Text( - '删除视频', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - SizedBox(height: 8), - - // 描述 - Text( - '确认要删除这个视频吗?\n删除后将无法恢复', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - height: 1.4, - ), - ), - SizedBox(height: 24), - - // 按钮区域 - Row( - children: [ - // 取消按钮 - Expanded( - child: OutlinedButton( - onPressed: () => Get.back(), - style: OutlinedButton.styleFrom( - backgroundColor: Colors.transparent, - foregroundColor: Colors.grey[700], - side: BorderSide(color: Colors.grey[300]!), - padding: EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text('取消'), - ), - ), - SizedBox(width: 12), - - // 确认按钮 - Expanded( - child: ElevatedButton( - onPressed: () { - Get.back(); - deletePersonalVideo(videoId); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - elevation: 0, - ), - child: Text('确认删除'), - ), - ), - ], - ), - ], - ), - ), - ), - barrierDismissible: true, - ); - } // 二维码名片弹窗 void qrcodeAlertDialog(BuildContext context) { showDialog( @@ -397,13 +296,14 @@ class MyPageState extends State with SingleTickerProviderStateMixin { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Image.asset('assets/images/pic1.jpg', width: 250.0, fit: BoxFit.contain), - const SizedBox(height: 15.0), - const Text('扫一扫,加好友', - style: TextStyle( - color: Colors.white38, - fontSize: 14.0, - )), + MyQrcode(prefix: QrTypeCode.tgm) + // Image.asset('assets/images/pic1.jpg', width: 250.0, fit: BoxFit.contain), + // const SizedBox(height: 15.0), + // const Text('扫一扫,加好友', + // style: TextStyle( + // color: Colors.white38, + // fontSize: 14.0, + // )), ], ), ), @@ -657,7 +557,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { borderRadius: BorderRadius.circular(10.0), ), alignment: Alignment.center, - child: _buildVdCard(listToShow[index],tabIndex), + child: _buildVdCard(listToShow[index], tabIndex), ); }, childCount: listToShow.length, @@ -683,48 +583,55 @@ class MyPageState extends State with SingleTickerProviderStateMixin { }); } - Widget _buildVdCard(item,tabIndex) { + Widget _buildVdCard(item, tabIndex) { return InkWell( onTap: () { //去视频详情 Get.toNamed('/videoDetail', arguments: {'videoId': item['id']}); }, onLongPress: () { - if(tabIndex == 0){ // 个人发布作品可以长按删除 + if (tabIndex == 0) { + // 个人发布作品可以长按删除 showModalBottomSheet( - context: Get.context!, - backgroundColor: Colors.white, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), - builder: (context) { - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // ListTile( - // leading: const Icon(Icons.lock, color: Colors.black), - // title: const Text('设为私密', style: TextStyle(color: Colors.black)), - // onTap: () { - // Navigator.pop(context); - // // TODO: 修改为私密逻辑 - // }, - // ), - ListTile( - leading: const Icon(Icons.delete, color: Colors.redAccent), - title: const Text('删除视频', style: TextStyle(color: Colors.redAccent)), - onTap: () async { - Get.back(); - showDeleteConfirmDialog(item['id']); - }, - ), - ], - ), - ); - }, - ); + context: Get.context!, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ListTile( + // leading: const Icon(Icons.lock, color: Colors.black), + // title: const Text('设为私密', style: TextStyle(color: Colors.black)), + // onTap: () { + // Navigator.pop(context); + // // TODO: 修改为私密逻辑 + // }, + // ), + ListTile( + leading: const Icon(Icons.delete, color: Colors.redAccent), + title: const Text('删除视频', style: TextStyle(color: Colors.redAccent)), + onTap: () async { + Get.back(); + final confirmed = await ConfirmDialog.show( + title: "提示", + content: "确认要删除吗?", + ); + if (confirmed == true) { + Get.back(); + deletePersonalVideo(item['id']); + } + }, + ), + ], + ), + ); + }, + ); } - }, child: Container( decoration: BoxDecoration( @@ -830,35 +737,72 @@ class MyPageState extends State with SingleTickerProviderStateMixin { Row( children: [ // 昵称 Obx - Obx(() { - final nickname = imUserInfoController?.nickname.value ?? ''; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: Colors.black.withAlpha(76), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - nickname.isNotEmpty ? nickname : '昵称', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, + Obx( + () { + final nickname = imUserInfoController?.nickname.value ?? ''; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: Colors.black.withAlpha(76), + borderRadius: BorderRadius.circular(20), ), - ), - ); - }), - const SizedBox(width: 8), - InkWell( - onTap: () => qrcodeAlertDialog(context), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), - decoration: BoxDecoration( - color: Colors.black.withAlpha(76), - borderRadius: BorderRadius.circular(20), - ), - child: const Icon(Icons.qr_code_outlined, size: 18, color: Colors.white), - ), + child: Text( + nickname.isNotEmpty ? nickname : '昵称', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ); + }, + ), + + // 团长的二维码 + Obx( + () { + // MERCHANT(2, "商家"), + // AGENT(3, "代理"), + // PLATFORM(4, "平台"), + // REFERENCE(5, "团长") + final role = imUserInfoController?.role.value; + if (role == 4) { + return Row(children: [ + SizedBox(width: 8), + // + InkWell( + onTap: () => qrcodeAlertDialog(context), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + decoration: BoxDecoration( + color: Colors.black.withAlpha(176), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Icon( + Icons.qr_code_outlined, + size: 18, + color: FStyle.secondaryColor, + ), + SizedBox(width: 4), + Text( + '团长邀请码', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: FStyle.secondaryColor, // 彩色文字 + ), + ) + ], + ), + ), + ), + ]); + } else { + return const SizedBox(); + } + }, ), ], ), @@ -912,7 +856,11 @@ class MyPageState extends State with SingleTickerProviderStateMixin { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // '已售${Utils.graceNumber(int.tryParse(vlogLikeCount?.toString() ?? '0') ?? 0)}', - Column(children: [Text('${Utils.graceNumber(vlogLikeCount)}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]), + Column(children: [ + Text(Utils.graceNumber(vlogLikeCount), style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), + SizedBox(height: 3.0), + Text('获赞') + ]), GestureDetector( onTap: () async { // 互相关注 diff --git a/lib/pages/my/merchant/balance/balance.dart b/lib/pages/my/merchant/balance/balance.dart new file mode 100644 index 0000000..5ed1a32 --- /dev/null +++ b/lib/pages/my/merchant/balance/balance.dart @@ -0,0 +1,125 @@ +// 钱包页面 +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/pages/my/merchant/balance/controller.dart'; + +class Balance extends StatefulWidget { + const Balance({super.key}); + + @override + State createState() => _BalanceState(); +} + +class _BalanceState extends State { + final controller = Get.put(BalanceController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("我的钱包")), + body: ScrollConfiguration( + behavior: CustomScrollBehavior().copyWith(scrollbars: false), + child: Column( + children: [ + // 上半部分:余额 + 充值 + Container( + padding: const EdgeInsets.all(16), + color: Colors.blueAccent, + width: double.infinity, + child: Obx(() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "钱包余额", + style: TextStyle(color: Colors.white, fontSize: 16), + ), + const SizedBox(height: 8), + Text( + "¥ ${controller.balance.value.toStringAsFixed(2)}", + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + ElevatedButton( + onPressed: () => controller.recharge(100.0), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + ), + child: const Text("充值 100 元"), + ), + ], + ); + }), + ), + + // 下半部分:流水记录分页列表 + Expanded( + child: Obx(() { + return EasyRefresh.builder( + callLoadOverOffset: 20, //触底距离 + callRefreshOverOffset: 20, // 下拉距离 + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + footer: ClassicFooter( + dragText: '加载更多', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: controller.hasMore.value ? '加载完成' : '没有更多了~', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + succeededIcon: controller.hasMore.value ? Icon(Icons.check_circle, color: Colors.green) : Icon(Icons.warning, color: Colors.orange), + ), + onRefresh: () async { + await controller.getData(reset: true); + }, + onLoad: () async { + if (controller.hasMore.value) { + await controller.getData(); + } + }, + childBuilder: (context, physics) { + return ListView.builder( + physics: physics, + itemCount: controller.data.length, + itemBuilder: (context, index) { + final tx = controller.data[index]; + logger.w(tx.source); + + return ListTile( + title: Text(tx.source), + subtitle: Text(tx.createTime), + trailing: Text( + "变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}", // 变动金额 + style: TextStyle( + fontWeight: FontWeight.bold, + color: tx.changeType == 1 ? Colors.green : Colors.red, + ), + ), + ); + }, + ); + }, + ); + }), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/my/merchant/balance/controller.dart b/lib/pages/my/merchant/balance/controller.dart new file mode 100644 index 0000000..223644a --- /dev/null +++ b/lib/pages/my/merchant/balance/controller.dart @@ -0,0 +1,56 @@ +import 'package:get/get.dart'; +import 'package:loopin/pages/my/merchant/balance/model.dart'; + +class BalanceController extends GetxController { + /// 钱包余额 + final balance = 0.0.obs; + + final data = [].obs; + + int currentPage = 1; + + final isLoading = false.obs; + + /// 是否还有更多 + var hasMore = true.obs; + + @override + void onInit() { + super.onInit(); + getData(reset: true); + } + + /// 充值 + void recharge(double amount) { + balance.value += amount; + // 同时加一条流水 + } + + /// 分页数据 + Future getData({bool reset = false}) async { + if (isLoading.value) return; + isLoading.value = true; + + if (reset) { + currentPage = 1; + data.clear(); + hasMore.value = true; + } + + await Future.delayed(const Duration(seconds: 3)); // 模拟网络延迟 + + List newData = List.generate( + 10, + (index) => AccountBill(id: index), + ); + + data.addAll(newData); + currentPage++; + + if (currentPage > 3) { + hasMore.value = false; + } + + isLoading.value = false; + } +} diff --git a/lib/pages/my/merchant/balance/model.dart b/lib/pages/my/merchant/balance/model.dart new file mode 100644 index 0000000..65c195e --- /dev/null +++ b/lib/pages/my/merchant/balance/model.dart @@ -0,0 +1,49 @@ +class AccountBill { + final int id; + final int? memberId; + final int? accountId; + final double moneyBalance; // 当前余额 + final double beforeBalance; // 变动前余额 + final double afterBalance; // 变动后余额 + final double changeAmount; // 变动金额 + final int changeType; // 变动类型(1收入 2支出) + final String changeDesc; // 变动描述 + final String source; // 变动来源 + final String createTime; // 创建时间 + final int createBy; + final String updateTime; + + AccountBill({ + required this.id, + this.memberId, + this.accountId, + this.moneyBalance = 0.0, + this.beforeBalance = 0.0, + this.afterBalance = 0.0, + this.changeAmount = 0.0, + this.changeType = 1, + this.changeDesc = '', + this.source = '未知来源', + this.createTime = '', + this.createBy = 0, + this.updateTime = '', + }); + + factory AccountBill.fromJson(Map json) { + return AccountBill( + id: json['id'] ?? 0, + memberId: json['member_id'], + accountId: json['account_id'], + moneyBalance: (json['money_balance'] as num?)?.toDouble() ?? 0.0, + beforeBalance: (json['before_balance'] as num?)?.toDouble() ?? 0.0, + afterBalance: (json['after_balance'] as num?)?.toDouble() ?? 0.0, + changeAmount: (json['change_amount'] as num?)?.toDouble() ?? 0.0, + changeType: json['change_type'] ?? 1, + changeDesc: json['change_desc'] ?? '', + source: json['source'] ?? '未知来源', + createTime: json['create_time'] ?? '', + createBy: json['create_by'] ?? 0, + updateTime: json['update_time'] ?? '', + ); + } +}