store_page.dart 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. import 'package:auto_size_text/auto_size_text.dart';
  2. import 'package:carousel_slider/carousel_slider.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_screenutil/flutter_screenutil.dart';
  5. import 'package:get/get.dart';
  6. import 'package:keyboard/base/base_page.dart';
  7. import 'package:keyboard/data/consts/payment_type.dart';
  8. import 'package:keyboard/data/consts/web_url.dart';
  9. import 'package:keyboard/module/store/store_controller.dart';
  10. import 'package:keyboard/module/store/store_user_reviews_bean.dart';
  11. import 'package:keyboard/resource/string.gen.dart';
  12. import '../../resource/assets.gen.dart';
  13. import '../../router/app_pages.dart';
  14. import '../../utils/date_util.dart';
  15. import '../../widget/horizontal_dashed_line.dart';
  16. import '../../utils/styles.dart';
  17. import '../../widget/click_text_span.dart';
  18. class StorePage extends BasePage<StoreController> {
  19. const StorePage({super.key});
  20. static start() {
  21. Get.toNamed(RoutePath.store);
  22. }
  23. @override
  24. backgroundColor() => const Color(0xFFFFF8D4);
  25. @override
  26. bool immersive() {
  27. return true;
  28. }
  29. @override
  30. Widget buildBody(BuildContext context) {
  31. return PopScope(
  32. canPop: false,
  33. onPopInvokedWithResult: (didPop, result) {
  34. if (didPop) {
  35. return;
  36. }
  37. controller.clickBack();
  38. },
  39. child: Stack(
  40. children: [
  41. SingleChildScrollView(
  42. child: Column(
  43. children: [
  44. _buildBanner(context),
  45. SizedBox(height: 12.h),
  46. _buildGoodsCard(),
  47. _buildVipDesc(),
  48. SizedBox(height: 32.h),
  49. _buildUserReviews(),
  50. SizedBox(height: 20.h),
  51. _buildUserNotice(),
  52. ],
  53. ),
  54. ),
  55. Positioned(top: 0, left: 0, right: 0, child: _buildTitle()),
  56. Positioned(
  57. bottom: 0,
  58. left: 0,
  59. right: 0,
  60. child: _buildBuyButtonCard(),
  61. ),
  62. ],
  63. ),
  64. );
  65. }
  66. _buildTitle() {
  67. return SafeArea(
  68. child: Container(
  69. alignment: Alignment.centerLeft,
  70. padding: EdgeInsets.only(top: 16.h, left: 16.w, right: 16.w),
  71. child: Row(
  72. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  73. children: [
  74. GestureDetector(
  75. onTap: controller.clickBack,
  76. child: Assets.images.iconStoreBack.image(
  77. width: 32.w,
  78. height: 32.w,
  79. ),
  80. ),
  81. Obx(() {
  82. return SizedBox(
  83. height: 32.h,
  84. child: Row(
  85. mainAxisSize: MainAxisSize.min,
  86. mainAxisAlignment: MainAxisAlignment.center,
  87. crossAxisAlignment: CrossAxisAlignment.center,
  88. children: [
  89. // 会员状态信息
  90. _buildMemberInfo(),
  91. SizedBox(width: 8.w),
  92. controller.isLogin
  93. ? Assets.images.iconMineUserLogged.image(
  94. width: 28.w,
  95. height: 28.w,
  96. )
  97. : Assets.images.iconMineUserNoLogin.image(
  98. width: 28.w,
  99. height: 28.w,
  100. ),
  101. ],
  102. ),
  103. );
  104. }),
  105. ],
  106. ),
  107. ),
  108. );
  109. }
  110. // 会员信息
  111. Widget _buildMemberInfo() {
  112. return Container(
  113. height: 32.h,
  114. alignment: Alignment.center,
  115. padding: EdgeInsets.symmetric(horizontal: 12.w),
  116. decoration: BoxDecoration(
  117. color: Colors.black.withAlpha(77),
  118. borderRadius: BorderRadius.circular(21.r),
  119. ),
  120. child: Text.rich(
  121. TextSpan(children: _getMemberStatusText()),
  122. textAlign: TextAlign.right,
  123. ),
  124. );
  125. }
  126. // 会员状态文字逻辑提取
  127. List<TextSpan> _getMemberStatusText() {
  128. // 未登录
  129. if (!controller.isLogin) {
  130. return [
  131. TextSpan(
  132. text: StringName.memberNoLogged,
  133. style: _vipTextStyle(isHighlight: true),
  134. ),
  135. TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()),
  136. ];
  137. }
  138. final isMember = controller.memberStatusInfo?.isMember ?? false;
  139. final isPermanent = controller.memberStatusInfo?.permanent ?? false;
  140. final username = controller.userInfo?.name ?? '';
  141. if (isMember) {
  142. if (isPermanent) {
  143. return [
  144. TextSpan(
  145. text: StringName.memberCardPermanentVipDesc1,
  146. style: _vipTextStyle(),
  147. ),
  148. TextSpan(
  149. text: StringName.memberCardPermanentVipDesc2,
  150. style: _vipTextStyle(isHighlight: true),
  151. ),
  152. ];
  153. } else {
  154. return [
  155. TextSpan(text: StringName.memberVipDesc, style: _vipTextStyle()),
  156. TextSpan(
  157. text: DateUtil.fromMillisecondsSinceEpoch(
  158. 'yyyy.MM.dd',
  159. controller.memberStatusInfo?.endTimestamp ?? 0,
  160. ),
  161. style: _vipTextStyle(isHighlight: true),
  162. ),
  163. ];
  164. }
  165. }
  166. // 登录但不是会员
  167. return [
  168. TextSpan(
  169. text: controller.userInfo?.name ?? controller.getUserName(),
  170. style: _vipTextStyle(isHighlight: true),
  171. ),
  172. TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()),
  173. ];
  174. }
  175. // 统一的会员状态文本样式
  176. TextStyle _vipTextStyle({bool isHighlight = false}) {
  177. return TextStyle(
  178. color: isHighlight ? const Color(0xFFFFD273) : Colors.white,
  179. fontSize: 12.sp,
  180. fontWeight: FontWeight.w400,
  181. );
  182. }
  183. Widget _buildGoodsCard() {
  184. return Container(
  185. margin: EdgeInsets.symmetric(horizontal: 16.w),
  186. padding: EdgeInsets.only(
  187. top: 16.h,
  188. left: 16.w,
  189. right: 16.w,
  190. bottom: 24.h,
  191. ),
  192. decoration: BoxDecoration(
  193. color: Colors.white,
  194. borderRadius: BorderRadius.only(
  195. topLeft: Radius.circular(16.r),
  196. topRight: Radius.circular(16.r),
  197. ),
  198. ),
  199. child: Column(
  200. crossAxisAlignment: CrossAxisAlignment.start,
  201. mainAxisAlignment: MainAxisAlignment.start,
  202. children: [
  203. Assets.images.iconGoodsInfoTitle.image(
  204. width: 115.w,
  205. fit: BoxFit.cover,
  206. ),
  207. SizedBox(height: 16.h),
  208. Obx(() {
  209. return Column(
  210. children:
  211. controller.filteredGoodsList.map((item) {
  212. return Obx(() {
  213. return GestureDetector(
  214. onTap: () => controller.onGoodsItemClick(item),
  215. child: _buildGoodsItem(
  216. item,
  217. controller.selectedGoodsInfoItem?.id == item.id,
  218. ),
  219. );
  220. });
  221. }).toList(),
  222. );
  223. }),
  224. _buildPayWayCard(),
  225. ],
  226. ),
  227. );
  228. }
  229. Widget _buildPayWayCard() {
  230. return GestureDetector(
  231. onTap: () => controller.clickPayWaySwitch(),
  232. child: Container(
  233. height: 36.h,
  234. padding: EdgeInsets.symmetric(horizontal: 10.w),
  235. decoration: ShapeDecoration(
  236. shape: RoundedRectangleBorder(
  237. side: BorderSide(width: 1, color: const Color(0xFFECEBE0)),
  238. borderRadius: BorderRadius.circular(10.r),
  239. ),
  240. ),
  241. child: Row(
  242. children: [
  243. Text(
  244. StringName.storePayWay,
  245. style: Styles.getTextStyleBlack204W400(14.sp),
  246. ),
  247. const Spacer(),
  248. Obx(() {
  249. if (controller.selectedPayWay == null) {
  250. return SizedBox.shrink();
  251. }
  252. return Row(
  253. children: [
  254. Image.asset(
  255. getPaymentIconPath(
  256. payMethod: controller.selectedPayWay!.payMethod,
  257. payPlatform: controller.selectedPayWay!.payPlatform,
  258. ),
  259. width: 20.w,
  260. height: 20.w,
  261. ),
  262. SizedBox(width: 4.w),
  263. Text(
  264. controller.selectedPayWay?.title ?? '',
  265. style: Styles.getTextStyleBlack204W400(14.sp),
  266. ),
  267. SizedBox(width: 6.w),
  268. Assets.images.iconStoreSwitchPay.image(
  269. width: 20.w,
  270. height: 20.w,
  271. fit: BoxFit.fill,
  272. ),
  273. ],
  274. );
  275. }),
  276. ],
  277. ),
  278. ),
  279. );
  280. }
  281. Widget _buildGoodsItem(item, bool isSelected) {
  282. return Container(
  283. margin: EdgeInsets.only(bottom: 8.h),
  284. width: 296.w,
  285. height: 70.h,
  286. decoration: ShapeDecoration(
  287. gradient: LinearGradient(
  288. begin: Alignment(0.77, -0.00),
  289. end: Alignment(0.77, 1.00),
  290. colors:
  291. isSelected
  292. ? [const Color(0xFFFF9416), const Color(0xFFFF7813)]
  293. : [const Color(0xFFFEE057), const Color(0xFFFFC400)],
  294. ),
  295. shape: RoundedRectangleBorder(
  296. borderRadius: BorderRadius.circular(10.r),
  297. ),
  298. ),
  299. child: Row(
  300. children: [
  301. Container(
  302. width: 212.w,
  303. height: 70.h,
  304. decoration: ShapeDecoration(
  305. gradient:
  306. isSelected
  307. ? LinearGradient(
  308. begin: Alignment(-0.06, 0.50),
  309. end: Alignment(1.14, 0.50),
  310. colors: [
  311. const Color(0xFFFFF895),
  312. const Color(0xFFFFE941),
  313. ],
  314. )
  315. : null,
  316. color: isSelected ? null : const Color(0xFFFFFDEE),
  317. shape: RoundedRectangleBorder(
  318. side: BorderSide(width: 1, color: const Color(0xFFFEE86B)),
  319. borderRadius: BorderRadius.circular(isSelected ? 8 : 10.r),
  320. ),
  321. ),
  322. child: Stack(
  323. children: [
  324. if (isSelected)
  325. IgnorePointer(
  326. child: Assets.images.bgStoreSelectedItem.image(
  327. width: 212.w,
  328. height: 70.h,
  329. fit: BoxFit.fill,
  330. ),
  331. ),
  332. Padding(
  333. padding: EdgeInsets.only(left: 16.w),
  334. child: Column(
  335. crossAxisAlignment: CrossAxisAlignment.start,
  336. mainAxisAlignment: MainAxisAlignment.center,
  337. children: [
  338. Row(
  339. children: [
  340. RichText(
  341. text: TextSpan(
  342. children: [
  343. TextSpan(
  344. text: '¥',
  345. style: Styles.getTextStyleFF663300W700(14.sp),
  346. ),
  347. TextSpan(
  348. text: item.priceDescNumber,
  349. style: Styles.getTextStyleFF663300W700(18.sp),
  350. ),
  351. TextSpan(
  352. text: item.priceDescUnit,
  353. style: Styles.getTextStyleFF663300W400(13.sp),
  354. ),
  355. ],
  356. ),
  357. ),
  358. if (item.mostDesc?.isNotEmpty == true)
  359. Container(
  360. constraints: BoxConstraints(
  361. minHeight: 20.w,
  362. maxHeight: 20.w,
  363. minWidth: 40.w,
  364. maxWidth: 120.w,
  365. ),
  366. padding: EdgeInsets.only(
  367. left: 16.w,
  368. top: 2.h,
  369. bottom: 2.h,
  370. right: 4.w,
  371. ),
  372. decoration: BoxDecoration(
  373. image: DecorationImage(
  374. image: Assets.images.iconStoreMost.provider(),
  375. fit: BoxFit.fill,
  376. alignment: Alignment.bottomLeft,
  377. ),
  378. ),
  379. child: AutoSizeText(
  380. item.mostDesc!,
  381. style: TextStyle(
  382. color: Colors.white,
  383. fontSize: 12.sp,
  384. fontWeight: FontWeight.w500,
  385. letterSpacing: -0.60,
  386. ),
  387. maxLines: 1,
  388. overflow: TextOverflow.ellipsis,
  389. minFontSize: 8,
  390. // 最小字体
  391. stepGranularity: 0.5, // 缩小步长,越小越丝滑
  392. ),
  393. ),
  394. ],
  395. ),
  396. Text(
  397. item.description!,
  398. style: Styles.getTextStyle99673300W400(12.sp),
  399. ),
  400. ],
  401. ),
  402. ),
  403. ],
  404. ),
  405. ),
  406. // 右侧
  407. Expanded(
  408. child: Column(
  409. mainAxisAlignment: MainAxisAlignment.center,
  410. crossAxisAlignment: CrossAxisAlignment.center,
  411. children: [
  412. Text(
  413. item.name,
  414. style:
  415. isSelected
  416. ? Styles.getTextStyleFFECBBW500(15.sp)
  417. : Styles.getTextStyleFF663300W500(15.sp),
  418. ),
  419. Container(
  420. padding: EdgeInsets.symmetric(horizontal: 8.w),
  421. decoration: ShapeDecoration(
  422. color: isSelected ? const Color(0xFFFFECBB) : null,
  423. gradient:
  424. isSelected
  425. ? null
  426. : LinearGradient(
  427. begin: Alignment(0.77, -0.00),
  428. end: Alignment(0.77, 1.00),
  429. colors: [
  430. const Color(0xFFFF9416),
  431. const Color(0xFFFF7813),
  432. ],
  433. ),
  434. shape: RoundedRectangleBorder(
  435. borderRadius: BorderRadius.circular(
  436. isSelected ? 17.r : 10.r,
  437. ),
  438. ),
  439. ),
  440. child: Text(
  441. '¥${item.amountText}',
  442. textAlign: TextAlign.center,
  443. style:
  444. isSelected
  445. ? Styles.getTextStyleFF7F14W500(12.sp)
  446. : Styles.getTextStyleWhiteW500(12.sp),
  447. ),
  448. ),
  449. ],
  450. ),
  451. ),
  452. ],
  453. ),
  454. );
  455. }
  456. Widget _buildVipDesc() {
  457. return Container(
  458. alignment: Alignment.centerLeft,
  459. margin: EdgeInsets.symmetric(horizontal: 16.w),
  460. padding: EdgeInsets.symmetric(horizontal: 16.w),
  461. width: double.infinity,
  462. height: 36.h,
  463. decoration: ShapeDecoration(
  464. gradient: LinearGradient(
  465. begin: Alignment(0.00, 0.50),
  466. end: Alignment(1.00, 0.50),
  467. colors: [const Color(0x7FFFF3A3), const Color(0x21FFF3A3)],
  468. ),
  469. shape: RoundedRectangleBorder(
  470. borderRadius: BorderRadius.only(
  471. bottomLeft: Radius.circular(20.r),
  472. bottomRight: Radius.circular(20.r),
  473. ),
  474. ),
  475. shadows: [
  476. BoxShadow(
  477. color: Colors.black.withAlpha(10),
  478. blurRadius: 10.r,
  479. spreadRadius: 0.r,
  480. offset: Offset(0, 8.r),
  481. ),
  482. ],
  483. ),
  484. child: Obx(() {
  485. return Text(
  486. controller.selectedGoodsInfoItem?.selectDesc ?? "",
  487. style: Styles.getTextStyle99673300W400(12.sp),
  488. );
  489. }),
  490. );
  491. }
  492. // 轮播图
  493. Widget _buildBanner(BuildContext context) {
  494. return SizedBox(
  495. width: double.infinity,
  496. child: Stack(
  497. children: [
  498. CarouselSlider(
  499. carouselController: controller.carouselSliderController,
  500. options: CarouselOptions(
  501. height: 280.h,
  502. viewportFraction: 1,
  503. initialPage: 0,
  504. enableInfiniteScroll: true,
  505. reverse: false,
  506. autoPlay: true,
  507. autoPlayInterval: const Duration(seconds: 3),
  508. autoPlayAnimationDuration: const Duration(milliseconds: 800),
  509. autoPlayCurve: Curves.fastOutSlowIn,
  510. scrollDirection: Axis.horizontal,
  511. onPageChanged: (index, reason) {
  512. controller.onBannerChanged(index, reason);
  513. },
  514. ),
  515. items:
  516. controller.bannerList.map((item) {
  517. return item.banner.image(
  518. width: double.infinity,
  519. fit: BoxFit.cover,
  520. );
  521. }).toList(),
  522. ),
  523. Positioned(bottom: 0, left: 0, right: 0, child: _buildIndicator()),
  524. ],
  525. ),
  526. );
  527. }
  528. // 指示器
  529. _buildIndicator() {
  530. return Container(
  531. height: 36.h,
  532. margin: EdgeInsets.symmetric(horizontal: 16.w),
  533. decoration: ShapeDecoration(
  534. color: const Color(0xE5121212),
  535. shape: RoundedRectangleBorder(
  536. borderRadius: BorderRadius.circular(21.r),
  537. ),
  538. ),
  539. child: Row(
  540. mainAxisAlignment: MainAxisAlignment.spaceAround,
  541. children:
  542. controller.bannerList
  543. .asMap()
  544. .entries
  545. .map((entry) {
  546. return Obx(() {
  547. final isSelectedBanner =
  548. controller.currentBannerIndex == entry.key;
  549. return Row(
  550. mainAxisAlignment: MainAxisAlignment.spaceAround,
  551. children: [
  552. GestureDetector(
  553. onTap:
  554. () =>
  555. controller.carouselSliderController
  556. .animateToPage(entry.key),
  557. child: SizedBox(
  558. width: 100.w,
  559. child: Stack(
  560. alignment: Alignment.center,
  561. clipBehavior: Clip.none,
  562. children: [
  563. if (isSelectedBanner)
  564. Positioned(
  565. top: -8.h,
  566. child: controller
  567. .bannerList[entry.key]
  568. .indicatorImg
  569. .image(
  570. width: 100.w,
  571. height: 40.h,
  572. fit: BoxFit.fill,
  573. ),
  574. )
  575. else
  576. Text(
  577. controller.bannerList[entry.key].unSelectedDesc,
  578. style: Styles.getTextStyleWhiteW400(14.sp),
  579. ),
  580. ],
  581. ),
  582. ),
  583. ),
  584. if (entry.key != controller.bannerList.length - 1)
  585. Padding(
  586. padding: EdgeInsets.only(left: 4.w),
  587. child: Assets.images.iconStoreDivider.image(
  588. width: 2.w,
  589. height: 17.h,
  590. fit: BoxFit.fill,
  591. ),
  592. ),
  593. ],
  594. );
  595. });
  596. }).toList(),
  597. ),
  598. );
  599. }
  600. Widget _buildUserReviews() {
  601. return Container(
  602. width: double.infinity,
  603. child: Column(
  604. crossAxisAlignment: CrossAxisAlignment.center,
  605. children: [
  606. Assets.images.iconStoreUserReviewsTitle.image(
  607. width: 240.w,
  608. fit: BoxFit.cover,
  609. ),
  610. SizedBox(height: 16.h),
  611. Container(
  612. width: double.infinity,
  613. decoration: BoxDecoration(
  614. borderRadius: BorderRadius.circular(19.r),
  615. gradient: LinearGradient(
  616. begin: Alignment.topCenter,
  617. end: Alignment.bottomCenter,
  618. colors: [Colors.white, Color(0xfffff8d4)],
  619. ),
  620. ),
  621. child: Column(
  622. children: [
  623. SizedBox(height: 20.h),
  624. Container(
  625. width: 326.w,
  626. decoration: BoxDecoration(
  627. borderRadius: BorderRadius.circular(19.r),
  628. image: DecorationImage(
  629. alignment: Alignment.topCenter,
  630. image: Assets.images.bgStoreUserReviews.provider(),
  631. fit: BoxFit.contain,
  632. ),
  633. ),
  634. child: Column(
  635. children: [
  636. Container(
  637. padding: EdgeInsets.only(left: 16.w),
  638. alignment: Alignment.centerLeft,
  639. height: 39.h,
  640. child: Assets.images.iconStoreUserReviewsLogo.image(
  641. width: 92.w,
  642. height: 24.h,
  643. ),
  644. ),
  645. Container(
  646. width: 326.w,
  647. decoration: ShapeDecoration(
  648. color: Colors.white,
  649. shape: RoundedRectangleBorder(
  650. borderRadius: BorderRadius.circular(16.r),
  651. ),
  652. shadows: [
  653. BoxShadow(
  654. color: Colors.black.withAlpha(10),
  655. blurRadius: 10.r,
  656. spreadRadius: 0.r,
  657. offset: Offset(0, 5.r),
  658. ),
  659. ],
  660. ),
  661. child: Column(
  662. children:
  663. controller.userReviewsList.map((item) {
  664. return _buildReviewsItem(item);
  665. }).toList(),
  666. ),
  667. ),
  668. ],
  669. ),
  670. ),
  671. ],
  672. ),
  673. ),
  674. ],
  675. ),
  676. );
  677. }
  678. Widget _buildReviewsItem(StoreUserReviewsBean item) {
  679. return Container(
  680. padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h),
  681. child: Column(
  682. crossAxisAlignment: CrossAxisAlignment.start,
  683. children: [
  684. Row(
  685. children: [
  686. item.avatar.image(width: 28.w, height: 28.h, fit: BoxFit.cover),
  687. SizedBox(width: 10.w),
  688. Text(
  689. item.userName,
  690. style: Styles.getTextStyleBlack204W500(14.sp),
  691. ),
  692. SizedBox(width: 6.w),
  693. Assets.images.iconStorePermanentMember.image(
  694. width: 70.w,
  695. height: 20.h,
  696. fit: BoxFit.cover,
  697. ),
  698. ],
  699. ),
  700. SizedBox(height: 4.h),
  701. Padding(
  702. padding: EdgeInsets.only(left: 38.w),
  703. child: Text(
  704. item.userReviews,
  705. style: TextStyle(
  706. color: Colors.black.withAlpha(153),
  707. fontSize: 12.sp,
  708. fontWeight: FontWeight.w400,
  709. ),
  710. ),
  711. ),
  712. SizedBox(height: 12.h),
  713. if (controller.userReviewsList.indexOf(item) !=
  714. controller.userReviewsList.length - 1)
  715. HorizontalDashedLine(
  716. width: 304.w,
  717. color: const Color(0XFFEDEBE1),
  718. strokeWidth: 2.h,
  719. dashLength: 4.w,
  720. dashSpace: 4.w,
  721. ),
  722. ],
  723. ),
  724. );
  725. }
  726. // 用户须知
  727. Widget _buildUserNotice() {
  728. return Container(
  729. margin: EdgeInsets.symmetric(horizontal: 16.w),
  730. decoration: BoxDecoration(borderRadius: BorderRadius.circular(16.r)),
  731. child: Column(
  732. crossAxisAlignment: CrossAxisAlignment.start,
  733. children: [
  734. Text('购买须知', style: Styles.getTextStyleFF663300W400(12.sp)),
  735. SizedBox(height: 8.h),
  736. Obx(() {
  737. return Text(
  738. controller.configRepository.memberTips.value,
  739. style: Styles.getTextStyle99673300W400(10.sp),
  740. );
  741. }),
  742. SizedBox(height: 150.h),
  743. ],
  744. ),
  745. );
  746. }
  747. Widget _buildBuyButtonCard() {
  748. return Container(
  749. width: 360.w,
  750. padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h),
  751. decoration: ShapeDecoration(
  752. color: Colors.white,
  753. shape: RoundedRectangleBorder(
  754. borderRadius: BorderRadius.only(
  755. topLeft: Radius.circular(24.r),
  756. topRight: Radius.circular(24.r),
  757. ),
  758. ),
  759. shadows: [
  760. BoxShadow(
  761. color: Color(0x99FFE498),
  762. blurRadius: 20,
  763. offset: Offset(0, 0),
  764. spreadRadius: 0,
  765. ),
  766. ],
  767. ),
  768. child: Column(
  769. children: [
  770. GestureDetector(
  771. onTap: controller.clickPayNow,
  772. child: Container(
  773. alignment: Alignment.topCenter,
  774. width: 328.w,
  775. height: 56.w,
  776. decoration: ShapeDecoration(
  777. color: const Color(0xFFFFF587),
  778. shape: RoundedRectangleBorder(
  779. borderRadius: BorderRadius.circular(30.55.r),
  780. ),
  781. ),
  782. child: Container(
  783. width: 328.w,
  784. height: 54.w,
  785. decoration: ShapeDecoration(
  786. gradient: LinearGradient(
  787. begin: Alignment(0.60, -0.39),
  788. end: Alignment(0.60, 0.95),
  789. colors: [
  790. const Color(0xFFF95FAC),
  791. const Color(0xFFFD5E4D),
  792. const Color(0xFFFD5E4D),
  793. const Color(0xFFFB8A3C),
  794. ],
  795. ),
  796. shape: RoundedRectangleBorder(
  797. borderRadius: BorderRadius.circular(30.55.r),
  798. ),
  799. ),
  800. child: Center(
  801. child: Text(
  802. StringName.storePayNow,
  803. style: Styles.getTextStyleWhiteW500(17.sp),
  804. ),
  805. ),
  806. ),
  807. ),
  808. ),
  809. _buildPrivacy(),
  810. ],
  811. ),
  812. );
  813. }
  814. Widget _buildPrivacy() {
  815. return Row(
  816. mainAxisAlignment: MainAxisAlignment.center,
  817. children: [
  818. Obx(() {
  819. return GestureDetector(
  820. behavior: HitTestBehavior.opaque,
  821. onTap: () {
  822. controller.isAgree.value = !controller.isAgree.value;
  823. },
  824. child: Padding(
  825. padding: EdgeInsets.symmetric(vertical: 10.w, horizontal: 10.w),
  826. child:
  827. controller.isAgree.value
  828. ? Assets.images.iconStoreAgreePrivacy.image(
  829. width: 16.w,
  830. height: 16.w,
  831. )
  832. : SizedBox(
  833. child: Container(
  834. padding: EdgeInsets.all(1.w),
  835. width: 16.w,
  836. height: 16.w,
  837. child: Container(
  838. decoration: BoxDecoration(
  839. shape: BoxShape.circle,
  840. border: Border.all(
  841. color: Colors.black.withAlpha(153),
  842. width: 1.w,
  843. ),
  844. ),
  845. ),
  846. ),
  847. ),
  848. ),
  849. );
  850. }),
  851. Transform.translate(
  852. offset: Offset(-10.w, 0),
  853. child: Text.rich(
  854. TextSpan(
  855. children: [
  856. TextSpan(
  857. text: StringName.textSpanIHaveReadAndAgree,
  858. style: TextStyle(
  859. color: Colors.black.withAlpha(153),
  860. fontSize: 10.sp,
  861. fontWeight: FontWeight.w400,
  862. ),
  863. ),
  864. ClickTextSpan(
  865. text: StringName.textSpanPrivacyPolicy,
  866. url: WebUrl.privacyPolicy,
  867. ),
  868. ClickTextSpan(
  869. text: StringName.textSpanServiceTerms,
  870. url: WebUrl.serviceAgreement,
  871. ),
  872. TextSpan(
  873. text: StringName.textSpanAnd,
  874. style: TextStyle(
  875. color: Colors.black.withAlpha(153),
  876. fontSize: 10.sp,
  877. fontWeight: FontWeight.w400,
  878. ),
  879. ),
  880. ClickTextSpan(
  881. text: StringName.textSpanMembershipAgreement,
  882. url: WebUrl.memberServiceAgreement,
  883. ),
  884. ],
  885. ),
  886. ),
  887. ),
  888. ],
  889. );
  890. }
  891. }