feat: 医生端完整实现 + 双角色登录 + 健康日历 + 饮食记录重做 + 配色系统重构
后端: - User表加Role字段 + DoctorProfile表 + 废弃旧Doctor数据 - JWT加Role claim + 注册/登录拆分(/api/auth/register + /api/auth/login) - 医生端点全量加JWT鉴权+Role校验(15个端点) - 日历端点改造(用药/运动/随访按日期+星期匹配) - 饮食记录PUT端点 + 健康记录DELETE端点 - 用户Profile端点返回Role 前端: - 登录页重做(角色选择卡片 用户紫/医生蓝 + 审核码6666 + 注册返登录) - App路由分流(userRole→患者端/医生端) - 医生端: 工作台Dashboard + 患者管理(搜索分页) + 问诊/报告/随访CRUD - 报告审核: AI预分析+指标表格+严重程度4级+模板多选+评语 - 随访编辑: 患者选择器+日期时间+打通患者端 - 健康日历: 可点击日历+当日安排列表+用药/运动/随访颜色标记 - 饮食记录: 热量趋势7/30天+日历(一周)+当日胶囊详情+侧滑编辑删除 - 健康档案: 日期选择器+手术可添加多条+白底渐变保存按钮 - 配色系统: 清理25个未使用色+新增doctorBlue/drawerGradient/pageGrey - 患者端侧边栏: 新渐变+白底按钮+紫色汉堡图标 - AI对话: 用户气泡白底黑字+药管家改蓝色 - App启动闪屏: 防登录页闪烁 文档: color_design_system.md(20页面完整配色设计+图标底色对照表)
This commit is contained in:
@@ -7,6 +7,11 @@ namespace Health.WebApi.Endpoints;
|
||||
/// </summary>
|
||||
public static class AuthEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// 医生注册审核码(后期可移到配置表)
|
||||
/// </summary>
|
||||
private const string DoctorInviteCode = "6666";
|
||||
|
||||
public static void MapAuthEndpoints(this WebApplication app)
|
||||
{
|
||||
// 发送短信验证码
|
||||
@@ -34,14 +39,14 @@ public static class AuthEndpoints
|
||||
return Results.Ok(new { code = 0, data = new { success = true, devCode = code }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 手机号+验证码登录
|
||||
app.MapPost("/api/auth/login", async (
|
||||
LoginRequest request,
|
||||
// ── 注册(新用户,需选身份)──
|
||||
app.MapPost("/api/auth/register", async (
|
||||
RegisterRequest request,
|
||||
AppDbContext db,
|
||||
JwtProvider jwt,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
// 开发阶段:任意6位数字通过
|
||||
// 验证码
|
||||
var validCode = await db.VerificationCodes
|
||||
.Where(v => v.Phone == request.Phone
|
||||
&& v.Code == request.SmsCode
|
||||
@@ -55,44 +60,49 @@ public static class AuthEndpoints
|
||||
|
||||
validCode.IsUsed = true;
|
||||
|
||||
// 查找或创建用户
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
var isNew = false;
|
||||
if (user == null)
|
||||
// 检查手机号是否已注册
|
||||
var existing = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
if (existing != null)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "该手机号已注册,请直接登录" });
|
||||
|
||||
// 校验角色
|
||||
var role = request.Role;
|
||||
if (role != "User" && role != "Doctor")
|
||||
return Results.Ok(new { code = 40003, data = (object?)null, message = "角色参数无效" });
|
||||
|
||||
// 医生注册需校验审核码
|
||||
if (role == "Doctor" && request.InviteCode != DoctorInviteCode)
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "审核码错误" });
|
||||
|
||||
// 创建用户
|
||||
var user = new User
|
||||
{
|
||||
user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
isNew = true;
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
Role = role,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
|
||||
// 创建默认通知偏好
|
||||
db.NotificationPreferences.Add(new NotificationPreference
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
});
|
||||
|
||||
// 创建空健康档案
|
||||
db.HealthArchives.Add(new HealthArchive
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
});
|
||||
// 用户端:创建通知偏好+健康档案
|
||||
if (role == "User")
|
||||
{
|
||||
db.NotificationPreferences.Add(new NotificationPreference { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
db.HealthArchives.Add(new HealthArchive { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
}
|
||||
|
||||
// 医生端:创建空医生档案
|
||||
if (role == "Doctor")
|
||||
{
|
||||
db.DoctorProfiles.Add(new DoctorProfile { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
}
|
||||
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// 生成 token
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone);
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, role);
|
||||
var refreshToken = jwt.GenerateRefreshToken();
|
||||
|
||||
// 保存 refresh token
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -109,15 +119,65 @@ public static class AuthEndpoints
|
||||
{
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user = new
|
||||
{
|
||||
user.Id,
|
||||
user.Phone,
|
||||
user.Name,
|
||||
user.Gender,
|
||||
user = new {
|
||||
user.Id, user.Phone, user.Role, isNew = true
|
||||
}
|
||||
},
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
|
||||
// ── 登录(已有账号)──
|
||||
app.MapPost("/api/auth/login", async (
|
||||
LoginRequest request,
|
||||
AppDbContext db,
|
||||
JwtProvider jwt,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var validCode = await db.VerificationCodes
|
||||
.Where(v => v.Phone == request.Phone
|
||||
&& v.Code == request.SmsCode
|
||||
&& v.ExpiresAt > DateTime.UtcNow
|
||||
&& !v.IsUsed)
|
||||
.OrderByDescending(v => v.CreatedAt)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (validCode == null)
|
||||
return Results.Ok(new { code = 40001, data = (object?)null, message = "验证码错误或已过期" });
|
||||
|
||||
validCode.IsUsed = true;
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
if (user == null)
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "该手机号未注册,请先注册" });
|
||||
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, user.Role);
|
||||
var refreshToken = jwt.GenerateRefreshToken();
|
||||
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
Token = refreshToken,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new
|
||||
{
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user = new {
|
||||
user.Id, user.Phone, user.Role, user.Name,
|
||||
user.Gender, user.AvatarUrl,
|
||||
BirthDate = user.BirthDate?.ToString("yyyy-MM-dd"),
|
||||
user.AvatarUrl,
|
||||
isNew
|
||||
isNew = false
|
||||
}
|
||||
},
|
||||
message = (string?)null
|
||||
@@ -145,7 +205,7 @@ public static class AuthEndpoints
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "用户不存在" });
|
||||
|
||||
// 生成新 token(续期)
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone);
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, user.Role);
|
||||
var newRefreshToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
@@ -159,7 +219,7 @@ public static class AuthEndpoints
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new { accessToken, refreshToken = newRefreshToken },
|
||||
data = new { accessToken, refreshToken = newRefreshToken, user = new { user.Role } },
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
@@ -182,5 +242,6 @@ public static class AuthEndpoints
|
||||
|
||||
// ---- 请求 DTO ----
|
||||
public sealed record SendSmsRequest(string Phone);
|
||||
public sealed record RegisterRequest(string Phone, string SmsCode, string? Role, string? InviteCode);
|
||||
public sealed record LoginRequest(string Phone, string SmsCode);
|
||||
public sealed record RefreshRequest(string RefreshToken);
|
||||
|
||||
Reference in New Issue
Block a user