copy_trading_repository.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import 'package:dio/dio.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import '../../core/network/dio_client.dart';
  4. import '../services/futures_service.dart';
  5. /// 合约跟单 Repository — 对应 Android FollowDefService + FollowService
  6. class CopyTradingRepository {
  7. CopyTradingRepository(this._dio);
  8. final Dio _dio;
  9. /// 获取交易员列表
  10. /// [orderBy] 排序:10=综合, 20=14日收益率, 30=14日胜率, 40=资金实力
  11. /// [traderType] ""=全部, "1"=普通跟单, "2"=无损跟单
  12. Future<List<Map<String, dynamic>>> getTraderList({
  13. required String orderBy,
  14. String traderType = '',
  15. String nickName = '',
  16. int page = 1,
  17. int pageSize = 20,
  18. }) async {
  19. final resp = await _dio.post(
  20. 'follow/customer/trader-list',
  21. data: {
  22. 'orderBy': int.tryParse(orderBy) ?? 10,
  23. 'traderType': traderType.isEmpty ? null : int.tryParse(traderType),
  24. 'nickName': nickName,
  25. 'currentPage': '$page',
  26. 'pageSize': '$pageSize',
  27. },
  28. );
  29. final body = resp.data;
  30. if (body is Map) {
  31. final data = body['data'];
  32. if (data is Map) {
  33. final records = data['records'];
  34. if (records is List) {
  35. return records.whereType<Map<String, dynamic>>().toList();
  36. }
  37. }
  38. }
  39. return [];
  40. }
  41. /// 获取公开交易员列表(无需登录,支持 traderType / orderBy 过滤)
  42. Future<List<Map<String, dynamic>>> getPublicTraderList({
  43. int page = 1,
  44. int pageSize = 20,
  45. String traderType = '',
  46. String orderBy = '10',
  47. }) async {
  48. final Map<String, dynamic> params = {
  49. 'pageNum': page,
  50. 'pageSize': pageSize,
  51. 'orderBy': int.tryParse(orderBy) ?? 10
  52. };
  53. if (traderType.isNotEmpty) params['traderType'] = int.tryParse(traderType);
  54. final resp = await _dio.get(
  55. 'follow/customer/top-trader-list',
  56. queryParameters: params,
  57. );
  58. final body = resp.data;
  59. if (body is Map) {
  60. final data = body['data'];
  61. if (data is Map) {
  62. final records = data['records'];
  63. if (records is List) {
  64. return records.whereType<Map<String, dynamic>>().toList();
  65. }
  66. }
  67. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  68. }
  69. return [];
  70. }
  71. /// 获取跟单账户权益(balance、availableBalance、currentRevenue、totalRevenue)
  72. Future<Map<String, dynamic>?> getFollowWallet() async {
  73. final resp = await _dio.get('swap/follow-wallet/get');
  74. final body = resp.data;
  75. if (body is Map) {
  76. final data = body['data'];
  77. if (data is Map<String, dynamic>) return data;
  78. }
  79. return null;
  80. }
  81. /// 获取合约账户信息(currentCapital = 合约账户权益,balance = 可用余额)
  82. Future<Map<String, dynamic>?> getContractWallet() async {
  83. final resp = await _dio.get('swap/wallet-new/get');
  84. final body = resp.data;
  85. if (body is Map) {
  86. final data = body['data'];
  87. if (data is Map<String, dynamic>) return data;
  88. }
  89. return null;
  90. }
  91. /// 获取当前用户跟单/交易员信息
  92. /// 关键字段:traderLevel("20"=交易员,其他=跟单员)、id(交易员ID)
  93. Future<Map<String, dynamic>?> getFollowerInfo() async {
  94. final resp = await _dio.get('follow/customer/');
  95. final body = resp.data;
  96. if (body is Map) {
  97. final data = body['data'];
  98. if (data is Map<String, dynamic>) return data;
  99. if (data is Map) return Map<String, dynamic>.from(data);
  100. if (data is List && data.isNotEmpty) {
  101. final first = data.first;
  102. if (first is Map<String, dynamic>) return first;
  103. if (first is Map) return Map<String, dynamic>.from(first);
  104. }
  105. }
  106. if (body is List && body.isNotEmpty) {
  107. final first = body.first;
  108. if (first is Map<String, dynamic>) return first;
  109. if (first is Map) return Map<String, dynamic>.from(first);
  110. }
  111. return null;
  112. }
  113. /// 获取交易员自己的带单统计信息
  114. /// 字段:currentClearedProfit(本期结算收益)、totalFollowProfit(累计分润收益)、
  115. /// following(当前跟单人数)、maxFollow(最大跟单人数)
  116. Future<Map<String, dynamic>?> getTraderInfo(String traderId) async {
  117. final resp = await _dio.put('follow/customer/trader-info/$traderId');
  118. final body = resp.data;
  119. if (body is Map) {
  120. final data = body['data'];
  121. if (data is Map<String, dynamic>) return data;
  122. }
  123. return null;
  124. }
  125. /// 提交交易员申请
  126. Future<bool> applyTrader() async {
  127. await _dio.get('follow/customer/apply-trader');
  128. return true;
  129. }
  130. /// 获取我的跟单用户列表(带单员视角)
  131. Future<List<Map<String, dynamic>>> getMyFollowers(
  132. {int page = 1, int pageSize = 20}) async {
  133. final resp = await _dio.post(
  134. 'follow/follow/followers',
  135. data: {'currentPage': page, 'pageSize': pageSize},
  136. );
  137. final body = resp.data;
  138. if (body is Map) {
  139. final data = body['data'];
  140. if (data is Map) {
  141. final records = data['records'] ?? data['list'];
  142. if (records is List)
  143. return records.whereType<Map<String, dynamic>>().toList();
  144. }
  145. // 有些接口直接 data 就是 list
  146. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  147. }
  148. return [];
  149. }
  150. /// 获取带单仓位(当前/历史)
  151. /// [type] "current" = 当前带单, "history" = 历史带单
  152. Future<List<Map<String, dynamic>>> getTraderOrders({
  153. required String traderId,
  154. required String type,
  155. int page = 1,
  156. int pageSize = 20,
  157. }) async {
  158. final resp = await _dio.post(
  159. 'follow/position/lead',
  160. data: {
  161. 'traderId': traderId,
  162. 'type': type,
  163. 'currentPage': page,
  164. 'pageSize': pageSize,
  165. },
  166. );
  167. final body = resp.data;
  168. if (body is Map) {
  169. final data = body['data'];
  170. if (data is Map) {
  171. final records = data['records'] ?? data['list'];
  172. if (records is List)
  173. return records.whereType<Map<String, dynamic>>().toList();
  174. }
  175. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  176. }
  177. return [];
  178. }
  179. /// 移除跟单用户
  180. Future<bool> removeFollower(String followId) async {
  181. await _dio.put('follow/follow/remove-follow/$followId');
  182. return true;
  183. }
  184. // ── 带单设置 ────────────────────────────────────────────
  185. /// 获取所有可选标签(GET follow/tag/)
  186. Future<List<Map<String, dynamic>>> getAllTags() async {
  187. final resp = await _dio.get('follow/tag/');
  188. final body = resp.data;
  189. if (body is Map) {
  190. final data = body['data'];
  191. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  192. }
  193. return [];
  194. }
  195. /// 获取交易员当前已选标签(GET follow/trader-tag/)
  196. Future<List<Map<String, dynamic>>> getMyTags() async {
  197. final resp = await _dio.get('follow/trader-tag/');
  198. final body = resp.data;
  199. if (body is Map) {
  200. final data = body['data'];
  201. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  202. }
  203. return [];
  204. }
  205. /// 获取交易员可选合约(PUT follow/trader-symbol/{traderId})
  206. Future<List<Map<String, dynamic>>> getTraderSymbols(String traderId) async {
  207. final resp = await _dio.put('follow/trader-symbol/$traderId');
  208. final body = resp.data;
  209. if (body is Map) {
  210. final data = body['data'];
  211. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  212. }
  213. return [];
  214. }
  215. /// 更新昵称 / 签名(POST follow/customer/update)
  216. Future<bool> updateTraderProfile(
  217. {String? nickname, String? description}) async {
  218. final Map<String, dynamic> data = {};
  219. if (nickname != null) data['nickName'] = nickname;
  220. if (description != null) data['description'] = description;
  221. await _dio.post('follow/customer/update', data: data);
  222. return true;
  223. }
  224. /// 更新标签(POST follow/trader-tag/update),传标签 id 数组
  225. Future<bool> updateTraderTags(List<String> tagIds) async {
  226. await _dio.post('follow/trader-tag/update', data: tagIds);
  227. return true;
  228. }
  229. /// 更新带单合约(POST follow/trader-symbol/update),传 symbol 名称数组
  230. Future<bool> updateTraderSymbols(List<String> symbols) async {
  231. await _dio.post('follow/trader-symbol/update', data: symbols);
  232. return true;
  233. }
  234. /// 关注交易员(PUT follow/favorite/{traderId})
  235. /// 拦截器已对 code≠0 抛异常,此处不抛即表示成功
  236. Future<bool> favoriteTrader(String traderId) async {
  237. await _dio.put('follow/favorite/$traderId');
  238. return true;
  239. }
  240. /// 取消关注交易员(PUT follow/favorite/unfavorite/{traderId})
  241. Future<bool> unfavoriteTrader(String traderId) async {
  242. await _dio.put('follow/favorite/unfavorite/$traderId');
  243. return true;
  244. }
  245. /// 收藏的交易员列表(POST follow/favorite/list)
  246. Future<List<Map<String, dynamic>>> getFavoriteList({
  247. int currentPage = 1,
  248. int pageSize = 200,
  249. }) async {
  250. final resp = await _dio.post(
  251. 'follow/favorite/list',
  252. data: {
  253. 'currentPage': currentPage,
  254. 'pageSize': pageSize,
  255. },
  256. );
  257. return _extractRecords(resp.data);
  258. }
  259. /// 开始跟单(POST follow/follow/)
  260. Future<bool> followTrader(Map<String, dynamic> params) async {
  261. await _dio.post('follow/follow/', data: params);
  262. return true;
  263. }
  264. /// 取消跟单(PUT follow/follow/unfollow/{traderId})
  265. Future<bool> unfollowTrader(String traderId) async {
  266. await _dio.put('follow/follow/unfollow/$traderId');
  267. return true;
  268. }
  269. /// 获取邀请链接(uc/member/my-info → promotionPrefix + promotionCode)
  270. Future<String?> getInviteUrl() async {
  271. try {
  272. final resp = await _dio.post('uc/member/my-info');
  273. final data = resp.data is Map ? resp.data['data'] : null;
  274. if (data is Map) {
  275. final prefix = data['promotionPrefix']?.toString() ?? '';
  276. final code = data['promotionCode']?.toString() ?? '';
  277. if (prefix.isNotEmpty || code.isNotEmpty) return '$prefix$code';
  278. }
  279. } catch (_) {}
  280. return null;
  281. }
  282. /// 更新头像(POST follow/customer/update-avatar,multipart/form-data,字段名 "avatar")
  283. /// 返回新头像 URL
  284. Future<String?> updateAvatar(String filePath) async {
  285. final formData = FormData.fromMap({
  286. 'avatar': await MultipartFile.fromFile(filePath, filename: 'avatar.jpg'),
  287. });
  288. final resp =
  289. await _dio.post('follow/customer/update-avatar', data: formData);
  290. final body = resp.data;
  291. if (body is Map) {
  292. final data = body['data'];
  293. if (data != null) return data.toString();
  294. }
  295. return null;
  296. }
  297. /// 获取合约持仓列表(GET swap/wallet-new/get-with-positions),用于与带单仓位交叉匹配保证金比率
  298. Future<List<Map<String, dynamic>>> getFuturesPositions() async {
  299. try {
  300. final data = await FuturesService(_dio).getWithPositions();
  301. final list = data['currentPositionWithCutList'];
  302. if (list is List) return list.whereType<Map<String, dynamic>>().toList();
  303. } catch (_) {}
  304. return [];
  305. }
  306. /// 申请取消交易员资格(GET follow/customer/cancel-trader)
  307. Future<bool> cancelTraderQualification() async {
  308. await _dio.get('follow/customer/cancel-trader');
  309. return true;
  310. }
  311. /// 获取当前带单员的跟单用户总数(GET follow/follow/follower-count)
  312. Future<int> getMyFollowerCount() async {
  313. final resp = await _dio.get('follow/follow/follower-count');
  314. final body = resp.data;
  315. if (body is Map) {
  316. final data = body['data'];
  317. if (data != null) return int.tryParse(data.toString()) ?? 0;
  318. }
  319. if (body != null) return int.tryParse(body.toString()) ?? 0;
  320. return 0;
  321. }
  322. // ── 我的跟单 3 个 Tab ────────────────────────────────────
  323. /// 当前跟单仓位(POST follow/position/current-follow)
  324. Future<List<Map<String, dynamic>>> getCurrentCopyPositions(
  325. {int page = 1, int pageSize = 20}) async {
  326. final resp = await _dio.post(
  327. 'follow/position/current-follow',
  328. data: {'currentPage': '$page', 'pageSize': '$pageSize'},
  329. );
  330. return _extractRecords(resp.data);
  331. }
  332. /// 我的交易员列表(POST follow/follow/following)
  333. Future<List<Map<String, dynamic>>> getFollowingTraders(
  334. {int page = 1, int pageSize = 20}) async {
  335. final resp = await _dio.post(
  336. 'follow/follow/following',
  337. data: {'currentPage': '$page', 'pageSize': '$pageSize'},
  338. );
  339. return _extractRecords(resp.data);
  340. }
  341. /// 历史跟单仓位(POST follow/position/history-follow)
  342. Future<List<Map<String, dynamic>>> getHistoryCopyPositions(
  343. {int page = 1, int pageSize = 20}) async {
  344. final resp = await _dio.post(
  345. 'follow/position/history-follow',
  346. data: {'currentPage': '$page', 'pageSize': '$pageSize'},
  347. );
  348. return _extractRecords(resp.data);
  349. }
  350. /// 手动平仓(POST swap/order/close-all,市价全平)
  351. /// 服务端用 @RequestParam 读取,必须以 application/x-www-form-urlencoded 发送
  352. Future<bool> closeCopyPosition(String positionId) async {
  353. await _dio.post(
  354. 'swap/order/close-all',
  355. data: {'positionId': positionId},
  356. options: Options(contentType: Headers.formUrlEncodedContentType),
  357. );
  358. return true;
  359. }
  360. List<Map<String, dynamic>> _extractRecords(dynamic body) {
  361. if (body is! Map) return [];
  362. final data = body['data'];
  363. if (data is Map) {
  364. final records = data['records'] ?? data['list'];
  365. if (records is List)
  366. return records.whereType<Map<String, dynamic>>().toList();
  367. }
  368. if (data is List) return data.whereType<Map<String, dynamic>>().toList();
  369. return [];
  370. }
  371. }
  372. final copyTradingRepositoryProvider = Provider<CopyTradingRepository>((ref) {
  373. return CopyTradingRepository(ref.read(dioClientProvider));
  374. });