import 'package:get/get.dart'; import '../../../data/models/goods.dart'; import '../../../data/services/network/api.dart'; import 'goods_base_controller.dart'; class GoodsvipController extends GoodsBaseController { /// 会员与算力状态(`user_getcompute`),页面顶部 VIP 卡片下展示。 /// 服务端已把自购与设备赋予的 VIP 合并好、算力也拆好了来源,这里**不做任何换算**。 final compute = Rxn>(); @override void onInit() { super.onInit(); refreshCompute(); } Future refreshCompute() async { try { final resp = await Api.getCompute(); if (resp is Map) compute.value = Map.from(resp); } catch (_) { // 展示项,拿不到就不显示 } } /// 「设备 N + 账户 M」形式的算力摘要;没数据返回 null。 String? get computeSummary { final c = compute.value?['compute']; if (c is! Map) return null; final dev = (c['deviceleft'] as num?)?.toInt() ?? 0; final usr = (c['userbalance'] as num?)?.toInt() ?? 0; return 'computeSummary'.trParams({'device': '$dev', 'user': '$usr'}); } /// 需要提示「VIP 快到期了」吗:剩余天数 ≤ 后台配的阈值(阈值为 0 = 后台关掉了这个提示)。 /// 判定用服务端下发的 vipwarndays,客户端不自己拍数字——运营改阈值不用发版。 bool get vipExpiringSoon { final v = compute.value?['vip']; final r = compute.value?['rates']; if (v is! Map || r is! Map) return false; final days = (r['vipwarndays'] as num?)?.toInt() ?? 0; if (days <= 0) return false; final exp = (v['exptime'] as num?)?.toInt() ?? 0; if (exp <= 0) return false; final left = exp - DateTime.now().millisecondsSinceEpoch ~/ 1000; return left > 0 && left <= days * 86400; } /// VIP 剩余天数(向上取整,至少 1);无 VIP 或已过期返回 0。 int get vipDaysLeft { final v = compute.value?['vip']; if (v is! Map) return 0; final exp = (v['exptime'] as num?)?.toInt() ?? 0; final left = exp - DateTime.now().millisecondsSinceEpoch ~/ 1000; if (left <= 0) return 0; return (left / 86400).ceil(); } /// 需要提示「算力不多了」吗:剩余总量 ≤ 后台配的阈值(阈值 0 = 不提示)。 bool get computeLow { final c = compute.value?['compute']; final r = compute.value?['rates']; if (c is! Map || r is! Map) return false; final warn = (r['computewarn'] as num?)?.toInt() ?? 0; if (warn <= 0) return false; final left = ((c['deviceleft'] as num?)?.toInt() ?? 0) + ((c['userbalance'] as num?)?.toInt() ?? 0); return left <= warn; } /// 当前 VIP 是否来自设备(用于副标题标注来源)。 bool get vipFromDevice { final v = compute.value?['vip']; if (v is! Map) return false; final self = (v['selfexptime'] as num?)?.toInt() ?? 0; final exp = (v['exptime'] as num?)?.toInt() ?? 0; final level = (v['level'] as num?)?.toInt() ?? 0; return level > 0 && exp > self; } @override Future> fetchProducts() async { // 2026-09 起在售的只有 VIP 时长与算力包;AI/Meet/Translation 是旧资源点包, // 仍然拉取是为了让还挂在架上的存量商品能买(发货时会折成算力),但不再新建。 final types = [ UsageType.Vip, UsageType.Compute, UsageType.AI, UsageType.Meet, UsageType.Translation, ]; final Map all = {}; for (final t in types) { final resp = await Api.getGoods({'usagetype': t.toInt}); if (resp == null || resp is! Map) continue; if (resp.containsKey('error') || resp.containsKey('msg')) { final msg = resp['error'] ?? resp['msg'] ?? '服务器错误'; throw Exception(msg); } final container = (resp is Map && resp['data'] is Map) ? resp['data'] : resp; final data = (container is Map) ? (container['goodss'] ?? container['goodes'] ?? container['goods']) : null; if (data is List) { final list = data .whereType>() .map(DBGoods.fromJson) .toList(); for (final g in list) { all[g.id] = g; } } } return all.values.toList(); } // Future purchase(DBGoods item) async => super.purchase(item); }