trader.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import '../../../core/utils/avatar_urls.dart';
  2. /// 交易员(跟单广场列表项)
  3. class Trader {
  4. final String id;
  5. final String name;
  6. final String avatarLetter;
  7. final String? avatarUrl; // 真实头像 URL(来自 API)
  8. final int followers;
  9. final int? maxFollowers; // 最大跟随人数
  10. final double roi30d; // 近14日收益率 %
  11. final double profit30d; // 近30日收益 USDT
  12. final double profitShare; // 分润比例 %(来自 dividendPercent)
  13. final double winRate; // 近14日胜率 %
  14. final bool isNoLoss; // 是否无损跟单
  15. final bool isFollowing; // 是否已跟随
  16. /// 当前用户是否已收藏(跟单广场「我的收藏」与列表星标)
  17. final bool isFavorited;
  18. final List<double> trendData; // 走势迷你图数据
  19. final String? levelName; // 交易员等级名称(如 L3、L5)
  20. // ── 我的交易员 tab 额外字段 ──────────────────────────────
  21. final String? description; // 个人介绍
  22. final List<String> tags; // 标签列表(如超短、中长、稳健型)
  23. final double profitAmount; // 累计收益 USDT(对跟单员是从该交易员获得的收益)
  24. final int tradingDays; // 累计交易天数
  25. final int followCustomer; // 累计跟单人数(总人数,非当前)
  26. const Trader({
  27. required this.id,
  28. required this.name,
  29. required this.avatarLetter,
  30. this.avatarUrl,
  31. required this.followers,
  32. this.maxFollowers,
  33. required this.roi30d,
  34. required this.profit30d,
  35. required this.profitShare,
  36. required this.winRate,
  37. this.isNoLoss = false,
  38. this.isFollowing = false,
  39. this.isFavorited = false,
  40. this.trendData = const [],
  41. this.levelName,
  42. this.description,
  43. this.tags = const [],
  44. this.profitAmount = 0,
  45. this.tradingDays = 0,
  46. this.followCustomer = 0,
  47. });
  48. /// 是否与 Web 一致的布尔解析(支持 1/true/"true")。
  49. static bool parseFavoriteFlag(dynamic value) {
  50. if (value == null) {
  51. return false;
  52. }
  53. if (value is bool) {
  54. return value;
  55. }
  56. if (value is num) {
  57. return value != 0;
  58. }
  59. final s = value.toString().trim().toLowerCase();
  60. if (s.isEmpty || s == '0' || s == 'false') {
  61. return false;
  62. }
  63. if (s == '1' || s == 'true' || s == 'yes') {
  64. return true;
  65. }
  66. return false;
  67. }
  68. Trader copyWith({
  69. bool? isFavorited,
  70. bool? isFollowing,
  71. }) {
  72. return Trader(
  73. id: id,
  74. name: name,
  75. avatarLetter: avatarLetter,
  76. avatarUrl: avatarUrl,
  77. followers: followers,
  78. maxFollowers: maxFollowers,
  79. roi30d: roi30d,
  80. profit30d: profit30d,
  81. profitShare: profitShare,
  82. winRate: winRate,
  83. isNoLoss: isNoLoss,
  84. isFollowing: isFollowing ?? this.isFollowing,
  85. isFavorited: isFavorited ?? this.isFavorited,
  86. trendData: trendData,
  87. levelName: levelName,
  88. description: description,
  89. tags: tags,
  90. profitAmount: profitAmount,
  91. tradingDays: tradingDays,
  92. followCustomer: followCustomer,
  93. );
  94. }
  95. /// 从 API 返回的 FollowTrader / TraderVo Map 构建
  96. factory Trader.fromApi(
  97. Map<String, dynamic> json, {
  98. bool isNoLoss = false,
  99. bool? isFavoritedOverride,
  100. }) {
  101. final nickname =
  102. (json['nickname'] ?? json['nickName'])?.toString().trim() ?? '';
  103. final letter = nickname.isNotEmpty ? nickname[0].toUpperCase() : '?';
  104. // 将 yield14List 数组转为 double 列表(用于走势图)
  105. List<double> trendData = [];
  106. final yields = json['yield14List'];
  107. if (yields is List) {
  108. trendData = yields
  109. .map((e) {
  110. if (e is Map) {
  111. // 安卓 Yield14 model: @SerializedName("dayYield14")
  112. final v =
  113. e['dayYield14'] ?? e['yield'] ?? e['rate'] ?? e['value'] ?? 0;
  114. return (v is num)
  115. ? v.toDouble()
  116. : double.tryParse(v.toString()) ?? 0.0;
  117. }
  118. return (e is num)
  119. ? e.toDouble()
  120. : double.tryParse(e.toString()) ?? 0.0;
  121. })
  122. .toList()
  123. .reversed // 后台按 date DESC 返回(最新在前),翻转为时间正序(旧→新,左→右)
  124. .toList();
  125. }
  126. double _d(String key) =>
  127. double.tryParse((json[key] ?? '0').toString()) ?? 0.0;
  128. int _i(String key) => int.tryParse((json[key] ?? '0').toString()) ?? 0;
  129. // 解析标签列表(可能是 List<String> 或 List<Map>)
  130. List<String> tags = [];
  131. final rawTags = json['tags'];
  132. if (rawTags is List) {
  133. tags = rawTags
  134. .map((e) {
  135. if (e is String) return e;
  136. if (e is Map) return (e['name'] ?? '').toString();
  137. return '';
  138. })
  139. .where((s) => s.isNotEmpty)
  140. .toList();
  141. }
  142. // 分润比例:不同接口可能用不同字段名
  143. double resolvedProfitShare = 0.0;
  144. for (final key in [
  145. 'dividendPercent',
  146. 'profitRatio',
  147. 'dividendRatio',
  148. 'sharePercent',
  149. 'profitPercent'
  150. ]) {
  151. final v = double.tryParse((json[key] ?? '').toString());
  152. if (v != null && v != 0.0) {
  153. resolvedProfitShare = v;
  154. break;
  155. }
  156. }
  157. return Trader(
  158. id: json['id']?.toString() ?? '',
  159. name: nickname,
  160. avatarLetter: letter,
  161. avatarUrl: resolvedAvatarUrlFromRecord(json),
  162. followers: _i('following'),
  163. maxFollowers: json['maxFollow'] != null ? _i('maxFollow') : null,
  164. roi30d: _d('dayYield14'),
  165. profit30d: _d('profitAmount'),
  166. profitShare: resolvedProfitShare,
  167. winRate: _d('winRate14'),
  168. isNoLoss: isNoLoss || json['specialTrader'] == true,
  169. isFollowing: parseFavoriteFlag(json['isFollow'] ?? json['follow']),
  170. isFavorited: isFavoritedOverride ??
  171. parseFavoriteFlag(
  172. json['isFavorite'] ?? json['isFavorited'] ?? json['favorite']),
  173. trendData: trendData,
  174. levelName: json['levelName'] as String?,
  175. description: json['description'] as String?,
  176. tags: tags,
  177. profitAmount: _d('profitAmount'),
  178. tradingDays: _i('tradingDays'),
  179. followCustomer: _i('defaultFollowCustomer') + _i('followCustomer'),
  180. );
  181. }
  182. }