From 8e38370b78aa2878a7981015edf190c6b9527ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 31 Jul 2026 16:38:17 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E5=B9=B6=E5=85=A5=E6=88=91=E7=9A=84=20tab=EF=BC=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=8B=AC=E7=AB=8B=E4=BC=9A=E5=91=98=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 我的 tab 直接展示:用户卡片 + 会员卡(开通/会员码/权益)+ 免费获取 权益(广告激励)+ 最近优惠 + 形象/衣橱入口 + 退出登录 - 删除 member_center_page.dart 与 /commercial 路由(内容全部迁移) Co-Authored-By: Claude Opus 4.7 --- lib/features/member/member_center_page.dart | 419 ------------------- lib/features/mine/mine_page.dart | 424 +++++++++++++++++++- lib/main.dart | 5 - 3 files changed, 413 insertions(+), 435 deletions(-) delete mode 100644 lib/features/member/member_center_page.dart diff --git a/lib/features/member/member_center_page.dart b/lib/features/member/member_center_page.dart deleted file mode 100644 index 93d8be7..0000000 --- a/lib/features/member/member_center_page.dart +++ /dev/null @@ -1,419 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:go_router/go_router.dart'; - -import '../../core/ads/ads_service.dart'; -import '../../core/config/app_config.dart'; -import '../../features/cps/cps_provider.dart'; -import '../../shared/widgets/loading_view.dart'; -import 'member_provider.dart'; -import 'pay_page.dart'; - -/// 会员中心:会员状态/套餐充值/广告激励 + 最近优惠 -class MemberCenterPage extends ConsumerStatefulWidget { - const MemberCenterPage({super.key}); - - @override - ConsumerState createState() => _MemberCenterPageState(); -} - -class _MemberCenterPageState extends ConsumerState { - bool _rewarding = false; - - // web 上无 Platform(dart:io 不可用),且 web 端默认走系统浏览器收银台 - bool get _isIOS => - !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; - - Future _openPlans() async { - final plans = await showModalBottomSheet( - context: context, - builder: (ctx) => const _PlanSheet(), - ); - if (plans == null || !mounted) return; - try { - final result = await createMemberOrder(ref, plans.id); - if (!mounted || result.payUrl.isEmpty) return; - await context.push('/pay', extra: PayArgs(orderNo: result.orderNo, payUrl: result.payUrl)); - ref.read(memberProvider.notifier).refresh(); - } catch (e) { - if (!mounted) return; - Fluttertoast.showToast( - msg: e.toString().replaceFirst('Exception: ', '')); - } - } - - Future _claimReward(String adType, String successMsg) async { - if (_rewarding) return; - final ads = ref.read(adsServiceProvider); - if (!ads.enabled) { - Fluttertoast.showToast(msg: '广告功能暂未开通'); - return; - } - setState(() => _rewarding = true); - try { - final watched = await ads.showRewarded(); - if (!watched) { - Fluttertoast.showToast(msg: '未完整观看,无法领取'); - return; - } - final remaining = - await ref.read(memberProvider.notifier).claimReward(adType); - if (!mounted) return; - Fluttertoast.showToast(msg: '$successMsg(今日剩余 $remaining 次)'); - } catch (e) { - if (!mounted) return; - Fluttertoast.showToast( - msg: e.toString().replaceFirst('Exception: ', '')); - } finally { - if (mounted) setState(() => _rewarding = false); - } - } - - @override - Widget build(BuildContext context) { - final member = ref.watch(memberProvider); - return Scaffold( - appBar: AppBar(title: const Text('会员中心')), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - _MemberCard( - member: member, - isIOS: _isIOS, - onOpenPlans: _openPlans, - ), - const SizedBox(height: 12), - const _RecentDealsCard(), - if (member.value?.isVip == false) ...[ - const SizedBox(height: 12), - _AdsRewardCard( - rewarding: _rewarding, - onClaim: (adType, msg) => _claimReward(adType, msg), - ), - ], - ], - ), - ); - } -} - -class _MemberCard extends ConsumerWidget { - final AsyncValue member; - final bool isIOS; - final VoidCallback onOpenPlans; - - const _MemberCard( - {required this.member, required this.isIOS, required this.onOpenPlans}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final scheme = Theme.of(context).colorScheme; - return Card( - color: scheme.primaryContainer, - child: Padding( - padding: const EdgeInsets.all(16), - child: member.when( - loading: () => const Text('加载会员状态...', - style: TextStyle(fontSize: 13)), - error: (e, _) => Text('会员状态加载失败:$e', - style: const TextStyle(fontSize: 12)), - data: (m) { - if (m.isVip) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(children: [ - Icon(Icons.workspace_premium, size: 30, color: scheme.primary), - const SizedBox(width: 10), - const Text('形象会员', - style: - TextStyle(fontSize: 17, fontWeight: FontWeight.bold)), - const Spacer(), - Chip( - label: Text(m.planName), - labelStyle: - TextStyle(color: scheme.primary, fontSize: 12), - visualDensity: VisualDensity.compact, - ), - ]), - const SizedBox(height: 6), - Text('有效期至 ${m.expireAt}', - style: TextStyle(fontSize: 12, color: scheme.primary)), - const SizedBox(height: 8), - Align( - alignment: Alignment.centerRight, - child: FilledButton.tonalIcon( - onPressed: () => _showMemberCode(context, m), - icon: const Icon(Icons.qr_code_2, size: 18), - label: const Text('会员码'), - ), - ), - if (benefitTexts(m).isNotEmpty) ...[ - const SizedBox(height: 8), - Wrap( - spacing: 6, - runSpacing: 6, - children: [ - for (final b in benefitTexts(m)) - Chip( - label: Text(b), - labelStyle: const TextStyle(fontSize: 11), - visualDensity: VisualDensity.compact, - ), - ], - ), - ], - ], - ); - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Row(children: [ - Icon(Icons.workspace_premium, size: 30), - SizedBox(width: 10), - Text('形象会员', - style: - TextStyle(fontSize: 17, fontWeight: FontWeight.bold)), - ]), - const SizedBox(height: 6), - const Text('会员专享:无限次效果图生成 · 优先 AI 方案 · 门店专属折扣', - style: TextStyle(fontSize: 12)), - const SizedBox(height: 10), - if (isIOS) - const Text('iOS 端暂不支持充值(App Store 政策),可观看广告获得体验会员', - style: TextStyle(fontSize: 11, color: Colors.grey)) - else - FilledButton.icon( - onPressed: onOpenPlans, - icon: const Icon(Icons.payment, size: 18), - label: const Text('开通会员'), - ), - ], - ); - }, - ), - ), - ); - } -} - -/// 会员码(纯客户端展示,门店出示核销) -void _showMemberCode(BuildContext context, MemberInfo m) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('会员码'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 120, - height: 120, - decoration: BoxDecoration( - color: Theme.of(ctx).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: const Icon(Icons.qr_code_2, size: 96), - ), - const SizedBox(height: 12), - Text('${m.planName} · 有效期至 ${m.expireAt}', - style: const TextStyle(fontSize: 12, color: Colors.grey)), - const SizedBox(height: 4), - const Text('到店出示即可享受会员价与专属折扣', - style: TextStyle(fontSize: 12)), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('关闭'), - ), - ], - ), - ); -} - -/// 最近优惠卡:接口错误或空列表时整卡隐藏 -class _RecentDealsCard extends ConsumerWidget { - const _RecentDealsCard(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final deals = ref.watch(cpsRecentProvider).value; - if (deals == null || deals.isEmpty) return const SizedBox.shrink(); - return Card( - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('最近优惠', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - for (final p in deals.take(5)) - ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: ClipRRect( - borderRadius: BorderRadius.circular(6), - child: p.coverUrl.isEmpty - ? const SizedBox( - width: 40, - height: 40, - child: Icon(Icons.shopping_bag_outlined, - color: Colors.grey), - ) - : Image.network( - AppConfig.resolveUrl(p.coverUrl), - width: 40, - height: 40, - fit: BoxFit.cover, - errorBuilder: (_, _, _) => const SizedBox( - width: 40, - height: 40, - child: Icon(Icons.broken_image_outlined, - color: Colors.grey), - ), - ), - ), - title: Text(p.name, - maxLines: 1, overflow: TextOverflow.ellipsis), - subtitle: Text( - '¥${p.priceText}${p.shopName.isNotEmpty ? ' · ${p.shopName}' : ''}', - maxLines: 1, - overflow: TextOverflow.ellipsis), - trailing: - const Icon(Icons.chevron_right, size: 18, color: Colors.grey), - onTap: () => context.push('/cps-product-list', extra: CpsListArgs( - title: '最近优惠', - source: p.source, - categoryCode: p.categoryCode, - city: p.city, - scene: 'member_benefit', - )), - ), - ], - ), - ), - ); - } -} - -class _AdsRewardCard extends ConsumerWidget { - final bool rewarding; - final void Function(String adType, String msg) onClaim; - - const _AdsRewardCard({required this.rewarding, required this.onClaim}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Card( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('免费获取权益', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: const Icon(Icons.ondemand_video, color: Colors.deepPurple), - title: const Text('看视频 · 效果图 +1'), - subtitle: const Text('每日最多 2 次,次日重置'), - trailing: OutlinedButton( - onPressed: rewarding - ? null - : () => onClaim('effect_extra', '已获得 1 次效果图'), - child: const Text('看视频'), - ), - ), - ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: const Icon(Icons.ondemand_video, color: Colors.teal), - title: const Text('看视频 · 体验会员 1 天'), - subtitle: const Text('每日最多 1 次,含无限效果图'), - trailing: OutlinedButton( - onPressed: rewarding - ? null - : () => onClaim('vip_trial', '已获得 1 天体验会员'), - child: const Text('看视频'), - ), - ), - ], - ), - ), - ); - } -} - -class _PlanSheet extends ConsumerWidget { - const _PlanSheet(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final plans = ref.watch(memberPlanProvider); - return SafeArea( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Text('选择会员套餐', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - plans.when( - loading: () => const Padding( - padding: EdgeInsets.all(24), child: LoadingView()), - error: (e, _) => Text('套餐加载失败:$e', - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey)), - data: (list) => list.isEmpty - ? const Padding( - padding: EdgeInsets.all(24), - child: Text('暂未开放套餐', textAlign: TextAlign.center), - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final p in list) - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8)), - tileColor: Theme.of(context) - .colorScheme - .primaryContainer - .withValues(alpha: 0.5), - title: Text(p.name, - style: const TextStyle( - fontSize: 15, fontWeight: FontWeight.w600)), - subtitle: Text( - '${p.durationDays} 天 · ${p.features.map((f) => benefitLabels[f] ?? f).join(' · ')}', - style: const TextStyle(fontSize: 12), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - trailing: Text(p.priceText, - style: TextStyle( - color: - Theme.of(context).colorScheme.primary, - fontSize: 16, - fontWeight: FontWeight.bold)), - onTap: () => Navigator.pop(context, p), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/features/mine/mine_page.dart b/lib/features/mine/mine_page.dart index 1822917..65f6200 100644 --- a/lib/features/mine/mine_page.dart +++ b/lib/features/mine/mine_page.dart @@ -1,22 +1,87 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:go_router/go_router.dart'; +import '../../core/ads/ads_service.dart'; import '../../core/auth/auth_provider.dart'; -import '../../features/member/member_provider.dart'; +import '../../core/config/app_config.dart'; +import '../../features/cps/cps_provider.dart'; +import '../../shared/widgets/loading_view.dart'; +import '../member/member_provider.dart'; +import '../member/pay_page.dart'; -/// 我的:个人信息管理入口(形象 / 衣橱 / 会员)+ 退出登录 -class MinePage extends ConsumerWidget { +/// 我的:个人信息管理(会员卡/免费权益/最近优惠 + 形象/衣橱入口 + 退出登录) +class MinePage extends ConsumerStatefulWidget { const MinePage({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _MinePageState(); +} + +class _MinePageState extends ConsumerState { + bool _rewarding = false; + + // web 上无 Platform(dart:io 不可用),且 web 端默认走系统浏览器收银台 + bool get _isIOS => + !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; + + Future _openPlans() async { + final plans = await showModalBottomSheet( + context: context, + builder: (ctx) => const _PlanSheet(), + ); + if (plans == null || !mounted) return; + try { + final result = await createMemberOrder(ref, plans.id); + if (!mounted || result.payUrl.isEmpty) return; + await context.push( + '/pay', extra: PayArgs(orderNo: result.orderNo, payUrl: result.payUrl)); + ref.read(memberProvider.notifier).refresh(); + } catch (e) { + if (!mounted) return; + Fluttertoast.showToast( + msg: e.toString().replaceFirst('Exception: ', '')); + } + } + + Future _claimReward(String adType, String successMsg) async { + if (_rewarding) return; + final ads = ref.read(adsServiceProvider); + if (!ads.enabled) { + Fluttertoast.showToast(msg: '广告功能暂未开通'); + return; + } + setState(() => _rewarding = true); + try { + final watched = await ads.showRewarded(); + if (!watched) { + Fluttertoast.showToast(msg: '未完整观看,无法领取'); + return; + } + final remaining = + await ref.read(memberProvider.notifier).claimReward(adType); + if (!mounted) return; + Fluttertoast.showToast(msg: '$successMsg(今日剩余 $remaining 次)'); + } catch (e) { + if (!mounted) return; + Fluttertoast.showToast( + msg: e.toString().replaceFirst('Exception: ', '')); + } finally { + if (mounted) setState(() => _rewarding = false); + } + } + + @override + Widget build(BuildContext context) { + final member = ref.watch(memberProvider); final auth = ref.watch(authProvider).value; - final member = ref.watch(memberProvider).value; final scheme = Theme.of(context).colorScheme; final rawName = auth?.name?.trim() ?? ''; final name = rawName.isEmpty ? '我的' : rawName; + return Scaffold( appBar: AppBar(title: const Text('我的')), body: ListView( @@ -36,15 +101,28 @@ class MinePage extends ConsumerWidget { style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold)), subtitle: Text( - member?.isVip == true - ? '形象会员 · ${member!.planName} · 有效期至 ${member.expireAt}' + member.value?.isVip == true + ? '形象会员 · ${member.value!.planName} · 有效期至 ${member.value!.expireAt}' : '普通用户', style: const TextStyle(fontSize: 12), ), - trailing: const Icon(Icons.chevron_right, color: Colors.grey), - onTap: () => context.push('/commercial'), ), ), + const SizedBox(height: 12), + _MemberCard( + member: member, + isIOS: _isIOS, + onOpenPlans: _openPlans, + ), + const SizedBox(height: 12), + const _RecentDealsCard(), + if (member.value?.isVip == false) ...[ + const SizedBox(height: 12), + _AdsRewardCard( + rewarding: _rewarding, + onClaim: (adType, msg) => _claimReward(adType, msg), + ), + ], const SizedBox(height: 16), Card( child: Column( @@ -53,7 +131,8 @@ class MinePage extends ConsumerWidget { leading: const Icon(Icons.face_retouching_natural), title: const Text('我的形象'), subtitle: const Text('3D 化身 · 形象照片 · 身形参数'), - trailing: const Icon(Icons.chevron_right, color: Colors.grey), + trailing: + const Icon(Icons.chevron_right, color: Colors.grey), onTap: () => context.push('/profile'), ), const Divider(height: 1, indent: 56), @@ -61,7 +140,8 @@ class MinePage extends ConsumerWidget { leading: const Icon(Icons.checkroom), title: const Text('我的衣橱'), subtitle: const Text('管理服装单品,AI 生成时自动搭配'), - trailing: const Icon(Icons.chevron_right, color: Colors.grey), + trailing: + const Icon(Icons.chevron_right, color: Colors.grey), onTap: () => context.push('/wardrobe'), ), ], @@ -82,3 +162,325 @@ class MinePage extends ConsumerWidget { ); } } + +class _MemberCard extends ConsumerWidget { + final AsyncValue member; + final bool isIOS; + final VoidCallback onOpenPlans; + + const _MemberCard( + {required this.member, required this.isIOS, required this.onOpenPlans}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final scheme = Theme.of(context).colorScheme; + return Card( + color: scheme.primaryContainer, + child: Padding( + padding: const EdgeInsets.all(16), + child: member.when( + loading: () => const Text('加载会员状态...', + style: TextStyle(fontSize: 13)), + error: (e, _) => Text('会员状态加载失败:$e', + style: const TextStyle(fontSize: 12)), + data: (m) { + if (m.isVip) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Icon(Icons.workspace_premium, + size: 30, color: scheme.primary), + const SizedBox(width: 10), + const Text('形象会员', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold)), + const Spacer(), + Chip( + label: Text(m.planName), + labelStyle: + TextStyle(color: scheme.primary, fontSize: 12), + visualDensity: VisualDensity.compact, + ), + ]), + const SizedBox(height: 6), + Text('有效期至 ${m.expireAt}', + style: + TextStyle(fontSize: 12, color: scheme.primary)), + const SizedBox(height: 8), + Align( + alignment: Alignment.centerRight, + child: FilledButton.tonalIcon( + onPressed: () => _showMemberCode(context, m), + icon: const Icon(Icons.qr_code_2, size: 18), + label: const Text('会员码'), + ), + ), + if (benefitTexts(m).isNotEmpty) ...[ + const SizedBox(height: 8), + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + for (final b in benefitTexts(m)) + Chip( + label: Text(b), + labelStyle: const TextStyle(fontSize: 11), + visualDensity: VisualDensity.compact, + ), + ], + ), + ], + ], + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row(children: [ + Icon(Icons.workspace_premium, size: 30), + SizedBox(width: 10), + Text('形象会员', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold)), + ]), + const SizedBox(height: 6), + const Text('会员专享:无限次效果图生成 · 优先 AI 方案 · 门店专属折扣', + style: TextStyle(fontSize: 12)), + const SizedBox(height: 10), + if (isIOS) + const Text('iOS 端暂不支持充值(App Store 政策),可观看广告获得体验会员', + style: TextStyle(fontSize: 11, color: Colors.grey)) + else + FilledButton.icon( + onPressed: onOpenPlans, + icon: const Icon(Icons.payment, size: 18), + label: const Text('开通会员'), + ), + ], + ); + }, + ), + ), + ); + } +} + +/// 会员码(纯客户端展示,门店出示核销) +void _showMemberCode(BuildContext context, MemberInfo m) { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('会员码'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Theme.of(ctx).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(Icons.qr_code_2, size: 96), + ), + const SizedBox(height: 12), + Text('${m.planName} · 有效期至 ${m.expireAt}', + style: const TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 4), + const Text('到店出示即可享受会员价与专属折扣', + style: TextStyle(fontSize: 12)), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('关闭'), + ), + ], + ), + ); +} + +/// 最近优惠卡:接口错误或空列表时整卡隐藏 +class _RecentDealsCard extends ConsumerWidget { + const _RecentDealsCard(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final deals = ref.watch(cpsRecentProvider).value; + if (deals == null || deals.isEmpty) return const SizedBox.shrink(); + return Card( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('最近优惠', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + for (final p in deals.take(5)) + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: ClipRRect( + borderRadius: BorderRadius.circular(6), + child: p.coverUrl.isEmpty + ? const SizedBox( + width: 40, + height: 40, + child: Icon(Icons.shopping_bag_outlined, + color: Colors.grey), + ) + : Image.network( + AppConfig.resolveUrl(p.coverUrl), + width: 40, + height: 40, + fit: BoxFit.cover, + errorBuilder: (_, _, _) => const SizedBox( + width: 40, + height: 40, + child: Icon(Icons.broken_image_outlined, + color: Colors.grey), + ), + ), + ), + title: Text(p.name, + maxLines: 1, overflow: TextOverflow.ellipsis), + subtitle: Text( + '¥${p.priceText}${p.shopName.isNotEmpty ? ' · ${p.shopName}' : ''}', + maxLines: 1, + overflow: TextOverflow.ellipsis), + trailing: + const Icon(Icons.chevron_right, size: 18, color: Colors.grey), + onTap: () => context.push('/cps-product-list', + extra: CpsListArgs( + title: '最近优惠', + source: p.source, + categoryCode: p.categoryCode, + city: p.city, + scene: 'member_benefit', + )), + ), + ], + ), + ), + ); + } +} + +class _AdsRewardCard extends ConsumerWidget { + final bool rewarding; + final void Function(String adType, String msg) onClaim; + + const _AdsRewardCard({required this.rewarding, required this.onClaim}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Card( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('免费获取权益', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.ondemand_video, color: Colors.deepPurple), + title: const Text('看视频 · 效果图 +1'), + subtitle: const Text('每日最多 2 次,次日重置'), + trailing: OutlinedButton( + onPressed: rewarding + ? null + : () => onClaim('effect_extra', '已获得 1 次效果图'), + child: const Text('看视频'), + ), + ), + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.ondemand_video, color: Colors.teal), + title: const Text('看视频 · 体验会员 1 天'), + subtitle: const Text('每日最多 1 次,含无限效果图'), + trailing: OutlinedButton( + onPressed: rewarding + ? null + : () => onClaim('vip_trial', '已获得 1 天体验会员'), + child: const Text('看视频'), + ), + ), + ], + ), + ), + ); + } +} + +class _PlanSheet extends ConsumerWidget { + const _PlanSheet(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final plans = ref.watch(memberPlanProvider); + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Text('选择会员套餐', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + plans.when( + loading: () => const Padding( + padding: EdgeInsets.all(24), child: LoadingView()), + error: (e, _) => Text('套餐加载失败:$e', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey)), + data: (list) => list.isEmpty + ? const Padding( + padding: EdgeInsets.all(24), + child: Text('暂未开放套餐', textAlign: TextAlign.center), + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final p in list) + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + tileColor: Theme.of(context) + .colorScheme + .primaryContainer + .withValues(alpha: 0.5), + title: Text(p.name, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w600)), + subtitle: Text( + '${p.durationDays} 天 · ${p.features.map((f) => benefitLabels[f] ?? f).join(' · ')}', + style: const TextStyle(fontSize: 12), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + trailing: Text(p.priceText, + style: TextStyle( + color: + Theme.of(context).colorScheme.primary, + fontSize: 16, + fontWeight: FontWeight.bold)), + onTap: () => Navigator.pop(context, p), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 75f193e..dda16a6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import 'features/auth/login_page.dart'; import 'features/cps/cps_product_list_page.dart'; import 'features/cps/cps_provider.dart'; import 'features/home/home_page.dart'; -import 'features/member/member_center_page.dart'; import 'features/member/pay_page.dart'; import 'features/outfit/outfit_page.dart'; import 'features/outfit/outfit_provider.dart'; @@ -78,10 +77,6 @@ final routerProvider = Provider((ref) { path: '/outfit', builder: (ctx, state) => const OutfitPage(), ), - GoRoute( - path: '/commercial', - builder: (ctx, state) => const MemberCenterPage(), - ), GoRoute( path: '/pay', builder: (context, state) => PayPage(args: state.extra! as PayArgs),