feat: 健康录入提醒 + 多指标异常 + 用药漏服 + UI 优化

后端
- NotificationPreference 增加健康录入提醒总开关和 5 子项(血压/心率/血糖/血氧/体重)
- EF 迁移 AddHealthRecordReminder
- 新增 GET/PUT /api/notification-prefs
- 新增 HealthRecordReminderService 每天 9 点/12 点扫描未录入用户推送提醒

前端
- 通知偏好对接真实后端 API
- 设置页新增「健康录入提醒」总开关+5 子项
- 通知中心右上角加齿轮跳设置页
- 今日健康卡片:4 项异常检测+多项合并;用药只显漏服并按时间窗口区分文案
- 侧边栏头像改圆形白底灰图标,去掉紫色品牌渐变
- 整体背景统一浅灰白 #F6F8FC,去掉浅紫渐变
- 设置页简化(去掉分组/彩色图标方块)
- 健康档案保存按钮移到右上角,TextButton 全局默认黑色
- API baseUrl 跟随网络环境
This commit is contained in:
MingNian
2026-06-24 22:11:21 +08:00
parent 7ff429e071
commit 53ce19e8c3
18 changed files with 2379 additions and 565 deletions

View File

@@ -56,8 +56,77 @@ public static class NotificationEndpoints
? Results.Ok(new { code = 0, data = new { deleted = true }, message = (string?)null })
: Results.NotFound(new { code = 404, data = (object?)null, message = "通知不存在" });
});
// ── 通知偏好设置 ──
var prefsGroup = app.MapGroup("/api/notification-prefs").RequireAuthorization();
prefsGroup.MapGet("", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
if (userId == Guid.Empty) return Results.Unauthorized();
var pref = await db.NotificationPreferences.FirstOrDefaultAsync(p => p.UserId == userId, ct);
if (pref == null)
{
pref = new NotificationPreference { Id = Guid.NewGuid(), UserId = userId };
db.NotificationPreferences.Add(pref);
await db.SaveChangesAsync(ct);
}
return Results.Ok(new { code = 0, data = ToDto(pref), message = (string?)null });
});
prefsGroup.MapPut("", async (NotificationPrefsUpdateDto body, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
if (userId == Guid.Empty) return Results.Unauthorized();
var pref = await db.NotificationPreferences.FirstOrDefaultAsync(p => p.UserId == userId, ct);
if (pref == null)
{
pref = new NotificationPreference { Id = Guid.NewGuid(), UserId = userId };
db.NotificationPreferences.Add(pref);
}
if (body.MedicationReminder.HasValue) pref.MedicationReminder = body.MedicationReminder.Value;
if (body.FollowUpReminder.HasValue) pref.FollowUpReminder = body.FollowUpReminder.Value;
if (body.DoctorReply.HasValue) pref.DoctorReply = body.DoctorReply.Value;
if (body.AbnormalAlert.HasValue) pref.AbnormalAlert = body.AbnormalAlert.Value;
if (body.HealthRecordReminder.HasValue) pref.HealthRecordReminder = body.HealthRecordReminder.Value;
if (body.HealthRecordReminderBloodPressure.HasValue) pref.HealthRecordReminderBloodPressure = body.HealthRecordReminderBloodPressure.Value;
if (body.HealthRecordReminderHeartRate.HasValue) pref.HealthRecordReminderHeartRate = body.HealthRecordReminderHeartRate.Value;
if (body.HealthRecordReminderGlucose.HasValue) pref.HealthRecordReminderGlucose = body.HealthRecordReminderGlucose.Value;
if (body.HealthRecordReminderSpO2.HasValue) pref.HealthRecordReminderSpO2 = body.HealthRecordReminderSpO2.Value;
if (body.HealthRecordReminderWeight.HasValue) pref.HealthRecordReminderWeight = body.HealthRecordReminderWeight.Value;
pref.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = ToDto(pref), message = (string?)null });
});
}
private static Guid GetUserId(HttpContext http) =>
Guid.TryParse(http.User.FindFirst(ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty;
private static object ToDto(NotificationPreference pref) => new
{
medicationReminder = pref.MedicationReminder,
followUpReminder = pref.FollowUpReminder,
doctorReply = pref.DoctorReply,
abnormalAlert = pref.AbnormalAlert,
healthRecordReminder = pref.HealthRecordReminder,
healthRecordReminderBloodPressure = pref.HealthRecordReminderBloodPressure,
healthRecordReminderHeartRate = pref.HealthRecordReminderHeartRate,
healthRecordReminderGlucose = pref.HealthRecordReminderGlucose,
healthRecordReminderSpO2 = pref.HealthRecordReminderSpO2,
healthRecordReminderWeight = pref.HealthRecordReminderWeight,
};
}
public sealed record NotificationPrefsUpdateDto(
bool? MedicationReminder,
bool? FollowUpReminder,
bool? DoctorReply,
bool? AbnormalAlert,
bool? HealthRecordReminder,
bool? HealthRecordReminderBloodPressure,
bool? HealthRecordReminderHeartRate,
bool? HealthRecordReminderGlucose,
bool? HealthRecordReminderSpO2,
bool? HealthRecordReminderWeight
);