member_page.dart 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. import 'dart:io';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/gestures.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/src/widgets/framework.dart';
  7. import 'package:flutter_screenutil/flutter_screenutil.dart';
  8. import 'package:get/get.dart';
  9. import 'package:get/get_core/src/get_main.dart';
  10. import 'package:location/base/base_page.dart';
  11. import 'package:location/module/member/member_controller.dart';
  12. import 'package:location/resource/assets.gen.dart';
  13. import 'package:location/resource/colors.gen.dart';
  14. import 'package:location/utils/common_expand.dart';
  15. import 'package:location/utils/project_expand.dart';
  16. import 'package:location/widget/auto_scroll_list_view.dart';
  17. import '../../data/bean/goods_bean.dart';
  18. import '../../data/bean/goods_evaluate_info.dart';
  19. import '../../data/bean/member_status_info.dart';
  20. import '../../data/bean/pay_item_bean.dart';
  21. import '../../data/consts/payment_type.dart';
  22. import '../../resource/string.gen.dart';
  23. import '../../router/app_pages.dart';
  24. import '../../utils/date_util.dart';
  25. import '../../widget/animated_switcher_widget.dart';
  26. import 'member_discount_countdown_widget.dart';
  27. import 'member_header_cycle_widget.dart';
  28. ///进入会员类型
  29. enum MemberPageType {
  30. ///通用进入
  31. universalAccessEnter,
  32. ///倒计时试用完会员进入
  33. afeterTrialMemberEnter,
  34. ///加好友进入
  35. addFriendToEnter,
  36. }
  37. class MemberPage extends BasePage<MemberController> {
  38. MemberPage({super.key,this.pageType});
  39. late MemberPageType? pageType = MemberPageType.universalAccessEnter;
  40. static void start({MemberPageType? enterTyp = MemberPageType.universalAccessEnter}) {
  41. Get.toNamed(RoutePath.member,arguments: enterTyp);
  42. }
  43. @override
  44. bool immersive() {
  45. return true;
  46. }
  47. @override
  48. bool statusBarDarkFont() {
  49. return false;
  50. }
  51. @override
  52. Widget buildBody(BuildContext context) {
  53. return PopScope(
  54. canPop: false,
  55. onPopInvokedWithResult: (bool didPop, dynamic result) async {
  56. if (didPop) {
  57. return;
  58. }
  59. controller.onPopBack();
  60. },
  61. child: Stack(
  62. children: [
  63. SingleChildScrollView(
  64. physics: const ClampingScrollPhysics(),
  65. controller: controller.scrollController,
  66. child: Stack(
  67. children: [
  68. MemberHeaderCycleWidget(),
  69. Column(
  70. children: [
  71. SizedBox(height: 249.w + MediaQuery.of(Get.context!).padding.top - 24.w),
  72. Container(
  73. decoration: BoxDecoration(
  74. color: ColorName.white,
  75. borderRadius: BorderRadius.only(
  76. topLeft: Radius.circular(20.w),
  77. topRight: Radius.circular(20.w)),
  78. ),
  79. child: Stack(
  80. children: [
  81. Container(
  82. decoration: BoxDecoration(
  83. borderRadius: BorderRadius.only(
  84. topLeft: Radius.circular(20.w),
  85. topRight: Radius.circular(20.w)),
  86. image: DecorationImage(
  87. image: Assets.images.iconMemberVipMiddleBg.provider(),
  88. )
  89. ),
  90. width: double.infinity,
  91. height: 174,
  92. ),
  93. Container(
  94. //width: double.infinity,
  95. decoration: BoxDecoration(
  96. borderRadius: BorderRadius.only(
  97. topLeft: Radius.circular(20.w),
  98. topRight: Radius.circular(20.w)),
  99. ),
  100. child: Column(
  101. crossAxisAlignment: CrossAxisAlignment.start,
  102. children: [
  103. SizedBox(height: 15.w),
  104. buildUserInfoView(),
  105. SizedBox(height: 23.w),
  106. buildGoodsList(),
  107. SizedBox(height: 12.w),
  108. buildPayWayView(),
  109. SizedBox(height: 12.w),
  110. Padding(
  111. padding: EdgeInsets.only(left: 16.w),
  112. child: Text(StringName.memberUserEvaluate,
  113. style: TextStyle(
  114. fontSize: 16.sp,
  115. color: ColorName.black,
  116. fontWeight: FontWeight.bold)),
  117. ),
  118. //SizedBox(height: 8.w),
  119. buildUserEvaluateList(),
  120. SizedBox(height: 190.w + MediaQuery.of(Get.context!).padding.bottom)
  121. ],
  122. ),
  123. ),
  124. ],
  125. ),
  126. )
  127. ],
  128. ),
  129. ],
  130. )
  131. ),
  132. buildHeadBar(),
  133. buildMemberBottomView()
  134. ],
  135. ),
  136. );
  137. }
  138. Widget buildGoodsList() {
  139. return Obx(() {
  140. final goods = controller.goodsList;
  141. final itemCount = goods.length;
  142. if (itemCount == 0) {
  143. return Container();
  144. }
  145. final widgets = <Widget>[];
  146. // 添加第一个商品(特殊样式)
  147. if (itemCount >= 1) {
  148. widgets.add(_createSpecialProduct(goods[0]));
  149. }
  150. // 如果有多个商品,添加普通样式商品
  151. if (itemCount >= 2) {
  152. widgets.add(SizedBox(height: 7.w));
  153. final rowItems = <Widget>[];
  154. // 计算可显示的普通商品数量(最多2个)
  155. final ordinaryCount = itemCount >= 3 ? 2 : 1;
  156. for (int i = 1; i <= ordinaryCount + 1 && i < itemCount; i++) {
  157. rowItems.add(Expanded(child: _ordinaryProductWidget(goods[i])));
  158. // 为除最后一个商品外的每个商品添加间距
  159. if (i < ordinaryCount + 1 && i < itemCount - 1) {
  160. rowItems.add(SizedBox(width: 8));
  161. }
  162. }
  163. widgets.add(Row(
  164. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  165. children: rowItems,
  166. ));
  167. }
  168. return Container(
  169. padding: EdgeInsets.symmetric(horizontal: 14.w),
  170. child: Column(
  171. children: widgets,
  172. ),
  173. );
  174. });
  175. }
  176. //特惠产品
  177. Widget _createSpecialProduct(GoodsBean goodsInfo) {
  178. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  179. return GestureDetector(
  180. onTap: () {
  181. controller.onGoodsItemClick(goodsInfo);
  182. },
  183. child: Container(
  184. height: 96.w,
  185. padding: EdgeInsets.only(left: 17.w),
  186. decoration: BoxDecoration(
  187. image: DecorationImage(
  188. image: isSelected ? Assets.images.iconMemberSpecialProductsSelect.provider() : Assets.images.iconMemberSpecialProductsNormal.provider(),
  189. fit: BoxFit.cover
  190. ),
  191. ),
  192. child: Column(
  193. children: [
  194. SizedBox(height: 29.w,),
  195. Row(
  196. children: [
  197. Column(
  198. children: [
  199. RichText(
  200. text: TextSpan(
  201. style: TextStyle(
  202. color: isSelected
  203. ? '#FF5656'.color
  204. : "#323133".color,
  205. fontWeight: FontWeight.bold),
  206. children: [
  207. TextSpan(
  208. text: '¥',
  209. style: TextStyle(
  210. fontSize: 16.sp, height: 1)),
  211. TextSpan(
  212. text: goodsInfo.amount.divideBy100(),
  213. style: TextStyle(
  214. fontSize: 28.sp,
  215. height: 1,
  216. //fontFamily: FontFamily.oppoSans
  217. )
  218. )
  219. ]
  220. )
  221. ),
  222. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  223. style: TextStyle(
  224. decoration: TextDecoration.lineThrough,
  225. decorationColor: ColorName.black40,
  226. decorationThickness: 1.0,
  227. fontSize: 12.sp,
  228. color: ColorName.black40))
  229. ],
  230. ),
  231. SizedBox(width: 22.24.w,),
  232. Column(
  233. crossAxisAlignment: CrossAxisAlignment.start,
  234. children: [
  235. Text(
  236. goodsInfo.name,
  237. style: TextStyle(
  238. fontSize: 17.sp,
  239. color: "#333333".color,
  240. fontWeight: FontWeight.bold),
  241. ),
  242. SizedBox(
  243. height: 3.w,
  244. ),
  245. Text(
  246. goodsInfo.description ?? "",
  247. style: TextStyle(
  248. fontSize: 11.sp ,
  249. color: "#9191BA".color,
  250. fontWeight: FontWeight.bold),
  251. ),
  252. ],
  253. ),
  254. ],
  255. ),
  256. ],
  257. ),
  258. ),
  259. );
  260. }
  261. ///普通产品
  262. Widget _ordinaryProductWidget(GoodsBean goodsInfo) {
  263. bool isSelected = controller.selectedGoods?.id == goodsInfo.id;
  264. return GestureDetector(
  265. onTap: () {
  266. controller.onGoodsItemClick(goodsInfo);
  267. },
  268. child: Container(
  269. height: 62.w,
  270. padding: EdgeInsets.only(left: 16.w,right: 12.w),
  271. decoration: BoxDecoration(
  272. image: DecorationImage(
  273. image: isSelected ? Assets.images.iconMemberOrdinaryProductSelect.provider() : Assets.images.iconMemberOrdinaryProductNormal.provider(),
  274. fit: BoxFit.cover
  275. ),
  276. ),
  277. child: Column(
  278. children: [
  279. SizedBox(height: 15.w,),
  280. Row(
  281. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  282. children: [
  283. Column(
  284. crossAxisAlignment: CrossAxisAlignment.start,
  285. children: [
  286. Text(
  287. goodsInfo.name,
  288. style: TextStyle(
  289. fontSize: 12.sp,
  290. color: "#333333".color,
  291. fontWeight: FontWeight.bold),
  292. ),
  293. //SizedBox(height: 6.w,),
  294. Text(
  295. goodsInfo.description ?? "",
  296. style: TextStyle(
  297. fontSize: 10.sp ,
  298. color: ColorName.black40,
  299. fontWeight: FontWeight.bold),
  300. ),
  301. ],
  302. ),
  303. //SizedBox(width: 22.24.w,),
  304. Column(
  305. crossAxisAlignment: CrossAxisAlignment.end,
  306. children: [
  307. RichText(
  308. text: TextSpan(
  309. style: TextStyle(
  310. color: isSelected
  311. ? '#FF5656'.color
  312. : "#323133".color,
  313. fontWeight: FontWeight.bold),
  314. children: [
  315. TextSpan(
  316. text: '¥',
  317. style: TextStyle(
  318. color: isSelected? "#FF5656".color : ColorName.black80,
  319. fontSize: 11.sp, height: 1)),
  320. TextSpan(
  321. text: goodsInfo.amount.divideBy100(),
  322. style: TextStyle(
  323. fontSize: 20.sp,
  324. height: 1,))
  325. ]
  326. )
  327. ),
  328. Text('¥${goodsInfo.originalAmount.divideBy100()}',
  329. style: TextStyle(
  330. decoration: TextDecoration.lineThrough,
  331. decorationColor: ColorName.black30,
  332. decorationThickness: 1.0,
  333. fontSize: 10.sp,
  334. color: ColorName.black30))
  335. ],
  336. ),
  337. ],
  338. ),
  339. ],
  340. ),
  341. ),
  342. );
  343. }
  344. Widget buildMemberBottomView() {
  345. return Obx(() {
  346. return Visibility(
  347. visible: controller.memberStatusInfo == null ||
  348. controller.memberStatusInfo?.permanent == false,
  349. child: Align(
  350. alignment: Alignment.bottomCenter,
  351. child: Container(//top: 98.w,
  352. alignment: Alignment.bottomCenter,
  353. height: 190.w + MediaQuery.of(Get.context!).padding.bottom,
  354. padding: EdgeInsets.only(
  355. left: 12.w, right: 12.w, bottom: 17.w + MediaQuery.of(Get.context!).padding.bottom),
  356. decoration: BoxDecoration(
  357. gradient: LinearGradient(
  358. begin: Alignment.bottomCenter, // 0deg 相当于从底部开始
  359. end: Alignment.topCenter,
  360. colors: [
  361. Colors.white, // #FFF
  362. "#00FFFFFF".color,
  363. ],
  364. stops: [0.4368, 1.0], // 对应 43.68% 和 100%
  365. ),
  366. ),
  367. child: Column(
  368. mainAxisAlignment: MainAxisAlignment.end,
  369. children: [
  370. Container(
  371. width: double.infinity, // 设置容器宽度
  372. height: 50.w, // 设置容器高度
  373. padding: EdgeInsets.only(left: 20.w),
  374. decoration: BoxDecoration(
  375. image: DecorationImage(
  376. image: Assets.images.iconMemberSettlementBg.provider(),
  377. fit: BoxFit.fill,
  378. ),
  379. ),
  380. // 实现内阴影效果
  381. child: Row(
  382. children: [
  383. Transform.translate(
  384. offset: Offset(0, 3), // 向下偏移4像素
  385. child: Text('¥',
  386. style: TextStyle(
  387. fontSize: 14.sp,
  388. color: '#FFF8EF'.color,
  389. fontWeight: FontWeight.bold)),
  390. ),
  391. SizedBox(width: 3.w,),
  392. Text(
  393. controller.selectedGoods?.amount.divideBy100() ?? '--',
  394. style: TextStyle(
  395. fontSize: 24.sp,
  396. color: '#FFF8EF'.color,
  397. fontWeight: FontWeight.bold),
  398. ),
  399. SizedBox(width: 3.w,),
  400. Text("/",
  401. style: TextStyle(
  402. fontSize: 12.sp,
  403. fontWeight: FontWeight.w400,
  404. color: "#FFF8EF".color),),
  405. Text('原价${controller.selectedGoods?.originalAmount.divideBy100()}',
  406. style: TextStyle(
  407. decoration: TextDecoration.lineThrough,
  408. decorationColor: Colors.white,
  409. decorationThickness: 1.0,
  410. fontSize: 12.sp,
  411. fontWeight: FontWeight.w400,
  412. color: "#FFF8EF".color)),
  413. Spacer(),
  414. GestureDetector(
  415. onTap: controller.onBuyClick,
  416. child: Container(
  417. decoration: BoxDecoration(
  418. image: DecorationImage(
  419. image: Assets.images.iconMemberSettlementConfirm.provider(),
  420. fit: BoxFit.fill,
  421. )
  422. ),
  423. // width: 164.w,
  424. // height: 44.w,
  425. padding: EdgeInsets.only(left: 64.w,right: 29.w),
  426. child: Center(
  427. child: Text(
  428. controller.memberStatusInfo?.expired == false
  429. ? StringName.memberVipRenew
  430. : StringName.memberVipUnlock,
  431. style: TextStyle(
  432. fontSize: 18.sp,
  433. color: "#9B3800".color,
  434. fontWeight: FontWeight.bold),
  435. ),
  436. ),
  437. ),
  438. )
  439. ],
  440. ),
  441. ),
  442. SizedBox(height: 8.w,),
  443. buildPrivacyPolicyView(),
  444. ],
  445. ),
  446. ),
  447. ),
  448. );
  449. });
  450. }
  451. Widget buildUserInfoView() {
  452. return Row(
  453. children: [
  454. SizedBox(width: 18.w),
  455. controller.isLogin ? ClipOval(
  456. child: Container(
  457. width: 32.w,
  458. height: 32.w,
  459. child: CachedNetworkImage(
  460. imageUrl: controller.memberStatusInfo?.avatar ?? "",
  461. fit: BoxFit.cover,
  462. ),
  463. ),
  464. ) : Assets.images.iconMemberAvatar.image(width: 32.w, height: 32.w),
  465. SizedBox(width: 7.w),
  466. Column(
  467. crossAxisAlignment: CrossAxisAlignment.start,
  468. children: [
  469. Obx(() {
  470. return GestureDetector(
  471. onTap: controller.onLoginClick,
  472. child: Text(
  473. controller.phone?.isNotEmpty == true
  474. ? controller.getUserName(controller.phone!)
  475. : StringName.mineAccountGoLogin,
  476. style: TextStyle(
  477. fontSize: 13.sp,
  478. color: "#333333".color,
  479. fontWeight: FontWeight.bold)),
  480. );
  481. }),
  482. Container(
  483. height: 16.w,
  484. width: MediaQuery.of(Get.context!).size.width - 77.w,
  485. child: Row(
  486. // 主轴默认左对齐,通过Spacer推挤右侧内容
  487. children: [
  488. // 左侧内容:会员等级描述 + VIP卡片描述
  489. Visibility(
  490. visible: MemberStatusInfo.getLevelDesc(controller.memberStatusInfo).isNotEmpty,
  491. child: Text(
  492. MemberStatusInfo.getLevelDesc(controller.memberStatusInfo),
  493. style: TextStyle(
  494. fontSize: 11.sp,
  495. fontWeight: FontWeight.w700,
  496. color: '#9144F8'.color
  497. )
  498. ),
  499. ),
  500. buildMemberCardVipDesc(),
  501. Spacer(),
  502. Container(
  503. child: MemberDiscountCountdownWidget(
  504. onExpired: () {
  505. print("sssfsdfs");
  506. controller.isShowCount.value = false;
  507. },
  508. ),
  509. )
  510. ],
  511. ),
  512. )
  513. ],
  514. ),
  515. SizedBox(width: 20.w)
  516. ],
  517. );
  518. }
  519. Widget buildMemberCardVipDesc() {
  520. return Obx(() {
  521. String desc = '';
  522. if (!controller.isLogin) {
  523. if ((controller.memberStatusInfo?.endTimestamp?? 0) > 0) {
  524. desc =
  525. '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}';
  526. } else {
  527. desc = StringName.memberCardNoLoginDesc;
  528. }
  529. } else if (controller.memberStatusInfo == null ||
  530. controller.memberStatusInfo?.expired == true) {
  531. desc = StringName.memberCardNoVipDesc;
  532. } else if (controller.memberStatusInfo?.expired == false &&
  533. controller.memberStatusInfo?.permanent == true) {
  534. desc = StringName.memberCardPermanentVipDesc;
  535. } else {
  536. desc =
  537. '${DateUtil.fromMillisecondsSinceEpoch('yyyy-MM-dd', controller.memberStatusInfo?.endTimestamp ?? 0)} ${StringName.memberCardExpirationDesc}';
  538. }
  539. return Text(desc,
  540. style: TextStyle(fontSize: 11.sp, color: ColorName.black50,fontWeight: FontWeight.w400));
  541. });
  542. }
  543. Widget buildHeadBar() {
  544. return Column(
  545. children: [
  546. SizedBox(height: MediaQuery.of(Get.context!).padding.top,),
  547. Container(
  548. width: double.infinity,
  549. height: 56.w,
  550. child: Row(
  551. crossAxisAlignment: CrossAxisAlignment.center,
  552. children: [
  553. SizedBox(width: 15.w,),
  554. GestureDetector(
  555. onTap: () => controller.onPopBack(),
  556. child: Assets.images.iconMemberVipBack
  557. .image(width: 26.w, height: 26..w),
  558. ),
  559. SizedBox(width: 10.w,),
  560. Container(
  561. width: MediaQuery.of(Get.context!).size.width - 51.w,
  562. padding: EdgeInsets.only(right: 12.w),
  563. child: buildVerticalSlideshowWidget()
  564. ),
  565. ],
  566. ),
  567. )
  568. ],
  569. );
  570. }
  571. Widget buildVerticalSlideshowWidget() {
  572. return Row(
  573. children: [
  574. Expanded(child: Center(
  575. child: Container(
  576. width: 192.w,
  577. height: 26.w,
  578. decoration: BoxDecoration(
  579. color: ColorName.black40,
  580. borderRadius: BorderRadius.circular(87.w),
  581. ),
  582. child: Center(
  583. child: AnimatedSwitcherWidget(
  584. controller: controller.switcherController)),
  585. ),
  586. ),
  587. ),
  588. if (Platform.isAndroid) SizedBox(width: 40.w,),
  589. if (Platform.isIOS) Obx(() {
  590. return Visibility(
  591. visible: Platform.isIOS && controller.accountRepository.isLogin.value,
  592. child: Row(
  593. children: [
  594. SizedBox(width: 26.w,),
  595. GestureDetector(
  596. onTap: controller.clickRecoverSubscribe,
  597. child: Container(
  598. height: 26.w,
  599. decoration: BoxDecoration(
  600. color: ColorName.black40,
  601. borderRadius: BorderRadius.circular(26.w / 2.0),
  602. ),
  603. padding: EdgeInsets.symmetric(horizontal: 10.w),
  604. child: Row(
  605. children: [
  606. Assets.images.iconAppleRecoverSubscribe.image(width: 14.w,height: 14.w),
  607. Text(StringName.appleRecoverSubscribeTxt,
  608. style: TextStyle(
  609. fontSize: 11.sp,
  610. color: ColorName.white,
  611. fontWeight: FontWeight.w500)),
  612. ],
  613. ),
  614. ),
  615. )
  616. ],
  617. ));
  618. })
  619. ],
  620. );
  621. }
  622. Widget buildPrivacyPolicyView() {
  623. return Padding(
  624. padding: EdgeInsets.only(left: 12.w),
  625. child: RichText(
  626. text: TextSpan(
  627. style: TextStyle(fontSize: 12.sp, color: ColorName.black40),
  628. children: [
  629. TextSpan(text: '购买前请先阅读'),
  630. TextSpan(
  631. recognizer: TapGestureRecognizer()
  632. ..onTap = () {
  633. controller.onPrivacyPolicyClick();
  634. },
  635. text: '隐私政策',
  636. style: TextStyle(
  637. color: ColorName.black60,
  638. decoration: TextDecoration.underline)),
  639. TextSpan(text: '&'),
  640. TextSpan(
  641. recognizer: TapGestureRecognizer()
  642. ..onTap = () {
  643. controller.onTermOfServiceClick();
  644. },
  645. text: '服务条款',
  646. style: TextStyle(
  647. color: ColorName.black60,
  648. decoration: TextDecoration.underline)),
  649. ])),
  650. );
  651. }
  652. Widget buildFunctionList() {
  653. return SizedBox(
  654. height: 80.w,
  655. child: AutoScrollListView(
  656. padding: EdgeInsets.only(left: 12.w),
  657. itemBuilder: (ctx, index) {
  658. final item = controller.funList[index];
  659. return Padding(
  660. padding: EdgeInsets.only(right: 20.w),
  661. child: Column(
  662. children: [
  663. Image.asset(item.iconPath, width: 36.w, height: 36.w),
  664. Spacer(flex: 3),
  665. Text(item.funName,
  666. style: TextStyle(
  667. fontSize: 12.8.sp,
  668. color: ColorName.black90,
  669. fontWeight: FontWeight.bold)),
  670. Spacer(flex: 2),
  671. Text(item.funDesc,
  672. style: TextStyle(
  673. fontSize: 10.6.sp,
  674. color: ColorName.black50,
  675. )),
  676. ],
  677. ),
  678. );
  679. },
  680. itemCount: controller.funList.length));
  681. }
  682. Widget buildUserEvaluateList() {
  683. return Obx(() {
  684. return Column(
  685. children: [
  686. for (int index = 0; index < controller.evaluateList.length; index++)
  687. buildUserEvaluateItem(controller.evaluateList.value[index],
  688. index == controller.evaluateList.value.length - 1,index == 0)
  689. ],
  690. );
  691. });
  692. }
  693. Widget buildUserEvaluateItem(GoodsEvaluateInfo item, bool isLast,bool isFirst) {
  694. return Container(
  695. padding: EdgeInsets.symmetric(horizontal: 16.w),
  696. child: Column(
  697. crossAxisAlignment: CrossAxisAlignment.start,
  698. children: [
  699. SizedBox(height: isFirst? 16.w : 20.w),
  700. Row(
  701. children: [
  702. CachedNetworkImage(imageUrl: item.avatar,width: 34.w, height: 34.w,),
  703. SizedBox(width: 10.w),
  704. // 使用 Expanded 确保 Column 占据剩余宽度
  705. Expanded(
  706. child: Column(
  707. crossAxisAlignment: CrossAxisAlignment.start,
  708. children: [
  709. Row(
  710. children: [
  711. Text(
  712. item.name,
  713. style: TextStyle(fontSize: 14.sp, color: "#333333".color, fontWeight: FontWeight.w400),
  714. ),
  715. Spacer(),
  716. Assets.images.iconMemberCommentVerySatisfied.image(width: 16.w,height: 16.w),
  717. SizedBox(width: 5.w),
  718. Text(
  719. item.description,
  720. style: TextStyle(fontSize: 11.sp, color: "#DC8514".color, fontWeight: FontWeight.w500),
  721. )
  722. ],
  723. ),
  724. SizedBox(height: 5.w),
  725. Text(
  726. item.content,
  727. style: TextStyle(fontSize: 11.sp, color: '#333333'.color, fontWeight: FontWeight.w400),
  728. maxLines: null, // 允许无限行数
  729. softWrap: true, // 允许文本换行
  730. )
  731. ],
  732. ),
  733. )
  734. ],
  735. ),
  736. SizedBox(height: 20.w),
  737. Visibility(
  738. child: Container(
  739. color: '#EEEEEE'.color,
  740. height: 1.w,
  741. ),
  742. )
  743. ],
  744. ),
  745. );
  746. }
  747. Widget buildPayWayView() {
  748. return Obx(() {
  749. return Visibility(
  750. visible: Platform.isIOS ? false : controller.payItemList.isNotEmpty,
  751. child: Container(
  752. margin: EdgeInsets.symmetric(horizontal: 16.w),
  753. child: Column(
  754. crossAxisAlignment: CrossAxisAlignment.start,
  755. children: [
  756. SizedBox(height: 12.w),
  757. Text("支付方式",
  758. style: TextStyle(fontSize: 16.sp, color: ColorName.black,fontWeight: FontWeight.bold)),
  759. SizedBox(height: 5.w,),
  760. for (PayItemBean item in controller.payItemList)
  761. buildPayWayItem(item)
  762. ],
  763. ),
  764. ),
  765. );
  766. });
  767. }
  768. Widget buildPayWayItem(PayItemBean item) {
  769. return GestureDetector(
  770. behavior: HitTestBehavior.translucent,
  771. onTap: () => controller.onPayWayItemClick(item),
  772. child: Obx(() {
  773. bool isSelected = controller.selectedPayWay?.id == item.id;
  774. return Container(
  775. padding: EdgeInsets.symmetric(vertical: 12.w),
  776. child: Row(
  777. children: [
  778. Image.asset(
  779. getPaymentIconPath(
  780. payMethod:item.payMethod, payPlatform: item.payPlatform),
  781. width: 20.w,
  782. height: 20.w),
  783. SizedBox(width: 8.w),
  784. Text(item.title,
  785. style: TextStyle(fontSize: 12.sp, color: ColorName.black,fontWeight: FontWeight.w400)),
  786. Spacer(),
  787. Image.asset(
  788. isSelected
  789. ? Assets.images.iconCbSelected.path
  790. : Assets.images.iconCbUnSelect.path,
  791. width: 16.w,
  792. height: 16.w),],
  793. ),
  794. );
  795. }),
  796. );
  797. }
  798. }