feat: 后台管理页全量重构 + backoffice 共享模块 + 启动脚本优化
## 后台管理页重构 - admin 端: add_doctor / doctors / home / patients 四页重构 - doctor 端: consultations / dashboard / followup_edit / followups / home / patient_detail / patients / profile / report_detail / reports / settings 十一页重构 - 新增 backoffice 共享模块: backoffice_refresh_providers + backoffice_formatters + backoffice_ui ## 后端 - AdminService 增强(分页/搜索/统计) - doctor_endpoints 微调 - appsettings.Development 调整 - 新增 admin_service_tests ## 前端其他 - 今日健康卡片 taskRow 行高 5->7(每行 44->48px) - 健康仪表盘配色定 #4FACFE 纯色蓝 - admin_drawer / doctor_drawer 微调 - api_client IP 适配 ## 启动脚本 - start-dev.bat: 加后端就绪检测(轮询 openapi 端点, 最多等 30s) ## 清理 - 删除旧品牌图(agent_welcome_abstract / login_background_v1) - 删除 app_status_badge 组件 - 删除旧 HANDOFF 文档, 新增 07-17 版 ## 测试 - 新增 backoffice_formatters / backoffice_refresh / backoffice_ui 三个测试 - app_router_test 扩展
This commit is contained in:
@@ -16,25 +16,32 @@ public sealed class AdminService(AppDbContext db) : IAdminService
|
||||
|
||||
public async Task<AdminResult> AddDoctorAsync(AddDoctorCommand command, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command.Phone)) return Error(40001, "手机号不能为空");
|
||||
if (string.IsNullOrWhiteSpace(command.Name)) return Error(40002, "姓名不能为空");
|
||||
var phone = command.Phone.Trim();
|
||||
var name = command.Name.Trim();
|
||||
if (string.IsNullOrWhiteSpace(phone)) return Error(40001, "手机号不能为空");
|
||||
if (string.IsNullOrWhiteSpace(name)) return Error(40002, "姓名不能为空");
|
||||
if (await _db.Users.AnyAsync(x => x.Phone == phone, ct) ||
|
||||
await _db.Doctors.AnyAsync(x => x.Phone == phone, ct))
|
||||
return Error(40003, "该手机号已被其他账号使用");
|
||||
|
||||
var doctor = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(), Name = command.Name, Title = command.Title, Department = command.Department,
|
||||
Phone = command.Phone, ProfessionalDirection = command.ProfessionalDirection, IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
Id = Guid.NewGuid(), Name = name, Title = command.Title?.Trim(), Department = command.Department?.Trim(),
|
||||
Phone = phone, ProfessionalDirection = command.ProfessionalDirection?.Trim(), IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(), Phone = phone, Role = "Doctor", Name = name,
|
||||
CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
_db.Doctors.Add(doctor);
|
||||
if (!await _db.Users.AnyAsync(x => x.Phone == command.Phone, ct))
|
||||
_db.Users.Add(user);
|
||||
_db.DoctorProfiles.Add(new DoctorProfile
|
||||
{
|
||||
var user = new User { Id = Guid.NewGuid(), Phone = command.Phone, Role = "Doctor", Name = command.Name, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow };
|
||||
_db.Users.Add(user);
|
||||
_db.DoctorProfiles.Add(new DoctorProfile
|
||||
{
|
||||
Id = Guid.NewGuid(), UserId = user.Id, DoctorId = doctor.Id, Name = command.Name,
|
||||
Title = command.Title, Department = command.Department, IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
Id = Guid.NewGuid(), UserId = user.Id, DoctorId = doctor.Id, Name = name,
|
||||
Title = doctor.Title, Department = doctor.Department, IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
|
||||
});
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id, doctor.Name });
|
||||
}
|
||||
@@ -43,11 +50,43 @@ public sealed class AdminService(AppDbContext db) : IAdminService
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
if (command.Name != null) doctor.Name = command.Name;
|
||||
if (command.Title != null) doctor.Title = command.Title;
|
||||
if (command.Department != null) doctor.Department = command.Department;
|
||||
if (command.Phone != null) doctor.Phone = command.Phone;
|
||||
if (command.ProfessionalDirection != null) doctor.ProfessionalDirection = command.ProfessionalDirection;
|
||||
var profile = await _db.DoctorProfiles.Include(x => x.User)
|
||||
.FirstOrDefaultAsync(x => x.DoctorId == id, ct);
|
||||
if (profile == null) return Error(40901, "医生登录资料不完整,请联系技术人员修复");
|
||||
|
||||
var user = profile.User;
|
||||
if (command.Phone != null)
|
||||
{
|
||||
var phone = command.Phone.Trim();
|
||||
if (phone.Length == 0) return Error(40001, "手机号不能为空");
|
||||
if (await _db.Users.AnyAsync(x => x.Id != user.Id && x.Phone == phone, ct) ||
|
||||
await _db.Doctors.AnyAsync(x => x.Id != id && x.Phone == phone, ct))
|
||||
return Error(40003, "该手机号已被其他账号使用");
|
||||
doctor.Phone = phone;
|
||||
user.Phone = phone;
|
||||
}
|
||||
if (command.Name != null)
|
||||
{
|
||||
var name = command.Name.Trim();
|
||||
if (name.Length == 0) return Error(40002, "姓名不能为空");
|
||||
doctor.Name = name;
|
||||
user.Name = name;
|
||||
profile.Name = name;
|
||||
}
|
||||
if (command.Title != null)
|
||||
{
|
||||
doctor.Title = command.Title.Trim();
|
||||
profile.Title = doctor.Title;
|
||||
}
|
||||
if (command.Department != null)
|
||||
{
|
||||
doctor.Department = command.Department.Trim();
|
||||
profile.Department = doctor.Department;
|
||||
}
|
||||
if (command.ProfessionalDirection != null)
|
||||
doctor.ProfessionalDirection = command.ProfessionalDirection.Trim();
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
profile.UpdatedAt = DateTime.UtcNow;
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id });
|
||||
}
|
||||
@@ -56,7 +95,11 @@ public sealed class AdminService(AppDbContext db) : IAdminService
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
var profile = await _db.DoctorProfiles.FirstOrDefaultAsync(x => x.DoctorId == id, ct);
|
||||
if (profile == null) return Error(40901, "医生登录资料不完整,请联系技术人员修复");
|
||||
doctor.IsActive = !doctor.IsActive;
|
||||
profile.IsActive = doctor.IsActive;
|
||||
profile.UpdatedAt = DateTime.UtcNow;
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id, doctor.IsActive });
|
||||
}
|
||||
@@ -65,7 +108,22 @@ public sealed class AdminService(AppDbContext db) : IAdminService
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
await _db.Users.Where(x => x.DoctorId == id).ExecuteUpdateAsync(s => s.SetProperty(x => x.DoctorId, (Guid?)null), ct);
|
||||
|
||||
if (await _db.Users.AnyAsync(x => x.Role == "User" && x.DoctorId == id, ct))
|
||||
return Error(40004, "该医生仍有绑定患者,请先停用,不能直接删除");
|
||||
if (await _db.Consultations.AnyAsync(x => x.DoctorId == id, ct))
|
||||
return Error(40005, "该医生已有问诊记录,只能停用,不能删除");
|
||||
|
||||
var profile = await _db.DoctorProfiles.Include(x => x.User)
|
||||
.FirstOrDefaultAsync(x => x.DoctorId == id, ct);
|
||||
if (profile != null)
|
||||
{
|
||||
var user = profile.User;
|
||||
var refreshTokens = await _db.RefreshTokens.Where(x => x.UserId == user.Id).ToListAsync(ct);
|
||||
_db.RefreshTokens.RemoveRange(refreshTokens);
|
||||
_db.DoctorProfiles.Remove(profile);
|
||||
_db.Users.Remove(user);
|
||||
}
|
||||
_db.Doctors.Remove(doctor);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { success = true });
|
||||
|
||||
@@ -105,6 +105,7 @@ public static class DoctorEndpoints
|
||||
doctorHospital = profile.Hospital,
|
||||
doctorAvatar = profile.AvatarUrl,
|
||||
doctorOnline = profile.IsOnline,
|
||||
doctorActive = profile.IsActive,
|
||||
stats = new { totalPatients, activeConsultations, pendingReports, todayFollowUps },
|
||||
pendingConsultations,
|
||||
pendingReports = pendingReportList,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
backend/src/Health.WebApi/wwwroot/app-icon.png
Normal file
BIN
backend/src/Health.WebApi/wwwroot/app-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Reference in New Issue
Block a user