Welcome: 完整用户流程 — 创建账户 + 设备仪表盘(无需登录)
This commit is contained in:
197
welcome.html
Normal file
197
welcome.html
Normal 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>
|
||||||
Reference in New Issue
Block a user