feat: init
This commit is contained in:
461
data/index.html
Normal file
461
data/index.html
Normal file
@@ -0,0 +1,461 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user