159 lines
8.2 KiB
HTML
159 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>3D打印控制面板</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{--bg:#0f1923;--card:#1a2736;--border:#2a3a4a;--text:#e0e6ed;--dim:#7a8a9a;--green:#00e676;--red:#ff5252;--blue:#448aff;--orange:#ff9100;--r:8px}
|
|
body{font-family:'Segoe UI',system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);font-size:13px;padding:12px}
|
|
.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
|
|
h1{font-size:1.15rem;background:linear-gradient(135deg,var(--blue),var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
.status-bar{display:flex;align-items:center;gap:6px;font-size:.8rem;color:var(--dim)}
|
|
.dot{width:8px;height:8px;border-radius:50%;background:var(--red);transition:.3s}
|
|
.dot.on{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
.grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;max-width:640px;margin:0 auto}
|
|
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--r);padding:10px 12px}
|
|
.card-title{font-size:.75rem;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}
|
|
.btn-row{display:flex;flex-wrap:wrap;gap:6px}
|
|
.btn{padding:6px 14px;border:none;border-radius:6px;font-size:.82rem;font-weight:600;cursor:pointer;transition:all .15s;outline:none;line-height:1.4}
|
|
.btn:active{transform:scale(.96)}
|
|
.btn-blue{background:var(--blue);color:#fff}.btn-blue:hover{background:#5c9aff}
|
|
.btn-green{background:var(--green);color:#0f1923}.btn-green:hover{background:#33eb91}
|
|
.btn-red{background:var(--red);color:#fff}.btn-red:hover{background:#ff6b6b}
|
|
.btn-orange{background:var(--orange);color:#fff}.btn-orange:hover{background:#ffa733}
|
|
.row{display:flex;align-items:center;gap:8px;margin-top:6px}
|
|
.row label{color:var(--dim);font-size:.8rem;min-width:60px}
|
|
.row input[type=range]{flex:1;accent-color:var(--blue);height:4px}
|
|
.row .val{min-width:28px;text-align:center;font-weight:600;color:var(--blue)}
|
|
.full{grid-column:1/-1}
|
|
.json-box{width:100%;height:50px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 8px;font-family:Consolas,Monaco,monospace;font-size:.8rem;resize:vertical;outline:none}
|
|
.json-box:focus{border-color:var(--blue)}
|
|
.log-area{max-height:180px;overflow-y:auto;padding-right:2px}
|
|
.log-area::-webkit-scrollbar{width:4px}
|
|
.log-area::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
|
.log-item{padding:4px 6px;margin-bottom:3px;border-radius:4px;background:var(--bg);font-size:.78rem;word-break:break-all;line-height:1.4}
|
|
.log-item .lt{color:var(--dim);font-size:.7rem}
|
|
.log-item .lp{color:var(--text)}
|
|
.log-item.error .lp{color:var(--red)}
|
|
.clear-btn{margin-top:6px;padding:3px 10px;border:1px solid var(--border);background:0;color:var(--dim);border-radius:4px;font-size:.75rem;cursor:pointer}
|
|
.clear-btn:hover{border-color:var(--red);color:var(--red)}
|
|
footer{text-align:center;color:var(--dim);font-size:.7rem;margin-top:10px}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<h1>3D 打印控制面板</h1>
|
|
<div class="status-bar"><div class="dot" id="dot"></div><span id="connText">连接中...</span></div>
|
|
</div>
|
|
|
|
<div class="grid">
|
|
|
|
<div class="card">
|
|
<div class="card-title">查询</div>
|
|
<div class="btn-row">
|
|
<button class="btn btn-blue" onclick="send({cmd:'status'})">查询状态</button>
|
|
<button class="btn btn-red" onclick="if(confirm('确认恢复默认?'))send({cmd:'reset'})">恢复默认</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">灯光</div>
|
|
<div class="btn-row">
|
|
<button class="btn btn-green" onclick="send({cmd:'light',value:'on'})">开灯</button>
|
|
<button class="btn btn-red" onclick="send({cmd:'light',value:'off'})">关灯</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">风扇开关</div>
|
|
<div class="btn-row">
|
|
<button class="btn btn-green" onclick="send({cmd:'fan',value:'on'})">开启</button>
|
|
<button class="btn btn-red" onclick="send({cmd:'fan',value:'off'})">关闭</button>
|
|
</div>
|
|
<div class="row">
|
|
<label>模式</label>
|
|
<button class="btn btn-blue" style="padding:4px 10px;font-size:.78rem" onclick="send({cmd:'fan_mode',value:'auto'})">自动</button>
|
|
<button class="btn btn-orange" style="padding:4px 10px;font-size:.78rem" onclick="send({cmd:'fan_mode',value:'manual'})">手动</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">风扇阈值</div>
|
|
<div class="row">
|
|
<label>温度 °C</label>
|
|
<input type="range" id="threshold" min="15" max="80" step="1" value="30" oninput="document.getElementById('thVal').textContent=this.value">
|
|
<span class="val" id="thVal">30</span>
|
|
<button class="btn btn-blue" style="padding:4px 10px;font-size:.78rem" onclick="send({cmd:'fan_threshold',value:+document.getElementById('threshold').value})">设置</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card full">
|
|
<div class="card-title">自定义指令</div>
|
|
<textarea class="json-box" id="customJson" placeholder='{"cmd":"status"}'></textarea>
|
|
<div class="btn-row" style="margin-top:6px">
|
|
<button class="btn btn-blue" style="padding:4px 14px;font-size:.78rem" onclick="sendCustom()">发送</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card full">
|
|
<div class="card-title" style="display:flex;justify-content:space-between;align-items:center">
|
|
<span>消息日志</span>
|
|
<button class="clear-btn" onclick="clearLog()">清空</button>
|
|
</div>
|
|
<div class="log-area" id="logArea">
|
|
<div style="color:var(--dim);text-align:center;padding:16px;font-size:.78rem">等待消息...</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<footer>mqtt.beihong.wang · /3D/message → STM32 · /3D/message/put ← STM32</footer>
|
|
|
|
<script>
|
|
const API='';let firstMsg=true;
|
|
function send(o){fetch(API+'/api/send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(o)}).then(r=>r.json()).then(d=>{if(!d.ok)addLog('SYS',d.msg||'失败',true)})}
|
|
function sendCustom(){const t=document.getElementById('customJson').value.trim();if(!t)return;try{send(JSON.parse(t))}catch(e){addLog('SYS','JSON错误: '+e.message,true)}}
|
|
|
|
function fmtPayload(payload){
|
|
try{
|
|
const j=JSON.parse(payload);
|
|
if(j.status==='error') return `<span style="color:var(--red)">✗ ${j.msg||'错误'}</span>`;
|
|
if(j.data){
|
|
const d=j.data;
|
|
const mode=d.fan_mode==='auto'?'自动':'手动';
|
|
const fan=d.fan==='on'?'<span style="color:var(--green)">开启</span>':'<span style="color:var(--dim)">关闭</span>';
|
|
const light=d.light==='on'?'<span style="color:var(--green)">开启</span>':'<span style="color:var(--dim)">关闭</span>';
|
|
return `风扇: ${fan} | 模式: ${mode} | 阈值: ${d.fan_threshold}°C | 灯光: ${light}`;
|
|
}
|
|
if(j.msg) return `<span style="color:var(--green)">✓ ${j.msg}</span>`;
|
|
}catch(e){}
|
|
return esc(payload);
|
|
}
|
|
|
|
function addLog(topic,payload,isErr){
|
|
const a=document.getElementById('logArea');if(firstMsg){a.innerHTML='';firstMsg=false}
|
|
const d=document.createElement('div');d.className='log-item'+(isErr?' error':'');
|
|
d.innerHTML=`<span class="lt">${new Date().toLocaleTimeString()}</span> <span class="lp">${isErr?'<span style="color:var(--red)">✗ '+esc(payload)+'</span>':fmtPayload(payload)}</span>`;
|
|
a.prepend(d);while(a.children.length>80)a.removeChild(a.lastChild)
|
|
}
|
|
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
function clearLog(){document.getElementById('logArea').innerHTML='<div style="color:var(--dim);text-align:center;padding:16px;font-size:.78rem">等待消息...</div>';firstMsg=true}
|
|
|
|
/* 轮询消息 + 状态 */
|
|
function poll(){
|
|
fetch(API+'/api/status').then(r=>r.json()).then(d=>{
|
|
document.getElementById('dot').className='dot'+(d.connected?' on':'');
|
|
document.getElementById('connText').textContent=d.connected?'MQTT 已连接':'MQTT 已断开';
|
|
});
|
|
fetch(API+'/api/poll').then(r=>r.json()).then(d=>{
|
|
if(d.payload){addLog(d.topic||'MQTT',d.payload,false);console.log('poll got:',d.payload)}
|
|
}).catch(()=>{});
|
|
}
|
|
setInterval(poll,1500);poll();
|
|
</script>
|
|
</body>
|
|
</html>
|