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 {
|
class MemberInfo {
|
||||||
final bool isVip;
|
final bool isVip;
|
||||||
final String expireAt;
|
final String expireAt;
|
||||||
@@ -11,6 +17,13 @@ class MemberInfo {
|
|||||||
required this.planName,
|
required this.planName,
|
||||||
required this.benefits,
|
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 → 文案
|
/// 权益 key → 文案
|
||||||
@@ -23,3 +36,98 @@ const benefitLabels = {
|
|||||||
|
|
||||||
List<String> benefitTexts(MemberInfo m) =>
|
List<String> benefitTexts(MemberInfo m) =>
|
||||||
m.benefits.map((b) => benefitLabels[b] ?? b).toList();
|
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