store_page.dart 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  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.asMap().entries.map((entry) {
  543. return Obx(() {
  544. final isSelectedBanner =
  545. controller.currentBannerIndex == entry.key;
  546. return Row(
  547. mainAxisAlignment: MainAxisAlignment.spaceAround,
  548. children: [
  549. GestureDetector(
  550. onTap:
  551. () => controller.carouselSliderController
  552. .animateToPage(entry.key),
  553. child: SizedBox(
  554. width: 100.w,
  555. child: Stack(
  556. alignment: Alignment.center,
  557. clipBehavior: Clip.none,
  558. children: [
  559. if (isSelectedBanner)
  560. Positioned(
  561. top: -8.h,
  562. child: controller
  563. .bannerList[entry.key]
  564. .indicatorImg
  565. .image(
  566. width: 100.w,
  567. height: 40.h,
  568. fit: BoxFit.fill,
  569. ),
  570. )
  571. else
  572. Text(
  573. controller.bannerList[entry.key].unSelectedDesc,
  574. style: Styles.getTextStyleWhiteW400(14.sp),
  575. ),
  576. ],
  577. ),
  578. ),
  579. ),
  580. if (entry.key != controller.bannerList.length - 1)
  581. Padding(
  582. padding: EdgeInsets.only(left: 4.w),
  583. child: Assets.images.iconStoreDivider.image(
  584. width: 2.w,
  585. height: 17.h,
  586. fit: BoxFit.fill,
  587. ),
  588. ),
  589. ],
  590. );
  591. });
  592. }).toList(),
  593. ),
  594. );
  595. }
  596. Widget _buildUserReviews() {
  597. return Container(
  598. width: double.infinity,
  599. child: Column(
  600. crossAxisAlignment: CrossAxisAlignment.center,
  601. children: [
  602. Assets.images.iconStoreUserReviewsTitle.image(
  603. width: 240.w,
  604. fit: BoxFit.cover,
  605. ),
  606. SizedBox(height: 16.h),
  607. Container(
  608. width: double.infinity,
  609. decoration: BoxDecoration(
  610. borderRadius: BorderRadius.circular(19.r),
  611. gradient: LinearGradient(
  612. begin: Alignment.topCenter,
  613. end: Alignment.bottomCenter,
  614. colors: [Colors.white, Color(0xfffff8d4)],
  615. ),
  616. ),
  617. child: Column(
  618. children: [
  619. SizedBox(height: 20.h),
  620. Container(
  621. width: 326.w,
  622. decoration: BoxDecoration(
  623. borderRadius: BorderRadius.circular(19.r),
  624. image: DecorationImage(
  625. alignment: Alignment.topCenter,
  626. image: Assets.images.bgStoreUserReviews.provider(),
  627. fit: BoxFit.contain,
  628. ),
  629. ),
  630. child: Column(
  631. children: [
  632. Container(
  633. padding: EdgeInsets.only(left: 16.w),
  634. alignment: Alignment.centerLeft,
  635. height: 39.h,
  636. child: Assets.images.iconStoreUserReviewsLogo.image(
  637. width: 92.w,
  638. height: 24.h,
  639. ),
  640. ),
  641. Container(
  642. width: 326.w,
  643. decoration: ShapeDecoration(
  644. color: Colors.white,
  645. shape: RoundedRectangleBorder(
  646. borderRadius: BorderRadius.circular(16.r),
  647. ),
  648. shadows: [
  649. BoxShadow(
  650. color: Colors.black.withAlpha(10),
  651. blurRadius: 10.r,
  652. spreadRadius: 0.r,
  653. offset: Offset(0, 5.r),
  654. ),
  655. ],
  656. ),
  657. child: Column(
  658. children:
  659. controller.userReviewsList.map((item) {
  660. return _buildReviewsItem(item);
  661. }).toList(),
  662. ),
  663. ),
  664. ],
  665. ),
  666. ),
  667. ],
  668. ),
  669. ),
  670. ],
  671. ),
  672. );
  673. }
  674. Widget _buildReviewsItem(StoreUserReviewsBean item) {
  675. return Container(
  676. padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h),
  677. child: Column(
  678. crossAxisAlignment: CrossAxisAlignment.start,
  679. children: [
  680. Row(
  681. children: [
  682. item.avatar.image(width: 28.w, height: 28.h, fit: BoxFit.cover),
  683. SizedBox(width: 10.w),
  684. Text(
  685. item.userName,
  686. style: Styles.getTextStyleBlack204W500(14.sp),
  687. ),
  688. SizedBox(width: 6.w),
  689. Assets.images.iconStorePermanentMember.image(
  690. width: 70.w,
  691. height: 20.h,
  692. fit: BoxFit.cover,
  693. ),
  694. ],
  695. ),
  696. SizedBox(height: 4.h),
  697. Padding(
  698. padding: EdgeInsets.only(left: 38.w),
  699. child: Text(
  700. item.userReviews,
  701. style: TextStyle(
  702. color: Colors.black.withAlpha(153),
  703. fontSize: 12.sp,
  704. fontWeight: FontWeight.w400,
  705. ),
  706. ),
  707. ),
  708. SizedBox(height: 12.h),
  709. if (controller.userReviewsList.indexOf(item) !=
  710. controller.userReviewsList.length - 1)
  711. HorizontalDashedLine(
  712. width: 304.w,
  713. color: const Color(0XFFEDEBE1),
  714. strokeWidth: 2.h,
  715. dashLength: 4.w,
  716. dashSpace: 4.w,
  717. ),
  718. ],
  719. ),
  720. );
  721. }
  722. // 用户须知
  723. Widget _buildUserNotice() {
  724. return Container(
  725. margin: EdgeInsets.symmetric(horizontal: 16.w),
  726. decoration: BoxDecoration(borderRadius: BorderRadius.circular(16.r)),
  727. child: Column(
  728. crossAxisAlignment: CrossAxisAlignment.start,
  729. children: [
  730. Text('购买须知', style: Styles.getTextStyleFF663300W400(12.sp)),
  731. SizedBox(height: 8.h),
  732. Text(
  733. "1.会员权益将在购买成功后自动生效,如遇延迟,请耐心等待5-10分钟。若长时间未生效,请联系客服处理;\n"
  734. "2.本服务为虚拟商品,一经购买成功即视为使用,恕不支持退款或转让,感谢您的理解;\n"
  735. "3.您的支持是我们持续优化与服务的动力,我们将竭诚为您提供更好的体验;\n"
  736. "4.相关细则以《会员服务协议》为准,在法律允许范围内,我们保留最终解释权。开通即视为同意协议条款,请谨慎购买。\n"
  737. "如有疑问,欢迎随时咨询在线客服!",
  738. style: Styles.getTextStyle99673300W400(10.sp),
  739. ),
  740. SizedBox(height: 150.h),
  741. ],
  742. ),
  743. );
  744. }
  745. Widget _buildBuyButtonCard() {
  746. return Container(
  747. width: 360.w,
  748. padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h),
  749. decoration: ShapeDecoration(
  750. color: Colors.white,
  751. shape: RoundedRectangleBorder(
  752. borderRadius: BorderRadius.only(
  753. topLeft: Radius.circular(24.r),
  754. topRight: Radius.circular(24.r),
  755. ),
  756. ),
  757. shadows: [
  758. BoxShadow(
  759. color: Color(0x99FFE498),
  760. blurRadius: 20,
  761. offset: Offset(0, 0),
  762. spreadRadius: 0,
  763. ),
  764. ],
  765. ),
  766. child: Column(
  767. children: [
  768. GestureDetector(
  769. onTap: controller.clickPayNow,
  770. child: Container(
  771. alignment: Alignment.topCenter,
  772. width: 328.w,
  773. height: 56.w,
  774. decoration: ShapeDecoration(
  775. color: const Color(0xFFFFF587),
  776. shape: RoundedRectangleBorder(
  777. borderRadius: BorderRadius.circular(30.55.r),
  778. ),
  779. ),
  780. child: Container(
  781. width: 328.w,
  782. height: 54.w,
  783. decoration: ShapeDecoration(
  784. gradient: LinearGradient(
  785. begin: Alignment(0.60, -0.39),
  786. end: Alignment(0.60, 0.95),
  787. colors: [
  788. const Color(0xFFF95FAC),
  789. const Color(0xFFFD5E4D),
  790. const Color(0xFFFD5E4D),
  791. const Color(0xFFFB8A3C),
  792. ],
  793. ),
  794. shape: RoundedRectangleBorder(
  795. borderRadius: BorderRadius.circular(30.55.r),
  796. ),
  797. ),
  798. child: Center(
  799. child: Text(
  800. StringName.storePayNow,
  801. style: Styles.getTextStyleWhiteW500(17.sp),
  802. ),
  803. ),
  804. ),
  805. ),
  806. ),
  807. _buildPrivacy(),
  808. ],
  809. ),
  810. );
  811. }
  812. Widget _buildPrivacy() {
  813. return Row(
  814. mainAxisAlignment: MainAxisAlignment.center,
  815. children: [
  816. Obx(() {
  817. return GestureDetector(
  818. behavior: HitTestBehavior.opaque,
  819. onTap: () {
  820. controller.isAgree.value = !controller.isAgree.value;
  821. },
  822. child: Padding(
  823. padding: EdgeInsets.symmetric(vertical: 10.w, horizontal: 10.w),
  824. child:
  825. controller.isAgree.value
  826. ? Assets.images.iconStoreAgreePrivacy.image(
  827. width: 16.w,
  828. height: 16.w,
  829. )
  830. : SizedBox(
  831. child: Container(
  832. padding: EdgeInsets.all(1.w),
  833. width: 16.w,
  834. height: 16.w,
  835. child: Container(
  836. decoration: BoxDecoration(
  837. shape: BoxShape.circle,
  838. border: Border.all(
  839. color: Colors.black.withAlpha(153),
  840. width: 1.w,
  841. ),
  842. ),
  843. ),
  844. ),
  845. ),
  846. ),
  847. );
  848. }),
  849. Transform.translate(
  850. offset: Offset(-10.w, 0),
  851. child: Text.rich(
  852. TextSpan(
  853. children: [
  854. TextSpan(
  855. text: StringName.textSpanIHaveReadAndAgree,
  856. style: TextStyle(
  857. color: Colors.black.withAlpha(153),
  858. fontSize: 10.sp,
  859. fontWeight: FontWeight.w400,
  860. ),
  861. ),
  862. ClickTextSpan(
  863. text: StringName.textSpanPrivacyPolicy,
  864. url: WebUrl.privacyPolicy,
  865. ),
  866. ClickTextSpan(
  867. text: StringName.textSpanServiceTerms,
  868. url: WebUrl.serviceAgreement,
  869. ),
  870. TextSpan(
  871. text: StringName.textSpanAnd,
  872. style: TextStyle(
  873. color: Colors.black.withAlpha(153),
  874. fontSize: 10.sp,
  875. fontWeight: FontWeight.w400,
  876. ),
  877. ),
  878. ClickTextSpan(
  879. text: StringName.textSpanMembershipAgreement,
  880. url: WebUrl.memberServiceAgreement,
  881. ),
  882. ],
  883. ),
  884. ),
  885. ),
  886. ],
  887. );
  888. }
  889. }