Files
slogan/lib/features/cps/cps_provider.dart
T
2026-08-03 10:29:31 +08:00

202 lines
5.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/auth/auth_provider.dart';
/// CPS 联盟商品(后端 /cps/* 模型)
class CpsProductInfo {
final int id;
final String source;
final String outerId;
final String categoryCode;
final String name;
final String coverUrl;
final int priceFen;
final String shopName;
final int commissionRate; // 万分比
final String city;
const CpsProductInfo({
required this.id,
required this.source,
required this.outerId,
required this.categoryCode,
required this.name,
required this.coverUrl,
required this.priceFen,
required this.shopName,
required this.commissionRate,
required this.city,
});
String get priceText => (priceFen / 100).toStringAsFixed(0);
String get commissionText {
final pct = commissionRate / 100;
return pct > 0 ? '佣金 ${pct.toStringAsFixed(1)}%' : '';
}
}
CpsProductInfo _parseProduct(Map<String, dynamic> e) => CpsProductInfo(
id: (e['id'] as num).toInt(),
source: e['source'] as String? ?? '',
outerId: e['outer_id'] as String? ?? '',
categoryCode: e['category_code'] as String? ?? '',
name: e['name'] as String? ?? '',
coverUrl: e['cover_url'] as String? ?? '',
priceFen: (e['price_fen'] as num?)?.toInt() ?? 0,
shopName: e['shop_name'] as String? ?? '',
commissionRate: (e['commission_rate'] as num?)?.toInt() ?? 0,
city: e['city'] as String? ?? '',
);
/// 联盟分类(chips
class CpsCategoryInfo {
final String code;
final String name;
final String source;
const CpsCategoryInfo({
required this.code,
required this.name,
required this.source,
});
}
final cpsCategoryProvider = FutureProvider<List<CpsCategoryInfo>>((ref) async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/cps/category/list');
final list = data['list'] as List<dynamic>? ?? [];
return list
.map((e) => CpsCategoryInfo(
code: e['code'] as String? ?? '',
name: e['name'] as String? ?? '',
source: e['source'] as String? ?? '',
))
.toList();
});
/// 商品列表页参数
class CpsListArgs {
final String title;
final String source;
final String categoryCode;
final String city;
final String scene; // 点击日志场景标记(haircut/item_buy/...
const CpsListArgs({
required this.title,
this.source = '',
this.categoryCode = '',
this.city = '',
this.scene = '',
});
@override
bool operator ==(Object other) =>
other is CpsListArgs &&
other.source == source &&
other.categoryCode == categoryCode &&
other.city == city &&
other.scene == scene;
@override
int get hashCode => Object.hash(source, categoryCode, city, scene);
}
/// 选品池分页(上滑加载更多;Riverpod 3 family 参数经构造函数注入)
class CpsProductListNotifier extends AsyncNotifier<List<CpsProductInfo>> {
CpsProductListNotifier(this.arg);
final CpsListArgs arg;
int _page = 1;
bool _hasMore = true;
@override
Future<List<CpsProductInfo>> build() async {
_page = 1;
_hasMore = true;
return _fetch(1);
}
Future<void> loadMore() async {
if (!_hasMore || state.isLoading) return;
_page += 1;
try {
final next = await _fetch(_page);
state = AsyncData([...?state.value, ...next]);
} catch (_) {
_page -= 1; // 失败回退页码,允许下次重试
}
}
Future<List<CpsProductInfo>> _fetch(int page) async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/cps/product/list',
query: {
if (arg.source.isNotEmpty) 'source': arg.source,
if (arg.categoryCode.isNotEmpty) 'category_code': arg.categoryCode,
if (arg.city.isNotEmpty) 'city': arg.city,
'page': page,
});
_hasMore = data['has_more'] as bool? ?? false;
return (data['list'] as List<dynamic>? ?? [])
.map((e) => _parseProduct(e as Map<String, dynamic>))
.toList();
}
}
final cpsProductProvider = AsyncNotifierProvider.family<
CpsProductListNotifier, List<CpsProductInfo>, CpsListArgs>(
CpsProductListNotifier.new);
/// 方案驱动推荐(plan_id + scene
final cpsRecommendProvider = FutureProvider.family<List<CpsProductInfo>,
({int planId, String scene})>((ref, args) async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/cps/plan/recommend',
query: {'plan_id': args.planId, 'scene': args.scene});
return (data['list'] as List<dynamic>? ?? [])
.map((e) => _parseProduct(e as Map<String, dynamic>))
.toList();
});
/// 衣橱升级款
final cpsUpgradeProvider =
FutureProvider.family<List<CpsProductInfo>, int>((ref, itemId) async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/cps/wardrobe/upgrade',
query: {'item_id': itemId});
return (data['list'] as List<dynamic>? ?? [])
.map((e) => _parseProduct(e as Map<String, dynamic>))
.toList();
});
/// 转链动作(POST /cps/product/link,返回 deeplink
class CpsLinkAction {
Future<String> link(
WidgetRef ref, {
required int productId,
String scene = '',
int planId = 0,
}) async {
final api = ref.read(apiClientProvider);
final data = await api.post<Map<String, dynamic>>('/cps/product/link', {
'product_id': productId,
'scene': scene,
'plan_id': planId,
});
return data['deeplink'] as String? ?? '';
}
}
final cpsLinkProvider = Provider<CpsLinkAction>((ref) => CpsLinkAction());
/// 最近优惠(会员中心)
final cpsRecentProvider = FutureProvider<List<CpsProductInfo>>((ref) async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/cps/my/recent');
return (data['list'] as List<dynamic>? ?? [])
.map((e) => _parseProduct(e as Map<String, dynamic>))
.toList();
});