/// 商品详情页 library; import 'dart:convert'; import 'package:card_swiper/card_swiper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_message.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/api/shop_api.dart'; import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/models/share_type.dart'; import 'package:loopin/models/summary_type.dart'; import 'package:loopin/service/http.dart'; import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/wxsdk.dart'; import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; import '../../behavior/custom_scroll_behavior.dart'; import '../../components/backtop.dart'; class Goods extends StatefulWidget { const Goods({super.key}); @override State createState() => _GoodsState(); } class _GoodsState extends State { // late int shopId; //商品id dynamic shopObj; late ScrollController scrollController = ScrollController(); final ChatController chatController = Get.find(); // 滚动位置 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': '朋友圈'}, ]; @override void initState() { super.initState(); final goodsId = Get.arguments['goodsId'] ?? ''; scrollController.addListener(() { setState(() { scrollOffset = scrollController.offset; }); }); shopDetail(goodsId); } @override void dispose() { scrollController.dispose(); super.dispose(); } ///商品详情 void shopDetail(goodsId) async { try { final res = await Http.get('${ShopApi.shopDetail}/$goodsId'); logger.e(res['data']); setState(() { shopObj = res['data']; // 注意取 data 部分 }); } catch (e) { logger.e(e); Get.back(); } } ///创建定点杆 createOrder(String goodsId) async { var params = { "type": 1, // 订单类型:1->团购;2->拼团;3->秒杀; "distribution": 1, // 配送方式 1->到店核销;2->自提;3->配送; "skuItemBOList": [ {"skuId": goodsId, "quantity": 1} ] }; print('下单请求参数---->$params'); try { final res = await Http.post(ShopApi.createGoodsOrder, data: params); var resData = res['data']; print('1111111111111111111111111---->$res'); if (resData['id'].isNotEmpty) { return resData['id']; } else { return null; } } catch (e) { logger.e(e); return null; } } void handleShareClick(int index) { final description = shopObj['describe']; // 商品描述 if (index == 1) { // 好友 Wxsdk.shareToFriend(title: '快看看我分享的商品', description: description, webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}'); } else if (index == 2) { // 朋友圈 Wxsdk.shareToTimeline(title: '快看看我分享的商品', webpageUrl: '${ShareType.shop.name}?id=${shopObj['id']}'); } } void handlCoverClick(V2TimConversation conv) async { // 发送自定义消息 商品信息 final userId = conv.userID; //price,title,url,sell logger.w(shopObj['name']); final makeJson = jsonEncode({ "price": shopObj['price'], "title": shopObj['name'], "url": shopObj['pic'], "sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')), }); final res = await IMMessage().createCustomMessage( data: makeJson, ); if (res.success) { final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareTuangou); if (sendRes.success) { MyToast().tip( title: '分享成功', position: 'center', type: 'success', ); Get.back(); } else { logger.e(res.desc); } } else { logger.e(res.desc); } } // 分享弹框 void handleShare() { if (chatController.chatList.isNotEmpty) { chatController.getConversationList(); } showModalBottomSheet( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)), ), clipBehavior: Clip.antiAlias, context: context, isScrollControlled: true, builder: (context) { return Material( color: Colors.white, child: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 分享列表 SizedBox( height: 110, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: shareList.length, padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), itemBuilder: (context, index) { return GestureDetector( onTap: () => handleShareClick(index), child: Container( width: 64, margin: EdgeInsets.symmetric(horizontal: 8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.asset('${shareList[index]['icon']}', width: 48.0), SizedBox(height: 5), Text( '${shareList[index]['label']}', style: TextStyle(fontSize: 12.0), overflow: TextOverflow.ellipsis, ), ], ), ), ); }, ), ), // 会话列表 Obx(() { // 这里过滤掉有分组的会话 final filteredList = chatController.chatList.where((item) => conversationTypeFromString(item.isCustomAdmin) == null).toList(); if (filteredList.isEmpty) return SizedBox.shrink(); return SizedBox( height: 110, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: filteredList.length, padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), itemBuilder: (context, index) { return GestureDetector( // 点击分享 onTap: () => handlCoverClick(filteredList[index].conversation), child: Container( width: 64, margin: EdgeInsets.symmetric(horizontal: 8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0), ClipOval( child: NetworkOrAssetImage( imageUrl: filteredList[index].faceUrl, width: 48.0, height: 48.0, ), ), SizedBox(height: 5), Text( '${filteredList[index].conversation.showName}', style: TextStyle(fontSize: 12.0), overflow: TextOverflow.ellipsis, ), ], ), ), ); }, ), ); }), // 取消按钮 SafeArea( top: false, child: InkWell( onTap: () => Get.back(), child: Container( alignment: Alignment.center, width: double.infinity, height: 50.0, color: Colors.grey[50], child: Text( '取消', style: TextStyle(color: Colors.black87), ), ), ), ), ], ), ), ); }, ); } @override Widget build(BuildContext context) { if (shopObj == null) { return Center(child: CircularProgressIndicator()); } String swiperInfo = shopObj['albumPics'] ?? ""; List swiperList; if (swiperInfo.isNotEmpty) { swiperList = swiperInfo.split(','); // 商品详情轮播图 } else { swiperList = []; } dynamic attr = shopObj['productAttr']; //json数据 List attrList = []; if (!Utils.isEmpty(attr)) { attrList = jsonDecode(attr); } logger.e(attrList); return Scaffold( backgroundColor: Colors.grey[50], body: CustomScrollView( scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false), controller: scrollController, slivers: [ SliverAppBar( backgroundColor: Colors.transparent, foregroundColor: Colors.white, pinned: true, expandedHeight: 280.0, titleSpacing: 10.0, leading: IconButton( icon: Icon( Icons.arrow_back, size: 20.0, ), style: ButtonStyle( backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(20)), ), onPressed: () { Get.back(); }, ), actions: [ IconButton( icon: Icon( Icons.share, size: 20.0, ), onPressed: () { // 分享 handleShare(); }, ), ], // 自定义伸缩区域(轮播图) flexibleSpace: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFFFAA00)], ), ), child: FlexibleSpaceBar( background: ScrollConfiguration( behavior: CustomScrollBehavior(), child: Swiper.children( pagination: SwiperPagination( builder: DotSwiperPaginationBuilder( color: Colors.white70, activeColor: Colors.white, )), indicatorLayout: PageIndicatorLayout.SCALE, children: swiperList .map((sw) => NetworkOrAssetImage( imageUrl: sw, placeholderAsset: 'assets/images/bk.jpg', )) .toList(), ), ), ), ), ), SliverToBoxAdapter( child: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: Column( children: [ Container( padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 25.0), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF5000), Color(0xFFFFAA00)], ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 5.0, children: [ Row( spacing: 5.0, children: [ // 原价 // Text( // '¥${shopObj['price']}', // style: TextStyle( // color: Colors.white, // fontSize: 16.0, // decoration: TextDecoration.lineThrough, // decorationColor: Colors.black, // decorationThickness: 1.5, // ), // ), Container( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(50.0), ), child: Text( '¥${shopObj['price']}', style: TextStyle(color: Colors.red, fontSize: 12.0), ), ), Text( // '已售${Utils().graceNumber(shopObj['sales'] ?? 0)}', '已售${Utils.graceNumber(int.tryParse(shopObj['sales']?.toString() ?? '0') ?? 0)}', style: TextStyle(color: Colors.white, fontSize: 12.0), ), ], ), ], ), ), Container( padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0), width: double.infinity, decoration: BoxDecoration( color: Color(0xFFFAFAFA), borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)), ), transform: Matrix4.translationValues(0.0, -15.0, 0.0), child: Column( children: [ // 标题 Container( padding: EdgeInsets.all(5.0), child: Align( alignment: Alignment.centerLeft, child: Text.rich( TextSpan( children: [ WidgetSpan( alignment: PlaceholderAlignment.middle, child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( color: const Color(0xFFFF5000), borderRadius: BorderRadius.circular(4), ), child: Text( shopObj['productCategoryName'] ?? '未知分类名称', style: const TextStyle( fontSize: 12.0, color: Colors.white, ), ), ), ), const WidgetSpan(child: SizedBox(width: 4)), TextSpan( text: '${shopObj['describe'] ?? ''}', style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w700, ), ), ], ), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.left, ), ), ), // 规格 Container( margin: EdgeInsets.only(top: 10.0), padding: EdgeInsets.all(10.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.0), ), // child: Column( // spacing: 10.0, // children: [ // Row( // spacing: 5.0, // children: [ // Icon( // Icons.timer, // size: 16.0, // ), // Expanded( // child: Text( // '本商品请于2025.01.25前进行核销', // style: TextStyle(fontSize: 12.0), // ), // ), // ], // ), // Row( // spacing: 5.0, // children: [ // Icon( // Icons.house_outlined, // size: 16.0, // ), // Expanded( // child: Text( // '营业时间:7x24', // style: TextStyle(fontSize: 12.0), // ), // ), // ], // ), // Row( // spacing: 5.0, // children: [ // Icon( // Icons.location_on, // size: 16.0, // ), // Expanded( // child: Text( // '河北省唐山市玉田县', // style: TextStyle(fontSize: 12.0), // ), // ), // ], // ), // ], // ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: attrList.map((attr) { final attrName = attr['name'] ?? ''; final options = attr['options'] as List? ?? []; final optionNames = options.map((o) => o['name']).join(' / '); return Row( children: [ Text( '$attrName: ', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), Expanded( child: Text( optionNames, style: TextStyle(fontSize: 12), ), ), ], ); }).toList(), ), ), // 详情 Container( margin: EdgeInsets.only(top: 10.0), padding: EdgeInsets.all(10.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.0), ), child: Html( data: shopObj['detailMobileHtml'] ?? '暂无', )), ], ), ), ], ), ), ), ], ), // 商品导航栏 bottomNavigationBar: SafeArea( bottom: true, child: Container( height: 50.0, color: Colors.white, padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), child: Row( children: [ Expanded( child: Row( spacing: 15.0, children: [ // Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon( // Icons.store, // color: Color(0xFFFF5000), // size: 18.0, // ), // Text( // '店铺', // style: TextStyle(fontSize: 12.0), // ) // ], // ), // 联系商家 // GestureDetector( // onTap: () async { // // 可以在这里打开聊天 // logger.i('联系客服:$shopObj'); // final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}'); // V2TimConversation conversation = res.data; // logger.i(conversation.toLogString()); // if (res.success) { // // 客服聊天不用检测关注关系 // V2TimUserFullInfo? sellerInfo; // final resIm = await ImService.instance.otherInfo(shopObj['tenantId']); // if (resIm.success && resIm.data != null) { // sellerInfo = resIm.data!; // logger.i(sellerInfo!.toLogString()); // } else { // logger.e(resIm.desc); // } // conversation.showName = conversation.showName ?? sellerInfo!.nickName; // Get.toNamed('/chat', arguments: conversation); // } else { // MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); // } // }, // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon( // Icons.child_care_outlined, // size: 18.0, // ), // Text( // '联系商家', // style: TextStyle(fontSize: 12.0), // ) // ], // ), // ) // Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Badge.count( // backgroundColor: Color(0xFFFF5000), // count: 6, // child: Icon( // Icons.shopping_cart_outlined, // size: 18.0, // ), // ), // Text( // '购物车', // style: TextStyle(fontSize: 12.0), // ) // ], // ), ], ), ), Container( alignment: Alignment.center, height: 36.0, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Color(0xFFFFEBEB), borderRadius: BorderRadius.circular(30.0), ), child: Row( children: [ // Padding( // padding: EdgeInsets.symmetric(horizontal: 10.0), // child: Text( // '加入购物车', // style: TextStyle(color: Color(0xFFFF5000), fontSize: 14.0), // ), // ), Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 20.0), color: Color(0xFFFF5000), child: GestureDetector( onTap: () async { // 这里走生成预支付订单,拿到orderId // String orderId = '1958380183857659904'; //测试数据 String orderId = await createOrder(shopObj['skuList'][0]['id']); if (orderId.isNotEmpty) { Get.toNamed('/order/detail', arguments: orderId); } else { MyDialog.toast('生成订单失败', icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } }, child: Text( '立即购买', style: TextStyle(color: Colors.white, fontSize: 14.0), ), ), ), ], ), ), ], ), ), ), // 返回顶部 floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset), ); } }