fix: 用药模块重做 + 运动计划不限7天 + 打卡可切换 + 删除修复

- 用药: 重写列表页(标签筛选+详情弹窗+打卡切换+滑动删除)
- 用药: 重写编辑页(选择器替代输入+同列布局+频率改为每天几次)
- 用药: 加Notes字段+DELETE端点+修复重复端点
- 运动: 不限7天, startDate+索引推算当天
- 打卡: 药/运动打卡可切换(点✓取消)
- 删除: 修复滑动删除热区, 一次点击即删除
- 侧边栏: 一键清理历史对话
- 饮食/运动/用药/问诊 API路径加/api/前缀
- 通知时机修正: 新建计划不再提前弹窗
This commit is contained in:
MingNian
2026-06-04 21:15:23 +08:00
parent b944a31983
commit cd65bc4d65
11 changed files with 1069 additions and 1242 deletions

View File

@@ -17,6 +17,7 @@ public sealed class Medication
public DateOnly? EndDate { get; set; }
public bool IsActive { get; set; } = true;
public MedicationSource Source { get; set; }
public string? Notes { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;

View File

@@ -21,7 +21,8 @@ public static class ExerciseEndpoints
plan.Id, plan.WeekStartDate, plan.CreatedAt, plan.UpdatedAt,
items = plan.Items.Select(i => new
{
i.Id, i.DayOfWeek, i.ExerciseType, i.DurationMinutes,
i.Id, i.DayOfWeek,
i.ExerciseType, i.DurationMinutes,
i.IsCompleted, i.CompletedAt, i.IsRestDay
})
},
@@ -38,34 +39,85 @@ public static class ExerciseEndpoints
request.Body.Position = 0;
using var json = JsonDocument.Parse(body);
var root = json.RootElement;
var ws = root.GetProperty("weekStartDate").GetString()!;
var plan = new ExercisePlan { Id = Guid.NewGuid(), UserId = userId, WeekStartDate = DateOnly.Parse(ws) };
if (root.TryGetProperty("items", out var items))
var exerciseType = root.TryGetProperty("exerciseType", out var et) ? et.GetString()! : "运动";
var duration = root.TryGetProperty("durationMinutes", out var dm) ? dm.GetInt32() : 30;
var startDate = DateOnly.Parse((root.TryGetProperty("startDate", out var sd) ? sd.GetString()! : DateTime.Now.ToString("yyyy-MM-dd")));
var endDate = DateOnly.Parse((root.TryGetProperty("endDate", out var ed) ? ed.GetString()! : DateTime.Now.AddDays(6).ToString("yyyy-MM-dd")));
var plan = new ExercisePlan { Id = Guid.NewGuid(), UserId = userId, WeekStartDate = startDate };
for (var d = startDate; d <= endDate; d = d.AddDays(1))
{
foreach (var item in items.EnumerateArray())
plan.Items.Add(new ExercisePlanItem
{
plan.Items.Add(new ExercisePlanItem
{
Id = Guid.NewGuid(),
DayOfWeek = item.GetProperty("dayOfWeek").GetInt32(),
ExerciseType = item.TryGetProperty("exerciseType", out var et) ? et.GetString()! : "休息",
DurationMinutes = item.TryGetProperty("durationMinutes", out var dm) ? dm.GetInt32() : 0,
IsRestDay = item.TryGetProperty("isRestDay", out var rd) && rd.GetBoolean(),
});
}
Id = Guid.NewGuid(),
DayOfWeek = (int)d.DayOfWeek,
ExerciseType = exerciseType,
DurationMinutes = duration,
IsRestDay = false,
});
}
db.ExercisePlans.Add(plan);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { plan.Id }, message = (string?)null });
});
group.MapGet("/", async (HttpContext http, AppDbContext db) =>
{
var userId = GetUserId(http);
var plans = await db.ExercisePlans.Where(p => p.UserId == userId)
.OrderByDescending(p => p.WeekStartDate).Take(20)
.Select(p => new
{
p.Id, p.WeekStartDate, p.CreatedAt,
totalDays = p.Items.Count,
completedDays = p.Items.Count(i => i.IsCompleted),
items = p.Items.OrderBy(i => i.DayOfWeek).Select(i => new
{
i.Id, i.DayOfWeek,
i.ExerciseType, i.DurationMinutes,
i.IsCompleted, i.IsRestDay
})
}).ToListAsync();
return Results.Ok(new { code = 0, data = plans, message = (string?)null });
});
group.MapGet("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db) =>
{
var userId = GetUserId(http);
var plan = await db.ExercisePlans.Include(p => p.Items)
.FirstOrDefaultAsync(p => p.Id == id && p.UserId == userId);
if (plan == null) return Results.Ok(new { code = 40004, message = "不存在" });
return Results.Ok(new
{
code = 0,
data = new
{
plan.Id, plan.WeekStartDate, plan.CreatedAt,
items = plan.Items.OrderBy(i => i.DayOfWeek).Select(i => new
{
i.Id, i.DayOfWeek, i.ExerciseType, i.DurationMinutes,
i.IsCompleted, i.CompletedAt, i.IsRestDay
})
},
message = (string?)null
});
});
group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var plan = await db.ExercisePlans.Include(p => p.Items).FirstOrDefaultAsync(p => p.Id == id && p.UserId == userId, ct);
if (plan != null) { db.ExercisePlans.Remove(plan); await db.SaveChangesAsync(ct); }
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
});
group.MapPost("/items/{itemId:guid}/checkin", async (Guid itemId, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var item = await db.ExercisePlanItems.FindAsync([itemId], ct);
if (item == null) return Results.Ok(new { code = 40004, message = "不存在" });
item.IsCompleted = true; item.CompletedAt = DateTime.UtcNow;
item.IsCompleted = !item.IsCompleted;
item.CompletedAt = item.IsCompleted ? DateTime.UtcNow : null;
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
return Results.Ok(new { code = 0, data = new { completed = item.IsCompleted }, message = (string?)null });
});
}

