Initial commit: HealthManager full-stack health management platform
Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR Frontend patient: React 19 + TypeScript + Vite (mobile H5) Frontend doctor: React 19 + TypeScript + Vite (desktop web)
This commit is contained in:
120
backend/src/HealthManager.WebApi/Controllers/AuthController.cs
Normal file
120
backend/src/HealthManager.WebApi/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.DTOs.Auth;
|
||||
using HealthManager.Domain.Interfaces;
|
||||
using HealthManager.Application.Services;
|
||||
using HealthManager.Domain.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController(
|
||||
AuthService authService,
|
||||
IJwtProvider jwtProvider) : ControllerBase
|
||||
{
|
||||
[HttpPost("send-sms")]
|
||||
public IActionResult SendSms([FromBody] SendSmsRequest request)
|
||||
{
|
||||
// Demo: always succeed
|
||||
return Ok(new { message = "验证码已发送" });
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
var user = await authService.GetUserByPhoneAsync(request.Phone);
|
||||
if (user == null)
|
||||
return Unauthorized(new { message = "用户不存在" });
|
||||
|
||||
// Demo: accept any SMS code
|
||||
var accessToken = jwtProvider.GenerateAccessToken(user.Id, user.Name, user.Role);
|
||||
var refreshToken = jwtProvider.GenerateRefreshToken();
|
||||
await authService.SaveRefreshTokenAsync(user.Id, refreshToken, DateTime.UtcNow.AddDays(7));
|
||||
|
||||
return Ok(new AuthResponse(user.Id, user.Name, user.Role, accessToken, refreshToken));
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
|
||||
{
|
||||
var existing = await authService.GetUserByPhoneAsync(request.Phone);
|
||||
if (existing != null)
|
||||
return Conflict(new { message = "该手机号已注册" });
|
||||
|
||||
var user = new User
|
||||
{
|
||||
Phone = request.Phone,
|
||||
Name = request.Name,
|
||||
Role = "patient",
|
||||
PasswordHash = AuthService.HashPassword("demo123"),
|
||||
};
|
||||
|
||||
// Access DbContext via DI
|
||||
var db = HttpContext.RequestServices.GetRequiredService<Infrastructure.Data.AppDbContext>();
|
||||
db.Users.Add(user);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var accessToken = jwtProvider.GenerateAccessToken(user.Id, user.Name, user.Role);
|
||||
var refreshToken = jwtProvider.GenerateRefreshToken();
|
||||
await authService.SaveRefreshTokenAsync(user.Id, refreshToken, DateTime.UtcNow.AddDays(7));
|
||||
|
||||
return Ok(new AuthResponse(user.Id, user.Name, user.Role, accessToken, refreshToken));
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
|
||||
{
|
||||
var saved = await authService.GetRefreshTokenAsync(request.RefreshToken);
|
||||
if (saved == null)
|
||||
return Unauthorized(new { message = "无效的刷新令牌" });
|
||||
|
||||
await authService.RevokeRefreshTokenAsync(saved.UserId);
|
||||
|
||||
var accessToken = jwtProvider.GenerateAccessToken(saved.User.Id, saved.User.Name, saved.User.Role);
|
||||
var refreshToken = jwtProvider.GenerateRefreshToken();
|
||||
await authService.SaveRefreshTokenAsync(saved.UserId, refreshToken, DateTime.UtcNow.AddDays(7));
|
||||
|
||||
return Ok(new AuthResponse(saved.User.Id, saved.User.Name, saved.User.Role, accessToken, refreshToken));
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetProfile()
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
var db = HttpContext.RequestServices.GetRequiredService<Infrastructure.Data.AppDbContext>();
|
||||
var user = await db.Users.FindAsync(userId);
|
||||
if (user == null) return NotFound();
|
||||
|
||||
return Ok(new UserProfileResponse(
|
||||
user.Id, user.Name, user.Phone, user.Role,
|
||||
user.Gender, user.Birthday, user.HeightCm, user.WeightKg,
|
||||
user.MedicalHistory, user.StentDate, user.StentType,
|
||||
user.Department, user.Title, user.Specialty, user.Introduction));
|
||||
}
|
||||
|
||||
[HttpPut("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdateProfile([FromBody] UpdateProfileRequest request)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
var db = HttpContext.RequestServices.GetRequiredService<Infrastructure.Data.AppDbContext>();
|
||||
var user = await db.Users.FindAsync(userId);
|
||||
if (user == null) return NotFound();
|
||||
|
||||
if (request.Name != null) user.Name = request.Name;
|
||||
if (request.Gender != null) user.Gender = request.Gender;
|
||||
if (request.Birthday.HasValue) user.Birthday = request.Birthday;
|
||||
if (request.HeightCm.HasValue) user.HeightCm = request.HeightCm;
|
||||
if (request.WeightKg.HasValue) user.WeightKg = request.WeightKg;
|
||||
if (request.MedicalHistory != null) user.MedicalHistory = request.MedicalHistory;
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return Ok(new { message = "更新成功" });
|
||||
}
|
||||
}
|
||||
|
||||
public record RefreshTokenRequest(string RefreshToken);
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/consultations")]
|
||||
[Authorize]
|
||||
public class ConsultationController(ConsultationService consultationService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet("doctors")]
|
||||
public async Task<IActionResult> GetDoctors([FromQuery] string? department)
|
||||
{
|
||||
var doctors = await consultationService.GetDoctorsAsync(department);
|
||||
return Ok(doctors.Select(d => new
|
||||
{
|
||||
d.Id, d.Name, d.Department, d.Title, d.Specialty, d.Introduction, d.AvatarUrl,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetConsultations()
|
||||
{
|
||||
var consultations = Role == "doctor"
|
||||
? await consultationService.GetDoctorConsultationsAsync(UserId)
|
||||
: await consultationService.GetPatientConsultationsAsync(UserId);
|
||||
|
||||
return Ok(consultations.Select(c => new
|
||||
{
|
||||
c.Id, c.PatientId, c.DoctorId,
|
||||
PatientName = c.Patient?.Name,
|
||||
DoctorName = c.Doctor?.Name,
|
||||
c.Subject, c.Status, c.StartedAt, c.ClosedAt, c.Summary,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> StartConsultation([FromBody] StartConsultationRequest request)
|
||||
{
|
||||
var consultation = await consultationService.StartAsync(UserId, request.DoctorId, request.Subject);
|
||||
return Ok(new { consultation.Id, consultation.PatientId, consultation.DoctorId, consultation.Status });
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/messages")]
|
||||
public async Task<IActionResult> GetMessages(Guid id)
|
||||
{
|
||||
var messages = await consultationService.GetMessagesAsync(id);
|
||||
return Ok(messages.Select(m => new
|
||||
{
|
||||
m.Id, m.SenderId, m.SenderRole, m.ContentType, m.Content,
|
||||
m.ImageUrl, m.IsRead, m.CreatedAt,
|
||||
SenderName = m.Sender?.Name,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/messages")]
|
||||
public async Task<IActionResult> SendMessage(Guid id, [FromBody] SendMessageRequest request)
|
||||
{
|
||||
var message = await consultationService.SendMessageAsync(id, UserId, Role, request.Content,
|
||||
request.ContentType, request.ImageUrl);
|
||||
return Ok(new { message.Id, message.SenderId, message.SenderRole, message.Content, message.CreatedAt });
|
||||
}
|
||||
}
|
||||
|
||||
public record StartConsultationRequest(Guid DoctorId, string Subject);
|
||||
public record SendMessageRequest(string Content, string ContentType = "text", string? ImageUrl = null);
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/follow-ups")]
|
||||
[Authorize]
|
||||
public class FollowUpController(FollowUpService followUpService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetFollowUps()
|
||||
{
|
||||
var followUps = Role == "doctor"
|
||||
? await followUpService.GetDoctorFollowUpsAsync(UserId)
|
||||
: await followUpService.GetPatientFollowUpsAsync(UserId);
|
||||
|
||||
return Ok(followUps.Select(f => new
|
||||
{
|
||||
f.Id, f.PatientId, f.DoctorId, f.Title, f.Description,
|
||||
f.ScheduledAt, f.Status, f.Notes, f.ReminderEnabled, f.CreatedAt,
|
||||
PatientName = f.Patient?.Name,
|
||||
DoctorName = f.Doctor?.Name,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetFollowUp(Guid id)
|
||||
{
|
||||
var followUp = await followUpService.GetByIdAsync(id);
|
||||
if (followUp == null) return NotFound(new { message = "复查不存在" });
|
||||
return Ok(new
|
||||
{
|
||||
followUp.Id, followUp.PatientId, followUp.DoctorId, followUp.Title,
|
||||
followUp.Description, followUp.ScheduledAt, followUp.Status,
|
||||
followUp.Notes, followUp.ReminderEnabled, followUp.CreatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddFollowUp([FromBody] FollowUpCreateRequest request)
|
||||
{
|
||||
var followUp = await followUpService.AddAsync(UserId, request.Title, request.Description,
|
||||
request.ScheduledAt, request.ReminderEnabled);
|
||||
return Ok(new { followUp.Id, followUp.Title, followUp.Status });
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Roles = "doctor")]
|
||||
public async Task<IActionResult> UpdateFollowUp(Guid id, [FromBody] FollowUpUpdateRequest request)
|
||||
{
|
||||
var followUp = await followUpService.UpdateAsync(id, UserId, request.Title, request.Description,
|
||||
request.ScheduledAt, request.Status, request.Notes);
|
||||
if (followUp == null) return NotFound(new { message = "复查不存在" });
|
||||
return Ok(new { followUp.Id, followUp.Title, followUp.Status });
|
||||
}
|
||||
}
|
||||
|
||||
public record FollowUpCreateRequest(string Title, string? Description, DateTime ScheduledAt, bool ReminderEnabled = true);
|
||||
|
||||
public record FollowUpUpdateRequest(
|
||||
string? Title, string? Description, DateTime? ScheduledAt, string? Status, string? Notes);
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/health-records")]
|
||||
[Authorize]
|
||||
public class HealthController(HealthService healthService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetRecords([FromQuery] string? type, [FromQuery] int days = 30)
|
||||
{
|
||||
var targetUserId = UserId;
|
||||
|
||||
// Doctors can query any patient
|
||||
if (Role == "doctor" && Request.Query.ContainsKey("patientId"))
|
||||
targetUserId = Guid.Parse(Request.Query["patientId"]!);
|
||||
|
||||
var records = await healthService.GetRecordsAsync(targetUserId, type, days);
|
||||
return Ok(records.Select(r => new
|
||||
{
|
||||
r.Id, r.Type, Value = r.Value.RootElement.GetRawText(), r.Unit,
|
||||
r.RecordedAt, r.Source, r.Notes, r.CreatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("stats")]
|
||||
public async Task<IActionResult> GetStats()
|
||||
{
|
||||
var stats = await healthService.GetStatsAsync(UserId);
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
[HttpGet("latest/{type}")]
|
||||
public async Task<IActionResult> GetLatest(string type)
|
||||
{
|
||||
var record = await healthService.GetLatestAsync(UserId, type);
|
||||
if (record == null) return Ok((object?)null);
|
||||
return Ok(new { record.Id, record.Type, Value = record.Value.RootElement.GetRawText(), record.Unit, record.RecordedAt, record.Source });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddRecord([FromBody] HealthRecordCreateRequest request)
|
||||
{
|
||||
// Validate JSON
|
||||
try { JsonDocument.Parse(request.ValueJson); }
|
||||
catch (JsonException) { return BadRequest(new { message = "无效的数据格式" }); }
|
||||
|
||||
var record = await healthService.AddRecordAsync(UserId, request.Type, request.ValueJson, request.Unit, request.RecordedAt, request.Notes);
|
||||
return Ok(new { record.Id, record.Type, Value = record.Value.RootElement.GetRawText(), record.Unit, record.RecordedAt, record.Source });
|
||||
}
|
||||
}
|
||||
|
||||
public record HealthRecordCreateRequest(string Type, string ValueJson, string Unit, DateTime RecordedAt, string? Notes);
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/medications")]
|
||||
[Authorize]
|
||||
public class MedicationController(MedicationService medicationService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetMedications()
|
||||
{
|
||||
var targetUserId = UserId;
|
||||
if (Role == "doctor" && Request.Query.ContainsKey("patientId"))
|
||||
targetUserId = Guid.Parse(Request.Query["patientId"]!);
|
||||
|
||||
var medications = await medicationService.GetUserMedicationsAsync(targetUserId);
|
||||
return Ok(medications.Select(m => new
|
||||
{
|
||||
m.Id, m.UserId, m.DrugName, m.Dosage, m.Frequency, m.TimeSlots,
|
||||
m.StartDate, m.EndDate, m.Notes, m.Status, m.CreatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddMedication([FromBody] MedicationCreateRequest request)
|
||||
{
|
||||
var med = await medicationService.AddAsync(UserId, request.DrugName, request.Dosage,
|
||||
request.Frequency, request.TimeSlots, request.StartDate, request.EndDate, request.Notes);
|
||||
return Ok(new { med.Id, med.DrugName, med.Dosage, med.Frequency, med.Status });
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetMedication(Guid id)
|
||||
{
|
||||
var med = await medicationService.GetByIdAsync(id);
|
||||
if (med == null) return NotFound(new { message = "药品不存在" });
|
||||
return Ok(new
|
||||
{
|
||||
med.Id, med.UserId, med.DrugName, med.Dosage, med.Frequency, med.TimeSlots,
|
||||
med.StartDate, med.EndDate, med.Notes, med.Status, med.CreatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/records")]
|
||||
public async Task<IActionResult> GetRecords(Guid id)
|
||||
{
|
||||
var records = await medicationService.GetRecordsAsync(id);
|
||||
return Ok(records.Select(r => new
|
||||
{
|
||||
r.Id, r.MedicationId, r.TimeSlot, r.TakenAt, r.IsTaken, r.SkippedReason, r.CreatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/take")]
|
||||
public async Task<IActionResult> MarkTaken(Guid id, [FromBody] MarkTakenRequest request)
|
||||
{
|
||||
var record = await medicationService.MarkTakenAsync(id, UserId, request.TimeSlot);
|
||||
return Ok(new { record.Id, record.TimeSlot, record.TakenAt, record.IsTaken });
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/adherence")]
|
||||
public async Task<IActionResult> GetAdherence(Guid id)
|
||||
{
|
||||
var rate = await medicationService.GetAdherenceRateAsync(id);
|
||||
return Ok(new { medicationId = id, rate });
|
||||
}
|
||||
}
|
||||
|
||||
public record MedicationCreateRequest(
|
||||
string DrugName, string Dosage, string Frequency,
|
||||
List<string> TimeSlots, DateOnly StartDate, DateOnly? EndDate, string? Notes);
|
||||
|
||||
public record MarkTakenRequest(string TimeSlot);
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/notifications")]
|
||||
[Authorize]
|
||||
public class NotificationController(NotificationService notificationService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetNotifications()
|
||||
{
|
||||
var notifications = await notificationService.GetUserNotificationsAsync(UserId);
|
||||
return Ok(notifications.Select(n => new
|
||||
{
|
||||
n.Id, n.Type, n.Title, n.Content, n.RelatedId,
|
||||
n.IsRead, n.ReadAt, n.CreatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("unread-count")]
|
||||
public async Task<IActionResult> GetUnreadCount()
|
||||
{
|
||||
var count = await notificationService.GetUnreadCountAsync(UserId);
|
||||
return Ok(new { count });
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/read")]
|
||||
public async Task<IActionResult> MarkAsRead(Guid id)
|
||||
{
|
||||
await notificationService.MarkAsReadAsync(id);
|
||||
return Ok(new { message = "已标记为已读" });
|
||||
}
|
||||
|
||||
[HttpPut("read-all")]
|
||||
public async Task<IActionResult> MarkAllAsRead()
|
||||
{
|
||||
await notificationService.MarkAllAsReadAsync(UserId);
|
||||
return Ok(new { message = "全部已读" });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/patients")]
|
||||
[Authorize(Roles = "doctor")]
|
||||
public class PatientController(PatientService patientService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPatients([FromQuery] string? search, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var patients = await patientService.GetPatientsAsync(search, null, page, pageSize);
|
||||
return Ok(patients);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetPatientDetail(Guid id)
|
||||
{
|
||||
var patient = await patientService.GetPatientDetailAsync(id);
|
||||
if (patient == null) return NotFound(new { message = "患者不存在" });
|
||||
return Ok(patient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Security.Claims;
|
||||
using HealthManager.Application.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HealthManager.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/reports")]
|
||||
[Authorize]
|
||||
public class ReportController(ReportService reportService) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private string Role => User.FindFirstValue(ClaimTypes.Role)!;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetReports()
|
||||
{
|
||||
var targetUserId = UserId;
|
||||
if (Role == "doctor" && Request.Query.ContainsKey("patientId"))
|
||||
targetUserId = Guid.Parse(Request.Query["patientId"]!);
|
||||
|
||||
var reports = await reportService.GetPatientReportsAsync(targetUserId);
|
||||
return Ok(reports.Select(r => new
|
||||
{
|
||||
r.Id, r.PatientId, r.Title, r.Category, r.ImageUrls, r.Status,
|
||||
r.RiskLevel, r.UploadedAt, r.CompletedAt,
|
||||
DoctorName = r.Doctor?.Name,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("pending")]
|
||||
[Authorize(Roles = "doctor")]
|
||||
public async Task<IActionResult> GetPending()
|
||||
{
|
||||
var reports = await reportService.GetPendingAsync();
|
||||
return Ok(reports.Select(r => new
|
||||
{
|
||||
r.Id, r.PatientId, r.Title, r.Category, r.Status, r.UploadedAt,
|
||||
PatientName = r.Patient?.Name,
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetReport(Guid id)
|
||||
{
|
||||
var report = await reportService.GetByIdAsync(id);
|
||||
if (report == null) return NotFound(new { message = "报告不存在" });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
report.Id, report.PatientId, report.Title, report.Category, report.ImageUrls,
|
||||
report.Status, report.RiskLevel, report.Summary, report.Suggestions,
|
||||
report.UploadedAt, report.CompletedAt,
|
||||
PatientName = report.Patient?.Name,
|
||||
DoctorName = report.Doctor?.Name,
|
||||
Items = report.Items.Select(i => new
|
||||
{
|
||||
i.Id, i.ItemName, i.ResultValue, i.Unit, i.ReferenceRange, i.IsAbnormal,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> UploadReport([FromBody] ReportUploadRequest request)
|
||||
{
|
||||
var report = await reportService.UploadAsync(UserId, request.Title, request.Category, request.ImageUrls);
|
||||
return Ok(new { report.Id, report.Title, report.Status });
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/interpret")]
|
||||
[Authorize(Roles = "doctor")]
|
||||
public async Task<IActionResult> InterpretReport(Guid id, [FromBody] ReportInterpretRequest request)
|
||||
{
|
||||
var items = request.Items.Select(i => (i.ItemName, i.ResultValue, i.Unit, i.ReferenceRange, i.IsAbnormal)).ToList();
|
||||
var report = await reportService.InterpretAsync(id, UserId, request.Summary, items, request.RiskLevel, request.Suggestions);
|
||||
return Ok(new { report.Id, report.Status, report.RiskLevel });
|
||||
}
|
||||
}
|
||||
|
||||
public record ReportUploadRequest(string Title, string Category, List<string> ImageUrls);
|
||||
|
||||
public record ReportInterpretRequest(
|
||||
string Summary, List<ReportItemRequest> Items, string RiskLevel, string? Suggestions);
|
||||
|
||||
public record ReportItemRequest(
|
||||
string ItemName, string ResultValue, string? Unit, string? ReferenceRange, bool IsAbnormal);
|
||||
Reference in New Issue
Block a user