feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -19,7 +19,7 @@ class DoctorConsultationsPage extends ConsumerWidget {
|
||||
final list = ref.watch(_consListProvider);
|
||||
return list.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (items) => items.isEmpty
|
||||
? const Center(
|
||||
child: Text('暂无问诊', style: TextStyle(color: AppColors.textHint)),
|
||||
|
||||
@@ -63,7 +63,7 @@ class DoctorDashboardPage extends ConsumerWidget {
|
||||
|
||||
dash.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (data) => _buildContent(context, ref, data),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -82,7 +82,7 @@ class _DoctorFollowUpEditPageState
|
||||
const SizedBox(height: 8),
|
||||
pts.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
error: (_, _) => const Text('加载失败'),
|
||||
data: (list) => _dropdown(list),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -45,10 +45,11 @@ class _DoctorFollowupsPageState extends ConsumerState<DoctorFollowupsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var items = ref.watch(_fupList);
|
||||
if (_filter == 'Upcoming')
|
||||
if (_filter == 'Upcoming') {
|
||||
items = items.where((f) => f['status'] == 'Upcoming').toList();
|
||||
else if (_filter == 'Completed')
|
||||
} else if (_filter == 'Completed') {
|
||||
items = items.where((f) => f['status'] == 'Completed').toList();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
|
||||
@@ -35,7 +35,7 @@ class DoctorPatientDetailPage extends ConsumerWidget {
|
||||
),
|
||||
body: detail.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (data) => data == null
|
||||
? const Center(child: Text('患者不存在'))
|
||||
: _buildBody(data),
|
||||
@@ -50,9 +50,6 @@ class DoctorPatientDetailPage extends ConsumerWidget {
|
||||
final meds = data['medications'] as List? ?? [];
|
||||
final reports = data['reports'] as List? ?? [];
|
||||
final followUps = data['followUps'] as List? ?? [];
|
||||
final trends = data['trendRecords'] as List? ?? [];
|
||||
final diet = data['dietRecords'] as List? ?? [];
|
||||
final exercise = data['exercisePlan'] as Map<String, dynamic>?;
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -159,11 +156,12 @@ class DoctorPatientDetailPage extends ConsumerWidget {
|
||||
...latest.map((r) {
|
||||
final type = r['metricType']?.toString() ?? '';
|
||||
String val = '';
|
||||
if (type == 'BloodPressure')
|
||||
if (type == 'BloodPressure') {
|
||||
val =
|
||||
'${r['systolic'] ?? '--'}/${r['diastolic'] ?? '--'} mmHg';
|
||||
else
|
||||
} else {
|
||||
val = '${r['value'] ?? '--'} ${r['unit'] ?? ''}';
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
|
||||
@@ -51,10 +51,11 @@ class _DoctorPatientsPageState extends ConsumerState<DoctorPatientsPage> {
|
||||
final items =
|
||||
(data['items'] as List?)?.cast<Map<String, dynamic>>() ?? [];
|
||||
setState(() {
|
||||
if (reset)
|
||||
if (reset) {
|
||||
_patients = items;
|
||||
else
|
||||
} else {
|
||||
_patients.addAll(items);
|
||||
}
|
||||
_total = data['total'] ?? 0;
|
||||
_hasMore = _patients.length < _total;
|
||||
_page++;
|
||||
|
||||
@@ -53,7 +53,7 @@ class _DoctorProfileEditPageState extends ConsumerState<DoctorProfileEditPage> {
|
||||
),
|
||||
body: prof.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (d) {
|
||||
if (d != null) {
|
||||
_name.text = d['name'] ?? '';
|
||||
@@ -111,21 +111,23 @@ class _DoctorProfileEditPageState extends ConsumerState<DoctorProfileEditPage> {
|
||||
},
|
||||
);
|
||||
ref.invalidate(_docProfileProvider);
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('保存成功'),
|
||||
backgroundColor: AppColors.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('保存失败'),
|
||||
backgroundColor: AppColors.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
|
||||
@@ -72,18 +72,20 @@ class _DoctorReportDetailPageState
|
||||
);
|
||||
setState(() => _submitted = true);
|
||||
ref.invalidate(_reportDetailProvider(widget.id));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('审核提交成功'),
|
||||
backgroundColor: AppColors.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('提交失败: $e'), backgroundColor: AppColors.error),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _submitting = false);
|
||||
}
|
||||
@@ -110,7 +112,7 @@ class _DoctorReportDetailPageState
|
||||
),
|
||||
body: detail.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (data) => data == null
|
||||
? const Center(child: Text('报告不存在'))
|
||||
: _buildBody(data),
|
||||
@@ -253,7 +255,7 @@ class _DoctorReportDetailPageState
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${ind['value'] ?? ''}',
|
||||
ind['value'] ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -528,11 +530,11 @@ class _DoctorReportDetailPageState
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (severity != null)
|
||||
_InfoRow('严重程度', _severityLabels[severity] ?? severity),
|
||||
_infoRow('严重程度', _severityLabels[severity] ?? severity),
|
||||
if (doctorRec != null && doctorRec.isNotEmpty)
|
||||
_InfoRow('建议', doctorRec),
|
||||
_infoRow('建议', doctorRec),
|
||||
if (doctorComment != null && doctorComment.isNotEmpty)
|
||||
_InfoRow('评语', doctorComment),
|
||||
_infoRow('评语', doctorComment),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -546,8 +548,9 @@ class _DoctorReportDetailPageState
|
||||
if (raw == null || raw.isEmpty) return [];
|
||||
try {
|
||||
final list = jsonDecode(raw);
|
||||
if (list is List)
|
||||
if (list is List) {
|
||||
return list.map((e) => Map<String, String>.from(e as Map)).toList();
|
||||
}
|
||||
} catch (_) {}
|
||||
return [];
|
||||
}
|
||||
@@ -559,7 +562,7 @@ class _DoctorReportDetailPageState
|
||||
_ => AppColors.textHint,
|
||||
};
|
||||
|
||||
Widget _InfoRow(String label, String value) => Padding(
|
||||
Widget _infoRow(String label, String value) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -63,7 +63,7 @@ class _DoctorReportsPageState extends ConsumerState<DoctorReportsPage> {
|
||||
Expanded(
|
||||
child: reports.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => const Center(child: Text('加载失败')),
|
||||
error: (_, _) => const Center(child: Text('加载失败')),
|
||||
data: (items) => items.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
|
||||
Reference in New Issue
Block a user