SkinLens / static /index.html
leonsimon23's picture
Create static/index.html
ac24a6b verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>皮镜 (SkinLens) - 智能皮肤诊疗助手</title>
<style>
/* --- 全局样式 --- */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
body {
font-family: 'Noto Sans SC', sans-serif;
background-color: #f4f7f9;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
color: #333;
}
/* --- 主容器 --- */
.container {
width: 100%;
max-width: 900px;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
padding: 40px;
box-sizing: border-box;
}
/* --- 头部 --- */
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 20px;
}
.header h1 {
color: #1a73e8;
margin: 0;
font-weight: 500;
}
.header p {
color: #5f6368;
margin-top: 5px;
font-size: 1rem;
}
/* --- 主内容区 --- */
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
}
/* --- 输入卡片样式 --- */
.card {
background: #fff;
padding: 25px;
border-radius: 8px;
border: 1px solid #dfe1e5;
}
.card h2 {
font-size: 1.2rem;
margin-top: 0;
margin-bottom: 20px;
color: #1a73e8;
border-bottom: 2px solid #e8f0fe;
padding-bottom: 10px;
}
/* --- 图片上传区域 --- */
.image-uploader {
border: 2px dashed #d0d0d0;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s, background-color 0.3s;
position: relative;
}
.image-uploader:hover {
border-color: #1a73e8;
background-color: #f8f9fa;
}
.image-uploader .upload-text {
color: #5f6368;
}
#image-preview {
max-width: 100%;
max-height: 200px;
margin-top: 15px;
border-radius: 4px;
display: none;
}
#image-input {
display: none;
}
/* --- 文本输入区域 --- */
textarea {
width: 100%;
height: 150px;
border-radius: 4px;
border: 1px solid #d0d0d0;
padding: 10px;
font-family: inherit;
font-size: 0.95rem;
resize: vertical;
box-sizing: border-box;
}
textarea:focus {
outline: none;
border-color: #1a73e8;
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
}
/* --- 操作按钮 --- */
.actions {
grid-column: 1 / -1; /* 跨越所有列 */
text-align: center;
margin-top: 20px;
}
#analyze-btn {
background-color: #1a73e8;
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
#analyze-btn:hover {
background-color: #185abc;
box-shadow: 0 4px 12px rgba(26, 115, 232, 0.2);
}
#analyze-btn:disabled {
background-color: #a0c3ff;
cursor: not-allowed;
}
/* --- 加载动画 --- */
.loader {
width: 18px;
height: 18px;
border: 2px solid #fff;
border-bottom-color: transparent;
border-radius: 50%;
display: none; /* 初始隐藏 */
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* --- 结果展示区域 --- */
#results-container {
margin-top: 40px;
display: none; /* 初始隐藏 */
border-top: 1px solid #e0e0e0;
padding-top: 30px;
}
.results-grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.result-card {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
border: 1px solid #e0e0e0;
}
.result-card h3 {
margin-top: 0;
color: #1a73e8;
font-size: 1.1rem;
margin-bottom: 15px;
}
.result-card ul {
padding-left: 20px;
margin: 0;
color: #3c4043;
}
.result-card li {
margin-bottom: 8px;
}
.diagnosis-item {
border-bottom: 1px solid #e0e0e0;
padding-bottom: 10px;
margin-bottom: 10px;
}
.diagnosis-item:last-child {
border-bottom: none;
}
.diagnosis-item strong {
color: #202124;
}
.diagnosis-item .likelihood {
font-weight: 500;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8rem;
margin-left: 8px;
}
.likelihood-高 { background-color: #e57373; color: white; }
.likelihood-中 { background-color: #ffb74d; color: white; }
.likelihood-低 { background-color: #81c784; color: white; }
.error-message {
color: #d93025;
text-align: center;
margin-top: 20px;
display: none; /* 初始隐藏 */
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>皮镜 (SkinLens)</h1>
<p>您的智能皮肤科临床诊疗辅助伙伴</p>
</div>
<div class="main-content">
<!-- 图片上传卡片 -->
<div class="card">
<h2>1. 上传皮肤图像</h2>
<div class="image-uploader" id="image-uploader-box" onclick="document.getElementById('image-input').click();">
<p class="upload-text">点击或拖拽图片到此区域</p>
<img id="image-preview" src="#" alt="Image Preview"/>
</div>
<input type="file" id="image-input" accept="image/jpeg, image/png">
</div>
<!-- 病历信息卡片 -->
<div class="card">
<h2>2. 输入病历信息</h2>
<textarea id="prompt-input" placeholder="请输入患者主诉、现病史、查体等信息..."></textarea>
</div>
</div>
<div class="actions">
<button id="analyze-btn">
<span id="btn-text">开始智能分析</span>
<div class="loader" id="loader"></div>
</button>
</div>
<p id="error-message" class="error-message"></p>
<!-- 结果展示容器 -->
<div id="results-container">
<div class="results-grid">
<div class="result-card" id="diagnosis-card">
<h3>鉴别诊断列表</h3>
<div id="differential-diagnosis-content"></div>
</div>
<div class="result-card" id="features-card">
<h3>图像特征分析</h3>
<ul id="image-features-content"></ul>
</div>
<div class="result-card" id="analysis-card" style="grid-column: 1 / -1;">
<h3>综合分析</h3>
<p id="comprehensive-analysis-content"></p>
</div>
<div class="result-card" id="recommendations-card" style="grid-column: 1 / -1;">
<h3>下一步建议</h3>
<div class="results-grid">
<div>
<h4>辅助检查</h4>
<ul id="further-examinations-content"></ul>
</div>
<div>
<h4>治疗方向</h4>
<ul id="treatment-suggestions-content"></ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const imageInput = document.getElementById('image-input');
const imagePreview = document.getElementById('image-preview');
const imageUploaderBox = document.getElementById('image-uploader-box');
const uploadText = document.querySelector('.upload-text');
const analyzeBtn = document.getElementById('analyze-btn');
const btnText = document.getElementById('btn-text');
const loader = document.getElementById('loader');
const promptInput = document.getElementById('prompt-input');
const resultsContainer = document.getElementById('results-container');
const errorMessage = document.getElementById('error-message');
let imageBase64 = null;
// --- 事件监听:图片选择 ---
imageInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
uploadText.style.display = 'none';
// 转换为Base64
imageBase64 = e.target.result.split(',')[1];
};
reader.readAsDataURL(file);
}
});
// --- 事件监听:分析按钮点击 ---
analyzeBtn.addEventListener('click', async () => {
if (!imageBase64 || !promptInput.value.trim()) {
showError("请务必上传一张图片并填写病历信息。");
return;
}
// --- UI状态:开始分析 ---
toggleLoading(true);
resultsContainer.style.display = 'none';
errorMessage.style.display = 'none';
try {
const response = await fetch('/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
image: imageBase64,
text: promptInput.value
})
});
if (!response.ok) {
const errData = await response.json();
throw new Error(errData.error || `HTTP 错误! 状态: ${response.status}`);
}
const data = await response.json();
displayResults(data);
} catch (error) {
showError(`分析失败: ${error.message}`);
} finally {
// --- UI状态:分析结束 ---
toggleLoading(false);
}
});
// --- 功能函数:切换加载状态 ---
function toggleLoading(isLoading) {
analyzeBtn.disabled = isLoading;
if (isLoading) {
btnText.textContent = "分析中...";
loader.style.display = 'block';
} else {
btnText.textContent = "开始智能分析";
loader.style.display = 'none';
}
}
// --- 功能函数:显示错误信息 ---
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
}
// --- 功能函数:渲染结果到页面 ---
function displayResults(data) {
// 鉴别诊断
const diagnosisContent = document.getElementById('differential-diagnosis-content');
diagnosisContent.innerHTML = data.differential_diagnosis.map(item => `
<div class="diagnosis-item">
<strong>${item.diagnosis}</strong>
<span class="likelihood likelihood-${item.likelihood}">${item.likelihood}</span>
<p style="font-size:0.9rem; color:#5f6368; margin-top:5px;">依据: ${item.evidence}</p>
</div>
`).join('');
// 图像特征
document.getElementById('image-features-content').innerHTML = data.image_features.map(item => `<li>${item}</li>`).join('');
// 综合分析
document.getElementById('comprehensive-analysis-content').textContent = data.comprehensive_analysis;
// 建议
document.getElementById('further-examinations-content').innerHTML = data.recommendations.further_examinations.map(item => `<li>${item}</li>`).join('');
document.getElementById('treatment-suggestions-content').innerHTML = data.recommendations.treatment_suggestions.map(item => `<li>${item}</li>`).join('');
resultsContainer.style.display = 'block';
// 平滑滚动到结果区域
resultsContainer.scrollIntoView({ behavior: 'smooth' });
}
</script>
</body>
</html>