664 lines
27 KiB
C#
664 lines
27 KiB
C#
using Evergreen.Health.Application.Health.Entities;
|
||
using Evergreen.Health.Application.Health.Models;
|
||
using Evergreen.Health.Application.Health.Security;
|
||
using Furion.DataEncryption;
|
||
using Furion.DatabaseAccessor;
|
||
using Furion.FriendlyException;
|
||
using Microsoft.AspNetCore.Http;
|
||
using SqlSugar;
|
||
using System.Security.Claims;
|
||
using System.Text.Json;
|
||
|
||
namespace Evergreen.Health.Application.Health.Services;
|
||
|
||
[ApiDescriptionSettings("健康管理", Name = "Health", Order = 50)]
|
||
public sealed class HealthAppService : IDynamicApiController, ITransient
|
||
{
|
||
private const string HealthUserIdClaim = "health_user_id";
|
||
private static readonly HashSet<string> SupportedModules = new(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
"sleep", "exercise", "diet"
|
||
};
|
||
|
||
private readonly SqlSugarRepository<HealthUser> _userRepository;
|
||
private readonly SqlSugarRepository<HealthProfile> _profileRepository;
|
||
private readonly SqlSugarRepository<HealthModuleSelection> _moduleRepository;
|
||
private readonly SqlSugarRepository<HealthPlan> _planRepository;
|
||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||
|
||
public HealthAppService(
|
||
SqlSugarRepository<HealthUser> userRepository,
|
||
SqlSugarRepository<HealthProfile> profileRepository,
|
||
SqlSugarRepository<HealthModuleSelection> moduleRepository,
|
||
SqlSugarRepository<HealthPlan> planRepository,
|
||
IHttpContextAccessor httpContextAccessor)
|
||
{
|
||
_userRepository = userRepository;
|
||
_profileRepository = profileRepository;
|
||
_moduleRepository = moduleRepository;
|
||
_planRepository = planRepository;
|
||
_httpContextAccessor = httpContextAccessor;
|
||
}
|
||
|
||
[AllowAnonymous]
|
||
[HttpPost("/api/health/auth/register")]
|
||
[UnitOfWork]
|
||
public async Task<HealthAuthOutput> Register(HealthRegisterInput input)
|
||
{
|
||
var phone = input.Phone.Trim();
|
||
if (await _userRepository.IsAnyAsync(user => user.Phone == phone && !user.IsDelete))
|
||
throw Oops.Oh("该手机号已经注册");
|
||
|
||
var user = await _userRepository.AsInsertable(new HealthUser
|
||
{
|
||
Phone = phone,
|
||
DisplayName = input.DisplayName.Trim(),
|
||
PasswordHash = HealthPasswordHasher.Hash(input.Password),
|
||
Status = HealthUserStatus.Enabled
|
||
}).ExecuteReturnEntityAsync();
|
||
|
||
await _profileRepository.InsertAsync(new HealthProfile { UserId = user.Id });
|
||
await _moduleRepository.InsertRangeAsync(new List<HealthModuleSelection>
|
||
{
|
||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "sleep", IsEnabled = true },
|
||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "exercise", IsEnabled = true },
|
||
new HealthModuleSelection { UserId = user.Id, ModuleCode = "diet", IsEnabled = true }
|
||
});
|
||
|
||
return CreateAuthOutput(user);
|
||
}
|
||
|
||
[AllowAnonymous]
|
||
[HttpPost("/api/health/auth/login")]
|
||
public async Task<HealthAuthOutput> Login(HealthLoginInput input)
|
||
{
|
||
var phone = input.Phone.Trim();
|
||
var user = await _userRepository.GetFirstAsync(item => item.Phone == phone && !item.IsDelete);
|
||
if (user is null || !HealthPasswordHasher.Verify(input.Password, user.PasswordHash))
|
||
throw Oops.Oh("手机号或密码错误").StatusCode(401);
|
||
if (user.Status != HealthUserStatus.Enabled)
|
||
throw Oops.Oh("账号已停用").StatusCode(403);
|
||
|
||
user.LastLoginTime = DateTime.Now;
|
||
user.LastLoginIp = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
|
||
await _userRepository.AsUpdateable(user)
|
||
.UpdateColumns(item => new { item.LastLoginTime, item.LastLoginIp })
|
||
.ExecuteCommandAsync();
|
||
|
||
return CreateAuthOutput(user);
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpGet("/api/health/dashboard")]
|
||
public async Task<HealthDashboardOutput> GetDashboard()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var user = await GetCurrentUser(userId);
|
||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||
var modules = await _moduleRepository.AsQueryable()
|
||
.Where(item => item.UserId == userId && item.IsEnabled)
|
||
.Select(item => item.ModuleCode)
|
||
.ToListAsync();
|
||
var plan = await _planRepository.AsQueryable()
|
||
.Where(item => item.UserId == userId && item.IsCurrent)
|
||
.OrderByDescending(item => item.Version)
|
||
.FirstAsync();
|
||
|
||
var hasCompletedAssessment = profile != null && !string.IsNullOrWhiteSpace(profile.AssessmentJson);
|
||
decimal? bmi = null;
|
||
var bmiStatus = string.Empty;
|
||
if (profile?.HeightCm > 0 && profile.WeightKg > 0)
|
||
{
|
||
var h = profile.HeightCm.Value / 100m;
|
||
bmi = Math.Round(profile.WeightKg.Value / (h * h), 1);
|
||
bmiStatus = bmi < 18.5m ? "偏瘦" : bmi <= 23.9m ? "正常" : bmi <= 27.9m ? "超重" : "肥胖";
|
||
}
|
||
|
||
PersonalizedSceneConfig? sceneConfig = null;
|
||
if (!string.IsNullOrWhiteSpace(profile?.CustomSceneConfigJson))
|
||
{
|
||
try
|
||
{
|
||
sceneConfig = JsonSerializer.Deserialize<PersonalizedSceneConfig>(profile.CustomSceneConfigJson);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
if (sceneConfig == null && hasCompletedAssessment)
|
||
{
|
||
var answers = JsonSerializer.Deserialize<HealthAssessmentAnswers>(profile!.AssessmentJson!) ?? new HealthAssessmentAnswers();
|
||
sceneConfig = CalculatePersonalizedSceneConfig(answers, MapProfile(profile));
|
||
}
|
||
|
||
return new HealthDashboardOutput
|
||
{
|
||
User = MapUser(user),
|
||
Profile = profile is null ? null : MapProfile(profile),
|
||
SelectedModules = modules,
|
||
CurrentPlan = plan is null ? null : MapPlan(plan),
|
||
HasCompletedAssessment = hasCompletedAssessment,
|
||
Bmi = bmi,
|
||
BmiStatus = bmiStatus,
|
||
SceneConfig = sceneConfig
|
||
};
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpGet("/api/health/assessment")]
|
||
public async Task<HealthAssessmentOutput> GetAssessment()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var profile = await _profileRepository.GetFirstAsync(p => p.UserId == userId);
|
||
if (profile == null)
|
||
{
|
||
return new HealthAssessmentOutput
|
||
{
|
||
HasCompletedAssessment = false,
|
||
BasicInfo = new SaveHealthProfileInput(),
|
||
Answers = new HealthAssessmentAnswers(),
|
||
SceneConfig = new PersonalizedSceneConfig()
|
||
};
|
||
}
|
||
|
||
var answers = string.IsNullOrWhiteSpace(profile.AssessmentJson)
|
||
? new HealthAssessmentAnswers()
|
||
: JsonSerializer.Deserialize<HealthAssessmentAnswers>(profile.AssessmentJson) ?? new HealthAssessmentAnswers();
|
||
|
||
var hasCompleted = !string.IsNullOrWhiteSpace(profile.AssessmentJson);
|
||
var sceneConfig = string.IsNullOrWhiteSpace(profile.CustomSceneConfigJson)
|
||
? CalculatePersonalizedSceneConfig(answers, MapProfile(profile))
|
||
: JsonSerializer.Deserialize<PersonalizedSceneConfig>(profile.CustomSceneConfigJson) ?? new PersonalizedSceneConfig();
|
||
|
||
decimal? bmi = null;
|
||
var bmiStatus = "正常";
|
||
if (profile.HeightCm > 0 && profile.WeightKg > 0)
|
||
{
|
||
var h = profile.HeightCm.Value / 100m;
|
||
bmi = Math.Round(profile.WeightKg.Value / (h * h), 1);
|
||
bmiStatus = bmi < 18.5m ? "偏瘦" : bmi <= 23.9m ? "正常" : bmi <= 27.9m ? "超重" : "肥胖";
|
||
}
|
||
|
||
return new HealthAssessmentOutput
|
||
{
|
||
HasCompletedAssessment = hasCompleted,
|
||
BasicInfo = MapProfile(profile),
|
||
Answers = answers,
|
||
SceneConfig = sceneConfig,
|
||
Bmi = bmi,
|
||
BmiStatus = bmiStatus
|
||
};
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpPost("/api/health/assessment")]
|
||
[UnitOfWork]
|
||
public async Task<HealthAssessmentOutput> SaveAssessment(SaveHealthAssessmentInput input)
|
||
{
|
||
ValidateAssessment(input);
|
||
var userId = GetCurrentUserId();
|
||
var profile = await _profileRepository.GetFirstAsync(p => p.UserId == userId);
|
||
var calculatedConfig = CalculatePersonalizedSceneConfig(input.Answers, input.BasicInfo);
|
||
|
||
if (profile == null)
|
||
{
|
||
profile = new HealthProfile { UserId = userId };
|
||
ApplyProfile(profile, input.BasicInfo);
|
||
profile.AssessmentJson = JsonSerializer.Serialize(input.Answers);
|
||
profile.CustomSceneConfigJson = JsonSerializer.Serialize(calculatedConfig);
|
||
await _profileRepository.InsertAsync(profile);
|
||
}
|
||
else
|
||
{
|
||
ApplyProfile(profile, input.BasicInfo);
|
||
profile.AssessmentJson = JsonSerializer.Serialize(input.Answers);
|
||
profile.CustomSceneConfigJson = JsonSerializer.Serialize(calculatedConfig);
|
||
await _profileRepository.UpdateAsync(profile);
|
||
}
|
||
|
||
var modules = new List<string> { "sleep", "exercise", "diet" };
|
||
var existingModules = await _moduleRepository.AsQueryable().Where(m => m.UserId == userId).ToListAsync();
|
||
foreach (var mod in modules)
|
||
{
|
||
var existing = existingModules.FirstOrDefault(m => m.ModuleCode == mod);
|
||
if (existing == null)
|
||
{
|
||
await _moduleRepository.InsertAsync(new HealthModuleSelection { UserId = userId, ModuleCode = mod, IsEnabled = true });
|
||
}
|
||
else if (!existing.IsEnabled)
|
||
{
|
||
existing.IsEnabled = true;
|
||
await _moduleRepository.UpdateAsync(existing);
|
||
}
|
||
}
|
||
|
||
var maxVersion = await _planRepository.AsQueryable().Where(p => p.UserId == userId).MaxAsync(p => (int?)p.Version) ?? 0;
|
||
await _planRepository.UpdateAsync(p => new HealthPlan { IsCurrent = false }, p => p.UserId == userId && p.IsCurrent);
|
||
var planItems = BuildPlanItems(profile, modules);
|
||
var plan = new HealthPlan
|
||
{
|
||
UserId = userId,
|
||
Version = maxVersion + 1,
|
||
Title = $"个性化健康方案 v{maxVersion + 1}.0",
|
||
Summary = BuildAssessmentPlanSummary(profile, input.Answers),
|
||
ParametersJson = JsonSerializer.Serialize(planItems),
|
||
IsCurrent = true
|
||
};
|
||
await _planRepository.InsertAsync(plan);
|
||
|
||
return await GetAssessment();
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpPut("/api/health/profile")]
|
||
public async Task<SaveHealthProfileInput> SaveProfile(SaveHealthProfileInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||
if (profile is null)
|
||
{
|
||
profile = new HealthProfile { UserId = userId };
|
||
ApplyProfile(profile, input);
|
||
await _profileRepository.InsertAsync(profile);
|
||
}
|
||
else
|
||
{
|
||
ApplyProfile(profile, input);
|
||
await _profileRepository.UpdateAsync(profile);
|
||
}
|
||
|
||
return MapProfile(profile);
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpPut("/api/health/modules")]
|
||
[UnitOfWork]
|
||
public async Task<List<string>> SaveModules(SaveHealthModulesInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var modules = input.ModuleCodes
|
||
.Select(item => item.Trim().ToLowerInvariant())
|
||
.Where(SupportedModules.Contains)
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
if (modules.Count == 0)
|
||
throw Oops.Oh("请至少选择一个健康板块");
|
||
|
||
await _moduleRepository.DeleteAsync(item => item.UserId == userId);
|
||
await _moduleRepository.InsertRangeAsync(modules.Select(module => new HealthModuleSelection
|
||
{
|
||
UserId = userId,
|
||
ModuleCode = module,
|
||
IsEnabled = true
|
||
}).ToList());
|
||
|
||
return modules;
|
||
}
|
||
|
||
[Authorize]
|
||
[HttpPost("/api/health/plans/generate")]
|
||
[UnitOfWork]
|
||
public async Task<HealthPlanOutput> GeneratePlan()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var profile = await _profileRepository.GetFirstAsync(item => item.UserId == userId);
|
||
var modules = await _moduleRepository.AsQueryable()
|
||
.Where(item => item.UserId == userId && item.IsEnabled)
|
||
.Select(item => item.ModuleCode)
|
||
.ToListAsync();
|
||
|
||
if (modules.Count == 0)
|
||
throw Oops.Oh("请先选择健康板块");
|
||
|
||
var items = BuildPlanItems(profile, modules);
|
||
var currentVersion = await _planRepository.AsQueryable()
|
||
.Where(item => item.UserId == userId)
|
||
.MaxAsync(item => (int?)item.Version) ?? 0;
|
||
|
||
await _planRepository.Context.Updateable<HealthPlan>()
|
||
.SetColumns(item => item.IsCurrent == false)
|
||
.Where(item => item.UserId == userId && item.IsCurrent)
|
||
.ExecuteCommandAsync();
|
||
|
||
var plan = await _planRepository.AsInsertable(new HealthPlan
|
||
{
|
||
UserId = userId,
|
||
Version = currentVersion + 1,
|
||
Title = "您的专属健康方案",
|
||
Summary = BuildSummary(profile, modules),
|
||
ParametersJson = JsonSerializer.Serialize(items),
|
||
IsCurrent = true
|
||
}).ExecuteReturnEntityAsync();
|
||
|
||
return MapPlan(plan);
|
||
}
|
||
|
||
private long GetCurrentUserId()
|
||
{
|
||
var value = _httpContextAccessor.HttpContext?.User.FindFirstValue(HealthUserIdClaim);
|
||
if (!long.TryParse(value, out var userId))
|
||
throw Oops.Oh("登录状态已失效").StatusCode(401);
|
||
return userId;
|
||
}
|
||
|
||
private async Task<HealthUser> GetCurrentUser(long userId)
|
||
{
|
||
return await _userRepository.GetFirstAsync(item => item.Id == userId && !item.IsDelete)
|
||
?? throw Oops.Oh("用户不存在").StatusCode(401);
|
||
}
|
||
|
||
private static void ApplyProfile(HealthProfile profile, SaveHealthProfileInput input)
|
||
{
|
||
profile.Birthday = input.Birthday;
|
||
profile.Gender = input.Gender?.Trim();
|
||
profile.HeightCm = input.HeightCm;
|
||
profile.WeightKg = input.WeightKg;
|
||
profile.AverageSleepHours = input.AverageSleepHours;
|
||
profile.DailyExerciseMinutes = input.DailyExerciseMinutes;
|
||
profile.DietPreference = input.DietPreference?.Trim();
|
||
profile.HealthGoal = input.HealthGoal?.Trim();
|
||
}
|
||
|
||
private static SaveHealthProfileInput MapProfile(HealthProfile profile) => new()
|
||
{
|
||
Birthday = profile.Birthday,
|
||
Gender = profile.Gender,
|
||
HeightCm = profile.HeightCm,
|
||
WeightKg = profile.WeightKg,
|
||
AverageSleepHours = profile.AverageSleepHours,
|
||
DailyExerciseMinutes = profile.DailyExerciseMinutes,
|
||
DietPreference = profile.DietPreference,
|
||
HealthGoal = profile.HealthGoal
|
||
};
|
||
|
||
private static HealthUserOutput MapUser(HealthUser user) => new()
|
||
{
|
||
Id = user.Id,
|
||
Phone = user.Phone,
|
||
DisplayName = user.DisplayName,
|
||
AvatarUrl = user.AvatarUrl
|
||
};
|
||
|
||
private static List<HealthPlanItemOutput> BuildPlanItems(HealthProfile? profile, IEnumerable<string> modules)
|
||
{
|
||
var items = new List<HealthPlanItemOutput>();
|
||
foreach (var module in modules)
|
||
{
|
||
switch (module)
|
||
{
|
||
case "sleep":
|
||
items.Add(new HealthPlanItemOutput
|
||
{
|
||
ModuleCode = module,
|
||
Title = "睡眠质量",
|
||
Target = profile?.AverageSleepHours is < 7 ? "逐步达到每晚 7–8 小时" : "保持每晚 7–8 小时",
|
||
Advice = "固定入睡时间,睡前一小时降低灯光亮度,并使用卧室舒睡场景。"
|
||
});
|
||
break;
|
||
case "exercise":
|
||
items.Add(new HealthPlanItemOutput
|
||
{
|
||
ModuleCode = module,
|
||
Title = "运动情况",
|
||
Target = profile?.DailyExerciseMinutes is >= 30 ? "保持每日 30 分钟以上活动" : "从每日 20 分钟步行开始",
|
||
Advice = "优先安排低到中等强度运动,每坐一小时起身活动 3–5 分钟。"
|
||
});
|
||
break;
|
||
case "diet":
|
||
items.Add(new HealthPlanItemOutput
|
||
{
|
||
ModuleCode = module,
|
||
Title = "饮食情况",
|
||
Target = "三餐规律,晚餐不过量",
|
||
Advice = string.IsNullOrWhiteSpace(profile?.DietPreference)
|
||
? "记录三餐与饮水情况,优先增加蔬菜和优质蛋白。"
|
||
: $"结合“{profile.DietPreference}”偏好控制总量,并保持食物多样性。"
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
return items;
|
||
}
|
||
|
||
private static string BuildSummary(HealthProfile? profile, IReadOnlyCollection<string> modules)
|
||
{
|
||
var bmiText = string.Empty;
|
||
if (profile?.HeightCm > 0 && profile.WeightKg > 0)
|
||
{
|
||
var height = profile.HeightCm.Value / 100m;
|
||
var bmi = Math.Round(profile.WeightKg.Value / (height * height), 1);
|
||
bmiText = $" 当前 BMI 约为 {bmi}。";
|
||
}
|
||
|
||
return $"方案依据您选择的 {modules.Count} 个健康板块生成。{bmiText}第一版参数用于健康管理演示,不替代专业医疗诊断。";
|
||
}
|
||
|
||
private static HealthPlanOutput MapPlan(HealthPlan plan) => new()
|
||
{
|
||
Id = plan.Id,
|
||
Version = plan.Version,
|
||
Title = plan.Title,
|
||
Summary = plan.Summary,
|
||
CreateTime = plan.CreateTime,
|
||
Items = JsonSerializer.Deserialize<List<HealthPlanItemOutput>>(plan.ParametersJson) ?? []
|
||
};
|
||
|
||
private static HealthAuthOutput CreateAuthOutput(HealthUser user)
|
||
{
|
||
const int accessTokenMinutes = 24 * 60;
|
||
const int refreshTokenMinutes = 7 * 24 * 60;
|
||
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||
{
|
||
[HealthUserIdClaim] = user.Id,
|
||
[ClaimTypes.NameIdentifier] = user.Id,
|
||
[ClaimTypes.Name] = user.DisplayName,
|
||
[ClaimTypes.MobilePhone] = user.Phone,
|
||
["account_type"] = "health_user"
|
||
}, accessTokenMinutes);
|
||
|
||
return new HealthAuthOutput
|
||
{
|
||
AccessToken = accessToken,
|
||
RefreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenMinutes),
|
||
User = MapUser(user)
|
||
};
|
||
}
|
||
|
||
private static void ValidateAssessment(SaveHealthAssessmentInput input)
|
||
{
|
||
var info = input.BasicInfo ?? throw Oops.Oh("请填写个人基本信息");
|
||
var answers = input.Answers ?? throw Oops.Oh("请完成健康评估问卷");
|
||
|
||
if (info.Gender is not ("男" or "女")) throw Oops.Oh("请选择性别");
|
||
if (info.Birthday is null) throw Oops.Oh("请填写年龄");
|
||
var age = DateTime.Today.Year - info.Birthday.Value.Year;
|
||
if (info.Birthday.Value.Date > DateTime.Today.AddYears(-age)) age--;
|
||
if (age is < 1 or > 120) throw Oops.Oh("年龄应在 1 到 120 岁之间");
|
||
if (info.HeightCm is null or < 80 or > 250) throw Oops.Oh("身高应在 80 到 250cm 之间");
|
||
if (info.WeightKg is null or < 20 or > 300) throw Oops.Oh("体重应在 20 到 300kg 之间");
|
||
|
||
RequireChoice(answers.SleepDuration, "每晚平均睡眠时长", "<6", "6-7", "7-8", ">8");
|
||
RequireChoice(answers.FallingAsleepSpeed, "入睡耗时", "hard", "normal", "fast");
|
||
RequireChoice(answers.TemperaturePreference, "温度偏好", "heat_fear", "cold_fear", "normal");
|
||
RequireChoice(answers.NightAwakening, "夜醒情况", "frequent", "light", "none");
|
||
RequireChoice(answers.MattressPreference, "床垫偏好", "firm", "medium", "soft");
|
||
RequireChoice(answers.ExerciseFrequency, "运动频率", "none", "1-2", "3-5", "daily");
|
||
RequireChoice(answers.MuscleFatigueArea, "疲劳部位", "back_neck", "waist_back", "legs", "none");
|
||
RequireChoice(answers.ActiveTimeOfDay, "活跃时段", "morning", "evening", "night", "irregular");
|
||
RequireChoice(answers.MealRegularity, "三餐规律", "regular", "skip_breakfast", "irregular");
|
||
RequireChoice(answers.FlavorPreference, "口味偏好", "light", "moderate", "heavy");
|
||
RequireChoice(answers.LateNightEating, "夜宵频率", "often", "sometimes", "rarely");
|
||
RequireChoice(answers.DailyWaterIntake, "饮水情况", "abundant", "moderate", "scarce");
|
||
}
|
||
|
||
private static void RequireChoice(string value, string label, params string[] allowed)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(value) || !allowed.Contains(value, StringComparer.Ordinal))
|
||
throw Oops.Oh($"请完成“{label}”问题");
|
||
}
|
||
|
||
private static PersonalizedSceneConfig CalculatePersonalizedSceneConfig(HealthAssessmentAnswers answers, SaveHealthProfileInput basicInfo)
|
||
{
|
||
var config = new PersonalizedSceneConfig();
|
||
|
||
switch (answers.TemperaturePreference)
|
||
{
|
||
case "heat_fear":
|
||
config.TargetHumidity = 48;
|
||
config.FanLevel = "2挡";
|
||
config.MattressTargetTemp = 32m;
|
||
config.MattressHeatingLevel = 1;
|
||
break;
|
||
case "cold_fear":
|
||
config.TargetHumidity = 58;
|
||
config.FanLevel = "1挡";
|
||
config.MattressTargetTemp = 37.5m;
|
||
config.MattressHeatingLevel = 3;
|
||
break;
|
||
default:
|
||
config.TargetHumidity = 52;
|
||
config.FanLevel = "1挡";
|
||
config.MattressTargetTemp = 35.5m;
|
||
config.MattressHeatingLevel = 2;
|
||
break;
|
||
}
|
||
|
||
switch (answers.FallingAsleepSpeed)
|
||
{
|
||
case "hard":
|
||
config.LightBrightnessPercent = 8;
|
||
config.LightColorTemperatureKelvin = 2200;
|
||
config.FragranceLevel = 2;
|
||
config.EnableSpeakerSleepMode = true;
|
||
config.SpeakerAudioMode = "助眠白噪音";
|
||
config.SpeakerVolumePercent = 12;
|
||
break;
|
||
case "fast":
|
||
config.LightBrightnessPercent = 25;
|
||
config.LightColorTemperatureKelvin = 3000;
|
||
config.EnableSpeakerSleepMode = false;
|
||
config.SpeakerAudioMode = "关闭助眠音频";
|
||
config.SpeakerVolumePercent = 10;
|
||
break;
|
||
default:
|
||
config.LightBrightnessPercent = 18;
|
||
config.LightColorTemperatureKelvin = 2700;
|
||
config.FragranceLevel = 1;
|
||
config.EnableSpeakerSleepMode = true;
|
||
config.SpeakerAudioMode = "舒缓轻音乐";
|
||
config.SpeakerVolumePercent = 15;
|
||
break;
|
||
}
|
||
|
||
switch (answers.SleepDuration)
|
||
{
|
||
case "<6":
|
||
config.LightBrightnessPercent -= 3;
|
||
config.SpeakerVolumePercent += 3;
|
||
config.FragranceLevel++;
|
||
break;
|
||
case "6-7":
|
||
config.LightBrightnessPercent -= 1;
|
||
config.SpeakerVolumePercent += 1;
|
||
break;
|
||
case ">8":
|
||
config.LightBrightnessPercent += 2;
|
||
config.SpeakerVolumePercent -= 2;
|
||
break;
|
||
}
|
||
|
||
if (answers.MuscleFatigueArea is "waist_back" or "back_neck" || answers.MattressPreference == "firm")
|
||
{
|
||
config.MattressLeftFirmness = 7;
|
||
config.MattressRightFirmness = 7;
|
||
config.MattressMode = "智能腰托";
|
||
}
|
||
else if (answers.MattressPreference == "soft")
|
||
{
|
||
config.MattressLeftFirmness = 4;
|
||
config.MattressRightFirmness = 4;
|
||
config.MattressMode = "瑜伽助眠";
|
||
}
|
||
else
|
||
{
|
||
config.MattressLeftFirmness = answers.MuscleFatigueArea == "legs" ? 6 : 5;
|
||
config.MattressRightFirmness = answers.MuscleFatigueArea == "legs" ? 6 : 5;
|
||
config.MattressMode = "智能适配";
|
||
}
|
||
|
||
config.DehumidifierMode = answers.NightAwakening == "none" ? "智能" : "睡眠";
|
||
if (answers.NightAwakening == "frequent")
|
||
{
|
||
config.FanLevel = "1挡";
|
||
config.LightBrightnessPercent -= 2;
|
||
}
|
||
|
||
switch (answers.ExerciseFrequency)
|
||
{
|
||
case "none": config.MattressHeatingLevel++; break;
|
||
case "daily": config.MattressHeatingLevel--; break;
|
||
}
|
||
|
||
switch (answers.ActiveTimeOfDay)
|
||
{
|
||
case "morning": config.LightColorTemperatureKelvin += 150; break;
|
||
case "night":
|
||
config.LightColorTemperatureKelvin -= 250;
|
||
config.LightBrightnessPercent -= 2;
|
||
break;
|
||
case "irregular":
|
||
config.EnableSpeakerSleepMode = true;
|
||
config.SpeakerVolumePercent += 2;
|
||
break;
|
||
}
|
||
|
||
config.EnableUv = answers.LateNightEating is "often" or "sometimes" || answers.FlavorPreference == "heavy";
|
||
if (answers.MealRegularity == "irregular")
|
||
{
|
||
config.EnableUv = true;
|
||
config.FragranceLevel++;
|
||
}
|
||
else if (answers.MealRegularity == "skip_breakfast")
|
||
{
|
||
config.FragranceLevel++;
|
||
}
|
||
|
||
if (answers.DailyWaterIntake == "scarce") config.TargetHumidity += 3;
|
||
if (answers.DailyWaterIntake == "abundant") config.TargetHumidity -= 2;
|
||
|
||
if (basicInfo.HeightCm > 0 && basicInfo.WeightKg > 0)
|
||
{
|
||
var height = basicInfo.HeightCm.Value / 100m;
|
||
var bmi = basicInfo.WeightKg.Value / (height * height);
|
||
if (bmi < 18.5m) config.MattressHeatingLevel++;
|
||
if (bmi >= 28m) config.TargetHumidity -= 2;
|
||
}
|
||
if (basicInfo.Birthday.HasValue)
|
||
{
|
||
var age = DateTime.Today.Year - basicInfo.Birthday.Value.Year;
|
||
if (age >= 60)
|
||
{
|
||
config.LightBrightnessPercent += 3;
|
||
config.SpeakerVolumePercent = Math.Max(8, config.SpeakerVolumePercent - 2);
|
||
}
|
||
}
|
||
|
||
config.TargetHumidity = Math.Clamp(config.TargetHumidity, 40, 70);
|
||
config.LightBrightnessPercent = Math.Clamp(config.LightBrightnessPercent, 1, 100);
|
||
config.LightColorTemperatureKelvin = Math.Clamp(config.LightColorTemperatureKelvin, 2200, 5000);
|
||
config.SpeakerVolumePercent = Math.Clamp(config.SpeakerVolumePercent, 1, 50);
|
||
config.FragranceLevel = Math.Clamp(config.FragranceLevel, 1, 3);
|
||
config.MattressHeatingLevel = Math.Clamp(config.MattressHeatingLevel, 0, 4);
|
||
return config;
|
||
}
|
||
|
||
private static string BuildAssessmentPlanSummary(HealthProfile profile, HealthAssessmentAnswers answers)
|
||
{
|
||
var sleepNote = answers.FallingAsleepSpeed == "hard" ? "针对入睡耗时较长启用了音箱睡眠模式与极暗暖光;" : "";
|
||
var tempNote = answers.TemperaturePreference == "heat_fear" ? "依据体感偏热降低了床垫加热档位与室内湿度;" : answers.TemperaturePreference == "cold_fear" ? "依据体感偏冷提高了床垫保温档位;" : "";
|
||
var supportNote = answers.MuscleFatigueArea is "waist_back" or "back_neck" ? "针对腰背与颈部疲劳启用了床垫智能腰托模式;" : "";
|
||
return $"已完成全面健康评估,{sleepNote}{tempNote}{supportNote}全套智能家具启动参数已完成个性化配置。";
|
||
}
|
||
}
|