0
0

Welcome: 完整用户流程 — 创建账户 + 设备仪表盘(无需登录)

This commit is contained in:
xiaoou
2026-07-10 12:59:16 +08:00
parent a485b6b507
commit 5b042d01b0

197
welcome.html Normal file
View File

@ -0,0 +1,197 @@
<!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>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(135deg, #FF6B8A 0%, #FF8E9E 50%, #FFB3C1 100%); min-height:100vh; display:flex; align-items:center; justify-content:center; }
.card { background: white; border-radius: 24px; padding: 40px 36px; max-width: 460px; width: 92%; box-shadow: 0 24px 80px rgba(255,107,138,0.25); }
.logo { text-align:center; margin-bottom: 24px; }
.logo-icon { font-size:48px; }
h1 { color: #FF6B8A; font-size: 26px; text-align:center; margin-bottom: 6px; font-weight:700; }
.subtitle { color: #bbb; font-size: 13px; text-align:center; margin-bottom: 28px; }
.step { display:none; }
.step.active { display:block; }
label { display:block; color: #888; font-size: 13px; margin-bottom: 6px; margin-top: 14px; }
input { width:100%; padding: 12px 16px; border: 2px solid #f0f0f0; border-radius: 12px; font-size: 15px; outline:none; transition: border-color 0.2s; }
input:focus { border-color: #FF6B8A; }
.btn { width:100%; padding: 14px; background: #FF6B8A; color: white; border: none; border-radius: 14px; font-size: 16px; font-weight:600; cursor: pointer; margin-top: 20px; transition: background 0.2s; }
.btn:hover { background: #e55a77; }
.btn:disabled { background: #f0c0ca; cursor: not-allowed; }
.msg { text-align:center; font-size: 13px; margin-top: 12px; min-height:20px; }
.msg.error { color: #e74c3c; }
.msg.success { color: #27ae60; }
.stat { background: #FFF5F7; border-radius: 14px; padding: 18px; margin-bottom: 12px; display:flex; justify-content:space-between; align-items:center; }
.stat-label { color: #888; font-size: 13px; }
.stat-value { color: #FF6B8A; font-size: 26px; font-weight:700; }
.device { background: #FAFAFA; border-radius: 10px; padding: 12px 16px; margin-bottom: 8px; display:flex; justify-content:space-between; }
.device-name { color: #333; font-size: 13px; font-family: monospace; }
.device-state { color: #999; font-size: 13px; }
.version { text-align:center; color: #ddd; font-size: 11px; margin-top: 24px; }
.refresh { color: #FF6B8A; font-size: 13px; cursor: pointer; text-align:right; margin-top:8px; text-decoration:underline; }
.hint { color: #bbb; font-size: 12px; margin-top: 4px; }
</style>
</head>
<body>
<div class="card">
<div class="logo"><span class="logo-icon">🏠</span></div>
<h1>朴小护智慧家居</h1>
<div class="subtitle">SmartHome OS v1.0 — 初始化向导</div>
<!-- Step 1: 检测状态 -->
<div id="step-loading" class="step active">
<div class="msg">正在检测系统状态...</div>
</div>
<!-- Step 2: 创建管理员 -->
<div id="step-create" class="step">
<p style="text-align:center;color:#666;font-size:14px;margin-bottom:16px;">首次使用,请创建管理员账户</p>
<label>用户名</label>
<input type="text" id="username" placeholder="输入管理员用户名" autocomplete="off">
<label>密码</label>
<input type="password" id="password" placeholder="设置登录密码至少6位" autocomplete="new-password">
<label>确认密码</label>
<input type="password" id="password2" placeholder="再次输入密码">
<button class="btn" onclick="createUser()">创建账户并进入</button>
<div id="create-msg" class="msg"></div>
</div>
<!-- Step 3: Dashboard -->
<div id="step-dashboard" class="step">
<div class="stat">
<span class="stat-label">系统状态</span>
<span class="stat-value" id="sys-status">运行中</span>
</div>
<div class="stat">
<span class="stat-label">检测到设备</span>
<span class="stat-value" id="dev-count">-</span>
</div>
<div id="dev-list" style="max-height:300px;overflow-y:auto;"><div class="msg">加载设备列表...</div></div>
<div class="refresh" onclick="loadDevices()">刷新设备列表</div>
<button class="btn" onclick="localStorage.clear();location.reload()" style="background:#eee;color:#999;margin-top:12px;">退出登录</button>
</div>
<div class="version">朴小护智慧家居 v1.0 | HA + Hermes</div>
</div>
<script>
var API = ''; // 同源,使用相对路径
var token = localStorage.getItem('pxh_token');
async function api(method, path, body) {
var opts = { method: method, headers: {'Content-Type': 'application/json'} };
if (token) opts.headers['Authorization'] = 'Bearer ' + token;
if (body) opts.body = JSON.stringify(body);
var r = await fetch(API + path, opts);
return r;
}
function show(id) {
document.querySelectorAll('.step').forEach(function(s) { s.classList.remove('active'); });
document.getElementById(id).classList.add('active');
}
async function init() {
if (token) {
try {
var r = await api('GET', '/api/states');
if (r.ok) { show('step-dashboard'); loadDevices(); return; }
} catch(e) {}
token = null;
localStorage.removeItem('pxh_token');
}
// 检测是否需要创建用户
try {
var r = await fetch(API + '/api/onboarding');
if (!r.ok) { show('step-loading'); document.querySelector('#step-loading .msg').textContent = '系统未就绪,请稍后刷新'; return; }
var steps = await r.json();
var allDone = steps.every(function(s) { return s.done; });
if (allDone) {
// 已有用户,需要登录 — 但 trusted_networks 不可用
// 暂时降级:引导重新创建
show('step-create');
} else {
show('step-create');
}
} catch(e) {
show('step-loading');
document.querySelector('#step-loading .msg').textContent = '无法连接 HA请确认服务已启动';
}
}
async function createUser() {
var u = document.getElementById('username').value.trim();
var p = document.getElementById('password').value;
var p2 = document.getElementById('password2').value;
var msg = document.getElementById('create-msg');
if (!u || !p) { msg.className = 'msg error'; msg.textContent = '请输入用户名和密码'; return; }
if (p.length < 6) { msg.className = 'msg error'; msg.textContent = '密码至少6位'; return; }
if (p !== p2) { msg.className = 'msg error'; msg.textContent = '两次密码不一致'; return; }
msg.className = 'msg'; msg.textContent = '正在创建账户...';
var btn = document.querySelector('#step-create .btn');
btn.disabled = true;
try {
// Step 1: 创建用户
var r1 = await fetch(API + '/api/onboarding/users', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: u, username: u, password: p, client_id: window.location.origin + '/', language: 'zh-cn'})
});
if (!r1.ok) { var e = await r1.text(); throw new Error('创建失败: ' + e); }
var data1 = await r1.json();
var code = data1.auth_code;
// Step 2: 获取 token
var r2 = await fetch(API + '/auth/token', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'grant_type=authorization_code&code=' + code + '&client_id=' + window.location.origin + '/'
});
if (!r2.ok) throw new Error('获取 token 失败');
var data2 = await r2.json();
token = data2.access_token;
localStorage.setItem('pxh_token', token);
// Step 3: 完成 onboarding
await api('POST', '/api/onboarding/core_config', {});
await api('POST', '/api/onboarding/analytics', {});
await api('POST', '/api/onboarding/integration', {});
show('step-dashboard');
loadDevices();
} catch(e) {
msg.className = 'msg error';
msg.textContent = e.message;
btn.disabled = false;
}
}
async function loadDevices() {
document.getElementById('sys-status').textContent = '\u2705 运行中';
try {
var r = await api('GET', '/api/states');
if (!r.ok) throw new Error('获取设备失败');
var states = await r.json();
document.getElementById('dev-count').textContent = states.length + ' 个实体';
var html = '';
states.forEach(function(e) {
html += '<div class="device"><span class="device-name">' + e.entity_id + '</span><span class="device-state">' + e.state + '</span></div>';
});
document.getElementById('dev-list').innerHTML = html || '<div class="msg">暂无设备,接入硬件后自动发现</div>';
} catch(e) {
document.getElementById('sys-status').textContent = '\u274c 连接失败';
document.getElementById('dev-list').innerHTML = '<div class="msg error">' + e.message + '</div>';
}
}
init();
</script>
</body>
</html>