Spaces:
Running
Running
/** | |
* SarcoAdvisor-BSU 前端交互脚本 | |
* 处理用户输入、API调用和结果显示 | |
*/ | |
// 全局变量 | |
let currentAssessment = null; | |
let currentLanguage = 'zh'; // 默认中文 | |
// DOM加载完成后初始化 | |
document.addEventListener('DOMContentLoaded', function() { | |
console.log('SarcoAdvisor-BSU 前端已加载'); | |
// 初始化表单 | |
initializeForm(); | |
// 检查系统健康状态 | |
checkSystemHealth(); | |
// 添加输入验证 | |
addInputValidation(); | |
// 初始化语言 | |
initializeLanguage(); | |
// 初始化自动计算 | |
initializeAutoCalculation(); | |
// 初始化PAQ问卷逻辑 | |
initializePAQLogic(); | |
}); | |
/** | |
* 初始化表单 | |
*/ | |
function initializeForm() { | |
const form = document.getElementById('assessmentForm'); | |
const submitBtn = document.getElementById('submitBtn'); | |
// 添加表单提交事件监听 | |
form.addEventListener('submit', handleFormSubmit); | |
// 添加实时验证 | |
const inputs = form.querySelectorAll('input[required], select[required]'); | |
inputs.forEach(input => { | |
input.addEventListener('blur', validateInput); | |
input.addEventListener('input', updateSubmitButton); | |
}); | |
console.log('表单初始化完成'); | |
} | |
/** | |
* 检查系统健康状态 | |
*/ | |
async function checkSystemHealth() { | |
try { | |
const response = await fetch('/health'); | |
const health = await response.json(); | |
if (health.status === 'healthy') { | |
console.log('系统状态正常:', health); | |
} else { | |
console.warn('系统状态异常:', health); | |
const warningMsg = currentLanguage === 'zh' ? | |
'系统部分功能可能不可用,但基础评估功能正常' : | |
'Some system functions may be unavailable, but basic assessment functions are normal'; | |
showAlert(warningMsg, 'warning'); | |
} | |
} catch (error) { | |
console.error('健康检查失败:', error); | |
const errorMsg = currentLanguage === 'zh' ? | |
'无法连接到服务器,请稍后重试' : | |
'Unable to connect to server, please try again later'; | |
showAlert(errorMsg, 'danger'); | |
} | |
} | |
/** | |
* 处理表单提交 | |
*/ | |
async function handleFormSubmit(event) { | |
event.preventDefault(); | |
console.log('开始处理表单提交'); | |
// 验证表单 | |
if (!validateForm()) { | |
return; | |
} | |
// 收集表单数据 | |
const formData = collectFormData(); | |
// 显示加载状态 | |
showLoading(); | |
try { | |
// 调用完整评估API | |
const result = await performFullAssessment(formData); | |
// 显示结果 | |
displayResults(result); | |
// 保存当前评估 | |
currentAssessment = result; | |
} catch (error) { | |
console.error('评估失败:', error); | |
hideLoading(); | |
// 显示详细错误信息 | |
let errorMessage = currentLanguage === 'zh' ? '评估过程中出现错误' : 'An error occurred during assessment'; | |
if (error.message) { | |
errorMessage += currentLanguage === 'zh' ? ':' + error.message : ': ' + error.message; | |
} | |
errorMessage += currentLanguage === 'zh' ? '。请检查输入数据后重试。' : '. Please check your input data and try again.'; | |
showAlert(errorMessage, 'danger'); | |
// 同时在控制台输出用户数据用于调试 | |
console.error('失败的用户数据:', formData); | |
} | |
} | |
/** | |
* 验证表单 | |
*/ | |
function validateForm() { | |
const form = document.getElementById('assessmentForm'); | |
const inputs = form.querySelectorAll('input[required], select[required]'); | |
let isValid = true; | |
inputs.forEach(input => { | |
if (!validateInput({ target: input })) { | |
isValid = false; | |
} | |
}); | |
return isValid; | |
} | |
/** | |
* 验证单个输入 | |
*/ | |
function validateInput(event) { | |
const input = event.target; | |
const value = input.value; | |
const type = input.type; | |
const min = parseFloat(input.min); | |
const max = parseFloat(input.max); | |
let isValid = true; | |
let errorMessage = ''; | |
// 必填验证 | |
if (input.required && !value) { | |
isValid = false; | |
errorMessage = currentLanguage === 'zh' ? '此字段为必填项' : 'This field is required'; | |
} | |
// 数值范围验证 | |
if (type === 'number' && value) { | |
const numValue = parseFloat(value); | |
if (isNaN(numValue)) { | |
isValid = false; | |
errorMessage = currentLanguage === 'zh' ? '请输入有效数字' : 'Please enter a valid number'; | |
} else if (!isNaN(min) && numValue < min) { | |
isValid = false; | |
errorMessage = currentLanguage === 'zh' ? `值不能小于 ${min}` : `Value cannot be less than ${min}`; | |
} else if (!isNaN(max) && numValue > max) { | |
isValid = false; | |
errorMessage = currentLanguage === 'zh' ? `值不能大于 ${max}` : `Value cannot be greater than ${max}`; | |
} | |
} | |
// 显示验证结果 | |
showInputValidation(input, isValid, errorMessage); | |
return isValid; | |
} | |
/** | |
* 显示输入验证结果 | |
*/ | |
function showInputValidation(input, isValid, errorMessage) { | |
// 移除旧的验证状态 | |
input.classList.remove('is-valid', 'is-invalid'); | |
// 移除旧的错误信息 | |
const oldFeedback = input.parentNode.querySelector('.invalid-feedback'); | |
if (oldFeedback) { | |
oldFeedback.remove(); | |
} | |
if (!isValid && errorMessage) { | |
// 添加错误状态 | |
input.classList.add('is-invalid'); | |
// 添加错误信息 | |
const feedback = document.createElement('div'); | |
feedback.className = 'invalid-feedback'; | |
feedback.textContent = errorMessage; | |
input.parentNode.appendChild(feedback); | |
} else if (input.value) { | |
// 添加成功状态 | |
input.classList.add('is-valid'); | |
} | |
} | |
/** | |
* 更新提交按钮状态 | |
*/ | |
function updateSubmitButton() { | |
const form = document.getElementById('assessmentForm'); | |
const submitBtn = document.getElementById('submitBtn'); | |
const requiredInputs = form.querySelectorAll('input[required], select[required]'); | |
let allValid = true; | |
requiredInputs.forEach(input => { | |
if (!input.value || input.classList.contains('is-invalid')) { | |
allValid = false; | |
} | |
}); | |
submitBtn.disabled = !allValid; | |
} | |
/** | |
* 收集表单数据并计算衍生特征 | |
*/ | |
function collectFormData() { | |
const rawData = {}; | |
const form = document.getElementById('assessmentForm'); | |
const formElements = form.elements; | |
// 收集原始表单数据 | |
for (let element of formElements) { | |
if (element.name) { | |
let value = element.value; | |
// 处理空值 | |
if (value === '' || value === null || value === undefined) { | |
// 为必填字段提供默认值 | |
if (element.hasAttribute('required')) { | |
if (element.name === 'arthritis' || element.name === 'diabetes') { | |
value = '0'; // 默认无疾病史 | |
} else if (element.type === 'select-one' && element.name.startsWith('PAQ')) { | |
value = '2'; // PAQ问题默认选择"否" | |
} | |
} | |
} | |
if (value !== '' && value !== null && value !== undefined) { | |
// 转换数字类型 | |
if (element.type === 'number') { | |
value = parseFloat(value); | |
} else if (element.type === 'select-one') { | |
value = parseInt(value); | |
} | |
rawData[element.name] = value; | |
} | |
} | |
} | |
// 确保BMI和WWI有值(从自动计算获取) | |
if (!rawData.body_mass_index) { | |
const height = parseFloat(document.getElementById('height').value); | |
const weight = parseFloat(document.getElementById('weight').value); | |
if (height && weight) { | |
const heightInMeters = height / 100; | |
rawData.body_mass_index = parseFloat((weight / (heightInMeters * heightInMeters)).toFixed(1)); | |
} | |
} | |
if (!rawData.WWI) { | |
const waist = parseFloat(document.getElementById('waist').value); | |
const weight = parseFloat(document.getElementById('weight').value); | |
if (waist && weight) { | |
rawData.WWI = parseFloat((waist / Math.sqrt(weight)).toFixed(2)); | |
} | |
} | |
// 确保所有PAQ字段都有默认值(避免Pydantic验证错误) | |
const requiredPAQFields = { | |
// 必需的选择字段 | |
'PAQ605': 2, 'PAQ620': 2, 'PAQ635': 2, 'PAQ650': 2, 'PAQ665': 2, | |
// 可选的天数字段(默认0) | |
'PAQ610': 0, 'PAQ625': 0, 'PAQ640': 0, 'PAQ655': 0, 'PAQ670': 0, | |
// 可选的时长字段(默认0) | |
'PAD615': 0, 'PAD630': 0, 'PAD645': 0, 'PAD660': 0, 'PAD675': 0, | |
// 久坐时间(默认8小时) | |
'PAD680': 480, | |
// 医疗史字段(默认否) | |
'arthritis': 0, 'diabetes': 0 | |
}; | |
// 为缺失的字段设置默认值 | |
for (const [field, defaultValue] of Object.entries(requiredPAQFields)) { | |
if (!(field in rawData)) { | |
rawData[field] = defaultValue; | |
} | |
} | |
// 使用PAD680作为sedentary_minutes | |
rawData.sedentary_minutes = rawData.PAD680 || 480; | |
// 计算衍生特征 | |
const derivedFeatures = calculateDerivedFeatures(rawData); | |
// 合并原始数据和衍生特征 | |
const formData = { ...rawData, ...derivedFeatures }; | |
console.log('收集的表单数据(含默认值):', formData); | |
return formData; | |
} | |
/** | |
* 根据NHANES PAQ数据计算衍生特征 | |
* 完全按照create_pa_derived_features.py的逻辑实现 | |
*/ | |
function calculateDerivedFeatures(data) { | |
const derived = {}; | |
// ===================================================== | |
// A. 活动总量/剂量特征 (Activity Volume/Dose) | |
// ===================================================== | |
// A1. 每周总MET-分钟 (Total MET-minutes/week) | |
const vigorousWorkMETs = 8.0 * (data.PAD615 || 0) * (data.PAQ610 || 0); | |
const moderateWorkMETs = 4.0 * (data.PAD630 || 0) * (data.PAQ625 || 0); | |
const transportationMETs = 4.0 * (data.PAD645 || 0) * (data.PAQ640 || 0); | |
const vigorousRecMETs = 8.0 * (data.PAD660 || 0) * (data.PAQ655 || 0); | |
const moderateRecMETs = 4.0 * (data.PAD675 || 0) * (data.PAQ670 || 0); | |
const totalMETMinutesWeek = vigorousWorkMETs + moderateWorkMETs + transportationMETs + vigorousRecMETs + moderateRecMETs; | |
// A2. 每周高强度活动总分钟数 (Total Vigorous Minutes/week) | |
const vigorousWorkMins = (data.PAD615 || 0) * (data.PAQ610 || 0); | |
const vigorousRecMins = (data.PAD660 || 0) * (data.PAQ655 || 0); | |
const totalVigorousMinutesWeek = vigorousWorkMins + vigorousRecMins; | |
// A3. 每周中等强度活动总分钟数 (Total Moderate Minutes/week) | |
const moderateWorkMins = (data.PAD630 || 0) * (data.PAQ625 || 0); | |
const transportationMins = (data.PAD645 || 0) * (data.PAQ640 || 0); | |
const moderateRecMins = (data.PAD675 || 0) * (data.PAQ670 || 0); | |
const totalModerateMinutesWeek = moderateWorkMins + transportationMins + moderateRecMins; | |
// ===================================================== | |
// B. 活动模式/行为特征 (Activity Pattern/Behavior) | |
// ===================================================== | |
// B1. 平均每次高强度活动时长 | |
const totalVigorousDays = (data.PAQ610 || 0) + (data.PAQ655 || 0); | |
const avgVigorousDurationPerBout = totalVigorousDays > 0 ? totalVigorousMinutesWeek / totalVigorousDays : 0; | |
// B2. 平均每次中等强度活动时长 | |
const totalModerateDays = (data.PAQ625 || 0) + (data.PAQ640 || 0) + (data.PAQ670 || 0); | |
const avgModerateDurationPerBout = totalModerateDays > 0 ? totalModerateMinutesWeek / totalModerateDays : 0; | |
// B3. 活动多样性指数 | |
let activityDiversityIndex = 0; | |
if ((data.PAQ605 || 0) > 0) activityDiversityIndex++; // 工作高强度 | |
if ((data.PAQ620 || 0) > 0) activityDiversityIndex++; // 工作中等强度 | |
if ((data.PAQ635 || 0) > 0) activityDiversityIndex++; // 交通活动 | |
if ((data.PAQ650 || 0) > 0) activityDiversityIndex++; // 休闲高强度 | |
if ((data.PAQ665 || 0) > 0) activityDiversityIndex++; // 休闲中等强度 | |
// ===================================================== | |
// C. 活动比例/构成特征 (Activity Ratio/Composition) | |
// ===================================================== | |
// C1. 高强度活动MET-分钟占比 | |
const vigorousMETTotal = vigorousWorkMETs + vigorousRecMETs; | |
const vigorousMETRatio = totalMETMinutesWeek > 0 ? vigorousMETTotal / totalMETMinutesWeek : 0; | |
// C2. 活动/久坐比 | |
const totalActiveMinutesWeek = totalVigorousMinutesWeek + totalModerateMinutesWeek; | |
const totalSedentaryMinutesWeek = (data.sedentary_minutes || data.PAD680 || 480) * 7; // 每日久坐 × 7天 | |
const activitySedentaryRatio = totalSedentaryMinutesWeek > 0 ? totalActiveMinutesWeek / totalSedentaryMinutesWeek : 0; | |
// ===================================================== | |
// D. 指南达标特征 (Guideline Adherence) | |
// ===================================================== | |
// D1. 每周中等强度等效总分钟数 | |
const totalModerateEquivalentMinutes = totalModerateMinutesWeek + (2 * totalVigorousMinutesWeek); | |
// D2. 是否达到WHO体力活动推荐量 | |
const guidelineAdherenceBinary = totalModerateEquivalentMinutes >= 150 ? 1 : 0; | |
// D3. 体力活动水平分级 | |
let activityLevelCategorical; | |
if (totalModerateEquivalentMinutes >= 300) { | |
activityLevelCategorical = 3; // 非常活跃 | |
} else if (totalModerateEquivalentMinutes >= 150) { | |
activityLevelCategorical = 2; // 活跃 | |
} else if (totalModerateEquivalentMinutes > 0) { | |
activityLevelCategorical = 1; // 低度活跃 | |
} else { | |
activityLevelCategorical = 0; // 不活跃 | |
} | |
// ===================================================== | |
// 整理最终衍生特征 | |
// ===================================================== | |
// A. 活动总量/剂量特征 | |
derived.Total_MET_minutes_week = parseFloat(totalMETMinutesWeek.toFixed(2)); | |
derived.Total_Vigorous_Minutes_week = parseFloat(totalVigorousMinutesWeek.toFixed(2)); | |
derived.Total_Moderate_Minutes_week = parseFloat(totalModerateMinutesWeek.toFixed(2)); | |
// B. 活动模式/行为特征 | |
derived.Avg_Vigorous_Duration_Per_Bout = parseFloat(avgVigorousDurationPerBout.toFixed(2)); | |
derived.Avg_Moderate_Duration_Per_Bout = parseFloat(avgModerateDurationPerBout.toFixed(2)); | |
derived.Activity_Diversity_Index = activityDiversityIndex; | |
// C. 活动比例/构成特征 | |
derived.Vigorous_MET_Ratio = parseFloat(vigorousMETRatio.toFixed(3)); | |
derived.Activity_Sedentary_Ratio = parseFloat(activitySedentaryRatio.toFixed(3)); | |
// D. 指南达标特征 | |
derived.Total_Moderate_Equivalent_Minutes = parseFloat(totalModerateEquivalentMinutes.toFixed(2)); | |
derived.Guideline_Adherence_Binary = guidelineAdherenceBinary; | |
derived.Activity_Level_Categorical = activityLevelCategorical; | |
console.log('NHANES PAQ原始数据:', { | |
PAQ610: data.PAQ610, PAD615: data.PAD615, | |
PAQ625: data.PAQ625, PAD630: data.PAD630, | |
PAQ640: data.PAQ640, PAD645: data.PAD645, | |
PAQ655: data.PAQ655, PAD660: data.PAD660, | |
PAQ670: data.PAQ670, PAD675: data.PAD675, | |
PAD680: data.PAD680 | |
}); | |
console.log('计算的中间值:', {vigorousWorkMETs, moderateWorkMETs, transportationMETs, vigorousRecMETs, moderateRecMETs}); | |
console.log('计算的时间总量:', {totalVigorousMinutesWeek, totalModerateMinutesWeek, totalActiveMinutesWeek}); | |
console.log('最终衍生特征:', derived); | |
return derived; | |
} | |
/** | |
* 执行完整评估 | |
*/ | |
async function performFullAssessment(userData) { | |
console.log('开始完整评估...'); | |
const response = await fetch('/api/full_assessment', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify(userData) | |
}); | |
if (!response.ok) { | |
let errorMessage = `HTTP ${response.status}: `; | |
try { | |
const errorData = await response.json(); | |
console.error('服务器错误详情:', errorData); | |
if (response.status === 422 && errorData.detail) { | |
// 处理数据验证错误 | |
if (Array.isArray(errorData.detail)) { | |
const validationErrors = errorData.detail.map(err => | |
`字段"${err.loc.join('.')}"错误: ${err.msg}` | |
).join('; '); | |
errorMessage += validationErrors; | |
} else { | |
errorMessage += JSON.stringify(errorData.detail); | |
} | |
} else { | |
errorMessage += errorData.detail || JSON.stringify(errorData); | |
} | |
} catch (parseError) { | |
errorMessage += response.statusText || '服务器错误'; | |
} | |
throw new Error(errorMessage); | |
} | |
const result = await response.json(); | |
console.log('评估结果:', result); | |
return result; | |
} | |
/** | |
* 显示加载状态 | |
*/ | |
function showLoading() { | |
document.getElementById('loadingSection').style.display = 'block'; | |
document.getElementById('resultsSection').style.display = 'none'; | |
// 滚动到加载区域 | |
document.getElementById('loadingSection').scrollIntoView({ | |
behavior: 'smooth' | |
}); | |
} | |
/** | |
* 隐藏加载状态 | |
*/ | |
function hideLoading() { | |
document.getElementById('loadingSection').style.display = 'none'; | |
} | |
/** | |
* 显示评估结果 | |
*/ | |
function displayResults(assessment) { | |
hideLoading(); | |
const resultsSection = document.getElementById('resultsSection'); | |
const resultsContent = document.getElementById('resultsContent'); | |
// 构建结果HTML | |
const html = buildResultsHTML(assessment); | |
resultsContent.innerHTML = html; | |
// 显示结果区域 | |
resultsSection.style.display = 'block'; | |
resultsSection.classList.add('fade-in-up'); | |
// 滚动到结果区域 | |
resultsSection.scrollIntoView({ | |
behavior: 'smooth' | |
}); | |
console.log('结果显示完成'); | |
} | |
/** | |
* 构建结果HTML | |
*/ | |
function buildResultsHTML(assessment) { | |
const screening = assessment.screening; | |
const advisory = assessment.advisory; | |
const explanation = assessment.risk_explanation; | |
let html = ` | |
<!-- 风险评估结果 --> | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<h4 class="text-primary mb-3"> | |
<i class="fas fa-chart-pie me-2"></i> | |
<span class="lang-zh">风险评估结果</span> | |
<span class="lang-en" style="display: none;">Risk Assessment Results</span> | |
</h4> | |
</div> | |
</div> | |
<div class="row mb-4"> | |
<!-- 综合风险 --> | |
<div class="col-md-12 mb-3"> | |
<div class="risk-level risk-${screening.overall_risk}"> | |
<i class="fas fa-heartbeat fa-2x mb-2"></i> | |
<div> | |
<span class="lang-zh">综合风险评估: ${getRiskLevelText(screening.overall_risk)}</span> | |
<span class="lang-en" style="display: none;">Overall Risk Assessment: ${getRiskLevelText(screening.overall_risk, 'en')}</span> | |
</div> | |
<small> | |
<span class="lang-zh">系统置信度: ${(screening.confidence * 100).toFixed(1)}%</span> | |
<span class="lang-en" style="display: none;">System Confidence: ${(screening.confidence * 100).toFixed(1)}%</span> | |
</small> | |
</div> | |
</div> | |
</div> | |
<!-- 详细模型结果 --> | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<h5 class="text-secondary mb-3"> | |
<i class="fas fa-chart-bar me-2"></i> | |
<span class="lang-zh">详细评估结果</span> | |
<span class="lang-en" style="display: none;">Detailed Assessment Results</span> | |
</h5> | |
</div> | |
</div> | |
<!-- 筛查模型结果 --> | |
<div class="row mb-3"> | |
<div class="col-12"> | |
<h6 class="text-info"> | |
<i class="fas fa-search me-1"></i> | |
<span class="lang-zh">筛查阶段 (高召回率 - 不漏诊)</span> | |
<span class="lang-en" style="display: none;">Screening Stage (High Recall - No Missed Diagnosis)</span> | |
</h6> | |
</div> | |
</div> | |
<div class="row mb-4"> | |
<!-- SarcoI筛查 --> | |
<div class="col-md-6 mb-3"> | |
<div class="card border-info bg-light"> | |
<div class="card-body text-center"> | |
<div class="risk-level risk-${screening.sarcoI_risk} mb-2"> | |
<i class="fas fa-user-injured fa-lg mb-1"></i> | |
<div> | |
<strong> | |
<span class="lang-zh">SarcoI 筛查</span> | |
<span class="lang-en" style="display: none;">SarcoI Screening</span> | |
</strong> | |
</div> | |
<div class="small"> | |
<span class="lang-zh">RandomForest 模型</span> | |
<span class="lang-en" style="display: none;">RandomForest Model</span> | |
</div> | |
</div> | |
<div class="text-muted"> | |
<div> | |
<span class="lang-zh">风险等级: <strong>${getRiskLevelText(screening.sarcoI_risk)}</strong></span> | |
<span class="lang-en" style="display: none;">Risk Level: <strong>${getRiskLevelText(screening.sarcoI_risk, 'en')}</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">肌少症特征相似度: <strong>${(screening.sarcoI_probability * 100).toFixed(1)}%</strong> (筛查阈值: 50%)</span> | |
<span class="lang-en" style="display: none;">Sarcopenia Feature Similarity: <strong>${(screening.sarcoI_probability * 100).toFixed(1)}%</strong> (Screening Threshold: 50%)</span> | |
</div> | |
</div> | |
<div class="mt-2 small text-info"> | |
<div>Recall: <strong>91.14%</strong></div> | |
<div>Precision: <strong>43.05%</strong></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- SarcoII筛查 --> | |
<div class="col-md-6 mb-3"> | |
<div class="card border-info bg-light"> | |
<div class="card-body text-center"> | |
<div class="risk-level risk-${screening.sarcoII_risk} mb-2"> | |
<i class="fas fa-wheelchair fa-lg mb-1"></i> | |
<div> | |
<strong> | |
<span class="lang-zh">SarcoII 筛查</span> | |
<span class="lang-en" style="display: none;">SarcoII Screening</span> | |
</strong> | |
</div> | |
<div class="small"> | |
<span class="lang-zh">CatBoost 模型</span> | |
<span class="lang-en" style="display: none;">CatBoost Model</span> | |
</div> | |
</div> | |
<div class="text-muted"> | |
<div> | |
<span class="lang-zh">风险等级: <strong>${getRiskLevelText(screening.sarcoII_risk)}</strong></span> | |
<span class="lang-en" style="display: none;">Risk Level: <strong>${getRiskLevelText(screening.sarcoII_risk, 'en')}</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">肌少症特征相似度: <strong>${(screening.sarcoII_probability * 100).toFixed(1)}%</strong> (筛查阈值: 50%)</span> | |
<span class="lang-en" style="display: none;">Sarcopenia Feature Similarity: <strong>${(screening.sarcoII_probability * 100).toFixed(1)}%</strong> (Screening Threshold: 50%)</span> | |
</div> | |
</div> | |
<div class="mt-2 small text-info"> | |
<div>Precision: <strong>25.48%</strong></div> | |
<div>Recall: <strong>89.83%</strong></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- 建议模型结果 --> | |
<!-- 建议模型部分 - 始终显示 --> | |
<div class="row mb-3"> | |
<div class="col-12"> | |
<h6 class="text-success"> | |
<i class="fas fa-lightbulb me-1"></i> | |
<span class="lang-zh">建议阶段 (高精确率 - 减少误诊)</span> | |
<span class="lang-en" style="display: none;">Advisory Stage (High Precision - Reduce Misdiagnosis)</span> | |
</h6> | |
</div> | |
</div> | |
<div class="row mb-4"> | |
<!-- SarcoI建议 --> | |
<div class="col-md-6 mb-3"> | |
<div class="card border-success bg-light"> | |
<div class="card-body text-center"> | |
<div class="risk-level risk-${screening.sarcoI_advisory_risk} mb-2"> | |
<i class="fas fa-user-injured fa-lg mb-1"></i> | |
<div> | |
<strong> | |
<span class="lang-zh">SarcoI 建议</span> | |
<span class="lang-en" style="display: none;">SarcoI Advisory</span> | |
</strong> | |
</div> | |
<div class="small"> | |
<span class="lang-zh">CatBoost 模型</span> | |
<span class="lang-en" style="display: none;">CatBoost Model</span> | |
</div> | |
</div> | |
<div class="text-muted"> | |
<div> | |
<span class="lang-zh">风险等级: <strong>${screening.sarcoI_advisory_risk ? getRiskLevelText(screening.sarcoI_advisory_risk) : '未评估'}</strong></span> | |
<span class="lang-en" style="display: none;">Risk Level: <strong>${screening.sarcoI_advisory_risk ? getRiskLevelText(screening.sarcoI_advisory_risk, 'en') : 'Not Assessed'}</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">肌少症特征相似度: <strong>${screening.sarcoI_advisory_probability ? (screening.sarcoI_advisory_probability * 100).toFixed(1) + '%' : 'N/A'}</strong> (建议阈值: 36%)</span> | |
<span class="lang-en" style="display: none;">Sarcopenia Feature Similarity: <strong>${screening.sarcoI_advisory_probability ? (screening.sarcoI_advisory_probability * 100).toFixed(1) + '%' : 'N/A'}</strong> (Advisory Threshold: 36%)</span> | |
</div> | |
</div> | |
<div class="mt-2 small text-success"> | |
<div> | |
<span class="lang-zh">Precision: <strong>高精确率</strong></span> | |
<span class="lang-en" style="display: none;">Precision: <strong>High Precision</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">Recall: <strong>DiCE优化</strong></span> | |
<span class="lang-en" style="display: none;">Recall: <strong>DiCE Optimized</strong></span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- SarcoII建议 --> | |
<div class="col-md-6 mb-3"> | |
<div class="card border-success bg-light"> | |
<div class="card-body text-center"> | |
<div class="risk-level risk-${screening.sarcoII_advisory_risk} mb-2"> | |
<i class="fas fa-wheelchair fa-lg mb-1"></i> | |
<div> | |
<strong> | |
<span class="lang-zh">SarcoII 建议</span> | |
<span class="lang-en" style="display: none;">SarcoII Advisory</span> | |
</strong> | |
</div> | |
<div class="small"> | |
<span class="lang-zh">RandomForest 模型</span> | |
<span class="lang-en" style="display: none;">RandomForest Model</span> | |
</div> | |
</div> | |
<div class="text-muted"> | |
<div> | |
<span class="lang-zh">风险等级: <strong>${screening.sarcoII_advisory_risk ? getRiskLevelText(screening.sarcoII_advisory_risk) : '未评估'}</strong></span> | |
<span class="lang-en" style="display: none;">Risk Level: <strong>${screening.sarcoII_advisory_risk ? getRiskLevelText(screening.sarcoII_advisory_risk, 'en') : 'Not Assessed'}</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">肌少症特征相似度: <strong>${screening.sarcoII_advisory_probability ? (screening.sarcoII_advisory_probability * 100).toFixed(1) + '%' : 'N/A'}</strong> (建议阈值: 52%)</span> | |
<span class="lang-en" style="display: none;">Sarcopenia Feature Similarity: <strong>${screening.sarcoII_advisory_probability ? (screening.sarcoII_advisory_probability * 100).toFixed(1) + '%' : 'N/A'}</strong> (Advisory Threshold: 52%)</span> | |
</div> | |
</div> | |
<div class="mt-2 small text-success"> | |
<div> | |
<span class="lang-zh">Precision: <strong>高精确率</strong></span> | |
<span class="lang-en" style="display: none;">Precision: <strong>High Precision</strong></span> | |
</div> | |
<div> | |
<span class="lang-zh">Recall: <strong>DiCE优化</strong></span> | |
<span class="lang-en" style="display: none;">Recall: <strong>DiCE Optimized</strong></span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- 风险解释 --> | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<div class="alert alert-info"> | |
<h5 class="alert-heading"> | |
<i class="fas fa-info-circle me-2"></i>${explanation.title} | |
</h5> | |
<p class="mb-2">${explanation.description}</p> | |
<hr> | |
<p class="mb-0"> | |
<strong> | |
<span class="lang-zh">建议:</span> | |
<span class="lang-en" style="display: none;">Recommendation:</span> | |
</strong> ${explanation.recommendation} | |
</p> | |
</div> | |
</div> | |
</div> | |
`; | |
// 如果有个性化建议,显示建议内容 | |
if (advisory && assessment.needs_advisory) { | |
html += buildAdvisoryHTML(advisory); | |
} | |
// 添加处理时间信息 | |
html += ` | |
<div class="row mt-4"> | |
<div class="col-12"> | |
<small class="text-muted"> | |
<i class="fas fa-clock me-1"></i> | |
<span class="lang-zh">评估耗时: ${assessment.total_processing_time.toFixed(2)} 秒</span> | |
<span class="lang-en" style="display: none;">Processing Time: ${assessment.total_processing_time.toFixed(2)} seconds</span> | |
</small> | |
</div> | |
</div> | |
`; | |
return html; | |
} | |
/** | |
* 构建建议HTML | |
*/ | |
function buildAdvisoryHTML(advisory) { | |
let html = ` | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<h4 class="text-success mb-3"> | |
<i class="fas fa-lightbulb me-2"></i> | |
<span class="lang-zh">个性化建议</span> | |
<span class="lang-en" style="display: none;">Personalized Recommendations</span> | |
</h4> | |
</div> | |
</div> | |
`; | |
// 优先级行动 | |
if (advisory.priority_actions && advisory.priority_actions.length > 0) { | |
html += ` | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<div class="alert alert-warning"> | |
<h6 class="alert-heading"> | |
<i class="fas fa-exclamation-triangle me-2"></i> | |
<span class="lang-zh">优先建议</span> | |
<span class="lang-en" style="display: none;">Priority Recommendations</span> | |
</h6> | |
<ul class="mb-0"> | |
${advisory.priority_actions.map(action => `<li>${action}</li>`).join('')} | |
</ul> | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
// SarcoI建议 | |
if (advisory.sarcoI_recommendations && advisory.sarcoI_recommendations.length > 0) { | |
html += ` | |
<div class="row mb-3"> | |
<div class="col-12"> | |
<h5 class="text-secondary"> | |
<i class="fas fa-user-injured me-2"></i> | |
<span class="lang-zh">SarcoI相关建议</span> | |
<span class="lang-en" style="display: none;">SarcoI Related Recommendations</span> | |
</h5> | |
</div> | |
</div> | |
<div class="row mb-4"> | |
${advisory.sarcoI_recommendations.map(rec => buildRecommendationCard(rec)).join('')} | |
</div> | |
`; | |
} | |
// SarcoII建议 | |
if (advisory.sarcoII_recommendations && advisory.sarcoII_recommendations.length > 0) { | |
html += ` | |
<div class="row mb-3"> | |
<div class="col-12"> | |
<h5 class="text-secondary"> | |
<i class="fas fa-wheelchair me-2"></i> | |
<span class="lang-zh">SarcoII相关建议</span> | |
<span class="lang-en" style="display: none;">SarcoII Related Recommendations</span> | |
</h5> | |
</div> | |
</div> | |
<div class="row mb-4"> | |
${advisory.sarcoII_recommendations.map(rec => buildRecommendationCard(rec)).join('')} | |
</div> | |
`; | |
} | |
// 目标指标 | |
if (advisory.target_metrics && Object.keys(advisory.target_metrics).length > 0) { | |
html += ` | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<h5 class="text-secondary mb-3"> | |
<i class="fas fa-target me-2"></i> | |
<span class="lang-zh">目标指标</span> | |
<span class="lang-en" style="display: none;">Target Metrics</span> | |
</h5> | |
<div class="row"> | |
${Object.entries(advisory.target_metrics).map(([key, value]) => ` | |
<div class="col-md-6 mb-2"> | |
<div class="d-flex justify-content-between"> | |
<span class="fw-bold">${key}:</span> | |
<span class="text-primary">${value}</span> | |
</div> | |
</div> | |
`).join('')} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
// 降级提示 | |
if (advisory.fallback_used) { | |
html += ` | |
<div class="row mb-4"> | |
<div class="col-12"> | |
<div class="alert alert-info"> | |
<i class="fas fa-info-circle me-2"></i> | |
<span class="lang-zh">部分建议基于规则生成,建议咨询专业医生获取更详细的个性化指导。</span> | |
<span class="lang-en" style="display: none;">Some recommendations are rule-based. Please consult professional doctors for more detailed personalized guidance.</span> | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
return html; | |
} | |
/** | |
* 构建单个建议卡片 | |
*/ | |
function buildRecommendationCard(recommendation) { | |
// 获取优先级文本 | |
const getPriorityText = (priority, lang) => { | |
const priorityTexts = { | |
'zh': { | |
'High': '高', | |
'Medium': '中', | |
'Low': '低' | |
}, | |
'en': { | |
'High': 'High', | |
'Medium': 'Medium', | |
'Low': 'Low' | |
} | |
}; | |
return priorityTexts[lang]?.[priority] || priority; | |
}; | |
return ` | |
<div class="col-md-6 mb-3"> | |
<div class="recommendation-item"> | |
<div class="recommendation-title"> | |
<i class="fas fa-arrow-right me-2 text-primary"></i> | |
${recommendation.title} | |
</div> | |
<div class="recommendation-description"> | |
${recommendation.description} | |
</div> | |
${recommendation.target_change ? ` | |
<div class="text-muted small mb-2"> | |
<i class="fas fa-target me-1"></i> | |
${recommendation.target_change} | |
</div> | |
` : ''} | |
<div class="recommendation-meta"> | |
<span class="priority-badge priority-${recommendation.priority.toLowerCase()}"> | |
<span class="lang-zh">${getPriorityText(recommendation.priority, 'zh')}优先级</span> | |
<span class="lang-en" style="display: none;">${getPriorityText(recommendation.priority, 'en')} Priority</span> | |
</span> | |
${recommendation.expected_impact ? ` | |
<small class="text-success"> | |
<i class="fas fa-chart-line me-1"></i> | |
${recommendation.expected_impact} | |
</small> | |
` : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
/** | |
* 获取风险等级文本 | |
*/ | |
function getRiskLevelText(level, language = null) { | |
const lang = language || currentLanguage; | |
const levels = { | |
'zh': { | |
'low': '低风险', | |
'medium': '中等风险', | |
'high': '高风险' | |
}, | |
'en': { | |
'low': 'Low Risk', | |
'medium': 'Medium Risk', | |
'high': 'High Risk' | |
} | |
}; | |
return levels[lang]?.[level] || level; | |
} | |
/** | |
* 显示警告信息 | |
*/ | |
function showAlert(message, type = 'info') { | |
const alertHTML = ` | |
<div class="alert alert-${type} alert-dismissible fade show" role="alert"> | |
<i class="fas fa-${getAlertIcon(type)} me-2"></i> | |
${message} | |
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> | |
</div> | |
`; | |
// 在表单上方插入警告 | |
const form = document.getElementById('assessmentForm'); | |
const alertContainer = document.createElement('div'); | |
alertContainer.innerHTML = alertHTML; | |
form.parentNode.insertBefore(alertContainer, form); | |
// 5秒后自动关闭 | |
setTimeout(() => { | |
const alert = alertContainer.querySelector('.alert'); | |
if (alert) { | |
alert.remove(); | |
} | |
}, 5000); | |
} | |
/** | |
* 获取警告图标 | |
*/ | |
function getAlertIcon(type) { | |
const icons = { | |
'info': 'info-circle', | |
'warning': 'exclamation-triangle', | |
'danger': 'exclamation-circle', | |
'success': 'check-circle' | |
}; | |
return icons[type] || 'info-circle'; | |
} | |
/** | |
* 添加输入验证 | |
*/ | |
function addInputValidation() { | |
// 身高合理性检查 | |
const heightInput = document.getElementById('height'); | |
if (heightInput) { | |
heightInput.addEventListener('input', function() { | |
const height = parseFloat(this.value); | |
if (height && (height < 120 || height > 220)) { | |
const message = currentLanguage === 'zh' ? '身高数值请确认是否正确' : 'Please confirm if height value is correct'; | |
showInputWarning(this, message); | |
} else { | |
hideInputWarning(this); | |
} | |
}); | |
} | |
// 体重合理性检查 | |
const weightInput = document.getElementById('weight'); | |
if (weightInput) { | |
weightInput.addEventListener('input', function() { | |
const weight = parseFloat(this.value); | |
if (weight && (weight < 35 || weight > 150)) { | |
const message = currentLanguage === 'zh' ? '体重数值请确认是否正确' : 'Please confirm if weight value is correct'; | |
showInputWarning(this, message); | |
} else { | |
hideInputWarning(this); | |
} | |
}); | |
} | |
// 腰围合理性检查 | |
const waistInput = document.getElementById('waist'); | |
if (waistInput) { | |
waistInput.addEventListener('input', function() { | |
const waist = parseFloat(this.value); | |
if (waist && (waist < 60 || waist > 150)) { | |
const message = currentLanguage === 'zh' ? '腰围数值请确认是否正确' : 'Please confirm if waist circumference is correct'; | |
showInputWarning(this, message); | |
} else { | |
hideInputWarning(this); | |
} | |
}); | |
} | |
// 年龄合理性检查 | |
const ageInput = document.getElementById('age_years'); | |
if (ageInput) { | |
ageInput.addEventListener('input', function() { | |
const age = parseFloat(this.value); | |
if (age && age > 80) { | |
const message = currentLanguage === 'zh' ? '高龄用户建议咨询专业医生' : 'Elderly users are advised to consult professional doctors'; | |
showInputWarning(this, message); | |
} else { | |
hideInputWarning(this); | |
} | |
}); | |
} | |
} | |
/** | |
* 显示输入警告 | |
*/ | |
function showInputWarning(input, message) { | |
let warning = input.parentNode.querySelector('.input-warning'); | |
if (!warning) { | |
warning = document.createElement('div'); | |
warning.className = 'input-warning text-warning small mt-1'; | |
input.parentNode.appendChild(warning); | |
} | |
warning.innerHTML = `<i class="fas fa-exclamation-triangle me-1"></i>${message}`; | |
} | |
/** | |
* 隐藏输入警告 | |
*/ | |
function hideInputWarning(input) { | |
const warning = input.parentNode.querySelector('.input-warning'); | |
if (warning) { | |
warning.remove(); | |
} | |
} | |
/** | |
* 导出评估结果 (未来功能) | |
*/ | |
function exportResults() { | |
if (!currentAssessment) { | |
const noResultMsg = currentLanguage === 'zh' ? | |
'没有可导出的评估结果' : | |
'No assessment results to export'; | |
showAlert(noResultMsg, 'warning'); | |
return; | |
} | |
// TODO: 实现结果导出功能 | |
console.log('导出评估结果:', currentAssessment); | |
const devMsg = currentLanguage === 'zh' ? | |
'导出功能正在开发中' : | |
'Export function is under development'; | |
showAlert(devMsg, 'info'); | |
} | |
/** | |
* 初始化语言设置 | |
*/ | |
function initializeLanguage() { | |
// 从localStorage读取语言设置 | |
const savedLang = localStorage.getItem('sarco-language'); | |
if (savedLang) { | |
currentLanguage = savedLang; | |
if (currentLanguage === 'en') { | |
switchToEnglish(); | |
} | |
} | |
} | |
/** | |
* 切换语言 | |
*/ | |
function toggleLanguage() { | |
if (currentLanguage === 'zh') { | |
switchToEnglish(); | |
} else { | |
switchToChinese(); | |
} | |
} | |
/** | |
* 切换到英文 | |
*/ | |
function switchToEnglish() { | |
currentLanguage = 'en'; | |
document.querySelectorAll('.lang-zh').forEach(el => el.style.display = 'none'); | |
document.querySelectorAll('.lang-en').forEach(el => el.style.display = 'inline'); | |
document.getElementById('langToggleText').textContent = '中文'; | |
document.documentElement.lang = 'en'; | |
document.title = 'SarcoAdvisor-BSU - Sarcopenia Risk Assessment System'; | |
// 更新select选项 | |
updateSelectOptions('en'); | |
// 如果有已显示的结果,重新渲染 | |
if (currentAssessment) { | |
displayResults(currentAssessment); | |
} | |
// 保存设置 | |
localStorage.setItem('sarco-language', 'en'); | |
} | |
/** | |
* 切换到中文 | |
*/ | |
function switchToChinese() { | |
currentLanguage = 'zh'; | |
document.querySelectorAll('.lang-en').forEach(el => el.style.display = 'none'); | |
document.querySelectorAll('.lang-zh').forEach(el => el.style.display = 'inline'); | |
document.getElementById('langToggleText').textContent = 'English'; | |
document.documentElement.lang = 'zh-CN'; | |
document.title = 'SarcoAdvisor-BSU - 肌少症风险评估系统'; | |
// 更新select选项 | |
updateSelectOptions('zh'); | |
// 如果有已显示的结果,重新渲染 | |
if (currentAssessment) { | |
displayResults(currentAssessment); | |
} | |
// 保存设置 | |
localStorage.setItem('sarco-language', 'zh'); | |
} | |
/** | |
* 更新select选项的显示 | |
*/ | |
function updateSelectOptions(lang) { | |
const select = document.getElementById('race_ethnicity'); | |
const options = select.querySelectorAll('option'); | |
options.forEach(option => { | |
if (option.value === '') { | |
option.textContent = lang === 'zh' ? '请选择' : 'Please select'; | |
} else if (option.value === '0') { | |
option.textContent = lang === 'zh' ? '美洲原住民' : 'Native American'; | |
} else if (option.value === '1') { | |
option.textContent = lang === 'zh' ? '亚洲人' : 'Asian'; | |
} else if (option.value === '2') { | |
option.textContent = lang === 'zh' ? '非洲裔美国人' : 'African American'; | |
} else if (option.value === '3') { | |
option.textContent = lang === 'zh' ? '西班牙裔' : 'Hispanic'; | |
} else if (option.value === '4') { | |
option.textContent = lang === 'zh' ? '白人' : 'White'; | |
} | |
}); | |
} | |
/** | |
* 初始化自动计算功能 | |
*/ | |
function initializeAutoCalculation() { | |
// 获取输入元素 | |
const heightInput = document.getElementById('height'); | |
const weightInput = document.getElementById('weight'); | |
const waistInput = document.getElementById('waist'); | |
const bmiInput = document.getElementById('body_mass_index'); | |
const wwiInput = document.getElementById('WWI'); | |
// 添加事件监听器 | |
if (heightInput && weightInput && waistInput) { | |
heightInput.addEventListener('input', calculateBMIAndWWI); | |
weightInput.addEventListener('input', calculateBMIAndWWI); | |
waistInput.addEventListener('input', calculateBMIAndWWI); | |
console.log('自动计算功能已初始化'); | |
} else { | |
console.warn('未找到身体测量输入字段'); | |
} | |
} | |
/** | |
* 计算BMI和WWI | |
*/ | |
function calculateBMIAndWWI() { | |
const height = parseFloat(document.getElementById('height').value); | |
const weight = parseFloat(document.getElementById('weight').value); | |
const waist = parseFloat(document.getElementById('waist').value); | |
const bmiInput = document.getElementById('body_mass_index'); | |
const wwiInput = document.getElementById('WWI'); | |
// 计算BMI | |
if (height && weight && height > 0) { | |
const heightInMeters = height / 100; // 转换为米 | |
const bmi = weight / (heightInMeters * heightInMeters); | |
bmiInput.value = bmi.toFixed(1); | |
// 添加BMI颜色指示 | |
updateBMIStatus(bmi); | |
} else { | |
bmiInput.value = ''; | |
bmiInput.className = 'form-control bg-light'; | |
} | |
// 计算WWI | |
if (waist && weight && weight > 0) { | |
const wwi = waist / Math.sqrt(weight); | |
wwiInput.value = wwi.toFixed(2); | |
// 添加WWI颜色指示 | |
updateWWIStatus(wwi); | |
} else { | |
wwiInput.value = ''; | |
wwiInput.className = 'form-control bg-light'; | |
} | |
// 触发表单验证更新 | |
updateSubmitButton(); | |
} | |
/** | |
* 更新BMI状态指示 | |
*/ | |
function updateBMIStatus(bmi) { | |
const bmiInput = document.getElementById('body_mass_index'); | |
// 移除旧的状态类 | |
bmiInput.className = 'form-control bg-light'; | |
if (bmi < 18.5) { | |
bmiInput.classList.add('border-info'); // 偏瘦 | |
} else if (bmi >= 18.5 && bmi < 24) { | |
bmiInput.classList.add('border-success'); // 正常 | |
} else if (bmi >= 24 && bmi < 28) { | |
bmiInput.classList.add('border-warning'); // 超重 | |
} else if (bmi >= 28) { | |
bmiInput.classList.add('border-danger'); // 肥胖 | |
} | |
} | |
/** | |
* 更新WWI状态指示 | |
*/ | |
function updateWWIStatus(wwi) { | |
const wwiInput = document.getElementById('WWI'); | |
// 移除旧的状态类 | |
wwiInput.className = 'form-control bg-light'; | |
// WWI正常范围大约在9-12之间 | |
if (wwi < 9) { | |
wwiInput.classList.add('border-info'); // 较低 | |
} else if (wwi >= 9 && wwi <= 12) { | |
wwiInput.classList.add('border-success'); // 正常 | |
} else if (wwi > 12 && wwi <= 14) { | |
wwiInput.classList.add('border-warning'); // 较高 | |
} else if (wwi > 14) { | |
wwiInput.classList.add('border-danger'); // 很高 | |
} | |
} | |
/** | |
* 初始化PAQ问卷条件显示逻辑 | |
*/ | |
function initializePAQLogic() { | |
// 工作高强度活动条件显示 | |
const paq605 = document.getElementById('PAQ605'); | |
const vigorousWorkDetails = document.getElementById('vigorous_work_details'); | |
if (paq605 && vigorousWorkDetails) { | |
paq605.addEventListener('change', function() { | |
if (this.value === '1') { | |
vigorousWorkDetails.style.display = 'block'; | |
document.getElementById('PAQ610').required = true; | |
document.getElementById('PAD615').required = true; | |
} else { | |
vigorousWorkDetails.style.display = 'none'; | |
document.getElementById('PAQ610').required = false; | |
document.getElementById('PAD615').required = false; | |
document.getElementById('PAQ610').value = ''; | |
document.getElementById('PAD615').value = ''; | |
} | |
}); | |
} | |
// 工作中等强度活动条件显示 | |
const paq620 = document.getElementById('PAQ620'); | |
const moderateWorkDetails = document.getElementById('moderate_work_details'); | |
if (paq620 && moderateWorkDetails) { | |
paq620.addEventListener('change', function() { | |
if (this.value === '1') { | |
moderateWorkDetails.style.display = 'block'; | |
document.getElementById('PAQ625').required = true; | |
document.getElementById('PAD630').required = true; | |
} else { | |
moderateWorkDetails.style.display = 'none'; | |
document.getElementById('PAQ625').required = false; | |
document.getElementById('PAD630').required = false; | |
document.getElementById('PAQ625').value = ''; | |
document.getElementById('PAD630').value = ''; | |
} | |
}); | |
} | |
// 交通活动条件显示 | |
const paq635 = document.getElementById('PAQ635'); | |
const transportDetails = document.getElementById('transport_details'); | |
if (paq635 && transportDetails) { | |
paq635.addEventListener('change', function() { | |
if (this.value === '1') { | |
transportDetails.style.display = 'block'; | |
document.getElementById('PAQ640').required = true; | |
document.getElementById('PAD645').required = true; | |
} else { | |
transportDetails.style.display = 'none'; | |
document.getElementById('PAQ640').required = false; | |
document.getElementById('PAD645').required = false; | |
document.getElementById('PAQ640').value = ''; | |
document.getElementById('PAD645').value = ''; | |
} | |
}); | |
} | |
// 休闲高强度活动条件显示 | |
const paq650 = document.getElementById('PAQ650'); | |
const vigorousRecDetails = document.getElementById('vigorous_rec_details'); | |
if (paq650 && vigorousRecDetails) { | |
paq650.addEventListener('change', function() { | |
if (this.value === '1') { | |
vigorousRecDetails.style.display = 'block'; | |
document.getElementById('PAQ655').required = true; | |
document.getElementById('PAD660').required = true; | |
} else { | |
vigorousRecDetails.style.display = 'none'; | |
document.getElementById('PAQ655').required = false; | |
document.getElementById('PAD660').required = false; | |
document.getElementById('PAQ655').value = ''; | |
document.getElementById('PAD660').value = ''; | |
} | |
}); | |
} | |
// 休闲中等强度活动条件显示 | |
const paq665 = document.getElementById('PAQ665'); | |
const moderateRecDetails = document.getElementById('moderate_rec_details'); | |
if (paq665 && moderateRecDetails) { | |
paq665.addEventListener('change', function() { | |
if (this.value === '1') { | |
moderateRecDetails.style.display = 'block'; | |
document.getElementById('PAQ670').required = true; | |
document.getElementById('PAD675').required = true; | |
} else { | |
moderateRecDetails.style.display = 'none'; | |
document.getElementById('PAQ670').required = false; | |
document.getElementById('PAD675').required = false; | |
document.getElementById('PAQ670').value = ''; | |
document.getElementById('PAD675').value = ''; | |
} | |
}); | |
} | |
console.log('PAQ问卷条件显示逻辑已初始化'); | |
} | |
// 导出到全局作用域 | |
window.SarcoAdvisor = { | |
exportResults, | |
showAlert, | |
getCurrentAssessment: () => currentAssessment, | |
toggleLanguage | |
}; |