feat: 新增设备绑定、扫码与联系人管理功能

This commit is contained in:
TongTongStudio
2026-08-21 17:12:17 +08:00
parent 06cdc77172
commit 98de1e1132
49 changed files with 5699 additions and 204 deletions

View 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);
}
}

View 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);
});

View 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);
}