From d5fed9278463a107641b40359f3688829497d2f3 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Wed, 26 Nov 2025 00:46:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(live):=20=E6=B7=BB=E5=8A=A0=E7=A4=BC?= =?UTF-8?q?=E7=89=A9=E4=BA=A7=E5=93=81=E5=8A=9F=E8=83=BD=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=9B=B4=E6=92=AD=E9=97=B4=E7=BB=93=E6=9D=9F=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增礼物产品模型 GiftProductModel - 在 RoomController 中集成礼物产品列表加载逻辑 - 实现直播间结束页面 LiveEndPage - 添加获取礼物产品列表的 API 接口 - 更新网络服务以支持礼物产品相关请求 --- lib/controller/discover/room_controller.dart | 22 ++ lib/model/live/gift_product_model.dart | 66 ++++++ lib/network/api_urls.dart | 2 + lib/network/rtc_api.dart | 5 + lib/network/rtc_api.g.dart | 41 ++++ lib/pages/discover/live_end_page.dart | 207 +++++++++++++++++++ 6 files changed, 343 insertions(+) create mode 100644 lib/model/live/gift_product_model.dart create mode 100644 lib/pages/discover/live_end_page.dart diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index ffb85a9..bfee3d9 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -1,5 +1,6 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/global.dart'; +import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/network_service.dart'; @@ -36,6 +37,9 @@ class RoomController extends GetxController { /// 聊天消息列表 final RxList chatMessages = [].obs; + /// 礼物产品列表 + final RxList giftProducts = [].obs; + /// 消息服务实例 final LiveChatMessageService _messageService = LiveChatMessageService.instance; @@ -45,6 +49,8 @@ class RoomController extends GetxController { super.onInit(); // 注册消息监听 _registerMessageListener(); + // 加载礼物产品列表 + loadGiftProducts(); } @override @@ -288,6 +294,22 @@ class RoomController extends GetxController { await RTCManager.instance.leaveChannel(); } + /// 加载礼物产品列表 + Future loadGiftProducts() async { + try { + final response = await _networkService.rtcApi.listGiftProduct(); + final base = response.data; + if (base.isSuccess && base.data != null) { + giftProducts.assignAll(base.data!); + print('✅ 礼物产品列表加载成功,共 ${giftProducts.length} 个'); + } else { + print('❌ 加载礼物产品列表失败: ${base.message}'); + } + } catch (e) { + print('❌ 加载礼物产品列表异常: $e'); + } + } + /// 接收RTC消息 Future receiveRTCMessage(Map message) async { if (message['type'] == 'join_chat') { diff --git a/lib/model/live/gift_product_model.dart b/lib/model/live/gift_product_model.dart new file mode 100644 index 0000000..8aea714 --- /dev/null +++ b/lib/model/live/gift_product_model.dart @@ -0,0 +1,66 @@ +/// 礼物产品模型 +class GiftProductModel { + final String productId; + final String productSpecId; + final int mainCategory; + final int subCategory; + final String productTitle; + final String productDesc; + final String? detailDesc; + final double unitOriginalPrice; + final double unitSellingPrice; + final String mainPic; + final String svgaFile; + + GiftProductModel({ + required this.productId, + required this.productSpecId, + required this.mainCategory, + required this.subCategory, + required this.productTitle, + required this.productDesc, + this.detailDesc, + required this.unitOriginalPrice, + required this.unitSellingPrice, + required this.mainPic, + required this.svgaFile, + }); + + factory GiftProductModel.fromJson(Map json) { + return GiftProductModel( + productId: json['productId']?.toString() ?? '', + productSpecId: json['productSpecId']?.toString() ?? '', + mainCategory: json['mainCategory'] as int? ?? 0, + subCategory: json['subCategory'] as int? ?? 0, + productTitle: json['productTitle']?.toString() ?? '', + productDesc: json['productDesc']?.toString() ?? '', + detailDesc: json['detailDesc']?.toString(), + unitOriginalPrice: (json['unitOriginalPrice'] as num?)?.toDouble() ?? 0.0, + unitSellingPrice: (json['unitSellingPrice'] as num?)?.toDouble() ?? 0.0, + mainPic: json['mainPic']?.toString() ?? '', + svgaFile: json['svgaFile']?.toString() ?? '', + ); + } + + Map toJson() { + return { + 'productId': productId, + 'productSpecId': productSpecId, + 'mainCategory': mainCategory, + 'subCategory': subCategory, + 'productTitle': productTitle, + 'productDesc': productDesc, + 'detailDesc': detailDesc, + 'unitOriginalPrice': unitOriginalPrice, + 'unitSellingPrice': unitSellingPrice, + 'mainPic': mainPic, + 'svgaFile': svgaFile, + }; + } + + @override + String toString() { + return 'GiftProductModel(productId: $productId, productTitle: $productTitle, unitSellingPrice: $unitSellingPrice)'; + } +} + diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 83102e2..5aefe9b 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -58,6 +58,8 @@ class ApiUrls { 'dating-agency-chat-audio/user/disconnect/rtc-channel'; static const String getRtcChannelPage = 'dating-agency-chat-audio/user/page/rtc-channel'; + static const String listGiftProduct = + 'dating-agency-mall/user/list/gift-product'; static const String listBankCardByIndividual = 'dating-agency-mall/user/list/bank-card/by-individual'; static const String createBankCardByIndividual = diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 54e351e..6ec1931 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,4 +1,5 @@ import 'package:dating_touchme_app/model/discover/rtc_channel_model.dart'; +import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/api_urls.dart'; @@ -61,4 +62,8 @@ abstract class RtcApi { /// 获取 RTC 频道分页列表 @GET(ApiUrls.getRtcChannelPage) Future>>> getRtcChannelPage(); + + /// 获取礼物产品列表 + @GET(ApiUrls.listGiftProduct) + Future>>> listGiftProduct(); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index bbcdead..e9ee003 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -327,6 +327,47 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>>> + listGiftProduct() async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType>>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-mall/user/list/gift-product', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl), + ), + ); + final _result = await _dio.fetch>(_options); + late BaseResponse> _value; + try { + _value = BaseResponse>.fromJson( + _result.data!, + (json) => json is List + ? json + .map( + (i) => GiftProductModel.fromJson(i as Map), + ) + .toList() + : List.empty(), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/pages/discover/live_end_page.dart b/lib/pages/discover/live_end_page.dart new file mode 100644 index 0000000..3158392 --- /dev/null +++ b/lib/pages/discover/live_end_page.dart @@ -0,0 +1,207 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/pages/main/main_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class LiveEndPage extends StatelessWidget { + const LiveEndPage({super.key}); + + @override + Widget build(BuildContext context) { + final roomController = Get.find(); + final anchorInfo = roomController.rtcChannelDetail.value?.anchorInfo; + + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + const Color.fromRGBO(19, 16, 47, 1), + const Color.fromRGBO(19, 16, 47, 1).withOpacity(0.8), + ], + ), + ), + child: SafeArea( + child: Column( + children: [ + // 顶部返回按钮 + Padding( + padding: EdgeInsets.only(left: 16.w, top: 10.w), + child: Align( + alignment: Alignment.centerLeft, + child: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: Colors.white, + size: 20.w, + ), + onPressed: () { + Get.back(); + }, + ), + ), + ), + // 主要内容区域 + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 头像 + Container( + width: 120.w, + height: 120.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 2.w, + ), + ), + child: ClipOval( + child: anchorInfo?.profilePhoto != null && + anchorInfo!.profilePhoto.isNotEmpty + ? CachedNetworkImage( + imageUrl: + "${anchorInfo.profilePhoto}?x-oss-process=image/format,webp/resize,w_240", + width: 120.w, + height: 120.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.grey.withOpacity(0.3), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2.w, + color: Colors.white.withOpacity(0.5), + ), + ), + ), + errorWidget: (context, url, error) => + Image.asset( + Assets.imagesUserAvatar, + width: 120.w, + height: 120.w, + fit: BoxFit.cover, + ), + ) + : Image.asset( + Assets.imagesUserAvatar, + width: 120.w, + height: 120.w, + fit: BoxFit.cover, + ), + ), + ), + SizedBox(height: 20.w), + // 用户名和关注按钮 + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + anchorInfo?.nickName ?? '用户', + style: TextStyle( + fontSize: 18.w, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + SizedBox(width: 12.w), + // 关注按钮 + Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 6.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.w), + border: Border.all( + color: Colors.white, + width: 1.w, + ), + ), + child: Text( + '关注', + style: TextStyle( + fontSize: 14.w, + color: Colors.white, + ), + ), + ).onTap(() { + // TODO: 实现关注功能 + print('点击关注'); + }), + ], + ), + SizedBox(height: 60.w), + // 状态消息(带横线) + Row( + children: [ + Expanded( + child: Container( + height: 1.w, + color: Colors.white.withOpacity(0.3), + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: Text( + '当前相亲已结束', + style: TextStyle( + fontSize: 14.w, + color: Colors.white, + ), + ), + ), + Expanded( + child: Container( + height: 1.w, + color: Colors.white.withOpacity(0.3), + ), + ), + ], + ), + ], + ), + ), + // 底部返回首页按钮 + Padding( + padding: EdgeInsets.only( + left: 20.w, + right: 20.w, + bottom: 40.w + MediaQuery.of(context).padding.bottom, + ), + child: Container( + width: double.infinity, + height: 50.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.w), + color: const Color.fromRGBO(108, 105, 244, 1), + ), + child: Center( + child: Text( + '返回首页', + style: TextStyle( + fontSize: 16.w, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ).onTap(() { + Get.offAll(() => const MainPage()); + }), + ), + ], + ), + ), + ), + ); + } +} +