import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/network/dio_client.dart'; import '../services/futures_service.dart'; /// 合约跟单 Repository — 对应 Android FollowDefService + FollowService class CopyTradingRepository { CopyTradingRepository(this._dio); final Dio _dio; /// 获取交易员列表 /// [orderBy] 排序:10=综合, 20=14日收益率, 30=14日胜率, 40=资金实力 /// [traderType] ""=全部, "1"=普通跟单, "2"=无损跟单 Future>> getTraderList({ required String orderBy, String traderType = '', String nickName = '', int page = 1, int pageSize = 20, }) async { final resp = await _dio.post( 'follow/customer/trader-list', data: { 'orderBy': int.tryParse(orderBy) ?? 10, 'traderType': traderType.isEmpty ? null : int.tryParse(traderType), 'nickName': nickName, 'currentPage': '$page', 'pageSize': '$pageSize', }, ); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) { final records = data['records']; if (records is List) { return records.whereType>().toList(); } } } return []; } /// 获取公开交易员列表(无需登录,支持 traderType / orderBy 过滤) Future>> getPublicTraderList({ int page = 1, int pageSize = 20, String traderType = '', String orderBy = '10', }) async { final Map params = { 'pageNum': page, 'pageSize': pageSize, 'orderBy': int.tryParse(orderBy) ?? 10 }; if (traderType.isNotEmpty) params['traderType'] = int.tryParse(traderType); final resp = await _dio.get( 'follow/customer/top-trader-list', queryParameters: params, ); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) { final records = data['records']; if (records is List) { return records.whereType>().toList(); } } if (data is List) return data.whereType>().toList(); } return []; } /// 获取跟单账户权益(balance、availableBalance、currentRevenue、totalRevenue) Future?> getFollowWallet() async { final resp = await _dio.get('swap/follow-wallet/get'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) return data; } return null; } /// 获取合约账户信息(currentCapital = 合约账户权益,balance = 可用余额) Future?> getContractWallet() async { final resp = await _dio.get('swap/wallet-new/get'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) return data; } return null; } /// 获取当前用户跟单/交易员信息 /// 关键字段:traderLevel("20"=交易员,其他=跟单员)、id(交易员ID) Future?> getFollowerInfo() async { final resp = await _dio.get('follow/customer/'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) return data; if (data is Map) return Map.from(data); if (data is List && data.isNotEmpty) { final first = data.first; if (first is Map) return first; if (first is Map) return Map.from(first); } } if (body is List && body.isNotEmpty) { final first = body.first; if (first is Map) return first; if (first is Map) return Map.from(first); } return null; } /// 获取交易员自己的带单统计信息 /// 字段:currentClearedProfit(本期结算收益)、totalFollowProfit(累计分润收益)、 /// following(当前跟单人数)、maxFollow(最大跟单人数) Future?> getTraderInfo(String traderId) async { final resp = await _dio.put('follow/customer/trader-info/$traderId'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) return data; } return null; } /// 提交交易员申请 Future applyTrader() async { await _dio.get('follow/customer/apply-trader'); return true; } /// 获取我的跟单用户列表(带单员视角) Future>> getMyFollowers( {int page = 1, int pageSize = 20}) async { final resp = await _dio.post( 'follow/follow/followers', data: {'currentPage': page, 'pageSize': pageSize}, ); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) { final records = data['records'] ?? data['list']; if (records is List) return records.whereType>().toList(); } // 有些接口直接 data 就是 list if (data is List) return data.whereType>().toList(); } return []; } /// 获取带单仓位(当前/历史) /// [type] "current" = 当前带单, "history" = 历史带单 Future>> getTraderOrders({ required String traderId, required String type, int page = 1, int pageSize = 20, }) async { final resp = await _dio.post( 'follow/position/lead', data: { 'traderId': traderId, 'type': type, 'currentPage': page, 'pageSize': pageSize, }, ); final body = resp.data; if (body is Map) { final data = body['data']; if (data is Map) { final records = data['records'] ?? data['list']; if (records is List) return records.whereType>().toList(); } if (data is List) return data.whereType>().toList(); } return []; } /// 移除跟单用户 Future removeFollower(String followId) async { await _dio.put('follow/follow/remove-follow/$followId'); return true; } // ── 带单设置 ──────────────────────────────────────────── /// 获取所有可选标签(GET follow/tag/) Future>> getAllTags() async { final resp = await _dio.get('follow/tag/'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is List) return data.whereType>().toList(); } return []; } /// 获取交易员当前已选标签(GET follow/trader-tag/) Future>> getMyTags() async { final resp = await _dio.get('follow/trader-tag/'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is List) return data.whereType>().toList(); } return []; } /// 获取交易员可选合约(PUT follow/trader-symbol/{traderId}) Future>> getTraderSymbols(String traderId) async { final resp = await _dio.put('follow/trader-symbol/$traderId'); final body = resp.data; if (body is Map) { final data = body['data']; if (data is List) return data.whereType>().toList(); } return []; } /// 更新昵称 / 签名(POST follow/customer/update) Future updateTraderProfile( {String? nickname, String? description}) async { final Map data = {}; if (nickname != null) data['nickName'] = nickname; if (description != null) data['description'] = description; await _dio.post('follow/customer/update', data: data); return true; } /// 更新标签(POST follow/trader-tag/update),传标签 id 数组 Future updateTraderTags(List tagIds) async { await _dio.post('follow/trader-tag/update', data: tagIds); return true; } /// 更新带单合约(POST follow/trader-symbol/update),传 symbol 名称数组 Future updateTraderSymbols(List symbols) async { await _dio.post('follow/trader-symbol/update', data: symbols); return true; } /// 关注交易员(PUT follow/favorite/{traderId}) /// 拦截器已对 code≠0 抛异常,此处不抛即表示成功 Future favoriteTrader(String traderId) async { await _dio.put('follow/favorite/$traderId'); return true; } /// 取消关注交易员(PUT follow/favorite/unfavorite/{traderId}) Future unfavoriteTrader(String traderId) async { await _dio.put('follow/favorite/unfavorite/$traderId'); return true; } /// 收藏的交易员列表(POST follow/favorite/list) Future>> getFavoriteList({ int currentPage = 1, int pageSize = 200, }) async { final resp = await _dio.post( 'follow/favorite/list', data: { 'currentPage': currentPage, 'pageSize': pageSize, }, ); return _extractRecords(resp.data); } /// 开始跟单(POST follow/follow/) Future followTrader(Map params) async { await _dio.post('follow/follow/', data: params); return true; } /// 取消跟单(PUT follow/follow/unfollow/{traderId}) Future unfollowTrader(String traderId) async { await _dio.put('follow/follow/unfollow/$traderId'); return true; } /// 获取邀请链接(uc/member/my-info → promotionPrefix + promotionCode) Future getInviteUrl() async { try { final resp = await _dio.post('uc/member/my-info'); final data = resp.data is Map ? resp.data['data'] : null; if (data is Map) { final prefix = data['promotionPrefix']?.toString() ?? ''; final code = data['promotionCode']?.toString() ?? ''; if (prefix.isNotEmpty || code.isNotEmpty) return '$prefix$code'; } } catch (_) {} return null; } /// 更新头像(POST follow/customer/update-avatar,multipart/form-data,字段名 "avatar") /// 返回新头像 URL Future updateAvatar(String filePath) async { final formData = FormData.fromMap({ 'avatar': await MultipartFile.fromFile(filePath, filename: 'avatar.jpg'), }); final resp = await _dio.post('follow/customer/update-avatar', data: formData); final body = resp.data; if (body is Map) { final data = body['data']; if (data != null) return data.toString(); } return null; } /// 获取合约持仓列表(GET swap/wallet-new/get-with-positions),用于与带单仓位交叉匹配保证金比率 Future>> getFuturesPositions() async { try { final data = await FuturesService(_dio).getWithPositions(); final list = data['currentPositionWithCutList']; if (list is List) return list.whereType>().toList(); } catch (_) {} return []; } /// 申请取消交易员资格(GET follow/customer/cancel-trader) Future cancelTraderQualification() async { await _dio.get('follow/customer/cancel-trader'); return true; } /// 获取当前带单员的跟单用户总数(GET follow/follow/follower-count) Future getMyFollowerCount() async { final resp = await _dio.get('follow/follow/follower-count'); final body = resp.data; if (body is Map) { final data = body['data']; if (data != null) return int.tryParse(data.toString()) ?? 0; } if (body != null) return int.tryParse(body.toString()) ?? 0; return 0; } // ── 我的跟单 3 个 Tab ──────────────────────────────────── /// 当前跟单仓位(POST follow/position/current-follow) Future>> getCurrentCopyPositions( {int page = 1, int pageSize = 20}) async { final resp = await _dio.post( 'follow/position/current-follow', data: {'currentPage': '$page', 'pageSize': '$pageSize'}, ); return _extractRecords(resp.data); } /// 我的交易员列表(POST follow/follow/following) Future>> getFollowingTraders( {int page = 1, int pageSize = 20}) async { final resp = await _dio.post( 'follow/follow/following', data: {'currentPage': '$page', 'pageSize': '$pageSize'}, ); return _extractRecords(resp.data); } /// 历史跟单仓位(POST follow/position/history-follow) Future>> getHistoryCopyPositions( {int page = 1, int pageSize = 20}) async { final resp = await _dio.post( 'follow/position/history-follow', data: {'currentPage': '$page', 'pageSize': '$pageSize'}, ); return _extractRecords(resp.data); } /// 手动平仓(POST swap/order/close-all,市价全平) /// 服务端用 @RequestParam 读取,必须以 application/x-www-form-urlencoded 发送 Future closeCopyPosition(String positionId) async { await _dio.post( 'swap/order/close-all', data: {'positionId': positionId}, options: Options(contentType: Headers.formUrlEncodedContentType), ); return true; } List> _extractRecords(dynamic body) { if (body is! Map) return []; final data = body['data']; if (data is Map) { final records = data['records'] ?? data['list']; if (records is List) return records.whereType>().toList(); } if (data is List) return data.whereType>().toList(); return []; } } final copyTradingRepositoryProvider = Provider((ref) { return CopyTradingRepository(ref.read(dioClientProvider)); });