feat: 会员 provider(状态/套餐/下单/轮询/领奖)
This commit is contained in:
@@ -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<String, dynamic> 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<dynamic>? ?? []).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 权益 key → 文案
|
||||
@@ -23,3 +36,98 @@ const benefitLabels = {
|
||||
|
||||
List<String> benefitTexts(MemberInfo m) =>
|
||||
m.benefits.map((b) => benefitLabels[b] ?? b).toList();
|
||||
|
||||
class MemberNotifier extends AsyncNotifier<MemberInfo> {
|
||||
@override
|
||||
Future<MemberInfo> build() async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data = await api.get<Map<String, dynamic>>('/member/status');
|
||||
return MemberInfo.fromJson(data);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
state = await AsyncValue.guard(build);
|
||||
}
|
||||
|
||||
/// 领取广告激励(服务端限频);adType: effect_extra | vip_trial
|
||||
/// 返回当日剩余次数;超出限频抛 ApiException
|
||||
Future<int> claimReward(String adType) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data =
|
||||
await api.post<Map<String, dynamic>>('/ad/reward/claim', {'ad_type': adType});
|
||||
await refresh(); // vip_trial 可能开通体验会员
|
||||
return (data['reward']?['remaining_today'] as num?)?.toInt() ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
final memberProvider =
|
||||
AsyncNotifierProvider<MemberNotifier, MemberInfo>(MemberNotifier.new);
|
||||
|
||||
class MemberPlan {
|
||||
final int id;
|
||||
final String name;
|
||||
final int priceFen;
|
||||
final int durationDays;
|
||||
final List<String> features;
|
||||
|
||||
const MemberPlan({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.priceFen,
|
||||
required this.durationDays,
|
||||
required this.features,
|
||||
});
|
||||
|
||||
factory MemberPlan.fromJson(Map<String, dynamic> 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<String> _parseFeatures(String s) {
|
||||
try {
|
||||
return (jsonDecode(s) as List<dynamic>).cast<String>();
|
||||
} catch (_) {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
String get priceText =>
|
||||
'¥${(priceFen / 100).toStringAsFixed(priceFen % 100 == 0 ? 0 : 1)}';
|
||||
}
|
||||
|
||||
final memberPlanProvider = FutureProvider<List<MemberPlan>>((ref) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final list = await api.get<List<dynamic>>('/member/plan/list');
|
||||
return list
|
||||
.map((e) => MemberPlan.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
});
|
||||
|
||||
class OrderResult {
|
||||
final String orderNo;
|
||||
final String payUrl;
|
||||
|
||||
const OrderResult({required this.orderNo, required this.payUrl});
|
||||
}
|
||||
|
||||
/// 创建支付订单(后端调虎皮棋下单,返回收银台 URL)
|
||||
Future<OrderResult> createMemberOrder(WidgetRef ref, int planId) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data =
|
||||
await api.post<Map<String, dynamic>>('/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<String> fetchOrderStatus(WidgetRef ref, String orderNo) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data = await api.get<Map<String, dynamic>>('/member/order/status',
|
||||
query: {'order_no': orderNo});
|
||||
return data['status'] as String? ?? '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user