From 6e69f1085eaba1c0cabc8534df66df655055d179 Mon Sep 17 00:00:00 2001
From: MingNian <1281442923@qq.com>
Date: Tue, 2 Jun 2026 12:41:06 +0800
Subject: [PATCH] =?UTF-8?q?chore:=20=E5=85=A8=E9=9D=A2=E8=A7=84=E8=8C=83?=
=?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81=EF=BC=8C=E9=81=B5=E5=BE=AA=20CLAUDE?=
=?UTF-8?q?.md=20=E7=BC=96=E7=A0=81=E8=A7=84=E8=8C=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- C# 文件命名改为 snake_case(28 个文件重命名)
- C# 类转换为主构造函数(8 个类)
- 空 catch 添加异常类型(2 处)
- 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi)
- Flutter 移除 go_router,改用 Riverpod 路由栈
- Flutter 移除 flutter_secure_storage,改用 sqflite 持久化
- 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘)
- 后端端口改为 0.0.0.0:5000,支持局域网访问
---
.../{Consultation.cs => consultation.cs} | 0
.../{Conversation.cs => conversation.cs} | 0
.../{DietRecord.cs => diet_record.cs} | 0
.../{ExercisePlan.cs => exercise_plan.cs} | 0
.../{HealthRecord.cs => health_record.cs} | 0
.../Entities/{Medication.cs => medication.cs} | 0
.../Entities/{Report.cs => report.cs} | 0
...SupportEntities.cs => support_entities.cs} | 0
.../Entities/{User.cs => user.cs} | 0
.../Enums/{HealthEnums.cs => health_enums.cs} | 0
.../AI/{AiClients.cs => ai_clients.cs} | 44 ++--
...Client.cs => open_ai_compatible_client.cs} | 31 ++-
.../{PromptManager.cs => prompt_manager.cs} | 2 -
.../{AppDbContext.cs => app_db_context.cs} | 4 +-
.../Data/{DataSeeder.cs => data_seeder.cs} | 2 -
.../{DevDataSeeder.cs => dev_data_seeder.cs} | 2 -
.../src/Health.Infrastructure/GlobalUsings.cs | 4 +
.../{JwtProvider.cs => jwt_provider.cs} | 16 +-
.../{SmsService.cs => sms_service.cs} | 0
.../{CleanupService.cs => cleanup_service.cs} | 15 +-
...vice.cs => medication_reminder_service.cs} | 17 +-
...iChatEndpoints.cs => ai_chat_endpoints.cs} | 9 +-
.../{AuthEndpoints.cs => auth_endpoints.cs} | 4 -
...HealthEndpoints.cs => health_endpoints.cs} | 5 -
...ingEndpoints.cs => remaining_endpoints.cs} | 5 -
.../{UserEndpoints.cs => user_endpoints.cs} | 3 -
backend/src/Health.WebApi/GlobalUsings.cs | 5 +
...nMiddleware.cs => exception_middleware.cs} | 13 +-
.../Properties/launchSettings.json | 4 +-
.../{AuthTests.cs => auth_tests.cs} | 0
.../{EntityTests.cs => entity_tests.cs} | 0
.../{UnitTest1.cs => unit_test1.cs} | 0
health_app/android/settings.gradle.kts | 2 +-
health_app/lib/app.dart | 29 ++-
health_app/lib/core/api_client.dart | 20 +-
health_app/lib/core/app_router.dart | 95 ++++----
health_app/lib/core/local_database.dart | 58 +++++
health_app/lib/core/navigation_provider.dart | 56 +++++
health_app/lib/core/secure_storage.dart | 35 ---
health_app/lib/pages/auth/login_page.dart | 6 +-
.../medication/medication_list_page.dart | 6 +-
.../lib/pages/profile/profile_page.dart | 12 +-
.../lib/pages/settings/settings_pages.dart | 12 +-
health_app/lib/providers/auth_provider.dart | 18 +-
health_app/lib/widgets/health_drawer.dart | 16 +-
health_app/pubspec.lock | 212 +++++-------------
health_app/pubspec.yaml | 8 +-
47 files changed, 342 insertions(+), 428 deletions(-)
rename backend/src/Health.Domain/Entities/{Consultation.cs => consultation.cs} (100%)
rename backend/src/Health.Domain/Entities/{Conversation.cs => conversation.cs} (100%)
rename backend/src/Health.Domain/Entities/{DietRecord.cs => diet_record.cs} (100%)
rename backend/src/Health.Domain/Entities/{ExercisePlan.cs => exercise_plan.cs} (100%)
rename backend/src/Health.Domain/Entities/{HealthRecord.cs => health_record.cs} (100%)
rename backend/src/Health.Domain/Entities/{Medication.cs => medication.cs} (100%)
rename backend/src/Health.Domain/Entities/{Report.cs => report.cs} (100%)
rename backend/src/Health.Domain/Entities/{SupportEntities.cs => support_entities.cs} (100%)
rename backend/src/Health.Domain/Entities/{User.cs => user.cs} (100%)
rename backend/src/Health.Domain/Enums/{HealthEnums.cs => health_enums.cs} (100%)
rename backend/src/Health.Infrastructure/AI/{AiClients.cs => ai_clients.cs} (81%)
rename backend/src/Health.Infrastructure/AI/{OpenAiCompatibleClient.cs => open_ai_compatible_client.cs} (89%)
rename backend/src/Health.Infrastructure/AI/{PromptManager.cs => prompt_manager.cs} (99%)
rename backend/src/Health.Infrastructure/Data/{AppDbContext.cs => app_db_context.cs} (96%)
rename backend/src/Health.Infrastructure/Data/{DataSeeder.cs => data_seeder.cs} (97%)
rename backend/src/Health.Infrastructure/Data/{DevDataSeeder.cs => dev_data_seeder.cs} (99%)
create mode 100644 backend/src/Health.Infrastructure/GlobalUsings.cs
rename backend/src/Health.Infrastructure/Services/{JwtProvider.cs => jwt_provider.cs} (81%)
rename backend/src/Health.Infrastructure/Services/{SmsService.cs => sms_service.cs} (100%)
rename backend/src/Health.WebApi/BackgroundServices/{CleanupService.cs => cleanup_service.cs} (80%)
rename backend/src/Health.WebApi/BackgroundServices/{MedicationReminderService.cs => medication_reminder_service.cs} (80%)
rename backend/src/Health.WebApi/Endpoints/{AiChatEndpoints.cs => ai_chat_endpoints.cs} (99%)
rename backend/src/Health.WebApi/Endpoints/{AuthEndpoints.cs => auth_endpoints.cs} (98%)
rename backend/src/Health.WebApi/Endpoints/{HealthEndpoints.cs => health_endpoints.cs} (97%)
rename backend/src/Health.WebApi/Endpoints/{RemainingEndpoints.cs => remaining_endpoints.cs} (99%)
rename backend/src/Health.WebApi/Endpoints/{UserEndpoints.cs => user_endpoints.cs} (98%)
create mode 100644 backend/src/Health.WebApi/GlobalUsings.cs
rename backend/src/Health.WebApi/Middleware/{ExceptionMiddleware.cs => exception_middleware.cs} (74%)
rename backend/tests/Health.Tests/{AuthTests.cs => auth_tests.cs} (100%)
rename backend/tests/Health.Tests/{EntityTests.cs => entity_tests.cs} (100%)
rename backend/tests/Health.Tests/{UnitTest1.cs => unit_test1.cs} (100%)
create mode 100644 health_app/lib/core/local_database.dart
create mode 100644 health_app/lib/core/navigation_provider.dart
delete mode 100644 health_app/lib/core/secure_storage.dart
diff --git a/backend/src/Health.Domain/Entities/Consultation.cs b/backend/src/Health.Domain/Entities/consultation.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/Consultation.cs
rename to backend/src/Health.Domain/Entities/consultation.cs
diff --git a/backend/src/Health.Domain/Entities/Conversation.cs b/backend/src/Health.Domain/Entities/conversation.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/Conversation.cs
rename to backend/src/Health.Domain/Entities/conversation.cs
diff --git a/backend/src/Health.Domain/Entities/DietRecord.cs b/backend/src/Health.Domain/Entities/diet_record.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/DietRecord.cs
rename to backend/src/Health.Domain/Entities/diet_record.cs
diff --git a/backend/src/Health.Domain/Entities/ExercisePlan.cs b/backend/src/Health.Domain/Entities/exercise_plan.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/ExercisePlan.cs
rename to backend/src/Health.Domain/Entities/exercise_plan.cs
diff --git a/backend/src/Health.Domain/Entities/HealthRecord.cs b/backend/src/Health.Domain/Entities/health_record.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/HealthRecord.cs
rename to backend/src/Health.Domain/Entities/health_record.cs
diff --git a/backend/src/Health.Domain/Entities/Medication.cs b/backend/src/Health.Domain/Entities/medication.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/Medication.cs
rename to backend/src/Health.Domain/Entities/medication.cs
diff --git a/backend/src/Health.Domain/Entities/Report.cs b/backend/src/Health.Domain/Entities/report.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/Report.cs
rename to backend/src/Health.Domain/Entities/report.cs
diff --git a/backend/src/Health.Domain/Entities/SupportEntities.cs b/backend/src/Health.Domain/Entities/support_entities.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/SupportEntities.cs
rename to backend/src/Health.Domain/Entities/support_entities.cs
diff --git a/backend/src/Health.Domain/Entities/User.cs b/backend/src/Health.Domain/Entities/user.cs
similarity index 100%
rename from backend/src/Health.Domain/Entities/User.cs
rename to backend/src/Health.Domain/Entities/user.cs
diff --git a/backend/src/Health.Domain/Enums/HealthEnums.cs b/backend/src/Health.Domain/Enums/health_enums.cs
similarity index 100%
rename from backend/src/Health.Domain/Enums/HealthEnums.cs
rename to backend/src/Health.Domain/Enums/health_enums.cs
diff --git a/backend/src/Health.Infrastructure/AI/AiClients.cs b/backend/src/Health.Infrastructure/AI/ai_clients.cs
similarity index 81%
rename from backend/src/Health.Infrastructure/AI/AiClients.cs
rename to backend/src/Health.Infrastructure/AI/ai_clients.cs
index 0180f3d..dad8f3d 100644
--- a/backend/src/Health.Infrastructure/AI/AiClients.cs
+++ b/backend/src/Health.Infrastructure/AI/ai_clients.cs
@@ -1,29 +1,20 @@
using Microsoft.Extensions.Configuration;
using System.Net.Http.Headers;
-using System.Text;
-using System.Text.Json;
namespace Health.Infrastructure.AI;
///
/// DeepSeek LLM 客户端(对话 + Tool Calling)
///
-public sealed class DeepSeekClient
+public sealed class DeepSeekClient(HttpClient http, IConfiguration config)
{
- private readonly HttpClient _http;
- private readonly string _model;
- private readonly JsonSerializerOptions _jsonOptions;
-
- public DeepSeekClient(HttpClient http, IConfiguration config)
+ private readonly HttpClient _http = http;
+ private readonly string _model = config["DEEPSEEK_MODEL"] ?? "deepseek-chat";
+ private readonly JsonSerializerOptions _jsonOptions = new()
{
- _http = http;
- _model = config["DEEPSEEK_MODEL"] ?? "deepseek-chat";
- _jsonOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- PropertyNameCaseInsensitive = true
- };
- }
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ PropertyNameCaseInsensitive = true
+ };
///
/// 流式 Chat Completions
@@ -96,22 +87,15 @@ public sealed class DeepSeekClient
///
/// 千问 VL 视觉客户端(食物识别 + 报告解读)
///
-public sealed class QwenVisionClient
+public sealed class QwenVisionClient(HttpClient http, IConfiguration config)
{
- private readonly HttpClient _http;
- private readonly string _model;
- private readonly JsonSerializerOptions _jsonOptions;
-
- public QwenVisionClient(HttpClient http, IConfiguration config)
+ private readonly HttpClient _http = http;
+ private readonly string _model = config["QWEN_VISION_MODEL"] ?? "qwen-vl-max";
+ private readonly JsonSerializerOptions _jsonOptions = new()
{
- _http = http;
- _model = config["QWEN_VISION_MODEL"] ?? "qwen-vl-max";
- _jsonOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- PropertyNameCaseInsensitive = true
- };
- }
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ PropertyNameCaseInsensitive = true
+ };
public async Task VisionAsync(
string systemPrompt,
diff --git a/backend/src/Health.Infrastructure/AI/OpenAiCompatibleClient.cs b/backend/src/Health.Infrastructure/AI/open_ai_compatible_client.cs
similarity index 89%
rename from backend/src/Health.Infrastructure/AI/OpenAiCompatibleClient.cs
rename to backend/src/Health.Infrastructure/AI/open_ai_compatible_client.cs
index dd8f479..6f4dc21 100644
--- a/backend/src/Health.Infrastructure/AI/OpenAiCompatibleClient.cs
+++ b/backend/src/Health.Infrastructure/AI/open_ai_compatible_client.cs
@@ -1,33 +1,30 @@
using System.Net.Http.Headers;
-using System.Text;
-using System.Text.Json;
namespace Health.Infrastructure.AI;
///
/// OpenAI 兼容协议 HTTP 客户端,统一调用 DeepSeek / 千问 VL
///
-public sealed class OpenAiCompatibleClient
+public sealed class OpenAiCompatibleClient(string baseUrl, string apiKey, string model)
{
- private readonly HttpClient _http;
- private readonly string _model;
- private readonly JsonSerializerOptions _jsonOptions;
-
- public OpenAiCompatibleClient(string baseUrl, string apiKey, string model)
+ private readonly HttpClient _http = CreateHttpClient(baseUrl, apiKey);
+ private readonly string _model = model;
+ private readonly JsonSerializerOptions _jsonOptions = new()
{
- _http = new HttpClient
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ PropertyNameCaseInsensitive = true
+ };
+
+ private static HttpClient CreateHttpClient(string baseUrl, string apiKey)
+ {
+ var client = new HttpClient
{
BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"),
Timeout = TimeSpan.FromSeconds(60)
};
- _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
- _http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- _model = model;
- _jsonOptions = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- PropertyNameCaseInsensitive = true
- };
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ return client;
}
///
diff --git a/backend/src/Health.Infrastructure/AI/PromptManager.cs b/backend/src/Health.Infrastructure/AI/prompt_manager.cs
similarity index 99%
rename from backend/src/Health.Infrastructure/AI/PromptManager.cs
rename to backend/src/Health.Infrastructure/AI/prompt_manager.cs
index cf27012..26f7217 100644
--- a/backend/src/Health.Infrastructure/AI/PromptManager.cs
+++ b/backend/src/Health.Infrastructure/AI/prompt_manager.cs
@@ -1,5 +1,3 @@
-using Health.Domain.Enums;
-
namespace Health.Infrastructure.AI;
///
diff --git a/backend/src/Health.Infrastructure/Data/AppDbContext.cs b/backend/src/Health.Infrastructure/Data/app_db_context.cs
similarity index 96%
rename from backend/src/Health.Infrastructure/Data/AppDbContext.cs
rename to backend/src/Health.Infrastructure/Data/app_db_context.cs
index fa93333..09d8420 100644
--- a/backend/src/Health.Infrastructure/Data/AppDbContext.cs
+++ b/backend/src/Health.Infrastructure/Data/app_db_context.cs
@@ -1,4 +1,3 @@
-using Health.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -7,9 +6,8 @@ namespace Health.Infrastructure.Data;
///
/// 应用程序数据库上下文
///
-public sealed class AppDbContext : DbContext
+public sealed class AppDbContext(DbContextOptions options) : DbContext(options)
{
- public AppDbContext(DbContextOptions options) : base(options) { }
// 核心业务表
public DbSet Users => Set();
diff --git a/backend/src/Health.Infrastructure/Data/DataSeeder.cs b/backend/src/Health.Infrastructure/Data/data_seeder.cs
similarity index 97%
rename from backend/src/Health.Infrastructure/Data/DataSeeder.cs
rename to backend/src/Health.Infrastructure/Data/data_seeder.cs
index 2a07977..23215b3 100644
--- a/backend/src/Health.Infrastructure/Data/DataSeeder.cs
+++ b/backend/src/Health.Infrastructure/Data/data_seeder.cs
@@ -1,5 +1,3 @@
-using Health.Domain.Entities;
-
namespace Health.Infrastructure.Data;
///
diff --git a/backend/src/Health.Infrastructure/Data/DevDataSeeder.cs b/backend/src/Health.Infrastructure/Data/dev_data_seeder.cs
similarity index 99%
rename from backend/src/Health.Infrastructure/Data/DevDataSeeder.cs
rename to backend/src/Health.Infrastructure/Data/dev_data_seeder.cs
index 3ffb16f..720fa25 100644
--- a/backend/src/Health.Infrastructure/Data/DevDataSeeder.cs
+++ b/backend/src/Health.Infrastructure/Data/dev_data_seeder.cs
@@ -1,5 +1,3 @@
-using Health.Domain.Entities;
-using Health.Domain.Enums;
using Microsoft.Extensions.Configuration;
namespace Health.Infrastructure.Data;
diff --git a/backend/src/Health.Infrastructure/GlobalUsings.cs b/backend/src/Health.Infrastructure/GlobalUsings.cs
new file mode 100644
index 0000000..01a2b12
--- /dev/null
+++ b/backend/src/Health.Infrastructure/GlobalUsings.cs
@@ -0,0 +1,4 @@
+global using Health.Domain.Entities;
+global using Health.Domain.Enums;
+global using System.Text;
+global using System.Text.Json;
diff --git a/backend/src/Health.Infrastructure/Services/JwtProvider.cs b/backend/src/Health.Infrastructure/Services/jwt_provider.cs
similarity index 81%
rename from backend/src/Health.Infrastructure/Services/JwtProvider.cs
rename to backend/src/Health.Infrastructure/Services/jwt_provider.cs
index 3896e3f..5000567 100644
--- a/backend/src/Health.Infrastructure/Services/JwtProvider.cs
+++ b/backend/src/Health.Infrastructure/Services/jwt_provider.cs
@@ -3,25 +3,17 @@ using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
-using System.Text;
namespace Health.Infrastructure.Services;
///
/// JWT Token 生成与验证服务
///
-public sealed class JwtProvider
+public sealed class JwtProvider(IConfiguration configuration)
{
- private readonly string _secret;
- private readonly string _issuer;
- private readonly string _audience;
-
- public JwtProvider(IConfiguration configuration)
- {
- _secret = configuration["JWT_SECRET"] ?? "dev-secret-key-change-in-production-min-32-chars!!";
- _issuer = configuration["JWT_ISSUER"] ?? "health-manager";
- _audience = configuration["JWT_AUDIENCE"] ?? "health-manager-app";
- }
+ private readonly string _secret = configuration["JWT_SECRET"] ?? "dev-secret-key-change-in-production-min-32-chars!!";
+ private readonly string _issuer = configuration["JWT_ISSUER"] ?? "health-manager";
+ private readonly string _audience = configuration["JWT_AUDIENCE"] ?? "health-manager-app";
///
/// 生成 access_token(30 分钟有效)
diff --git a/backend/src/Health.Infrastructure/Services/SmsService.cs b/backend/src/Health.Infrastructure/Services/sms_service.cs
similarity index 100%
rename from backend/src/Health.Infrastructure/Services/SmsService.cs
rename to backend/src/Health.Infrastructure/Services/sms_service.cs
diff --git a/backend/src/Health.WebApi/BackgroundServices/CleanupService.cs b/backend/src/Health.WebApi/BackgroundServices/cleanup_service.cs
similarity index 80%
rename from backend/src/Health.WebApi/BackgroundServices/CleanupService.cs
rename to backend/src/Health.WebApi/BackgroundServices/cleanup_service.cs
index 783b44b..4694329 100644
--- a/backend/src/Health.WebApi/BackgroundServices/CleanupService.cs
+++ b/backend/src/Health.WebApi/BackgroundServices/cleanup_service.cs
@@ -1,21 +1,12 @@
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-
namespace Health.WebApi.BackgroundServices;
///
/// 数据清理后台服务(每小时检查一次)
///
-public sealed class CleanupService : BackgroundService
+public sealed class CleanupService(IServiceScopeFactory scopeFactory, ILogger logger) : BackgroundService
{
- private readonly IServiceScopeFactory _scopeFactory;
- private readonly ILogger _logger;
-
- public CleanupService(IServiceScopeFactory scopeFactory, ILogger logger)
- {
- _scopeFactory = scopeFactory;
- _logger = logger;
- }
+ private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
+ private readonly ILogger _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
diff --git a/backend/src/Health.WebApi/BackgroundServices/MedicationReminderService.cs b/backend/src/Health.WebApi/BackgroundServices/medication_reminder_service.cs
similarity index 80%
rename from backend/src/Health.WebApi/BackgroundServices/MedicationReminderService.cs
rename to backend/src/Health.WebApi/BackgroundServices/medication_reminder_service.cs
index 6db9727..6e0c0fe 100644
--- a/backend/src/Health.WebApi/BackgroundServices/MedicationReminderService.cs
+++ b/backend/src/Health.WebApi/BackgroundServices/medication_reminder_service.cs
@@ -1,23 +1,12 @@
-using Health.Domain.Entities;
-using Health.Domain.Enums;
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-
namespace Health.WebApi.BackgroundServices;
///
/// 用药提醒定时扫描服务(每分钟检查一次)
///
-public sealed class MedicationReminderService : BackgroundService
+public sealed class MedicationReminderService(IServiceScopeFactory scopeFactory, ILogger logger) : BackgroundService
{
- private readonly IServiceScopeFactory _scopeFactory;
- private readonly ILogger _logger;
-
- public MedicationReminderService(IServiceScopeFactory scopeFactory, ILogger logger)
- {
- _scopeFactory = scopeFactory;
- _logger = logger;
- }
+ private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
+ private readonly ILogger _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
diff --git a/backend/src/Health.WebApi/Endpoints/AiChatEndpoints.cs b/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs
similarity index 99%
rename from backend/src/Health.WebApi/Endpoints/AiChatEndpoints.cs
rename to backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs
index b8a4f1d..fe45e24 100644
--- a/backend/src/Health.WebApi/Endpoints/AiChatEndpoints.cs
+++ b/backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs
@@ -1,9 +1,4 @@
-using System.Text.Json;
-using Health.Domain.Entities;
-using Health.Domain.Enums;
using Health.Infrastructure.AI;
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
namespace Health.WebApi.Endpoints;
@@ -135,7 +130,7 @@ public static class AiChatEndpoints
await SseWriteAsync(http, new { action = "answer", data = content }, ct);
}
}
- catch { /* 跳过解析失败的 chunk */ }
+ catch (JsonException) { /* 跳过解析失败的 chunk */ }
}
completedNormally = true;
break;
@@ -311,7 +306,7 @@ public static class AiChatEndpoints
var sub = jwt.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
return sub != null && Guid.TryParse(sub, out var id) ? id : null;
}
- catch { return null; }
+ catch (Exception) { return null; }
}
private static List GetToolsForAgent(AgentType agentType) => agentType switch
diff --git a/backend/src/Health.WebApi/Endpoints/AuthEndpoints.cs b/backend/src/Health.WebApi/Endpoints/auth_endpoints.cs
similarity index 98%
rename from backend/src/Health.WebApi/Endpoints/AuthEndpoints.cs
rename to backend/src/Health.WebApi/Endpoints/auth_endpoints.cs
index b81684f..c97f049 100644
--- a/backend/src/Health.WebApi/Endpoints/AuthEndpoints.cs
+++ b/backend/src/Health.WebApi/Endpoints/auth_endpoints.cs
@@ -1,8 +1,4 @@
-using System.Text.Json;
-using Health.Domain.Entities;
-using Health.Infrastructure.Data;
using Health.Infrastructure.Services;
-using Microsoft.EntityFrameworkCore;
namespace Health.WebApi.Endpoints;
diff --git a/backend/src/Health.WebApi/Endpoints/HealthEndpoints.cs b/backend/src/Health.WebApi/Endpoints/health_endpoints.cs
similarity index 97%
rename from backend/src/Health.WebApi/Endpoints/HealthEndpoints.cs
rename to backend/src/Health.WebApi/Endpoints/health_endpoints.cs
index ca52c7e..f0ab047 100644
--- a/backend/src/Health.WebApi/Endpoints/HealthEndpoints.cs
+++ b/backend/src/Health.WebApi/Endpoints/health_endpoints.cs
@@ -1,8 +1,3 @@
-using Health.Domain.Entities;
-using Health.Domain.Enums;
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-
namespace Health.WebApi.Endpoints;
///
diff --git a/backend/src/Health.WebApi/Endpoints/RemainingEndpoints.cs b/backend/src/Health.WebApi/Endpoints/remaining_endpoints.cs
similarity index 99%
rename from backend/src/Health.WebApi/Endpoints/RemainingEndpoints.cs
rename to backend/src/Health.WebApi/Endpoints/remaining_endpoints.cs
index 70fcb6b..68c295c 100644
--- a/backend/src/Health.WebApi/Endpoints/RemainingEndpoints.cs
+++ b/backend/src/Health.WebApi/Endpoints/remaining_endpoints.cs
@@ -1,8 +1,3 @@
-using Health.Domain.Entities;
-using Health.Domain.Enums;
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-
namespace Health.WebApi.Endpoints;
///
diff --git a/backend/src/Health.WebApi/Endpoints/UserEndpoints.cs b/backend/src/Health.WebApi/Endpoints/user_endpoints.cs
similarity index 98%
rename from backend/src/Health.WebApi/Endpoints/UserEndpoints.cs
rename to backend/src/Health.WebApi/Endpoints/user_endpoints.cs
index 24ac71b..4c09227 100644
--- a/backend/src/Health.WebApi/Endpoints/UserEndpoints.cs
+++ b/backend/src/Health.WebApi/Endpoints/user_endpoints.cs
@@ -1,6 +1,3 @@
-using Health.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-
namespace Health.WebApi.Endpoints;
///
diff --git a/backend/src/Health.WebApi/GlobalUsings.cs b/backend/src/Health.WebApi/GlobalUsings.cs
new file mode 100644
index 0000000..ee3bdb6
--- /dev/null
+++ b/backend/src/Health.WebApi/GlobalUsings.cs
@@ -0,0 +1,5 @@
+global using Health.Domain.Entities;
+global using Health.Domain.Enums;
+global using Health.Infrastructure.Data;
+global using Microsoft.EntityFrameworkCore;
+global using System.Text.Json;
diff --git a/backend/src/Health.WebApi/Middleware/ExceptionMiddleware.cs b/backend/src/Health.WebApi/Middleware/exception_middleware.cs
similarity index 74%
rename from backend/src/Health.WebApi/Middleware/ExceptionMiddleware.cs
rename to backend/src/Health.WebApi/Middleware/exception_middleware.cs
index 59aa416..e8b5ba9 100644
--- a/backend/src/Health.WebApi/Middleware/ExceptionMiddleware.cs
+++ b/backend/src/Health.WebApi/Middleware/exception_middleware.cs
@@ -1,21 +1,14 @@
using System.Net;
-using System.Text.Json;
namespace Health.WebApi.Middleware;
///
/// 全局异常处理中间件——统一返回 {code, data, message} 格式
///
-public sealed class ExceptionMiddleware
+public sealed class ExceptionMiddleware(RequestDelegate next, ILogger logger)
{
- private readonly RequestDelegate _next;
- private readonly ILogger _logger;
-
- public ExceptionMiddleware(RequestDelegate next, ILogger logger)
- {
- _next = next;
- _logger = logger;
- }
+ private readonly RequestDelegate _next = next;
+ private readonly ILogger _logger = logger;
public async Task InvokeAsync(HttpContext context)
{
diff --git a/backend/src/Health.WebApi/Properties/launchSettings.json b/backend/src/Health.WebApi/Properties/launchSettings.json
index 76eddb6..a88d34c 100644
--- a/backend/src/Health.WebApi/Properties/launchSettings.json
+++ b/backend/src/Health.WebApi/Properties/launchSettings.json
@@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
- "applicationUrl": "http://localhost:5277",
+ "applicationUrl": "http://0.0.0.0:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -14,7 +14,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
- "applicationUrl": "https://localhost:7102;http://localhost:5277",
+ "applicationUrl": "https://localhost:7102;http://0.0.0.0:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/backend/tests/Health.Tests/AuthTests.cs b/backend/tests/Health.Tests/auth_tests.cs
similarity index 100%
rename from backend/tests/Health.Tests/AuthTests.cs
rename to backend/tests/Health.Tests/auth_tests.cs
diff --git a/backend/tests/Health.Tests/EntityTests.cs b/backend/tests/Health.Tests/entity_tests.cs
similarity index 100%
rename from backend/tests/Health.Tests/EntityTests.cs
rename to backend/tests/Health.Tests/entity_tests.cs
diff --git a/backend/tests/Health.Tests/UnitTest1.cs b/backend/tests/Health.Tests/unit_test1.cs
similarity index 100%
rename from backend/tests/Health.Tests/UnitTest1.cs
rename to backend/tests/Health.Tests/unit_test1.cs
diff --git a/health_app/android/settings.gradle.kts b/health_app/android/settings.gradle.kts
index 2a693b9..638b5a9 100644
--- a/health_app/android/settings.gradle.kts
+++ b/health_app/android/settings.gradle.kts
@@ -1,5 +1,5 @@
pluginManagement {
- val flutterSdkPath = "C:/flutter_sdk"
+ val flutterSdkPath = "D:/flutter"
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
diff --git a/health_app/lib/app.dart b/health_app/lib/app.dart
index 0d7d077..55a3bec 100644
--- a/health_app/lib/app.dart
+++ b/health_app/lib/app.dart
@@ -1,18 +1,39 @@
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'core/app_router.dart';
import 'core/app_theme.dart';
+import 'core/navigation_provider.dart';
/// 健康管家 App 根组件
-class HealthApp extends StatelessWidget {
+class HealthApp extends ConsumerWidget {
const HealthApp({super.key});
@override
- Widget build(BuildContext context) {
- return MaterialApp.router(
+ Widget build(BuildContext context, WidgetRef ref) {
+ return const MaterialApp(
title: '健康管家',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
- routerConfig: AppRouter.router,
+ home: _RootNavigator(),
+ );
+ }
+}
+
+/// 根导航——根据 Riverpod 路由状态切换页面
+class _RootNavigator extends ConsumerWidget {
+ const _RootNavigator();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final stack = ref.watch(routeStackProvider);
+ final current = stack.last;
+
+ return PopScope(
+ canPop: stack.length <= 1,
+ onPopInvokedWithResult: (didPop, result) {
+ if (!didPop) popRoute(ref);
+ },
+ child: buildPage(current),
);
}
}
diff --git a/health_app/lib/core/api_client.dart b/health_app/lib/core/api_client.dart
index beb228d..152b106 100644
--- a/health_app/lib/core/api_client.dart
+++ b/health_app/lib/core/api_client.dart
@@ -1,16 +1,16 @@
import 'package:dio/dio.dart';
-import 'secure_storage.dart';
+import 'local_database.dart';
/// API 基础地址
-const String baseUrl = 'http://10.4.172.93:5000';
+const String baseUrl = 'http://10.4.185.103:5000';
/// Dio HTTP 客户端封装——带 token 注入、401 自动刷新
class ApiClient {
final Dio _dio;
- final SecureStorage _storage;
+ final LocalDatabase _db;
- ApiClient({required SecureStorage storage})
- : _storage = storage,
+ ApiClient({required LocalDatabase db})
+ : _db = db,
_dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 15),
@@ -23,16 +23,16 @@ class ApiClient {
Dio get dio => _dio;
- Future get accessToken => _storage.readAccessToken();
- Future get refreshToken => _storage.readRefreshToken();
+ Future get accessToken => _db.read('access_token');
+ Future get refreshToken => _db.read('refresh_token');
Future saveTokens(String access, String refresh) async {
- await _storage.writeAccessToken(access);
- await _storage.writeRefreshToken(refresh);
+ await _db.write('access_token', access);
+ await _db.write('refresh_token', refresh);
}
Future clearTokens() async {
- await _storage.deleteAll();
+ await _db.deleteAll();
}
/// 带 token 的 GET 请求
diff --git a/health_app/lib/core/app_router.dart b/health_app/lib/core/app_router.dart
index 3126c2b..35f8b30 100644
--- a/health_app/lib/core/app_router.dart
+++ b/health_app/lib/core/app_router.dart
@@ -1,4 +1,5 @@
-import 'package:go_router/go_router.dart';
+import 'package:flutter/material.dart';
+import 'navigation_provider.dart';
import '../pages/auth/login_page.dart';
import '../pages/home/home_page.dart';
import '../pages/chart/trend_page.dart';
@@ -9,49 +10,51 @@ import '../pages/settings/settings_pages.dart';
import '../pages/profile/profile_page.dart';
import '../pages/remaining_pages.dart';
-/// 应用路由配置
-class AppRouter {
- AppRouter._();
-
- static final GoRouter router = GoRouter(
- initialLocation: '/login',
- routes: [
- GoRoute(path: '/login', builder: (_, _) => const LoginPage()),
- GoRoute(path: '/home', builder: (_, _) => const HomePage()),
- GoRoute(path: '/trend/:type', builder: (_, state) => TrendPage(metricType: state.pathParameters['type']!)),
- GoRoute(path: '/calendar', builder: (_, _) => const HealthCalendarPage()),
-
- // 用药
- GoRoute(path: '/medications', builder: (_, _) => const MedicationListPage()),
- GoRoute(path: '/medications/add', builder: (_, _) => const MedicationEditPage()),
- GoRoute(path: '/medications/:id/edit', builder: (_, state) => MedicationEditPage(id: state.pathParameters['id'])),
-
- // 报告
- GoRoute(path: '/reports', builder: (_, _) => const ReportListPage()),
- GoRoute(path: '/reports/:id', builder: (_, state) => ReportDetailPage(id: state.pathParameters['id']!)),
-
- // 问诊
- GoRoute(path: '/doctors', builder: (_, _) => const DoctorListPage()),
- GoRoute(path: '/consultation/:id', builder: (_, state) => DoctorChatPage(id: state.pathParameters['id']!)),
-
- // 运动
- GoRoute(path: '/exercise-plan', builder: (_, _) => const ExercisePlanPage()),
-
- // 饮食
- GoRoute(path: '/diet-records', builder: (_, _) => const DietRecordListPage()),
-
- // 个人中心
- GoRoute(path: '/profile', builder: (_, _) => const ProfilePage()),
- GoRoute(path: '/profile/edit', builder: (_, _) => const EditProfilePage()),
- GoRoute(path: '/health-archive', builder: (_, _) => const HealthArchivePage()),
-
- // 复查
- GoRoute(path: '/followups', builder: (_, _) => const FollowUpListPage()),
-
- // 设置
- GoRoute(path: '/settings', builder: (_, _) => const SettingsPage()),
- GoRoute(path: '/settings/notifications', builder: (_, _) => const NotificationPrefsPage()),
- GoRoute(path: '/page/:type', builder: (_, state) => StaticTextPage(type: state.pathParameters['type']!)),
- ],
- );
+/// 根据路由信息返回对应页面
+Widget buildPage(RouteInfo route) {
+ final params = route.params;
+ switch (route.name) {
+ case 'login':
+ return const LoginPage();
+ case 'home':
+ return const HomePage();
+ case 'trend':
+ return TrendPage(metricType: params['type'] ?? '');
+ case 'calendar':
+ return const HealthCalendarPage();
+ case 'medications':
+ return const MedicationListPage();
+ case 'medicationAdd':
+ return const MedicationEditPage();
+ case 'medicationEdit':
+ return MedicationEditPage(id: params['id']);
+ case 'reports':
+ return const ReportListPage();
+ case 'reportDetail':
+ return ReportDetailPage(id: params['id']!);
+ case 'doctors':
+ return const DoctorListPage();
+ case 'consultation':
+ return DoctorChatPage(id: params['id']!);
+ case 'exercisePlan':
+ return const ExercisePlanPage();
+ case 'dietRecords':
+ return const DietRecordListPage();
+ case 'profile':
+ return const ProfilePage();
+ case 'profileEdit':
+ return const EditProfilePage();
+ case 'healthArchive':
+ return const HealthArchivePage();
+ case 'followups':
+ return const FollowUpListPage();
+ case 'settings':
+ return const SettingsPage();
+ case 'notificationPrefs':
+ return const NotificationPrefsPage();
+ case 'staticText':
+ return StaticTextPage(type: params['type']!);
+ default:
+ return const LoginPage();
+ }
}
diff --git a/health_app/lib/core/local_database.dart b/health_app/lib/core/local_database.dart
new file mode 100644
index 0000000..3d5fe29
--- /dev/null
+++ b/health_app/lib/core/local_database.dart
@@ -0,0 +1,58 @@
+import 'package:sqflite/sqflite.dart';
+import 'package:path/path.dart';
+
+/// SQLite 本地数据库——存储 token 等关键信息
+class LocalDatabase {
+ static LocalDatabase? _instance;
+ Database? _db;
+
+ LocalDatabase._();
+
+ static LocalDatabase get instance => _instance ??= LocalDatabase._();
+
+ Future get database async {
+ _db ??= await _initDb();
+ return _db!;
+ }
+
+ Future _initDb() async {
+ final dbPath = await getDatabasesPath();
+ final path = join(dbPath, 'health_app.db');
+ return openDatabase(
+ path,
+ version: 1,
+ onCreate: (db, version) async {
+ await db.execute(
+ 'CREATE TABLE kv_store (key TEXT PRIMARY KEY, value TEXT)',
+ );
+ },
+ );
+ }
+
+ Future write(String key, String value) async {
+ final db = await database;
+ await db.insert(
+ 'kv_store',
+ {'key': key, 'value': value},
+ conflictAlgorithm: ConflictAlgorithm.replace,
+ );
+ }
+
+ Future read(String key) async {
+ final db = await database;
+ final result =
+ await db.query('kv_store', where: 'key = ?', whereArgs: [key]);
+ if (result.isEmpty) return null;
+ return result.first['value'] as String?;
+ }
+
+ Future delete(String key) async {
+ final db = await database;
+ await db.delete('kv_store', where: 'key = ?', whereArgs: [key]);
+ }
+
+ Future deleteAll() async {
+ final db = await database;
+ await db.delete('kv_store');
+ }
+}
diff --git a/health_app/lib/core/navigation_provider.dart b/health_app/lib/core/navigation_provider.dart
new file mode 100644
index 0000000..b8043f3
--- /dev/null
+++ b/health_app/lib/core/navigation_provider.dart
@@ -0,0 +1,56 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+/// 路由信息
+class RouteInfo {
+ final String name;
+ final Map params;
+ const RouteInfo(this.name, {this.params = const {}});
+
+ String param(String key) => params[key] ?? '';
+}
+
+/// 路由栈 Notifier
+class RouteStackNotifier extends Notifier> {
+ @override
+ List build() => [const RouteInfo('login')];
+
+ void replace(String name, {Map params = const {}}) {
+ state = [RouteInfo(name, params: params)];
+ }
+
+ void push(String name, {Map params = const {}}) {
+ state = [...state, RouteInfo(name, params: params)];
+ }
+
+ void pop() {
+ if (state.length > 1) {
+ state = state.sublist(0, state.length - 1);
+ }
+ }
+}
+
+/// 路由栈 Provider
+final routeStackProvider =
+ NotifierProvider>(RouteStackNotifier.new);
+
+/// 当前路由
+final currentRouteProvider = Provider((ref) {
+ final stack = ref.watch(routeStackProvider);
+ return stack.last;
+});
+
+/// 跳转(替换整个栈)
+void goRoute(WidgetRef ref, String name, {Map params = const {}}) {
+ ref.read(routeStackProvider.notifier).replace(name, params: params);
+}
+
+/// 推入新页面
+void pushRoute(WidgetRef ref, String name, {Map params = const {}}) {
+ ref.read(routeStackProvider.notifier).push(name, params: params);
+}
+
+/// 返回上一页
+void popRoute(WidgetRef ref) {
+ ref.read(routeStackProvider.notifier).pop();
+}
diff --git a/health_app/lib/core/secure_storage.dart b/health_app/lib/core/secure_storage.dart
deleted file mode 100644
index 5ff2259..0000000
--- a/health_app/lib/core/secure_storage.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-
-/// Token 安全存储(iOS Keychain / Android EncryptedSharedPreferences / Web 内存)
-class SecureStorage {
- final FlutterSecureStorage _storage;
- static final Map _webFallback = {};
-
- SecureStorage() : _storage = const FlutterSecureStorage();
- static const _access = 'access_token';
- static const _refresh = 'refresh_token';
-
- bool get _isWeb => kIsWeb;
-
- Future writeAccessToken(String t) async {
- if (_isWeb) { _webFallback[_access] = t; return; }
- await _storage.write(key: _access, value: t);
- }
- Future readAccessToken() async {
- if (_isWeb) return _webFallback[_access];
- return _storage.read(key: _access);
- }
- Future writeRefreshToken(String t) async {
- if (_isWeb) { _webFallback[_refresh] = t; return; }
- await _storage.write(key: _refresh, value: t);
- }
- Future readRefreshToken() async {
- if (_isWeb) return _webFallback[_refresh];
- return _storage.read(key: _refresh);
- }
- Future deleteAll() async {
- if (_isWeb) { _webFallback.clear(); return; }
- await _storage.deleteAll();
- }
-}
diff --git a/health_app/lib/pages/auth/login_page.dart b/health_app/lib/pages/auth/login_page.dart
index 36c6be0..6deb925 100644
--- a/health_app/lib/pages/auth/login_page.dart
+++ b/health_app/lib/pages/auth/login_page.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
+import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
/// 登录页——手机号 + 验证码
@@ -71,7 +71,7 @@ class _LoginPageState extends ConsumerState {
setState(() => _error = err);
return;
}
- if (mounted) context.go('/home');
+ goRoute(ref, 'home');
}
@override
@@ -80,7 +80,7 @@ class _LoginPageState extends ConsumerState {
// 已登录直接跳转
if (authState.isLoggedIn && !authState.isLoading) {
- WidgetsBinding.instance.addPostFrameCallback((_) => context.go('/home'));
+ WidgetsBinding.instance.addPostFrameCallback((_) => goRoute(ref, 'home'));
}
return Scaffold(
diff --git a/health_app/lib/pages/medication/medication_list_page.dart b/health_app/lib/pages/medication/medication_list_page.dart
index 35bf7bc..a3daf69 100644
--- a/health_app/lib/pages/medication/medication_list_page.dart
+++ b/health_app/lib/pages/medication/medication_list_page.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
+import '../../core/navigation_provider.dart';
import '../../providers/data_providers.dart';
/// 用药列表页
@@ -37,7 +37,7 @@ class MedicationListPage extends ConsumerWidget {
error: (_, _) => _empty(context),
),
floatingActionButton: FloatingActionButton.extended(
- onPressed: () => context.push('/medications/add').then((_) => ref.invalidate(medicationListProvider)),
+ onPressed: () { pushRoute(ref, 'medicationAdd'); ref.invalidate(medicationListProvider); },
icon: const Icon(Icons.add), label: const Text('添加药品'),
),
);
@@ -65,7 +65,7 @@ class _MedicationEditPageState extends ConsumerState {
'frequency': 'Daily', 'timeOfDay': [if (_timeCtrl.text.isNotEmpty) _timeCtrl.text],
'source': 'Manual', 'startDate': DateTime.now().toIso8601String().substring(0, 10),
});
- if (mounted) context.pop();
+ popRoute(ref);
}
@override Widget build(BuildContext context) => Scaffold(
diff --git a/health_app/lib/pages/profile/profile_page.dart b/health_app/lib/pages/profile/profile_page.dart
index d54e73e..0fe8be7 100644
--- a/health_app/lib/pages/profile/profile_page.dart
+++ b/health_app/lib/pages/profile/profile_page.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
+import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
/// 个人中心页面
@@ -38,12 +38,12 @@ class ProfilePage extends ConsumerWidget {
),
),
const SizedBox(height: 8),
- _MenuItem(icon: Icons.person, title: '编辑资料', onTap: () => context.push('/profile/edit')),
- _MenuItem(icon: Icons.folder, title: '健康档案', onTap: () => context.push('/health-archive')),
+ _MenuItem(icon: Icons.person, title: '编辑资料', onTap: () => pushRoute(ref, 'profileEdit')),
+ _MenuItem(icon: Icons.folder, title: '健康档案', onTap: () => pushRoute(ref, 'healthArchive')),
_MenuItem(icon: Icons.devices, title: '设备管理', onTap: () {}),
const Divider(),
- _MenuItem(icon: Icons.settings, title: '设置', onTap: () => context.push('/settings')),
- _MenuItem(icon: Icons.info, title: '关于', onTap: () => context.push('/page/about')),
+ _MenuItem(icon: Icons.settings, title: '设置', onTap: () => pushRoute(ref, 'settings')),
+ _MenuItem(icon: Icons.info, title: '关于', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'about'})),
const Divider(),
_MenuItem(
icon: Icons.logout, title: '退出登录', textColor: const Color(0xFFE53935),
@@ -52,7 +52,7 @@ class ProfilePage extends ConsumerWidget {
title: const Text('退出登录'), content: const Text('确定退出?'),
actions: [TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')),
TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定'))]));
- if (ok == true) { await ref.read(authProvider.notifier).logout(); if (context.mounted) context.go('/login'); }
+ if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); }
},
),
],
diff --git a/health_app/lib/pages/settings/settings_pages.dart b/health_app/lib/pages/settings/settings_pages.dart
index 1ed4ee3..a8ac35a 100644
--- a/health_app/lib/pages/settings/settings_pages.dart
+++ b/health_app/lib/pages/settings/settings_pages.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
+import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
/// 设置页
@@ -10,17 +10,17 @@ class SettingsPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) => Scaffold(
appBar: AppBar(title: const Text('设置')),
body: ListView(children: [
- _SetItem(icon: Icons.shield, title: '隐私保护中心', onTap: () => context.push('/page/privacy')),
- _SetItem(icon: Icons.notifications, title: '通知偏好', onTap: () => context.push('/settings/notifications')),
+ _SetItem(icon: Icons.shield, title: '隐私保护中心', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'privacy'})),
+ _SetItem(icon: Icons.notifications, title: '通知偏好', onTap: () => pushRoute(ref, 'notificationPrefs')),
_SetItem(icon: Icons.text_fields, title: '字体大小', trailing: _FontSlider()),
- _SetItem(icon: Icons.article, title: '协议与公告', onTap: () => context.push('/page/terms')),
- _SetItem(icon: Icons.info, title: '关于', onTap: () => context.push('/page/about')),
+ _SetItem(icon: Icons.article, title: '协议与公告', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'terms'})),
+ _SetItem(icon: Icons.info, title: '关于', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'about'})),
const Divider(),
_SetItem(icon: Icons.logout, title: '退出登录', textColor: const Color(0xFFE53935), onTap: () async {
final ok = await showDialog(context: context, builder: (ctx) => AlertDialog(
title: const Text('退出登录'), content: const Text('确定退出?'),
actions: [TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定'))]));
- if (ok == true) { await ref.read(authProvider.notifier).logout(); if (context.mounted) context.go('/login'); }
+ if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); }
}),
]),
);
diff --git a/health_app/lib/providers/auth_provider.dart b/health_app/lib/providers/auth_provider.dart
index deb8148..af2568d 100644
--- a/health_app/lib/providers/auth_provider.dart
+++ b/health_app/lib/providers/auth_provider.dart
@@ -1,7 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
import '../core/api_client.dart';
-import '../core/secure_storage.dart';
+import '../core/local_database.dart';
/// 用户简要信息
class UserInfo {
@@ -25,10 +25,10 @@ class AuthState {
/// 认证 Provider
final authProvider = NotifierProvider(AuthNotifier.new);
-final secureStorageProvider = Provider((ref) => SecureStorage());
+final localDbProvider = Provider((ref) => LocalDatabase.instance);
final apiClientProvider = Provider((ref) {
- return ApiClient(storage: ref.watch(secureStorageProvider));
+ return ApiClient(db: ref.watch(localDbProvider));
});
class AuthNotifier extends Notifier {
@@ -39,8 +39,8 @@ class AuthNotifier extends Notifier {
}
Future _checkAuth() async {
- final storage = ref.read(secureStorageProvider);
- final refresh = await storage.readRefreshToken();
+ final db = ref.read(localDbProvider);
+ final refresh = await db.read('refresh_token');
if (refresh == null) {
state = const AuthState(isLoggedIn: false, isLoading: false);
return;
@@ -51,8 +51,8 @@ class AuthNotifier extends Notifier {
.post('/api/auth/refresh', data: {'refreshToken': refresh});
final data = response.data['data'];
if (data != null) {
- await storage.writeAccessToken(data['accessToken']);
- await storage.writeRefreshToken(data['refreshToken']);
+ await db.write('access_token', data['accessToken']);
+ await db.write('refresh_token', data['refreshToken']);
state = AuthState(
isLoggedIn: true,
isLoading: false,
@@ -128,8 +128,8 @@ class AuthNotifier extends Notifier {
/// 登出
Future logout() async {
final api = ref.read(apiClientProvider);
- final storage = ref.read(secureStorageProvider);
- final refresh = await storage.readRefreshToken();
+ final db = ref.read(localDbProvider);
+ final refresh = await db.read('refresh_token');
if (refresh != null) {
try { await api.post('/api/auth/logout', data: {'refreshToken': refresh}); } catch (_) {}
}
diff --git a/health_app/lib/widgets/health_drawer.dart b/health_app/lib/widgets/health_drawer.dart
index d3a0015..edf4779 100644
--- a/health_app/lib/widgets/health_drawer.dart
+++ b/health_app/lib/widgets/health_drawer.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
+import '../core/navigation_provider.dart';
import '../providers/auth_provider.dart';
import '../providers/data_providers.dart';
@@ -26,7 +26,7 @@ class HealthDrawer extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
- onTap: () => context.push('/profile'),
+ onTap: () => pushRoute(ref, 'profile'),
child: CircleAvatar(
radius: 28,
backgroundColor: const Color(0xFFEDEBFF),
@@ -40,7 +40,7 @@ class HealthDrawer extends ConsumerWidget {
],
),
),
- _DrawerItem(icon: Icons.settings, label: '设置', onTap: () => context.push('/settings')),
+ _DrawerItem(icon: Icons.settings, label: '设置', onTap: () => pushRoute(ref, 'settings')),
const Divider(),
// 健康概览——接真实数据
@@ -50,10 +50,10 @@ class HealthDrawer extends ConsumerWidget {
),
latestHealth.when(
data: (data) => Column(children: [
- _HealthMetric(icon: Icons.favorite, label: '血压', value: _bpText(data['BloodPressure']), onTap: () => context.push('/trend/blood_pressure')),
- _HealthMetric(icon: Icons.monitor_heart, label: '心率', value: _metricText(data['HeartRate'], '次/分'), onTap: () => context.push('/trend/heart_rate')),
- _HealthMetric(icon: Icons.bloodtype, label: '血糖', value: _metricText(data['Glucose'], 'mmol/L'), onTap: () => context.push('/trend/glucose')),
- _HealthMetric(icon: Icons.air, label: '血氧', value: _metricText(data['SpO2'], '%'), onTap: () => context.push('/trend/spo2')),
+ _HealthMetric(icon: Icons.favorite, label: '血压', value: _bpText(data['BloodPressure']), onTap: () => pushRoute(ref, 'trend', params: {'type': 'blood_pressure'})),
+ _HealthMetric(icon: Icons.monitor_heart, label: '心率', value: _metricText(data['HeartRate'], '次/分'), onTap: () => pushRoute(ref, 'trend', params: {'type': 'heart_rate'})),
+ _HealthMetric(icon: Icons.bloodtype, label: '血糖', value: _metricText(data['Glucose'], 'mmol/L'), onTap: () => pushRoute(ref, 'trend', params: {'type': 'glucose'})),
+ _HealthMetric(icon: Icons.air, label: '血氧', value: _metricText(data['SpO2'], '%'), onTap: () => pushRoute(ref, 'trend', params: {'type': 'spo2'})),
]),
loading: () => const Padding(padding: EdgeInsets.all(16), child: Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)))),
error: (_, _) => Column(children: [
@@ -76,7 +76,7 @@ class HealthDrawer extends ConsumerWidget {
final ok = await showDialog(context: context, builder: (ctx) => AlertDialog(
title: const Text('退出登录'), content: const Text('确定退出?'),
actions: [TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定'))]));
- if (ok == true) { await ref.read(authProvider.notifier).logout(); if (context.mounted) context.go('/login'); }
+ if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); }
}),
],
),
diff --git a/health_app/pubspec.lock b/health_app/pubspec.lock
index c0f78e8..a0dac9d 100644
--- a/health_app/pubspec.lock
+++ b/health_app/pubspec.lock
@@ -65,14 +65,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
- code_assets:
- dependency: transitive
- description:
- name: code_assets
- sha256: "67cf6d84013f9c601e42a6f8a6b74c4c0d9dc1a1619d775f2b28b732d3551b85"
- url: "https://pub.dev"
- source: hosted
- version: "1.2.0"
collection:
dependency: transitive
description:
@@ -254,54 +246,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
- flutter_secure_storage:
- dependency: "direct main"
- description:
- name: flutter_secure_storage
- sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
- url: "https://pub.dev"
- source: hosted
- version: "9.2.4"
- flutter_secure_storage_linux:
- dependency: transitive
- description:
- name: flutter_secure_storage_linux
- sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
- url: "https://pub.dev"
- source: hosted
- version: "1.2.3"
- flutter_secure_storage_macos:
- dependency: transitive
- description:
- name: flutter_secure_storage_macos
- sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
- url: "https://pub.dev"
- source: hosted
- version: "3.1.3"
- flutter_secure_storage_platform_interface:
- dependency: transitive
- description:
- name: flutter_secure_storage_platform_interface
- sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
- url: "https://pub.dev"
- source: hosted
- version: "1.1.2"
- flutter_secure_storage_web:
- dependency: transitive
- description:
- name: flutter_secure_storage_web
- sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
- url: "https://pub.dev"
- source: hosted
- version: "1.2.1"
- flutter_secure_storage_windows:
- dependency: transitive
- description:
- name: flutter_secure_storage_windows
- sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
- url: "https://pub.dev"
- source: hosted
- version: "3.1.2"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -328,22 +272,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
- go_router:
- dependency: "direct main"
- description:
- name: go_router
- sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
- url: "https://pub.dev"
- source: hosted
- version: "14.8.1"
- hooks:
- dependency: transitive
- description:
- name: hooks
- sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31
- url: "https://pub.dev"
- source: hosted
- version: "2.0.0"
http:
dependency: transitive
description:
@@ -440,22 +368,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
- jni:
- dependency: transitive
- description:
- name: jni
- sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
- url: "https://pub.dev"
- source: hosted
- version: "1.0.0"
- jni_flutter:
- dependency: transitive
- description:
- name: jni_flutter
- sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
- url: "https://pub.dev"
- source: hosted
- version: "1.0.1"
js:
dependency: transitive
description:
@@ -544,14 +456,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
- objective_c:
- dependency: transitive
- description:
- name: objective_c
- sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
- url: "https://pub.dev"
- source: hosted
- version: "9.4.1"
package_config:
dependency: transitive
description:
@@ -561,61 +465,13 @@ packages:
source: hosted
version: "2.2.0"
path:
- dependency: transitive
+ dependency: "direct main"
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
- path_provider:
- dependency: transitive
- description:
- name: path_provider
- sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
- url: "https://pub.dev"
- source: hosted
- version: "2.1.5"
- path_provider_android:
- dependency: transitive
- description:
- name: path_provider_android
- sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
- url: "https://pub.dev"
- source: hosted
- version: "2.3.1"
- path_provider_foundation:
- dependency: transitive
- description:
- name: path_provider_foundation
- sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
- url: "https://pub.dev"
- source: hosted
- version: "2.6.0"
- path_provider_linux:
- dependency: transitive
- description:
- name: path_provider_linux
- sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
- url: "https://pub.dev"
- source: hosted
- version: "2.2.1"
- path_provider_platform_interface:
- dependency: transitive
- description:
- name: path_provider_platform_interface
- sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
- url: "https://pub.dev"
- source: hosted
- version: "2.1.2"
- path_provider_windows:
- dependency: transitive
- description:
- name: path_provider_windows
- sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
- url: "https://pub.dev"
- source: hosted
- version: "2.3.0"
petitparser:
dependency: transitive
description:
@@ -656,14 +512,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
- record_use:
- dependency: transitive
- description:
- name: record_use
- sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
- url: "https://pub.dev"
- source: hosted
- version: "0.6.0"
riverpod:
dependency: transitive
description:
@@ -733,6 +581,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.2"
+ sqflite:
+ dependency: "direct main"
+ description:
+ name: sqflite
+ sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2+1"
+ sqflite_android:
+ dependency: transitive
+ description:
+ name: sqflite_android
+ sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2+3"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.8"
+ sqflite_darwin:
+ dependency: transitive
+ description:
+ name: sqflite_darwin
+ sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ sqflite_platform_interface:
+ dependency: transitive
+ description:
+ name: sqflite_platform_interface
+ sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.0"
stack_trace:
dependency: transitive
description:
@@ -765,6 +653,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.0"
term_glyph:
dependency: transitive
description:
@@ -869,14 +765,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.15.0"
- xdg_directories:
- dependency: transitive
- description:
- name: xdg_directories
- sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
- url: "https://pub.dev"
- source: hosted
- version: "1.1.0"
xml:
dependency: transitive
description:
@@ -895,4 +783,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
- flutter: ">=3.38.4"
+ flutter: ">=3.38.0"
diff --git a/health_app/pubspec.yaml b/health_app/pubspec.yaml
index 3295c83..571c214 100644
--- a/health_app/pubspec.yaml
+++ b/health_app/pubspec.yaml
@@ -16,11 +16,9 @@ dependencies:
# HTTP 网络
dio: ^5.4.0
- # 安全存储
- flutter_secure_storage: ^9.2.0
-
- # 路由
- go_router: ^14.0.0
+ # 本地数据库
+ sqflite: ^2.4.0
+ path: ^1.9.0
# 图表
fl_chart: ^0.68.0