member_page.dart 34 KB

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