618 lines
27 KiB
HTML
618 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>孕育无忧 - 孕期报告智能解读</title>
|
||
<style>
|
||
:root{--pink:#e91e8c;--pink-light:#fce4f3;--pink-bg:#fff5fb;--green:#27ae60;--orange:#f39c12;--red:#e74c3c;--gray:#999;--border:#eee;--shadow:0 2px 12px rgba(0,0,0,.08);--radius:12px}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB",sans-serif;background:#f5f5f5;color:#333;max-width:480px;margin:0 auto;min-height:100vh;position:relative}
|
||
.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(255,255,255,.85);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:9999}
|
||
.spinner{width:40px;height:40px;border:4px solid var(--pink-light);border-top-color:var(--pink);border-radius:50%;animation:spin .8s linear infinite}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
.loading-overlay p{margin-top:12px;color:var(--pink);font-size:14px}
|
||
|
||
/* Header */
|
||
.header{background:linear-gradient(135deg,var(--pink),#c2185b);color:#fff;padding:20px;text-align:center;position:sticky;top:0;z-index:100}
|
||
.header h1{font-size:20px;font-weight:600}
|
||
.header p{font-size:12px;opacity:.85;margin-top:4px}
|
||
|
||
/* Tab bar */
|
||
.tab-bar{display:flex;background:#fff;border-bottom:1px solid var(--border);position:sticky;top:72px;z-index:99}
|
||
.tab-bar button{flex:1;padding:12px 0;border:none;background:none;font-size:13px;color:var(--gray);cursor:pointer;border-bottom:2px solid transparent;transition:.2s}
|
||
.tab-bar button.active{color:var(--pink);border-bottom-color:var(--pink);font-weight:600}
|
||
|
||
/* Pages */
|
||
.page{display:none;padding:16px}
|
||
.page.active{display:block}
|
||
|
||
/* Cards */
|
||
.card{background:#fff;border-radius:var(--radius);padding:16px;margin-bottom:12px;box-shadow:var(--shadow)}
|
||
.card h3{font-size:15px;margin-bottom:8px;color:#333}
|
||
.card p{font-size:13px;color:#666;line-height:1.6}
|
||
|
||
/* Upload area */
|
||
.upload-area{border:2px dashed #ddd;border-radius:var(--radius);padding:40px 20px;text-align:center;cursor:pointer;transition:.2s;background:#fafafa}
|
||
.upload-area:hover{border-color:var(--pink);background:var(--pink-bg)}
|
||
.upload-area .icon{font-size:48px;margin-bottom:12px}
|
||
.upload-area p{color:var(--gray);font-size:14px}
|
||
.upload-area input{display:none}
|
||
|
||
/* Image preview */
|
||
.preview-img{max-width:100%;border-radius:8px;margin:12px 0}
|
||
|
||
/* Status badges */
|
||
.badge{display:inline-block;padding:2px 10px;border-radius:20px;font-size:12px;font-weight:500}
|
||
.badge-normal{background:#e8f5e9;color:var(--green)}
|
||
.badge-attention{background:#fff3e0;color:var(--orange)}
|
||
.badge-warning{background:#ffebee;color:var(--red)}
|
||
.badge-high{background:#ffebee;color:var(--red)}
|
||
.badge-low{background:#fff3e0;color:var(--orange)}
|
||
|
||
/* Buttons */
|
||
.btn{display:inline-block;padding:10px 24px;border-radius:24px;border:none;font-size:14px;cursor:pointer;transition:.2s;font-weight:500}
|
||
.btn-primary{background:var(--pink);color:#fff}
|
||
.btn-primary:hover{background:#c2185b}
|
||
.btn-primary:disabled{background:#ccc;cursor:not-allowed}
|
||
.btn-outline{border:1px solid var(--pink);color:var(--pink);background:none}
|
||
.btn-block{display:block;width:100%;text-align:center}
|
||
|
||
/* Data items table */
|
||
.data-table{width:100%;border-collapse:collapse;font-size:13px;margin:8px 0}
|
||
.data-table th{text-align:left;padding:8px 6px;border-bottom:2px solid var(--border);color:var(--gray);font-weight:500}
|
||
.data-table td{padding:8px 6px;border-bottom:1px solid var(--border)}
|
||
|
||
/* Chat */
|
||
.chat-container{display:flex;flex-direction:column;height:calc(100vh - 170px)}
|
||
.chat-messages{flex:1;overflow-y:auto;padding:12px 0}
|
||
.chat-msg{margin-bottom:12px;display:flex}
|
||
.chat-msg.user{justify-content:flex-end}
|
||
.chat-bubble{max-width:80%;padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.6;white-space:pre-wrap}
|
||
.chat-msg.user .chat-bubble{background:var(--pink);color:#fff;border-bottom-right-radius:4px}
|
||
.chat-msg.assistant .chat-bubble{background:#fff;color:#333;border-bottom-left-radius:4px;box-shadow:var(--shadow)}
|
||
.chat-input-area{display:flex;gap:8px;padding:12px 0}
|
||
.chat-input-area input{flex:1;padding:10px 16px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none}
|
||
.chat-input-area input:focus{border-color:var(--pink)}
|
||
.quick-questions{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}
|
||
.quick-q{padding:6px 12px;background:var(--pink-bg);color:var(--pink);border-radius:16px;font-size:12px;cursor:pointer;border:none;transition:.2s}
|
||
.quick-q:hover{background:var(--pink-light)}
|
||
|
||
/* Report list */
|
||
.report-item{display:flex;align-items:center;padding:12px;border-bottom:1px solid var(--border);cursor:pointer;transition:.1s}
|
||
.report-item:hover{background:#fafafa}
|
||
.report-item .info{flex:1;margin-left:12px}
|
||
.report-item .info h4{font-size:14px;margin-bottom:4px}
|
||
.report-item .info p{font-size:12px;color:var(--gray)}
|
||
.report-icon{width:40px;height:40px;border-radius:10px;background:var(--pink-light);display:flex;align-items:center;justify-content:center;font-size:20px}
|
||
|
||
/* Analysis result */
|
||
.analysis-section{margin-bottom:16px}
|
||
.analysis-section h4{font-size:14px;margin-bottom:8px;color:var(--pink)}
|
||
.indicator-card{background:#fafafa;border-radius:8px;padding:12px;margin-bottom:8px}
|
||
.indicator-card .name{font-weight:600;font-size:14px}
|
||
.indicator-card .nickname{color:var(--pink);font-size:12px;margin-left:6px}
|
||
.indicator-card .detail{font-size:13px;color:#555;margin-top:6px;line-height:1.6}
|
||
|
||
/* Action items */
|
||
.action-item{display:flex;align-items:flex-start;gap:8px;padding:8px 0;border-bottom:1px solid var(--border)}
|
||
.action-dot{width:8px;height:8px;border-radius:50%;margin-top:5px;flex-shrink:0}
|
||
.action-dot.high{background:var(--red)}
|
||
.action-dot.medium{background:var(--orange)}
|
||
.action-dot.low{background:var(--green)}
|
||
|
||
/* Empty state */
|
||
.empty-state{text-align:center;padding:60px 20px;color:var(--gray)}
|
||
.empty-state .icon{font-size:64px;margin-bottom:16px}
|
||
|
||
/* Verify form */
|
||
.verify-item{display:flex;gap:8px;margin-bottom:8px;align-items:center;flex-wrap:wrap}
|
||
.verify-item input,.verify-item select{padding:6px 10px;border:1px solid #ddd;border-radius:6px;font-size:13px}
|
||
.verify-item input{width:80px}
|
||
.verify-item .item-name{font-size:13px;font-weight:500;min-width:100px}
|
||
textarea{width:100%;padding:10px;border:1px solid #ddd;border-radius:8px;font-size:13px;resize:vertical;min-height:60px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="loading-overlay" id="globalLoading" style="display:none">
|
||
<div class="spinner"></div>
|
||
<p id="loadingText">加载中...</p>
|
||
</div>
|
||
|
||
<div class="header">
|
||
<h1>孕育无忧</h1>
|
||
<p>AI 智能解读孕期检查报告</p>
|
||
</div>
|
||
|
||
<div class="tab-bar">
|
||
<button class="active" onclick="showPage('home')">首页</button>
|
||
<button onclick="showPage('reports')">报告</button>
|
||
<button onclick="showPage('chat')">AI问答</button>
|
||
<button onclick="showPage('me')">我的</button>
|
||
</div>
|
||
|
||
<!-- ===== 首页 ===== -->
|
||
<div class="page active" id="page-home">
|
||
<div class="card">
|
||
<h3>📸 上传检查报告</h3>
|
||
<p style="margin-bottom:12px">拍照或选择孕期检查报告图片,AI 自动识别并解读</p>
|
||
<div class="upload-area" id="uploadArea">
|
||
<div class="icon">📋</div>
|
||
<p>点击上传报告图片</p>
|
||
<p style="font-size:12px;margin-top:4px;color:#bbb">支持 JPG、PNG 格式</p>
|
||
<input type="file" id="fileInput" accept="image/*">
|
||
</div>
|
||
</div>
|
||
|
||
<div id="ocrResult" style="display:none">
|
||
<div class="card">
|
||
<h3>📝 OCR 识别结果</h3>
|
||
<p style="margin-bottom:8px;color:var(--gray);font-size:12px">请核对以下识别数据,如有误请修正后确认</p>
|
||
<img id="previewImg" class="preview-img" alt="报告图片">
|
||
<div id="ocrDataItems"></div>
|
||
<div style="margin-top:12px">
|
||
<label style="font-size:13px;font-weight:500">自觉症状(可选)</label>
|
||
<textarea id="symptomsInput" placeholder="如:头晕、水肿、腰痛等..." style="margin-top:6px"></textarea>
|
||
</div>
|
||
<div style="margin-top:12px;text-align:center">
|
||
<button class="btn btn-primary btn-block" onclick="verifyReport()">确认数据,生成 AI 解读</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="analysisResult" style="display:none"></div>
|
||
</div>
|
||
|
||
<!-- ===== 报告列表 ===== -->
|
||
<div class="page" id="page-reports">
|
||
<div class="card">
|
||
<h3>📋 我的报告</h3>
|
||
<div id="reportList">
|
||
<div class="empty-state">
|
||
<div class="icon">📭</div>
|
||
<p>暂无报告,去首页上传吧</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== AI 对话 ===== -->
|
||
<div class="page" id="page-chat">
|
||
<div class="chat-container">
|
||
<div id="chatReportSelect" class="card" style="padding:10px 14px">
|
||
<label style="font-size:13px;color:var(--gray)">选择报告进行追问:</label>
|
||
<select id="chatReportId" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:8px;margin-top:6px;font-size:13px">
|
||
<option value="">请先上传报告</option>
|
||
</select>
|
||
</div>
|
||
<div id="quickQuestions" class="quick-questions"></div>
|
||
<div class="chat-messages" id="chatMessages">
|
||
<div class="empty-state">
|
||
<div class="icon">💬</div>
|
||
<p>选择报告后可以向 AI 追问</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-input-area">
|
||
<input type="text" id="chatInput" placeholder="输入你的问题..." onkeydown="if(event.key==='Enter')sendChat()">
|
||
<button class="btn btn-primary" onclick="sendChat()">发送</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== 我的 ===== -->
|
||
<div class="page" id="page-me">
|
||
<div class="card" style="text-align:center;padding:30px">
|
||
<div style="width:64px;height:64px;border-radius:50%;background:var(--pink-light);margin:0 auto 12px;display:flex;align-items:center;justify-content:center;font-size:28px">👩</div>
|
||
<h3 id="userName">体验用户</h3>
|
||
<p style="color:var(--gray);margin-top:4px" id="userInfo">加载中...</p>
|
||
</div>
|
||
<div class="card">
|
||
<h3>🍎 营养建议</h3>
|
||
<p style="margin-bottom:12px;font-size:13px;color:var(--gray)">根据报告选择对应的营养建议</p>
|
||
<select id="nutritionReportId" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:8px;font-size:13px">
|
||
<option value="">选择报告</option>
|
||
</select>
|
||
<button class="btn btn-outline btn-block" style="margin-top:8px" onclick="getNutrition()">获取营养建议</button>
|
||
<div id="nutritionResult" style="margin-top:12px"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h3>🏃♀️ 运动建议</h3>
|
||
<button class="btn btn-outline btn-block" onclick="getExercise()">获取运动建议</button>
|
||
<div id="exerciseResult" style="margin-top:12px"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const API = '';
|
||
let TOKEN = '';
|
||
let currentReportId = '';
|
||
let ocrDataItems = [];
|
||
|
||
// ---- Helpers ----
|
||
function showLoading(text='加载中...'){
|
||
document.getElementById('loadingText').textContent=text;
|
||
document.getElementById('globalLoading').style.display='flex';
|
||
}
|
||
function hideLoading(){document.getElementById('globalLoading').style.display='none'}
|
||
|
||
async function api(path,opts={}){
|
||
const headers = {'Authorization':'Bearer '+TOKEN, ...(opts.headers||{})};
|
||
if(opts.body && !(opts.body instanceof FormData)){
|
||
headers['Content-Type']='application/json';
|
||
opts.body=JSON.stringify(opts.body);
|
||
}
|
||
const res = await fetch(API+path, {...opts, headers});
|
||
if(!res.ok){
|
||
const err = await res.json().catch(()=>({detail:'请求失败'}));
|
||
throw new Error(err.detail||'请求失败');
|
||
}
|
||
return res.json();
|
||
}
|
||
|
||
function showPage(name){
|
||
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
|
||
document.getElementById('page-'+name).classList.add('active');
|
||
document.querySelectorAll('.tab-bar button').forEach((b,i)=>{
|
||
b.classList.toggle('active', ['home','reports','chat','me'][i]===name);
|
||
});
|
||
if(name==='reports') loadReportList();
|
||
if(name==='chat') loadChatReportSelect();
|
||
if(name==='me') loadProfile();
|
||
}
|
||
|
||
function badgeHtml(status){
|
||
const map={normal:'正常',attention:'注意',warning:'警告',high:'偏高',low:'偏低'};
|
||
return `<span class="badge badge-${status}">${map[status]||status}</span>`;
|
||
}
|
||
|
||
// ---- Auth ----
|
||
async function demoLogin(){
|
||
showLoading('正在登录...');
|
||
try{
|
||
const data = await fetch(API+'/api/auth/demo-login',{method:'POST'}).then(r=>r.json());
|
||
TOKEN = data.access_token;
|
||
hideLoading();
|
||
}catch(e){
|
||
hideLoading();
|
||
alert('登录失败:'+e.message);
|
||
}
|
||
}
|
||
|
||
// ---- Upload ----
|
||
document.getElementById('uploadArea').onclick=()=>document.getElementById('fileInput').click();
|
||
document.getElementById('fileInput').onchange=async function(){
|
||
const file = this.files[0];
|
||
if(!file) return;
|
||
showLoading('AI 正在识别报告...');
|
||
const fd = new FormData();
|
||
fd.append('file', file);
|
||
try{
|
||
const data = await api('/api/report/upload',{method:'POST',body:fd});
|
||
currentReportId = data.report_id;
|
||
ocrDataItems = data.ocr_result.data_items || [];
|
||
|
||
// Show preview
|
||
document.getElementById('previewImg').src = API + '/' + data.image_url;
|
||
renderOcrItems(ocrDataItems, data.ocr_result.report_type);
|
||
document.getElementById('ocrResult').style.display='block';
|
||
document.getElementById('analysisResult').style.display='none';
|
||
hideLoading();
|
||
}catch(e){
|
||
hideLoading();
|
||
alert('上传失败:'+e.message);
|
||
}
|
||
};
|
||
|
||
function renderOcrItems(items, reportType){
|
||
let html = `<div class="verify-item" style="margin-bottom:12px">
|
||
<span class="item-name">报告类型</span>
|
||
<input id="reportTypeInput" value="${reportType||''}" style="flex:1" placeholder="如:血常规">
|
||
</div>`;
|
||
html += '<table class="data-table"><thead><tr><th>指标</th><th>数值</th><th>单位</th><th>参考范围</th><th>状态</th></tr></thead><tbody>';
|
||
items.forEach((item,i)=>{
|
||
html += `<tr>
|
||
<td><input data-idx="${i}" data-field="name" value="${item.name||''}" style="width:80px;border:1px solid #eee;padding:4px;border-radius:4px"></td>
|
||
<td><input data-idx="${i}" data-field="value" value="${item.value||''}" style="width:60px;border:1px solid #eee;padding:4px;border-radius:4px"></td>
|
||
<td><input data-idx="${i}" data-field="unit" value="${item.unit||''}" style="width:50px;border:1px solid #eee;padding:4px;border-radius:4px"></td>
|
||
<td><input data-idx="${i}" data-field="reference_range" value="${item.reference_range||''}" style="width:80px;border:1px solid #eee;padding:4px;border-radius:4px"></td>
|
||
<td><select data-idx="${i}" data-field="status" style="border:1px solid #eee;padding:4px;border-radius:4px">
|
||
<option value="normal" ${item.status==='normal'?'selected':''}>正常</option>
|
||
<option value="high" ${item.status==='high'?'selected':''}>偏高</option>
|
||
<option value="low" ${item.status==='low'?'selected':''}>偏低</option>
|
||
</select></td>
|
||
</tr>`;
|
||
});
|
||
html += '</tbody></table>';
|
||
document.getElementById('ocrDataItems').innerHTML = html;
|
||
}
|
||
|
||
async function verifyReport(){
|
||
// Collect edited data
|
||
const items = ocrDataItems.map((_,i)=>{
|
||
const get = f => document.querySelector(`[data-idx="${i}"][data-field="${f}"]`).value;
|
||
return {name:get('name'),value:get('value'),unit:get('unit'),reference_range:get('reference_range'),status:get('status'),category:''};
|
||
});
|
||
const reportType = document.getElementById('reportTypeInput').value;
|
||
const symptoms = document.getElementById('symptomsInput').value;
|
||
|
||
showLoading('正在确认数据...');
|
||
try{
|
||
await api(`/api/report/${currentReportId}/verify`,{
|
||
method:'PUT',
|
||
body:{data_items:items,symptoms:symptoms||null,notes:null,report_type:reportType||null}
|
||
});
|
||
hideLoading();
|
||
generateAnalysis();
|
||
}catch(e){
|
||
hideLoading();
|
||
alert('确认失败:'+e.message);
|
||
}
|
||
}
|
||
|
||
async function generateAnalysis(){
|
||
showLoading('AI 正在解读报告,请稍候...');
|
||
try{
|
||
const data = await api(`/api/analysis/${currentReportId}/generate`,{method:'POST'});
|
||
renderAnalysis(data);
|
||
hideLoading();
|
||
}catch(e){
|
||
hideLoading();
|
||
alert('解读失败:'+e.message);
|
||
}
|
||
}
|
||
|
||
function renderAnalysis(data){
|
||
let html = '<div class="card"><h3>🤖 AI 解读报告</h3>';
|
||
// Overall
|
||
html += `<div class="analysis-section">
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||
${badgeHtml(data.overall_status)}
|
||
<span style="font-size:13px;color:var(--gray)">风险等级:${({low:'低',medium:'中',high:'高'})[data.risk_level]||data.risk_level}</span>
|
||
</div>
|
||
<p style="font-size:14px;line-height:1.6">${data.overall_summary}</p>
|
||
<p style="font-size:14px;color:var(--pink);margin-top:6px">${data.emotional_summary}</p>
|
||
</div>`;
|
||
|
||
// Categories
|
||
if(data.categories && data.categories.length){
|
||
data.categories.forEach(cat=>{
|
||
html += `<div class="analysis-section">
|
||
<h4>${cat.category} ${badgeHtml(cat.status)}</h4>`;
|
||
(cat.indicators||[]).forEach(ind=>{
|
||
html += `<div class="indicator-card">
|
||
<div><span class="name">${ind.name}</span><span class="nickname">${ind.nickname||''}</span> ${badgeHtml(ind.status)} <span style="font-size:12px;color:var(--gray);margin-left:6px">${ind.value||''}</span></div>
|
||
<div class="detail">
|
||
${ind.what_is_it?'<div>📖 '+ind.what_is_it+'</div>':''}
|
||
${ind.impact?'<div>👶 '+ind.impact+'</div>':''}
|
||
${ind.suggestion?'<div>💡 '+ind.suggestion+'</div>':''}
|
||
${ind.recheck_reminder?'<div>🔔 '+ind.recheck_reminder+'</div>':''}
|
||
</div>
|
||
</div>`;
|
||
});
|
||
html += '</div>';
|
||
});
|
||
}
|
||
|
||
// Action items
|
||
if(data.action_items && data.action_items.length){
|
||
html += '<div class="analysis-section"><h4>📋 行动建议</h4>';
|
||
data.action_items.forEach(a=>{
|
||
html += `<div class="action-item"><div class="action-dot ${a.priority}"></div><div>
|
||
<div style="font-size:13px">${a.content}</div>
|
||
<div style="font-size:11px;color:var(--gray)">${a.category||''}</div>
|
||
</div></div>`;
|
||
});
|
||
html += '</div>';
|
||
}
|
||
|
||
// Tips
|
||
if(data.tips && data.tips.length){
|
||
html += '<div class="analysis-section"><h4>💝 温馨提示</h4>';
|
||
data.tips.forEach(t=>{html += `<p style="font-size:13px;padding:4px 0;color:#555">• ${t}</p>`});
|
||
html += '</div>';
|
||
}
|
||
|
||
html += `<div style="margin-top:12px;display:flex;gap:8px">
|
||
<button class="btn btn-outline" style="flex:1" onclick="showPage('chat')">💬 追问 AI</button>
|
||
<button class="btn btn-outline" style="flex:1" onclick="getDoctorQuestions()">🩺 问诊清单</button>
|
||
</div>`;
|
||
html += '</div>';
|
||
|
||
document.getElementById('analysisResult').innerHTML = html;
|
||
document.getElementById('analysisResult').style.display = 'block';
|
||
}
|
||
|
||
// ---- Report List ----
|
||
async function loadReportList(){
|
||
try{
|
||
const data = await api('/api/report/list');
|
||
if(!data.length){
|
||
document.getElementById('reportList').innerHTML='<div class="empty-state"><div class="icon">📭</div><p>暂无报告,去首页上传吧</p></div>';
|
||
return;
|
||
}
|
||
let html='';
|
||
data.forEach(r=>{
|
||
const date = new Date(r.created_at).toLocaleDateString('zh-CN');
|
||
html += `<div class="report-item" onclick="viewReport('${r.id}')">
|
||
<div class="report-icon">📋</div>
|
||
<div class="info"><h4>${r.report_type||'检查报告'}</h4><p>${date} · 孕${r.pregnancy_week||'?'}周 · ${r.item_count}项指标</p></div>
|
||
${badgeHtml(r.overall_status)}
|
||
</div>`;
|
||
});
|
||
document.getElementById('reportList').innerHTML=html;
|
||
}catch(e){}
|
||
}
|
||
|
||
async function viewReport(id){
|
||
currentReportId = id;
|
||
showLoading('加载报告...');
|
||
try{
|
||
const data = await api(`/api/analysis/${id}`);
|
||
renderAnalysis(data);
|
||
showPage('home');
|
||
document.getElementById('ocrResult').style.display='none';
|
||
document.getElementById('analysisResult').style.display='block';
|
||
hideLoading();
|
||
}catch(e){
|
||
hideLoading();
|
||
// No analysis yet, show report detail
|
||
try{
|
||
const report = await api(`/api/report/${id}`);
|
||
currentReportId = id;
|
||
ocrDataItems = report.data_items||[];
|
||
renderOcrItems(ocrDataItems, report.report_type);
|
||
showPage('home');
|
||
document.getElementById('ocrResult').style.display='block';
|
||
document.getElementById('analysisResult').style.display='none';
|
||
}catch(e2){alert(e2.message)}
|
||
}
|
||
}
|
||
|
||
// ---- Chat ----
|
||
async function loadChatReportSelect(){
|
||
try{
|
||
const data = await api('/api/report/list');
|
||
const sel = document.getElementById('chatReportId');
|
||
sel.innerHTML = data.length ? data.map(r=>`<option value="${r.id}">${r.report_type||'报告'} - ${new Date(r.created_at).toLocaleDateString('zh-CN')}</option>`).join('') : '<option value="">请先上传报告</option>';
|
||
if(currentReportId) sel.value=currentReportId;
|
||
loadChatHistory();
|
||
}catch(e){}
|
||
}
|
||
|
||
async function loadChatHistory(){
|
||
const rid = document.getElementById('chatReportId').value;
|
||
if(!rid) return;
|
||
try{
|
||
const data = await api(`/api/chat/${rid}/history`);
|
||
const container = document.getElementById('chatMessages');
|
||
if(!data.messages.length){
|
||
container.innerHTML='<div class="empty-state" style="padding:30px"><div class="icon">💬</div><p>有任何疑问都可以问我哦~</p></div>';
|
||
}else{
|
||
container.innerHTML = data.messages.map(m=>`<div class="chat-msg ${m.role}"><div class="chat-bubble">${m.content}</div></div>`).join('');
|
||
container.scrollTop = container.scrollHeight;
|
||
}
|
||
// Quick questions
|
||
const qqs = data.quick_questions||["这个指标偏高会影响宝宝吗?","我需要注意什么饮食?","什么时候需要复查?"];
|
||
document.getElementById('quickQuestions').innerHTML = qqs.map(q=>`<button class="quick-q" onclick="askQuick('${q}')">${q}</button>`).join('');
|
||
}catch(e){}
|
||
}
|
||
|
||
document.getElementById('chatReportId').onchange=loadChatHistory;
|
||
|
||
function askQuick(q){document.getElementById('chatInput').value=q;sendChat()}
|
||
|
||
async function sendChat(){
|
||
const rid = document.getElementById('chatReportId').value;
|
||
const input = document.getElementById('chatInput');
|
||
const msg = input.value.trim();
|
||
if(!rid||!msg) return;
|
||
input.value='';
|
||
|
||
const container = document.getElementById('chatMessages');
|
||
// Clear empty state
|
||
if(container.querySelector('.empty-state')) container.innerHTML='';
|
||
container.innerHTML+=`<div class="chat-msg user"><div class="chat-bubble">${msg}</div></div>`;
|
||
container.innerHTML+=`<div class="chat-msg assistant" id="typingIndicator"><div class="chat-bubble">思考中...</div></div>`;
|
||
container.scrollTop = container.scrollHeight;
|
||
|
||
try{
|
||
const data = await api(`/api/chat/${rid}/message`,{method:'POST',body:{content:msg}});
|
||
document.getElementById('typingIndicator').remove();
|
||
container.innerHTML+=`<div class="chat-msg assistant"><div class="chat-bubble">${data.ai_message.content}</div></div>`;
|
||
container.scrollTop = container.scrollHeight;
|
||
}catch(e){
|
||
document.getElementById('typingIndicator').remove();
|
||
container.innerHTML+=`<div class="chat-msg assistant"><div class="chat-bubble" style="color:var(--red)">回复失败:${e.message}</div></div>`;
|
||
}
|
||
}
|
||
|
||
// ---- Profile ----
|
||
async function loadProfile(){
|
||
try{
|
||
const data = await api('/api/user/profile');
|
||
document.getElementById('userName').textContent = data.name||'体验用户';
|
||
document.getElementById('userInfo').textContent = `${data.age||'?'}岁 · 孕${data.pregnancy_week||'?'}周${data.pregnancy_day||0}天 · ${data.blood_type||'?'}型血`;
|
||
// Also update nutrition select
|
||
const reports = await api('/api/report/list');
|
||
document.getElementById('nutritionReportId').innerHTML = reports.length ? reports.map(r=>`<option value="${r.id}">${r.report_type||'报告'} - ${new Date(r.created_at).toLocaleDateString('zh-CN')}</option>`).join('') : '<option value="">暂无报告</option>';
|
||
}catch(e){}
|
||
}
|
||
|
||
// ---- Nutrition ----
|
||
async function getNutrition(){
|
||
const rid = document.getElementById('nutritionReportId').value;
|
||
if(!rid){alert('请先选择报告');return}
|
||
showLoading('AI 正在生成营养建议...');
|
||
try{
|
||
const data = await api(`/api/nutrition/${rid}`);
|
||
let html='';
|
||
(data.recipes||[]).forEach(r=>{
|
||
html+=`<div style="background:#fafafa;border-radius:8px;padding:12px;margin-bottom:8px">
|
||
<div style="font-weight:600;font-size:14px">🍳 ${r.name}</div>
|
||
<p style="font-size:12px;color:var(--gray);margin-top:4px">${r.description||''}</p>
|
||
<p style="font-size:12px;margin-top:4px">⏱ ${r.time||''} · ${r.difficulty||''}</p>
|
||
<p style="font-size:12px;color:var(--pink);margin-top:4px">📌 ${r.reason||''}</p>
|
||
${r.ingredients?'<p style="font-size:12px;margin-top:4px">食材:'+r.ingredients.join('、')+'</p>':''}
|
||
</div>`;
|
||
});
|
||
if(data.dietary_tips&&data.dietary_tips.length){
|
||
html+='<div style="margin-top:8px"><h4 style="font-size:13px;color:var(--orange)">⚠️ 饮食提醒</h4>';
|
||
data.dietary_tips.forEach(t=>{html+=`<p style="font-size:13px;padding:2px 0">• ${t}</p>`});
|
||
html+='</div>';
|
||
}
|
||
document.getElementById('nutritionResult').innerHTML=html;
|
||
hideLoading();
|
||
}catch(e){hideLoading();alert('获取失败:'+e.message)}
|
||
}
|
||
|
||
// ---- Exercise ----
|
||
async function getExercise(){
|
||
showLoading('AI 正在生成运动建议...');
|
||
try{
|
||
const data = await api('/api/exercise/recommendations');
|
||
let html='';
|
||
(data.exercises||[]).forEach(ex=>{
|
||
html+=`<div style="background:#fafafa;border-radius:8px;padding:12px;margin-bottom:8px">
|
||
<div style="font-weight:600;font-size:14px">🏃 ${ex.name}</div>
|
||
<p style="font-size:12px;color:var(--gray);margin-top:4px">${ex.description||''}</p>
|
||
<p style="font-size:12px;margin-top:4px">⏱ ${ex.duration||''} · 🔥 ${ex.calories||''} · ${ex.difficulty||''}</p>
|
||
${ex.benefits?'<p style="font-size:12px;color:var(--green);margin-top:4px">✅ '+ex.benefits.join(' · ')+'</p>':''}
|
||
${ex.precautions?'<p style="font-size:12px;color:var(--orange);margin-top:4px">⚠️ '+ex.precautions.join(' · ')+'</p>':''}
|
||
</div>`;
|
||
});
|
||
if(data.daily_schedule&&data.daily_schedule.length){
|
||
html+='<div style="margin-top:8px"><h4 style="font-size:13px;color:var(--pink)">📅 每日运动安排</h4>';
|
||
data.daily_schedule.forEach(s=>{html+=`<p style="font-size:13px;padding:2px 0">${s.time} ${s.activity} (${s.duration})</p>`});
|
||
html+='</div>';
|
||
}
|
||
document.getElementById('exerciseResult').innerHTML=html;
|
||
hideLoading();
|
||
}catch(e){hideLoading();alert('获取失败:'+e.message)}
|
||
}
|
||
|
||
// ---- Doctor Questions ----
|
||
async function getDoctorQuestions(){
|
||
if(!currentReportId){alert('请先选择报告');return}
|
||
showLoading('AI 正在生成问诊清单...');
|
||
try{
|
||
const data = await api(`/api/doctor/questions/${currentReportId}`);
|
||
let html='<div class="card"><h3>🩺 问诊清单</h3><p style="font-size:12px;color:var(--gray);margin-bottom:12px">带着这些问题去看医生,沟通更高效</p>';
|
||
(data.questions||[]).forEach(q=>{
|
||
html+=`<div class="action-item"><div class="action-dot ${q.priority}"></div><div>
|
||
<div style="font-size:14px;font-weight:500">"${q.question}"</div>
|
||
<div style="font-size:12px;color:var(--gray);margin-top:2px">💡 ${q.reason}</div>
|
||
</div></div>`;
|
||
});
|
||
html+='</div>';
|
||
document.getElementById('analysisResult').innerHTML+=html;
|
||
hideLoading();
|
||
}catch(e){hideLoading();alert('获取失败:'+e.message)}
|
||
}
|
||
|
||
// ---- Init ----
|
||
demoLogin();
|
||
</script>
|
||
</body>
|
||
</html>
|