store_page.dart 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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. 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.filteredGoodsList.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: Obx(() {
  454. return Text(
  455. controller.selectedGoodsInfoItem?.selectDesc ?? "",
  456. style: Styles.getTextStyle99673300W400(12.sp),
  457. );
  458. }),
  459. );
  460. }
  461. // 轮播图
  462. Widget _buildBanner(BuildContext context) {
  463. return SizedBox(
  464. width: double.infinity,
  465. child: Stack(
  466. children: [
  467. CarouselSlider(
  468. carouselController: controller.carouselSliderController,
  469. options: CarouselOptions(
  470. height: 280.h,
  471. viewportFraction: 1,
  472. initialPage: 0,
  473. enableInfiniteScroll: true,
  474. reverse: false,
  475. autoPlay: true,
  476. autoPlayInterval: const Duration(seconds: 3),
  477. autoPlayAnimationDuration: const Duration(milliseconds: 800),
  478. autoPlayCurve: Curves.fastOutSlowIn,
  479. scrollDirection: Axis.horizontal,
  480. onPageChanged: (index, reason) {
  481. controller.onBannerChanged(index, reason);
  482. },
  483. ),
  484. items:
  485. controller.bannerList.map((item) {
  486. return item.banner.image(
  487. width: double.infinity,
  488. fit: BoxFit.cover,
  489. );
  490. }).toList(),
  491. ),
  492. Positioned(bottom: 0, left: 0, right: 0, child: _buildIndicator()),
  493. ],
  494. ),
  495. );
  496. }
  497. // 指示器
  498. _buildIndicator() {
  499. return Container(
  500. height: 36.h,
  501. margin: EdgeInsets.symmetric(horizontal: 16.w),
  502. decoration: ShapeDecoration(
  503. color: const Color(0xE5121212),
  504. shape: RoundedRectangleBorder(
  505. borderRadius: BorderRadius.circular(21.r),
  506. ),
  507. ),
  508. child: Row(
  509. mainAxisAlignment: MainAxisAlignment.spaceAround,
  510. children:
  511. controller.bannerList
  512. .asMap()
  513. .entries
  514. .map((entry) {
  515. return Obx(() {
  516. final isSelectedBanner =
  517. controller.currentBannerIndex == entry.key;
  518. return Row(
  519. mainAxisAlignment: MainAxisAlignment.spaceAround,
  520. children: [
  521. GestureDetector(
  522. onTap:
  523. () =>
  524. controller.carouselSliderController
  525. .animateToPage(entry.key),
  526. child: SizedBox(
  527. width: 100.w,
  528. child: Stack(
  529. alignment: Alignment.center,
  530. clipBehavior: Clip.none,
  531. children: [
  532. if (isSelectedBanner)
  533. Positioned(
  534. top: -8.h,
  535. child: controller
  536. .bannerList[entry.key]
  537. .indicatorImg
  538. .image(
  539. width: 100.w,
  540. height: 40.h,
  541. fit: BoxFit.fill,
  542. ),
  543. )
  544. else
  545. Text(
  546. controller.bannerList[entry.key].unSelectedDesc,
  547. style: Styles.getTextStyleWhiteW400(14.sp),
  548. ),
  549. ],
  550. ),
  551. ),
  552. ),
  553. if (entry.key != controller.bannerList.length - 1)
  554. Padding(
  555. padding: EdgeInsets.only(left: 4.w),
  556. child: Assets.images.iconStoreDivider.image(
  557. width: 2.w,
  558. height: 17.h,
  559. fit: BoxFit.fill,
  560. ),
  561. ),
  562. ],
  563. );
  564. });
  565. }).toList(),
  566. ),
  567. );
  568. }
  569. Widget _buildUserReviews() {
  570. return Container(
  571. width: double.infinity,
  572. child: Column(
  573. crossAxisAlignment: CrossAxisAlignment.center,
  574. children: [
  575. Assets.images.iconStoreUserReviewsTitle.image(
  576. width: 240.w,
  577. fit: BoxFit.cover,
  578. ),
  579. SizedBox(height: 16.h),
  580. Container(
  581. width: double.infinity,
  582. decoration: BoxDecoration(
  583. borderRadius: BorderRadius.circular(19.r),
  584. gradient: LinearGradient(
  585. begin: Alignment.topCenter,
  586. end: Alignment.bottomCenter,
  587. colors: [Colors.white, Color(0xfffff8d4)],
  588. ),
  589. ),
  590. child: Column(
  591. children: [
  592. SizedBox(height: 20.h),
  593. Container(
  594. width: 326.w,
  595. decoration: BoxDecoration(
  596. borderRadius: BorderRadius.circular(19.r),
  597. image: DecorationImage(
  598. alignment: Alignment.topCenter,
  599. image: Assets.images.bgStoreUserReviews.provider(),
  600. fit: BoxFit.contain,
  601. ),
  602. ),
  603. child: Column(
  604. children: [
  605. Container(
  606. padding: EdgeInsets.only(left: 16.w),
  607. alignment: Alignment.centerLeft,
  608. height: 39.h,
  609. child: Assets.images.iconStoreUserReviewsLogo.image(
  610. width: 92.w,
  611. height: 24.h,
  612. ),
  613. ),
  614. Container(
  615. width: 326.w,
  616. decoration: ShapeDecoration(
  617. color: Colors.white,
  618. shape: RoundedRectangleBorder(
  619. borderRadius: BorderRadius.circular(16.r),
  620. ),
  621. shadows: [
  622. BoxShadow(
  623. color: Colors.black.withAlpha(10),
  624. blurRadius: 10.r,
  625. spreadRadius: 0.r,
  626. offset: Offset(0, 5.r),
  627. ),
  628. ],
  629. ),
  630. child: Column(
  631. children:
  632. controller.userReviewsList.map((item) {
  633. return _buildReviewsItem(item);
  634. }).toList(),
  635. ),
  636. ),
  637. ],
  638. ),
  639. ),
  640. ],
  641. ),
  642. ),
  643. ],
  644. ),
  645. );
  646. }
  647. Widget _buildReviewsItem(StoreUserReviewsBean item) {
  648. return Container(
  649. padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h),
  650. child: Column(
  651. crossAxisAlignment: CrossAxisAlignment.start,
  652. children: [
  653. Row(
  654. children: [
  655. item.avatar.image(width: 28.w, height: 28.h, fit: BoxFit.cover),
  656. SizedBox(width: 10.w),
  657. Text(
  658. item.userName,
  659. style: Styles.getTextStyleBlack204W500(14.sp),
  660. ),
  661. SizedBox(width: 6.w),
  662. Assets.images.iconStorePermanentMember.image(
  663. width: 70.w,
  664. height: 20.h,
  665. fit: BoxFit.cover,
  666. ),
  667. ],
  668. ),
  669. SizedBox(height: 4.h),
  670. Padding(
  671. padding: EdgeInsets.only(left: 38.w),
  672. child: Text(
  673. item.userReviews,
  674. style: TextStyle(
  675. color: Colors.black.withAlpha(153),
  676. fontSize: 12.sp,
  677. fontWeight: FontWeight.w400,
  678. ),
  679. ),
  680. ),
  681. SizedBox(height: 12.h),
  682. if (controller.userReviewsList.indexOf(item) !=
  683. controller.userReviewsList.length - 1)
  684. HorizontalDashedLine(
  685. width: 304.w,
  686. color: const Color(0XFFEDEBE1),
  687. strokeWidth: 2.h,
  688. dashLength: 4.w,
  689. dashSpace: 4.w,
  690. ),
  691. ],
  692. ),
  693. );
  694. }
  695. // 用户须知
  696. Widget _buildUserNotice() {
  697. return Container(
  698. margin: EdgeInsets.symmetric(horizontal: 16.w),
  699. decoration: BoxDecoration(borderRadius: BorderRadius.circular(16.r)),
  700. child: Column(
  701. crossAxisAlignment: CrossAxisAlignment.start,
  702. children: [
  703. Text('购买须知', style: Styles.getTextStyleFF663300W400(12.sp)),
  704. SizedBox(height: 8.h),
  705. Text(
  706. "1. 本产品为互联网技术服务虚拟产品,会员购买成功立即生效,一经开通不支持退款。\n"
  707. "2. 本产品为付费会员制产品,会员费用是指开通平台所有功能使用权限的基础费用。开通权限后,部分功能可能会免费(具体以实际展示情况为准)使用,部分功能需消耗一定的虚拟币(金币)方可使用。\n"
  708. "3. 各种功能的金币消耗可参考虚拟币规则(金币中心)。\n"
  709. "4. 开通会员后,若赠送虚拟币(金币)消耗完毕,可单独购买金币。\n"
  710. "5. 根据业务情况,我方有权调整不同功能的虚拟币消耗价格,价格会在最新的金币中心展示。\n"
  711. "6. 请在购买前仔细阅读《会员服务协议》和购买须知,确定您同意所有条款后继续操作。",
  712. style: Styles.getTextStyle99673300W400(10.sp),
  713. ),
  714. SizedBox(height: 120.h),
  715. ],
  716. ),
  717. );
  718. }
  719. Widget _buildBuyButtonCard() {
  720. return Container(
  721. width: 360.w,
  722. padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h),
  723. decoration: ShapeDecoration(
  724. color: Colors.white,
  725. shape: RoundedRectangleBorder(
  726. borderRadius: BorderRadius.only(
  727. topLeft: Radius.circular(24.r),
  728. topRight: Radius.circular(24.r),
  729. ),
  730. ),
  731. shadows: [
  732. BoxShadow(
  733. color: Color(0x99FFE498),
  734. blurRadius: 20,
  735. offset: Offset(0, 0),
  736. spreadRadius: 0,
  737. ),
  738. ],
  739. ),
  740. child: Column(
  741. children: [
  742. GestureDetector(
  743. onTap: controller.clickPayNow,
  744. child: Container(
  745. width: 328.w,
  746. height: 54.h,
  747. decoration: ShapeDecoration(
  748. gradient: LinearGradient(
  749. begin: Alignment(0.60, -0.39),
  750. end: Alignment(0.60, 0.95),
  751. colors: [
  752. const Color(0xFFF95FAC),
  753. const Color(0xFFFD5E4D),
  754. const Color(0xFFFD5E4D),
  755. const Color(0xFFFB8A3C),
  756. ],
  757. ),
  758. shape: RoundedRectangleBorder(
  759. borderRadius: BorderRadius.circular(30.55.r),
  760. ),
  761. ),
  762. child: Center(
  763. child: Text(
  764. StringName.storePayNow,
  765. style: Styles.getTextStyleWhiteW500(17.sp),
  766. ),
  767. ),
  768. ),
  769. ),
  770. SizedBox(height: 11.h),
  771. Row(
  772. mainAxisAlignment: MainAxisAlignment.center,
  773. children: [
  774. Obx(() {
  775. return GestureDetector(
  776. behavior: HitTestBehavior.opaque,
  777. onTap: () {
  778. controller.isAgree.value = !controller.isAgree.value;
  779. },
  780. child:
  781. controller.isAgree.value
  782. ? Assets.images.iconStoreAgreePrivacy.image(
  783. width: 16.w,
  784. height: 16.w,
  785. )
  786. : SizedBox(
  787. child: Container(
  788. padding: EdgeInsets.all(1.w),
  789. width: 16.w,
  790. height: 16.w,
  791. child: Container(
  792. decoration: BoxDecoration(
  793. shape: BoxShape.circle,
  794. border: Border.all(
  795. color: Colors.black.withAlpha(153),
  796. width: 1.w,
  797. ),
  798. ),
  799. ),
  800. ),
  801. ),
  802. );
  803. }),
  804. Text.rich(
  805. TextSpan(
  806. children: [
  807. TextSpan(
  808. text: StringName.textSpanIHaveReadAndAgree,
  809. style: TextStyle(
  810. color: Colors.black.withAlpha(153),
  811. fontSize: 10.sp,
  812. fontWeight: FontWeight.w400,
  813. ),
  814. ),
  815. ClickTextSpan(
  816. text: StringName.textSpanPrivacyPolicy,
  817. url: WebUrl.privacyPolicy,
  818. ),
  819. TextSpan(
  820. text: StringName.textSpanAnd,
  821. style: TextStyle(
  822. color: Colors.black.withAlpha(153),
  823. fontSize: 10.sp,
  824. fontWeight: FontWeight.w400,
  825. ),
  826. ),
  827. ClickTextSpan(
  828. text: StringName.textSpanServiceTerms,
  829. url: WebUrl.serviceAgreement,
  830. ),
  831. ],
  832. ),
  833. ),
  834. ],
  835. ),
  836. ],
  837. ),
  838. );
  839. }
  840. }