feat: UI 系统中心化 + 趋势图重构 + 法律文档 H5 上线

- UI 系统: 新增 design_tokens / module_visuals / app_buttons / app_status_badge / app_toast / ai_content 六个共享模块; 28 个页面接入, SnackBar 全部替换为 AppToast
- 趋势图: 直线折线 + 渐变填充 + 阴影; 去网格线; X 轴日+月份分隔; Y 轴整数刻度最多 4 个; 点击数据点/录入记录显示上方浮层, 选中点变实心
- 法律文档 H5: 后端 wwwroot 托管隐私政策/服务协议/关于/个人信息收集清单/第三方 SDK 清单 + 索引页; Program.cs 启用 UseDefaultFiles + UseStaticFiles
- 其他: auth_provider 登录流程调整; api_client IP 适配; 主页智能体栏间距优化
This commit is contained in:
MingNian
2026-07-08 21:25:07 +08:00
parent 7a93237069
commit 1c020b8ae5
45 changed files with 3480 additions and 1000 deletions

View File

@@ -0,0 +1,19 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:health_app/core/api_client.dart';
void main() {
test('AuthExpiredNotifier notifies all current listeners once', () {
final notifier = AuthExpiredNotifier();
var first = 0;
var second = 0;
final removeFirst = notifier.addListener(() => first++);
notifier.addListener(() => second++);
notifier.notify();
removeFirst();
notifier.notify();
expect(first, 1);
expect(second, 2);
});
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:health_app/core/app_router.dart';
import 'package:health_app/core/navigation_provider.dart';
void main() {
testWidgets(
'detail routes show an error instead of throwing when id is missing',
(tester) async {
await tester.pumpWidget(
ProviderScope(
child: Consumer(
builder: (context, ref, _) {
return MaterialApp(
home: buildPage(const RouteInfo('aiAnalysis'), ref),
);
},
),
),
);
expect(tester.takeException(), isNull);
expect(find.text('页面参数错误'), findsOneWidget);
},
);
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:health_app/pages/report/report_pages.dart';
void main() {
test('report polling backs off and stops after the retry budget', () {
expect(reportAnalysisPollDelay(1), const Duration(seconds: 4));
expect(reportAnalysisPollDelay(3), const Duration(seconds: 8));
expect(reportAnalysisPollDelay(6), const Duration(seconds: 12));
expect(reportAnalysisPollDelay(16), isNull);
});
}