store_page.dart 29 KB

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