From 38cf41d44dbc2cff6fd3841fc96db9134191a0d3 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 13:50:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=9A=E5=91=98=20provider=EF=BC=88?= =?UTF-8?q?=E7=8A=B6=E6=80=81/=E5=A5=97=E9=A4=90/=E4=B8=8B=E5=8D=95/?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2/=E9=A2=86=E5=A5=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/member/member_provider.dart | 110 ++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/lib/features/member/member_provider.dart b/lib/features/member/member_provider.dart index 27ec83c..1981017 100644 --- a/lib/features/member/member_provider.dart +++ b/lib/features/member/member_provider.dart @@ -1,4 +1,10 @@ -/// 会员 provider:Task 1 仅含权益文案纯函数(Task 2 将补全状态/套餐/下单/轮询/领奖) +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../core/auth/auth_provider.dart'; + +/// 会员 provider:会员状态/套餐/下单/轮询/领奖 class MemberInfo { final bool isVip; final String expireAt; @@ -11,6 +17,13 @@ class MemberInfo { required this.planName, required this.benefits, }); + + factory MemberInfo.fromJson(Map e) => MemberInfo( + isVip: e['is_vip'] as bool? ?? false, + expireAt: e['expire_at'] as String? ?? '', + planName: e['plan_name'] as String? ?? '', + benefits: (e['benefits'] as List? ?? []).cast(), + ); } /// 权益 key → 文案 @@ -23,3 +36,98 @@ const benefitLabels = { List benefitTexts(MemberInfo m) => m.benefits.map((b) => benefitLabels[b] ?? b).toList(); + +class MemberNotifier extends AsyncNotifier { + @override + Future build() async { + final api = ref.read(apiClientProvider); + final data = await api.get>('/member/status'); + return MemberInfo.fromJson(data); + } + + Future refresh() async { + state = await AsyncValue.guard(build); + } + + /// 领取广告激励(服务端限频);adType: effect_extra | vip_trial + /// 返回当日剩余次数;超出限频抛 ApiException + Future claimReward(String adType) async { + final api = ref.read(apiClientProvider); + final data = + await api.post>('/ad/reward/claim', {'ad_type': adType}); + await refresh(); // vip_trial 可能开通体验会员 + return (data['reward']?['remaining_today'] as num?)?.toInt() ?? 0; + } +} + +final memberProvider = + AsyncNotifierProvider(MemberNotifier.new); + +class MemberPlan { + final int id; + final String name; + final int priceFen; + final int durationDays; + final List features; + + const MemberPlan({ + required this.id, + required this.name, + required this.priceFen, + required this.durationDays, + required this.features, + }); + + factory MemberPlan.fromJson(Map e) => MemberPlan( + id: (e['id'] as num).toInt(), + name: e['name'] as String? ?? '', + priceFen: (e['price_fen'] as num?)?.toInt() ?? 0, + durationDays: (e['duration_days'] as num?)?.toInt() ?? 30, + features: _parseFeatures(e['features'] as String? ?? ''), + ); + + static List _parseFeatures(String s) { + try { + return (jsonDecode(s) as List).cast(); + } catch (_) { + return const []; + } + } + + String get priceText => + '¥${(priceFen / 100).toStringAsFixed(priceFen % 100 == 0 ? 0 : 1)}'; +} + +final memberPlanProvider = FutureProvider>((ref) async { + final api = ref.read(apiClientProvider); + final list = await api.get>('/member/plan/list'); + return list + .map((e) => MemberPlan.fromJson(e as Map)) + .toList(); +}); + +class OrderResult { + final String orderNo; + final String payUrl; + + const OrderResult({required this.orderNo, required this.payUrl}); +} + +/// 创建支付订单(后端调虎皮棋下单,返回收银台 URL) +Future createMemberOrder(WidgetRef ref, int planId) async { + final api = ref.read(apiClientProvider); + final data = + await api.post>('/member/order/create', {'plan_id': planId}); + return OrderResult( + orderNo: data['order_no'] as String? ?? '', + payUrl: data['pay_url'] as String? ?? '', + ); +} + +/// 订单状态(支付页 2s 轮询):pending | paid | closed +Future fetchOrderStatus(WidgetRef ref, String orderNo) async { + final api = ref.read(apiClientProvider); + final data = await api.get>('/member/order/status', + query: {'order_no': orderNo}); + return data['status'] as String? ?? ''; +}