feat: 新增设备绑定、扫码与联系人管理功能
This commit is contained in:
91
lib/features/contacts/data/contact_api.dart
Normal file
91
lib/features/contacts/data/contact_api.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'package:flutter/foundation.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/contacts/domain/contact_models.dart';
|
||||
|
||||
/// 客户端(家属端)联系人接口访问层。
|
||||
///
|
||||
/// 后端接口前缀为 `/api/v1/client/sn/contact`,Dio 在 path 不以 `/` 开头时会
|
||||
/// 直接拼接 baseUrl 的 path 段,故此处使用相对路径 `v1/client/sn/contact/...`。
|
||||
/// [DioClient.get/post/put/delete] 已统一解析为后台返回体中的 `data` 字段;
|
||||
/// 业务失败时抛出 [ApiException],本层将其降级为「空数据/错误提示」以适配家属端展示。
|
||||
class ContactApi {
|
||||
const ContactApi();
|
||||
|
||||
/// 联系人列表(家属端)。
|
||||
Future<List<Contact>> getContacts(String sn) async {
|
||||
try {
|
||||
final data = await DioClient.get(
|
||||
'v1/client/sn/contact/list',
|
||||
queryParameters: {'sn': sn},
|
||||
);
|
||||
return _parseList(data, Contact.fromJson);
|
||||
} on ApiException {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 新增联系人(家属端)。
|
||||
///
|
||||
/// 成功返回 null;失败返回后台错误提示。
|
||||
Future<String?> addContact(String sn, Contact contact) async {
|
||||
try {
|
||||
await DioClient.post(
|
||||
'v1/client/sn/contact/insert',
|
||||
queryParameters: {'sn': sn},
|
||||
data: contact.toFormJson(),
|
||||
);
|
||||
debugPrint('[ContactApi] addContact sn=$sn name=${contact.name} success');
|
||||
return null;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[ContactApi] addContact sn=$sn name=${contact.name} failed: ${e.message}');
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/// 修改联系人(家属端)。
|
||||
///
|
||||
/// 成功返回 null;失败返回后台错误提示。
|
||||
/// 注意:[DioClient.put] 无 queryParameters 参数,故 id/sn 直接拼入路径。
|
||||
Future<String?> updateContact(String sn, Contact contact) async {
|
||||
try {
|
||||
final path =
|
||||
'v1/client/sn/contact/update?id=${contact.id}&sn=${Uri.encodeQueryComponent(sn)}';
|
||||
await DioClient.put(path, data: contact.toFormJson());
|
||||
debugPrint('[ContactApi] updateContact sn=$sn id=${contact.id} success');
|
||||
return null;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[ContactApi] updateContact sn=$sn id=${contact.id} failed: ${e.message}');
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除联系人(家属端)。
|
||||
///
|
||||
/// 成功返回 null;失败返回后台错误提示。
|
||||
Future<String?> deleteContact(String sn, int id) async {
|
||||
try {
|
||||
await DioClient.delete(
|
||||
'v1/client/sn/contact/delete?sn=${Uri.encodeQueryComponent(sn)}&id=$id',
|
||||
);
|
||||
debugPrint('[ContactApi] deleteContact sn=$sn id=$id success');
|
||||
return null;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[ContactApi] deleteContact sn=$sn id=$id failed: ${e.message}');
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static List<T> _parseList<T>(
|
||||
dynamic data,
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
) {
|
||||
if (data is! List) return const [];
|
||||
return data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(fromJson)
|
||||
.toList(growable: false);
|
||||
}
|
||||
}
|
||||
24
lib/features/contacts/data/contact_providers.dart
Normal file
24
lib/features/contacts/data/contact_providers.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/data/contact_api.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/data/contact_repository_impl.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/domain/contact_models.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/domain/contact_repository.dart';
|
||||
import 'package:ttstd_family_care/features/device/data/device_providers.dart';
|
||||
|
||||
/// 联系人仓储 Provider。
|
||||
final contactRepositoryProvider = Provider<ContactRepository>((ref) {
|
||||
const api = ContactApi();
|
||||
return ContactRepositoryImpl(api);
|
||||
});
|
||||
|
||||
/// 当前选中设备的联系人列表。
|
||||
///
|
||||
/// 依赖 [selectedDeviceSnProvider] 选择设备;未绑定设备时返回空列表。
|
||||
/// 通过 [autoDispose] 避免切换设备后残留旧数据。
|
||||
final contactsProvider =
|
||||
FutureProvider.autoDispose<List<Contact>>((ref) async {
|
||||
final sn = ref.watch(selectedDeviceSnProvider);
|
||||
if (sn == null || sn.isEmpty) return const [];
|
||||
final repo = ref.watch(contactRepositoryProvider);
|
||||
return repo.getContacts(sn);
|
||||
});
|
||||
25
lib/features/contacts/data/contact_repository_impl.dart
Normal file
25
lib/features/contacts/data/contact_repository_impl.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:ttstd_family_care/features/contacts/data/contact_api.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/domain/contact_models.dart';
|
||||
import 'package:ttstd_family_care/features/contacts/domain/contact_repository.dart';
|
||||
|
||||
/// [ContactRepository] 的默认实现,委托给 [ContactApi]。
|
||||
class ContactRepositoryImpl implements ContactRepository {
|
||||
const ContactRepositoryImpl(this._api);
|
||||
|
||||
final ContactApi _api;
|
||||
|
||||
@override
|
||||
Future<List<Contact>> getContacts(String sn) => _api.getContacts(sn);
|
||||
|
||||
@override
|
||||
Future<String?> addContact(String sn, Contact contact) =>
|
||||
_api.addContact(sn, contact);
|
||||
|
||||
@override
|
||||
Future<String?> updateContact(String sn, Contact contact) =>
|
||||
_api.updateContact(sn, contact);
|
||||
|
||||
@override
|
||||
Future<String?> deleteContact(String sn, int id) =>
|
||||
_api.deleteContact(sn, id);
|
||||
}
|
||||
Reference in New Issue
Block a user