store_page.dart 30 KB

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