You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

118 lines
4.3 KiB

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<Map<String, dynamic>>();
@override
void onInit() {
super.onInit();
refreshCompute();
}
Future<void> refreshCompute() async {
try {
final resp = await Api.getCompute();
if (resp is Map) compute.value = Map<String, dynamic>.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<List<DBGoods>> fetchProducts() async {
// 2026-09 起在售的只有 VIP 时长与算力包;AI/Meet/Translation 是旧资源点包,
// 仍然拉取是为了让还挂在架上的存量商品能买(发货时会折成算力),但不再新建。
final types = [
UsageType.Vip,
UsageType.Compute,
UsageType.AI,
UsageType.Meet,
UsageType.Translation,
];
final Map<String, DBGoods> 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<String, dynamic>>()
.map(DBGoods.fromJson)
.toList();
for (final g in list) {
all[g.id] = g;
}
}
}
return all.values.toList();
}
// Future<void> purchase(DBGoods item) async => super.purchase(item);
}