member_page.dart 34 KB

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