Files
health-wellness-device-2/data/index.html
yangzhijia 2ce4ac0b9c 版本1.2
2026-08-06 13:58:45 +08:00

462 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>健康监测系统 - 配网设置</title>
<style>
:root {
--primary: #4A90D9;
--danger: #E74C3C;
--success: #27AE60;
--bg: #F5F7FA;
--card-bg: #FFFFFF;
--text: #2C3E50;
--text-light: #7F8C8D;
--border: #E1E8ED;
--radius: 12px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
max-width: 420px;
width: 100%;
}
/* 头部 */
.header {
text-align: center;
padding: 24px 0;
}
.header .icon {
font-size: 48px;
margin-bottom: 8px;
}
.header h1 {
font-size: 22px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.header p {
font-size: 14px;
color: var(--text-light);
}
/* 卡片 */
.card {
background: var(--card-bg);
border-radius: var(--radius);
padding: 20px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.card-title .emoji {
font-size: 20px;
}
/* 表单 */
.form-group {
margin-bottom: 14px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-group label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-light);
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-group input {
width: 100%;
height: 44px;
padding: 0 14px;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 15px;
color: var(--text);
background: var(--bg);
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.form-group input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15);
background: var(--card-bg);
}
.form-group input::placeholder {
color: #BDC3C7;
}
/* 环境备注 - 高亮 */
.env-note-card {
background: linear-gradient(135deg, #FFF9E6, #FFF3CD);
border: 2px solid #FFC107;
}
.env-note-card .card-title {
color: #856404;
}
.env-note-card input {
background: #FFFFFF;
border-color: #FFC107;
font-size: 16px;
font-weight: 500;
}
.env-note-hint {
font-size: 12px;
color: #856404;
margin-top: 6px;
padding-left: 2px;
}
/* 提交按钮 */
.btn-submit {
width: 100%;
height: 50px;
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius);
font-size: 17px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
margin-top: 8px;
letter-spacing: 1px;
}
.btn-submit:active {
transform: scale(0.98);
background: #357ABD;
}
.btn-submit:hover {
background: #3D7EC0;
}
/* 重置按钮 */
.btn-reset {
width: 100%;
height: 44px;
background: transparent;
color: var(--danger);
border: 2px solid var(--danger);
border-radius: var(--radius);
font-size: 15px;
font-weight: 500;
cursor: pointer;
margin-top: 8px;
transition: background 0.2s;
}
.btn-reset:active {
background: #FDEDEC;
}
/* 状态栏 */
.status-bar {
text-align: center;
font-size: 12px;
color: var(--text-light);
padding: 16px 0;
}
.status-bar .dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
animation: pulse 2s infinite;
}
.dot.connected { background: var(--success); }
.dot.disconnected { background: var(--danger); }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Toast 提示 */
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: var(--success);
color: white;
padding: 12px 24px;
border-radius: 24px;
font-size: 14px;
font-weight: 500;
z-index: 1000;
display: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.toast.show {
display: block;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from { opacity: 0; transform: translateX(-50%) translateY(-10px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<div class="icon">🏥</div>
<h1>健康监测系统</h1>
<p>ESP32-S3 + 毫米波雷达 · 配网设置</p>
</div>
<!-- WiFi 设置 -->
<div class="card">
<div class="card-title">
<span class="emoji">📶</span> WiFi 设置
</div>
<div class="form-group">
<label>WiFi 名称 (SSID)</label>
<input type="text" id="wifi-ssid" placeholder="请输入 WiFi 名称" autocomplete="off">
</div>
<div class="form-group">
<label>WiFi 密码</label>
<input type="password" id="wifi-pass" placeholder="请输入 WiFi 密码">
</div>
</div>
<!-- MQTT 设置 -->
<div class="card">
<div class="card-title">
<span class="emoji">☁️</span> MQTT 云平台设置
</div>
<div class="form-group">
<label>MQTT 服务器地址</label>
<input type="text" id="mqtt-server" placeholder="例如: 192.168.1.100" value="192.168.1.100">
</div>
<div class="form-group">
<label>MQTT 端口</label>
<input type="number" id="mqtt-port" placeholder="1883" value="1883">
</div>
<div class="form-group">
<label>MQTT 用户名 (可选)</label>
<input type="text" id="mqtt-user" placeholder="用户名">
</div>
<div class="form-group">
<label>MQTT 密码 (可选)</label>
<input type="password" id="mqtt-pass" placeholder="密码">
</div>
</div>
<!-- 环境备注 - 重点突出 -->
<div class="card env-note-card">
<div class="card-title">
<span class="emoji">🏠</span> 环境备注
</div>
<div class="form-group">
<label>设备位置 / 房间名称</label>
<input type="text" id="env-note" placeholder="例如: 主卧 / 客厅 / 老人房 / 婴儿房" value="主卧">
</div>
<div class="env-note-hint">
💡 请备注此设备安装的环境位置,方便在云平台上区分不同房间的数据
</div>
</div>
<!-- 设备设置 -->
<div class="card">
<div class="card-title">
<span class="emoji">📱</span> 设备设置
</div>
<div class="form-group">
<label>设备名称 / ID</label>
<input type="text" id="device-id" placeholder="例如: health_001" value="health_001">
</div>
</div>
<!-- 按钮 -->
<button class="btn-submit" onclick="submitConfig()">
💾 保存配置并连接
</button>
<button class="btn-reset" onclick="resetConfig()">
🔄 恢复出厂设置
</button>
<!-- 状态 -->
<div class="status-bar" id="status-bar">
<span class="dot disconnected"></span> 等待配置...
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
// 显示 Toast
function showToast(msg, duration = 3000) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), duration);
}
// 读取已保存的配置 (从 LocalStorage)
function loadConfig() {
try {
const saved = JSON.parse(localStorage.getItem('health_config') || '{}');
if (saved.mqtt_server) document.getElementById('mqtt-server').value = saved.mqtt_server;
if (saved.mqtt_port) document.getElementById('mqtt-port').value = saved.mqtt_port;
if (saved.mqtt_user) document.getElementById('mqtt-user').value = saved.mqtt_user;
if (saved.mqtt_pass) document.getElementById('mqtt-pass').value = saved.mqtt_pass;
if (saved.device_id) document.getElementById('device-id').value = saved.device_id;
if (saved.env_note) document.getElementById('env-note').value = saved.env_note;
if (saved.wifi_ssid) document.getElementById('wifi-ssid').value = saved.wifi_ssid;
} catch(e) { /* ignore */ }
}
// 保存配置到 LocalStorage
function saveConfigLocal() {
const config = {
wifi_ssid: document.getElementById('wifi-ssid').value,
wifi_pass: document.getElementById('wifi-pass').value,
mqtt_server: document.getElementById('mqtt-server').value,
mqtt_port: document.getElementById('mqtt-port').value,
mqtt_user: document.getElementById('mqtt-user').value,
mqtt_pass: document.getElementById('mqtt-pass').value,
device_id: document.getElementById('device-id').value,
env_note: document.getElementById('env-note').value,
};
localStorage.setItem('health_config', JSON.stringify(config));
}
// 提交配置
function submitConfig() {
const envNote = document.getElementById('env-note').value.trim();
if (!envNote) {
showToast('⚠️ 请填写环境备注 (设备安装位置)');
document.getElementById('env-note').focus();
return;
}
const ssid = document.getElementById('wifi-ssid').value.trim();
if (!ssid) {
showToast('⚠️ 请填写 WiFi 名称');
document.getElementById('wifi-ssid').focus();
return;
}
// 保存到本地
saveConfigLocal();
// 构建提交参数
const params = new URLSearchParams();
params.append('s', ssid);
params.append('p', document.getElementById('wifi-pass').value);
params.append('ms', document.getElementById('mqtt-server').value);
params.append('mp', document.getElementById('mqtt-port').value);
params.append('mu', document.getElementById('mqtt-user').value);
params.append('mw', document.getElementById('mqtt-pass').value);
params.append('id', document.getElementById('device-id').value);
params.append('env', envNote);
// 更新状态
document.getElementById('status-bar').innerHTML =
'<span class="dot connected"></span> 正在保存配置...';
showToast('✅ 配置已保存! 设备正在连接...');
// 实际设备中,这里通过 WiFiManager API 提交
// 开发环境中模拟提交
fetch('/save', {
method: 'POST',
body: params
}).then(() => {
document.getElementById('status-bar').innerHTML =
'<span class="dot connected"></span> ✅ 配置成功! WiFi 已连接';
showToast('🎉 连接成功! 设备已上线');
}).catch(() => {
// 如果是离线 HTML (直接打开), 提示用户
document.getElementById('status-bar').innerHTML =
'<span class="dot connected"></span> 📋 配置已准备 (请通过 ESP32 配网页面提交)';
showToast('📋 配置参数已保存到本地浏览器');
});
}
// 重置
function resetConfig() {
if (confirm('确定要恢复出厂设置吗? 所有配置将被清除!')) {
localStorage.removeItem('health_config');
document.querySelectorAll('input').forEach(el => {
if (el.type !== 'button') {
el.value = el.defaultValue || '';
}
});
document.getElementById('env-note').value = '主卧';
document.getElementById('mqtt-server').value = '192.168.1.100';
document.getElementById('mqtt-port').value = '1883';
document.getElementById('device-id').value = 'health_001';
showToast('🔄 已恢复出厂设置');
}
}
// 页面加载
loadConfig();
// 每 5 秒检查设备状态 (模拟)
setInterval(() => {
const bar = document.getElementById('status-bar');
if (!bar.textContent.includes('连接成功') && !bar.textContent.includes('配置已准备')) {
const dots = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
const d = dots[Math.floor(Date.now()/300) % dots.length];
bar.innerHTML = `<span class="dot disconnected"></span> ${d} 等待配置...`;
}
}, 300);
</script>
</body>
</html>