114 lines
3.6 KiB
Dart
114 lines
3.6 KiB
Dart
import 'package:cupertino_ui/cupertino_ui.dart';
|
||
import 'package:ttstd_family_care/core/network/api_exception.dart';
|
||
import 'package:ttstd_family_care/core/network/dio_client.dart';
|
||
import 'package:ttstd_family_care/features/account/domain/account_models.dart';
|
||
|
||
/// 账号与安全接口访问层。
|
||
///
|
||
/// 后端接口前缀为 `/api/v1/open/account`(与设备接口所在的 `/api/v1/client` 不同)。
|
||
/// [DioClient] 已统一解析后台返回体的 `data` 字段,业务失败时抛出 [ApiException];
|
||
/// 本层将查询类接口的异常降级为空数据,写操作则把错误提示回传给控制器。
|
||
class AccountApi {
|
||
const AccountApi();
|
||
|
||
/// 登录设备(会话)列表。
|
||
Future<List<LoginDevice>> getLoginDevices() async {
|
||
try {
|
||
final data = await DioClient.get('account/login-devices');
|
||
return _parseList(data, LoginDevice.fromJson);
|
||
} on ApiException catch (e) {
|
||
debugPrint('[AccountApi] getLoginDevices failed: ${e.message}');
|
||
return const [];
|
||
}
|
||
}
|
||
|
||
/// 将指定登录设备下线。
|
||
Future<String?> revokeLoginDevice(String id) =>
|
||
_write(() => DioClient.post(
|
||
'account/login-devices/revoke',
|
||
queryParameters: {'id': id},
|
||
));
|
||
|
||
/// 修改密码。
|
||
Future<String?> changePassword({
|
||
required String oldPassword,
|
||
required String newPassword,
|
||
}) =>
|
||
_write(() => DioClient.post(
|
||
'account/password',
|
||
data: {'oldPassword': oldPassword, 'newPassword': newPassword},
|
||
));
|
||
|
||
/// 发送更换手机号验证码到新手机号。
|
||
Future<String?> sendChangeMobileCode(String newMobile) =>
|
||
_write(() => DioClient.post(
|
||
'account/mobile/sms/code',
|
||
queryParameters: {'mobile': newMobile},
|
||
));
|
||
|
||
/// 更换绑定手机号。
|
||
Future<String?> changeMobile({
|
||
required String newMobile,
|
||
required String code,
|
||
required String password,
|
||
}) =>
|
||
_write(() => DioClient.post(
|
||
'account/mobile',
|
||
data: {
|
||
'mobile': newMobile,
|
||
'code': code,
|
||
'password': password,
|
||
},
|
||
));
|
||
|
||
/// 查询微信绑定状态。
|
||
Future<WechatBinding> getWechatBinding() async {
|
||
try {
|
||
final data = await DioClient.get('account/wechat');
|
||
if (data is Map<String, dynamic>) return WechatBinding.fromJson(data);
|
||
return WechatBinding.unbound;
|
||
} on ApiException catch (e) {
|
||
debugPrint('[AccountApi] getWechatBinding failed: ${e.message}');
|
||
return WechatBinding.unbound;
|
||
}
|
||
}
|
||
|
||
/// 绑定微信。
|
||
Future<String?> bindWechat(String authCode) => _write(() => DioClient.post(
|
||
'account/wechat/bind',
|
||
data: {'code': authCode},
|
||
));
|
||
|
||
/// 解绑微信。
|
||
Future<String?> unbindWechat() =>
|
||
_write(() => DioClient.post('account/wechat/unbind'));
|
||
|
||
/// 统一处理写操作:成功返回 null,业务失败返回后台提示文案。
|
||
Future<String?> _write(Future<dynamic> Function() call) async {
|
||
try {
|
||
await call();
|
||
return null;
|
||
} on ApiException catch (e) {
|
||
return e.message;
|
||
}
|
||
}
|
||
|
||
/// 兼容后台可能返回「数组」或「分页对象(含 list/records)」两种结构。
|
||
List<T> _parseList<T>(
|
||
dynamic data,
|
||
T Function(Map<String, dynamic>) fromJson,
|
||
) {
|
||
Iterable<dynamic>? raw;
|
||
if (data is List) {
|
||
raw = data;
|
||
} else if (data is Map<String, dynamic>) {
|
||
raw = (data['list'] ?? data['records']) as Iterable<dynamic>?;
|
||
}
|
||
if (raw == null) return const [];
|
||
return raw
|
||
.whereType<Map<String, dynamic>>()
|
||
.map(fromJson)
|
||
.toList(growable: false);
|
||
}
|
||
}
|