View File

@@ -6,10 +6,19 @@ public static class MedicationEndpoints
{
var group = app.MapGroup("/api/medications").RequireAuthorization();
group.MapGet("/", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
group.MapGet("/", async (string? filter, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var meds = await db.Medications.Where(m => m.UserId == userId).OrderByDescending(m => m.CreatedAt).ToListAsync(ct);
var query = db.Medications.Where(m => m.UserId == userId);
if (filter == "active") query = query.Where(m => m.IsActive);
else if (filter == "inactive") query = query.Where(m => !m.IsActive);
var meds = await query.OrderByDescending(m => m.CreatedAt).Select(m => new
{
m.Id, m.Name, m.Dosage, Frequency = m.Frequency.ToString(),
m.TimeOfDay, m.StartDate, m.EndDate, m.IsActive, m.Notes,
Source = m.Source.ToString(), m.CreatedAt, m.UpdatedAt,
todayTaken = m.Logs.Any(l => l.CreatedAt.Date == DateTime.UtcNow.Date && l.Status == MedicationLogStatus.Taken)
}).ToListAsync(ct);
return Results.Ok(new { code = 0, data = meds, message = (string?)null });
});
@@ -37,6 +46,7 @@ public static class MedicationEndpoints
med.StartDate = DateOnly.TryParse(sd.GetString(), out var d) ? d : null;
if (root.TryGetProperty("endDate", out var ed))
med.EndDate = DateOnly.TryParse(ed.GetString(), out var d2) ? d2 : null;
if (root.TryGetProperty("notes", out var nt)) med.Notes = nt.GetString();
db.Medications.Add(med);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { med.Id }, message = (string?)null });
@@ -59,6 +69,7 @@ public static class MedicationEndpoints
if (root.TryGetProperty("timeOfDay", out var tod2)) med.TimeOfDay = tod2.EnumerateArray().Select(t => TimeOnly.Parse(t.GetString()!)).ToList();
if (root.TryGetProperty("startDate", out var sd2) && DateOnly.TryParse(sd2.GetString(), out var d3)) med.StartDate = d3;
if (root.TryGetProperty("endDate", out var ed2) && DateOnly.TryParse(ed2.GetString(), out var d4)) med.EndDate = d4;
if (root.TryGetProperty("notes", out var nt2)) med.Notes = nt2.GetString();
med.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
@@ -75,6 +86,13 @@ public static class MedicationEndpoints
group.MapPost("/{id:guid}/confirm", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
{
var userId = GetUserId(http);
var today = DateOnly.FromDateTime(DateTime.UtcNow);
var existing = await db.MedicationLogs.FirstOrDefaultAsync(l => l.MedicationId == id && l.CreatedAt.Date == DateTime.UtcNow.Date && l.Status == MedicationLogStatus.Taken, ct);
if (existing != null) {
db.MedicationLogs.Remove(existing);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { taken = false }, message = (string?)null });
}
var log = new MedicationLog
{
Id = Guid.NewGuid(), MedicationId = id, UserId = userId,
@@ -82,7 +100,7 @@ public static class MedicationEndpoints
};
db.MedicationLogs.Add(log);
await db.SaveChangesAsync(ct);
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
return Results.Ok(new { code = 0, data = new { taken = true }, message = (string?)null });
});
group.MapGet("/reminders", async (HttpContext http, AppDbContext db, CancellationToken ct) =>