git-subtree-dir: app git-subtree-mainline:6ebd902c6bgit-subtree-split:a11a668869
202 lines
5.9 KiB
Dart
202 lines
5.9 KiB
Dart
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();
|
||
});
|