member_page.dart 33 KB

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