feat: 引入 EF Core Migrations + 后台任务/校验修复 + 前端四态
后端 - 改用 EF Core Migrations(InitialCreate),Program.cs 用 MigrateAsync + AUTO_MIGRATE 开关 + 显式 MigrationsAssembly;移除 EnsureCreated、删除手写 DatabaseSchemaMigrator - 修复后台任务永久卡 Processing:三个队列对超时且达上限的任务原子标记 Failed - 修复报告重试失效:基础设施异常向上抛激活 RetryAsync,停机取消不计失败 - 修复 AI 用药天数 off-by-one(结束日为包含式) - AI 报告日志不再记录 VLM 健康正文 - 新增统一输入校验(ValidationException + 中间件映射 400):血压/心率/血糖/血氧/体重范围、药名/日期/服药时间去重、饮食热量与评分、运动时长上限 - 删除健康数据 AI 录入的影子直写路径,统一走 Service 校验 前端 - 新增 AppErrorState / AppFutureView,用药列表、服药打卡、运动计划、随访列表改为 加载/失败/空/数据 四态,失败可重试 瘦身 - 删除过时的 DataSeeder / DevDataSeeder 及调用 - 删除依赖实时服务的 ai_agent_tests 集成测试
This commit is contained in:
@@ -6,6 +6,8 @@ import '../core/navigation_provider.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
import '../providers/data_providers.dart';
|
||||
import '../widgets/common_widgets.dart';
|
||||
import '../widgets/app_error_state.dart';
|
||||
import '../widgets/app_future_view.dart';
|
||||
|
||||
/// 饮食记录列表(趋势+日历+胶囊详情+侧滑编辑删除)
|
||||
class DietRecordListPage extends ConsumerStatefulWidget {
|
||||
@@ -722,10 +724,11 @@ class _ExercisePlanPageState extends ConsumerState<ExercisePlanPage> {
|
||||
),
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
|
||||
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
child: AppFutureView<List<Map<String, dynamic>>>(
|
||||
future: _future,
|
||||
builder: (ctx, snap) {
|
||||
final plans = snap.data ?? [];
|
||||
onRetry: _load,
|
||||
errorTitle: '运动计划加载失败',
|
||||
onData: (ctx, plans) {
|
||||
if (plans.isEmpty) return _empty(context, '运动计划', '暂无计划,点击右下角新建');
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
@@ -1083,6 +1086,9 @@ class _FollowUpListPageState extends ConsumerState<FollowUpListPage> {
|
||||
child: CircularProgressIndicator(color: AppTheme.primaryLight),
|
||||
);
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return AppErrorState(title: '随访加载失败', onRetry: _load);
|
||||
}
|
||||
final list = snap.data ?? [];
|
||||
if (list.isEmpty) {
|
||||
return Center(
|
||||
@@ -1288,10 +1294,18 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
|
||||
final a = await srv.getHealthArchive();
|
||||
if (a != null && mounted) {
|
||||
_diagnosisCtrl.text = a['diagnosis'] ?? '';
|
||||
final st = a['surgeryType'] as String?;
|
||||
final sd = a['surgeryDate'] as String?;
|
||||
if (st != null && st.isNotEmpty) {
|
||||
_surgeries.add({'type': st, 'date': sd ?? ''});
|
||||
final surgeries = a['surgeries'] as List?;
|
||||
if (surgeries != null && surgeries.isNotEmpty) {
|
||||
_surgeries.addAll(surgeries.whereType<Map>().map((item) => {
|
||||
'type': item['type']?.toString() ?? '',
|
||||
'date': item['date']?.toString() ?? '',
|
||||
}));
|
||||
} else {
|
||||
final st = a['surgeryType'] as String?;
|
||||
final sd = a['surgeryDate'] as String?;
|
||||
if (st != null && st.isNotEmpty) {
|
||||
_surgeries.add({'type': st, 'date': sd ?? ''});
|
||||
}
|
||||
}
|
||||
_allergiesCtrl.text = (a['allergies'] as List?)?.join('、') ?? '';
|
||||
_chronicCtrl.text = (a['chronicDiseases'] as List?)?.join('、') ?? '';
|
||||
@@ -1333,18 +1347,19 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
|
||||
gender: _genderCtrl.text,
|
||||
birthDate: _birthDate,
|
||||
);
|
||||
final surgeryType = _surgeries
|
||||
.map((s) => s['type'] ?? '')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.join('、');
|
||||
final surgeryDate = _surgeries
|
||||
.map((s) => s['date'] ?? '')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.join('、');
|
||||
final surgeries = _surgeries
|
||||
.where((s) => (s['type'] ?? '').trim().isNotEmpty)
|
||||
.map((s) => {
|
||||
'type': s['type']!.trim(),
|
||||
'date': (s['date'] ?? '').trim().isEmpty ? null : s['date']!.trim(),
|
||||
})
|
||||
.toList();
|
||||
final firstSurgery = surgeries.isEmpty ? null : surgeries.first;
|
||||
await srv.updateHealthArchive({
|
||||
'diagnosis': _diagnosisCtrl.text,
|
||||
'surgeryType': surgeryType,
|
||||
'surgeryDate': surgeryDate,
|
||||
'surgeryType': firstSurgery?['type'],
|
||||
'surgeryDate': firstSurgery?['date'],
|
||||
'surgeries': surgeries,
|
||||
'allergies': _allergiesCtrl.text
|
||||
.split('、')
|
||||
.where((s) => s.isNotEmpty)
|
||||
@@ -1359,6 +1374,7 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
|
||||
.toList(),
|
||||
'familyHistory': _familyCtrl.text,
|
||||
});
|
||||
await ref.read(authProvider.notifier).refreshProfile();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
|
||||
Reference in New Issue
Block a user