store_page.dart 30 KB

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