store_page.dart 29 KB

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