| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset='UTF-8'>
- <meta name='viewport'
- content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover'>
- <meta name="apple-mobile-web-app-capable" content="yes">
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
- <title>Audio Control Panel</title>
- <style>
- @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=Inter:wght@300;400;600&display=swap');
- :root {
- --bg-color: #111111;
- --panel-bg: #1a1a1a;
- --gold: #d4af37;
- --gold-dark: #b5952f;
- --text-main: #f0f0f0;
- --text-dim: #888888;
- --border: #333333;
- }
- * {
- box-sizing: border-box;
- margin: 0;
- padding: 0
- }
- body,
- button,
- input {
- font-family: 'Songti SC', 'STSong', 'SimSun', 'Times New Roman', serif;
- }
- html,
- body {
- background: var(--bg-color);
- color: var(--text-main);
- min-height: 100vh;
- overflow-x: hidden;
- -ms-overflow-style: none;
- /* IE and Edge */
- scrollbar-width: none;
- /* Firefox */
- }
- *::-webkit-scrollbar {
- display: none !important;
- width: 0 !important;
- height: 0 !important;
- }
- /* Leather texture overlay */
- body::before {
- content: "";
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-image: url('data:image/svg+xml;utf8,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.05"/%3E%3C/svg%3E');
- pointer-events: none;
- z-index: -1;
- }
- .wrap {
- max-width: 500px;
- margin: 0 auto;
- padding: calc(10px + env(safe-area-inset-top)) 20px calc(20px + env(safe-area-inset-bottom)) 20px;
- }
- /* Dashboard Styling */
- .brand {
- font-family: 'Cinzel', serif;
- font-size: 32px;
- font-weight: 700;
- color: var(--gold);
- text-align: center;
- margin-bottom: 10px;
- letter-spacing: 2px;
- text-shadow: 0 2px 10px rgba(212, 175, 55, 0.2);
- }
- .subtitle {
- text-align: center;
- font-size: 12px;
- color: var(--text-dim);
- text-transform: uppercase;
- letter-spacing: 4px;
- margin-bottom: 40px;
- }
- .dashboard-card {
- background: var(--panel-bg);
- border: 1px solid var(--border);
- border-radius: 12px;
- padding: 30px 20px;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
- margin-bottom: 20px;
- }
- /* Status row */
- .status-row {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 30px;
- padding-bottom: 20px;
- border-bottom: 1px solid var(--border);
- }
- .status-item {
- text-align: center;
- flex: 1;
- }
- .status-label {
- font-size: 13px;
- color: var(--text-dim);
- text-transform: uppercase;
- letter-spacing: 1px;
- margin-bottom: 5px;
- }
- .status-val {
- font-size: 16px;
- font-weight: 600;
- color: var(--gold);
- margin-top: 4px;
- height: 22px;
- line-height: 22px;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- /* Now Playing Box */
- .now-playing {
- background: #000;
- border-radius: 12px;
- padding: 25px 15px;
- margin-bottom: 30px;
- border: 1px solid var(--border);
- text-align: center;
- box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.9);
- min-height: 120px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .np-title {
- font-size: 24px;
- font-weight: bold;
- color: var(--gold);
- margin-bottom: 8px;
- }
- .np-artist {
- font-size: 18px;
- color: var(--text-main);
- margin-bottom: 4px;
- }
- .np-album {
- font-size: 15px;
- color: var(--text-dim);
- }
- /* Source Toggle */
- .source-toggle {
- display: flex;
- background: #000;
- border-radius: 8px;
- padding: 5px;
- margin-bottom: 30px;
- border: 1px solid var(--border);
- }
- .source-toggle button {
- flex: 1;
- padding: 12px;
- background: transparent;
- border: none;
- color: var(--text-dim);
- font-weight: 600;
- font-size: 16px;
- cursor: pointer;
- border-radius: 6px;
- transition: all 0.3s ease;
- text-transform: uppercase;
- letter-spacing: 1px;
- }
- .source-toggle button.active {
- background: var(--gold);
- color: #000;
- box-shadow: 0 0 15px rgba(212, 175, 55, 0.3);
- }
- /* Volume Slider */
- .vol-container {
- margin-bottom: 40px;
- }
- .vol-header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 15px;
- }
- .vol-header label {
- font-size: 15px;
- color: var(--text-dim);
- text-transform: uppercase;
- letter-spacing: 2px;
- }
- .vol-header span {
- color: var(--gold);
- font-weight: 600;
- font-size: 16px;
- }
- input[type=range] {
- -webkit-appearance: none;
- appearance: none;
- width: 100%;
- height: 6px;
- background: linear-gradient(to right, var(--gold) var(--val, 50%), #000 var(--val, 50%));
- border-radius: 3px;
- outline: none;
- border: 1px solid var(--border);
- }
- input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background: radial-gradient(circle at 30% 30%, #ffd700, var(--gold-dark));
- cursor: pointer;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
- border: 2px solid #fff;
- }
- /* Buttons */
- .btn-gold {
- display: block;
- width: 100%;
- padding: 16px;
- background: transparent;
- border: 1px solid var(--gold);
- color: var(--gold);
- text-transform: uppercase;
- letter-spacing: 2px;
- font-weight: 600;
- font-size: 16px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
- text-align: center;
- text-decoration: none;
- }
- .btn-gold:hover,
- .btn-gold:active {
- background: var(--gold);
- color: #000;
- }
- .settings-btn {
- background: transparent;
- border: none;
- color: var(--text-dim);
- text-decoration: underline;
- cursor: pointer;
- font-size: 14px;
- display: block;
- margin: 30px auto 0;
- letter-spacing: 1px;
- }
- /* Settings Modal (Overlay) */
- .settings-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.9);
- backdrop-filter: blur(5px);
- z-index: 100;
- display: none;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
- overscroll-behavior-y: contain;
- padding: calc(20px + env(safe-area-inset-top)) 20px calc(20px + env(safe-area-inset-bottom)) 20px;
- }
- .settings-overlay.active {
- display: block;
- }
- .close-btn {
- color: #000;
- background: var(--gold);
- width: 38px;
- height: 38px;
- border-radius: 50%;
- font-size: 24px;
- font-weight: bold;
- border: none;
- float: right;
- cursor: pointer;
- line-height: 38px;
- text-align: center;
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
- margin-top: -5px;
- margin-right: -5px;
- }
- .settings-content {
- max-width: 500px;
- margin: 0 auto;
- padding-top: 15px;
- }
- /* Old UI Styles adapted for Settings */
- .card {
- background: #1a1a1a;
- border-radius: 12px;
- padding: 20px;
- margin-bottom: 16px;
- border: 1px solid #333;
- }
- .card h2 {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 16px;
- color: var(--gold);
- }
- .form-group {
- margin-bottom: 14px
- }
- .form-group label {
- display: block;
- font-size: 12px;
- color: #888;
- margin-bottom: 6px;
- }
- input[type=text],
- input[type=password] {
- width: 100%;
- padding: 12px;
- background: #000;
- border: 1px solid #333;
- border-radius: 6px;
- color: #fff;
- font-size: 14px
- }
- input:focus {
- outline: none;
- border-color: var(--gold)
- }
- .btn {
- display: inline-block;
- padding: 12px 20px;
- border: none;
- border-radius: 6px;
- font-size: 13px;
- cursor: pointer;
- background: #333;
- color: #fff;
- width: 100%;
- margin-bottom: 10px;
- }
- .btn-primary {
- background: var(--gold);
- color: #000;
- font-weight: 600;
- }
- .wifi-list {
- max-height: 180px;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
- margin: 12px 0;
- }
- .wifi-item {
- padding: 12px;
- background: #000;
- margin-bottom: 4px;
- border-radius: 6px;
- cursor: pointer;
- display: flex;
- justify-content: space-between;
- }
- .wifi-item.sel {
- border: 1px solid var(--gold);
- }
- .msg {
- padding: 10px;
- border-radius: 6px;
- margin-top: 10px;
- font-size: 12px
- }
- .msg.ok {
- color: #4ade80;
- }
- .msg.err {
- color: #f87171;
- }
- .info-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px
- }
- .info-item {
- background: #000;
- padding: 10px;
- border-radius: 6px;
- }
- .info-item label {
- font-size: 10px;
- color: #888;
- display: block;
- }
- .info-item span {
- font-size: 13px;
- font-weight: 500
- }
- </style>
- <script>
- (function () {
- const canvas = document.createElement('canvas');
- canvas.width = 180; canvas.height = 180;
- const ctx = canvas.getContext('2d');
- // Background
- ctx.fillStyle = '#111'; ctx.fillRect(0, 0, 180, 180);
- // Gold circle
- ctx.strokeStyle = '#d4af37'; ctx.lineWidth = 8;
- ctx.beginPath(); ctx.arc(90, 90, 65, 0, 2 * Math.PI); ctx.stroke();
- // Musical note
- ctx.fillStyle = '#d4af37';
- ctx.font = 'bold 85px Arial';
- ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
- ctx.fillText('♪', 90, 98);
- ctx.lineWidth = 3;
- ctx.strokeText('♪', 90, 98);
- const link = document.createElement('link');
- link.rel = 'apple-touch-icon';
- link.href = canvas.toDataURL('image/png');
- document.head.appendChild(link);
- })();
- </script>
- </head>
- <body>
- <!-- MAIN DASHBOARD -->
- <div class="wrap">
- <div class="brand" id="brand-name">音响系统</div>
- <div id="disconnected-banner"
- style="display:none; position:fixed; bottom:30px; left:50%; transform:translateX(-50%); width:90%; max-width:400px; z-index:9999; background:rgba(255, 68, 68, 0.5); color:white; border:1px solid rgba(255, 68, 68, 0.3); padding:12px; text-align:center; border-radius:12px; font-weight:600; font-size:14px; backdrop-filter:blur(12px); box-shadow:0 8px 20px rgba(0,0,0,0.3);">
- ⚠️ 设备未连接,正在重试...
- </div>
- <div class="dashboard-card">
- <div class="status-row">
- <div class="status-item">
- <div class="status-label">电池电量</div>
- <div class="status-val" id="dash-bat">--%</div>
- </div>
- <div class="status-item" style="border-left:1px solid #333; border-right:1px solid #333;">
- <div class="status-label">网络状态</div>
- <div class="status-val" id="dash-net">离线</div>
- </div>
- <div class="status-item">
- <div class="status-label">运行状态</div>
- <div class="status-val" id="dash-status" style="color:#4ade80;">正常</div>
- </div>
- <div class="status-item" style="border-left:1px solid #333;">
- <div class="status-label">播放源</div>
- <div class="status-val" id="dash-conn" style="color:#aaa;">未连接</div>
- </div>
- </div>
- <!-- Now Playing Box -->
- <div class="now-playing" id="now-playing-box">
- <div id="np-header" style="font-size:14px; color:var(--text-dim); margin-bottom:10px; letter-spacing:2px;">正在播放:
- </div>
- <div class="np-title" id="np-title">等待播放...</div>
- <div class="np-artist" id="np-artist">-</div>
- <div class="np-album" id="np-album"></div>
- <div id="np-progress-container" style="display:none; margin-top:25px; padding:0 15px;">
- <div
- style="display:flex; justify-content:space-between; font-size:13px; color:var(--text-dim); margin-bottom:8px;">
- <span id="np-pos" style="font-variant-numeric: tabular-nums;">00:00</span>
- <span id="np-dur" style="font-variant-numeric: tabular-nums;">00:00</span>
- </div>
- <div
- style="width:100%; height:6px; background:#1a1a1a; border-radius:3px; overflow:hidden; border: 1px solid #333;">
- <div id="np-progress-bar"
- style="width:0%; height:100%; background:var(--gold); transition:width 1s linear; box-shadow: 0 0 8px rgba(212, 175, 55, 0.5);">
- </div>
- </div>
- </div>
- </div>
- <div class="source-toggle">
- <button id="src-btn-0" onclick="setSource(0)">AirPlay / 蓝牙</button>
- <button id="src-btn-1" onclick="setSource(1)">AUX</button>
- </div>
- <div class="vol-container">
- <div class="vol-header">
- <label>主音量</label>
- <span id="vol-val">--%</span>
- </div>
- <input type="range" id="main-vol" min="0" max="100" value="50" style="--val: 50%"
- oninput="document.getElementById('vol-val').textContent=this.value+'%'; this.style.setProperty('--val', this.value+'%')"
- onchange="setVolume(this.value)">
- </div>
- <div class="vol-container">
- <div class="vol-header">
- <label>低音音量</label>
- <span id="bass-val">--%</span>
- </div>
- <input type="range" id="bass-vol" min="0" max="100" value="50" style="--val: 50%"
- oninput="document.getElementById('bass-val').textContent=this.value+'%'; this.style.setProperty('--val', this.value+'%')"
- onchange="setBassVolume(this.value)">
- </div>
- <button class="btn-gold" onclick="clearBT()" style="margin-bottom:15px;">重新配对蓝牙</button>
- </div>
- <button class="settings-btn" onclick="openSettings(event)">高级设置 (ADVANCED SETTINGS)</button>
- </div>
- <!-- SETTINGS MODAL -->
- <div id="settings-overlay" class="settings-overlay">
- <div class="settings-content">
- <button class="close-btn"
- onclick="document.getElementById('settings-overlay').classList.remove('active')">×</button>
- <div style="clear:both; margin-bottom:5px;"></div>
- <div class='card' style="padding-bottom: 30px;">
- <h2>音频高级设置</h2>
- <div class="vol-container" style="margin-bottom:0;">
- <div class="vol-header">
- <label style="color:#888;">提示音音量 (Prompt Volume)</label>
- <span id="prompt-val" style="color:#ddd;">--%</span>
- </div>
- <input type="range" id="prompt-vol" min="0" max="100" value="50" style="--val: 50%"
- oninput="document.getElementById('prompt-val').textContent=this.value+'%'; this.style.setProperty('--val', this.value+'%')"
- onchange="setPromptVolume(this.value)">
- </div>
- </div>
- <div class='card'>
- <h2>WiFi</h2>
- <button class='btn' onclick='scanWiFi()'>扫描</button>
- <div id='wifi-list' class='wifi-list'></div>
- <div class='form-group'><label>网络名称 (SSID)</label><input type='text' id='wifi-ssid' autocomplete='off'
- autocorrect='off' autocapitalize='off' spellcheck='false'></div>
- <div class='form-group'><label>WiFi 密码</label><input type='password' id='wifi-pass' autocomplete='new-password'>
- </div>
- <button class='btn btn-primary' onclick='saveWiFi()'>连接</button>
- <div id='wifi-msg'></div>
- </div>
- <div class='card'>
- <h2>设备名称</h2>
- <div class='form-group'><label>修改将显示在AirPlay和蓝牙设备列表以及网络名中</label><input type='text' id='dev-name'
- autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false'></div>
- <button class='btn btn-primary' onclick='saveName()'>保存名称</button>
- <div id='name-msg'></div>
- </div>
- <div class='card'>
- <h2>固件升级</h2>
- <input type='file' id='fw-file' accept='.bin' style='display:none'>
- <button class='btn' onclick='document.getElementById("fw-file").click()'>选择文件: <span
- id='fw-name'>未选择</span></button>
- <button class='btn btn-primary' id='ota-btn' onclick='startOTA()' disabled>上传并升级固件</button>
- <div id='ota-msg'></div>
- </div>
- <div class='card'>
- <h2>系统信息</h2>
- <div class='info-grid'>
- <div class='info-item'><label>IP 地址</label><span id='info-ip'>-</span></div>
- <div class='info-item'><label>设备名称</label><span id='info-name'>-</span></div>
- <div class='info-item'><label>固件版本</label><span id='info-fw'>-</span></div>
- <div class='info-item'><label>剩余内存</label><span id='info-heap'>-</span></div>
- </div>
- <button class='btn' onclick='restart()' style='margin-top:15px; background:#dc3545;'>重启设备</button>
- </div>
- </div>
- </div>
- <script>
- let initialLoadComplete = false;
- let isDragging = false;
- // 监听全局触摸/鼠标按下事件,判断是否正在拖动滑块
- document.addEventListener('mousedown', (e) => { if (e.target && e.target.type === 'range') isDragging = true; });
- document.addEventListener('touchstart', (e) => { if (e.target && e.target.type === 'range') isDragging = true; }, { passive: true });
- document.addEventListener('mouseup', () => { setTimeout(() => { isDragging = false; }, 100); });
- document.addEventListener('touchend', () => { setTimeout(() => { isDragging = false; }, 100); });
- // --- Dashboard Logic ---
- function openSettings(e) {
- if (e) e.preventDefault();
- // Delay slightly to prevent mobile ghost clicks from falling through and focusing inputs
- setTimeout(() => {
- document.getElementById('settings-overlay').classList.add('active');
- }, 150);
- }
- async function fetchStatus() {
- try {
- let r = await fetch('/api/status');
- if (!r.ok) throw new Error("Status failed");
- let d = await r.json();
- document.getElementById('disconnected-banner').style.display = 'none';
- document.getElementById('dash-bat').textContent = d.battery + '%' + (d.charging ? ' ⚡' : '');
- let statusEl = document.getElementById('dash-status');
- if (d.sys_status) {
- statusEl.textContent = d.sys_status;
- if (d.sys_status === '正常') {
- statusEl.style.color = '#4ade80';
- } else if (d.sys_status === '低电量') {
- statusEl.style.color = '#f87171';
- } else if (d.sys_status === '充电中') {
- statusEl.style.color = '#facc15';
- } else {
- statusEl.style.color = '#fb923c'; // fallback for warning/OTA
- }
- }
- let connEl = document.getElementById('dash-conn');
- if (d.conn_type) {
- connEl.textContent = d.conn_type;
- if (d.conn_type === '未连接') {
- connEl.style.color = '#aaa';
- } else {
- connEl.style.color = '#4ade80';
- }
- }
- let volEl = document.getElementById('main-vol');
- if (!isDragging && document.activeElement !== volEl) {
- volEl.value = d.volume;
- volEl.style.setProperty('--val', d.volume + '%');
- document.getElementById('vol-val').textContent = d.volume + '%';
- }
- let bassEl = document.getElementById('bass-vol');
- if (!isDragging && document.activeElement !== bassEl && d.bass_volume !== undefined) {
- bassEl.value = d.bass_volume;
- bassEl.style.setProperty('--val', d.bass_volume + '%');
- document.getElementById('bass-val').textContent = d.bass_volume + '%';
- }
- let promptEl = document.getElementById('prompt-vol');
- if (!isDragging && document.activeElement !== promptEl && d.prompt_volume !== undefined) {
- promptEl.value = d.prompt_volume;
- promptEl.style.setProperty('--val', d.prompt_volume + '%');
- document.getElementById('prompt-val').textContent = d.prompt_volume + '%';
- }
- let npHeader = document.getElementById('np-header');
- let npTitle = document.getElementById('np-title');
- let npArtist = document.getElementById('np-artist');
- let npAlbum = document.getElementById('np-album');
- if (d.source === 1) {
- npHeader.textContent = '当前模式:AUX 输入';
- npTitle.textContent = 'AUX 模拟输入';
- npArtist.textContent = '不支持歌曲信息显示';
- npArtist.style.color = 'var(--text-dim)';
- npAlbum.textContent = '';
- } else {
- npHeader.textContent = '正在播放:';
- npArtist.style.color = 'var(--text-main)';
- if (d.title && d.title !== "") {
- npTitle.textContent = d.title;
- npArtist.textContent = d.artist || '未知歌手';
- npAlbum.textContent = d.album || '';
- if (d.duration && d.duration > 0) {
- function formatSecs(secs) {
- if (secs == null || isNaN(secs)) return "00:00";
- let m = Math.floor(secs / 60);
- let s = Math.floor(secs % 60);
- return (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s);
- }
- document.getElementById('np-progress-container').style.display = 'block';
- document.getElementById('np-dur').textContent = formatSecs(d.duration);
- document.getElementById('np-pos').textContent = formatSecs(d.position);
- let pct = (d.position / d.duration) * 100;
- if (pct > 100) pct = 100;
- document.getElementById('np-progress-bar').style.width = pct + '%';
- } else {
- document.getElementById('np-progress-container').style.display = 'none';
- }
- } else {
- npTitle.textContent = '等待播放...';
- npArtist.textContent = '-';
- npAlbum.textContent = '';
- document.getElementById('np-progress-container').style.display = 'none';
- }
- }
- document.getElementById('src-btn-0').className = d.source === 0 ? 'active' : '';
- document.getElementById('src-btn-1').className = d.source === 1 ? 'active' : '';
- initialLoadComplete = true;
- } catch (e) {
- document.getElementById('disconnected-banner').style.display = 'block';
- }
- }
- async function setVolume(val) {
- if (!initialLoadComplete) return;
- try {
- await fetch('/api/control', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ action: 'set_volume', volume: parseInt(val) })
- });
- } catch (e) { }
- }
- async function setBassVolume(val) {
- if (!initialLoadComplete) return;
- try {
- await fetch('/api/control', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ action: 'set_bass_volume', volume: parseInt(val) })
- });
- } catch (e) { }
- }
- async function setPromptVolume(val) {
- if (!initialLoadComplete) return;
- try {
- await fetch('/api/control', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ action: 'set_prompt_volume', volume: parseInt(val) })
- });
- } catch (e) { }
- }
- async function setSource(src) {
- if (!initialLoadComplete) return;
- document.getElementById('src-btn-0').className = src === 0 ? 'active' : '';
- document.getElementById('src-btn-1').className = src === 1 ? 'active' : '';
- try {
- await fetch('/api/control', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ action: 'set_source', source: src })
- });
- } catch (e) { }
- }
- async function clearBT() {
- if (!confirm('将重置蓝牙连接,请确认')) return;
- try {
- await fetch('/api/control', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ action: 'clear_bt' })
- });
- alert('设备蓝牙已可被发现。');
- } catch (e) { }
- }
- // --- Old Settings Logic ---
- function msg(id, txt, type) { var e = document.getElementById(id); if (e) { e.innerHTML = '<div class="msg ' + type + '">' + txt + '</div>'; setTimeout(function () { e.innerHTML = ''; }, 4000); } }
- async function scanWiFi() {
- var l = document.getElementById('wifi-list'); l.innerHTML = '<div class="msg">扫描中...</div>';
- try {
- var r = await fetch('/api/wifi/scan'); var d = await r.json();
- if (d.success && d.networks.length > 0) {
- l.innerHTML = ''; d.networks.forEach(function (n) {
- var i = document.createElement('div'); i.className = 'wifi-item';
- i.innerHTML = '<span>' + n.ssid + '</span><span style="color:#888">' + n.rssi + ' dBm</span>';
- i.onclick = function () { document.querySelectorAll('.wifi-item').forEach(function (x) { x.classList.remove('sel'); }); i.classList.add('sel'); document.getElementById('wifi-ssid').value = n.ssid; };
- l.appendChild(i);
- });
- }
- else { l.innerHTML = '<div class="msg">未找到网络</div>'; }
- }
- catch (e) { l.innerHTML = '<div class="msg err">扫描失败</div>'; }
- }
- async function saveWiFi() {
- var s = document.getElementById('wifi-ssid').value.trim(); var p = document.getElementById('wifi-pass').value;
- if (!s) { msg('wifi-msg', '请输入网络名称', 'err'); return; }
- try {
- var r = await fetch('/api/wifi/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ssid: s, password: p }) });
- var d = await r.json(); if (d.success) { msg('wifi-msg', '已保存!正在重启...', 'ok'); } else { msg('wifi-msg', d.error, 'err'); }
- }
- catch (e) { msg('wifi-msg', '出错', 'err'); }
- }
- async function saveName() {
- var n = document.getElementById('dev-name').value.trim(); if (!n) return;
- try {
- var r = await fetch('/api/device/name', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: n }) });
- var d = await r.json(); if (d.success) { msg('name-msg', '已保存!重启生效。', 'ok'); } else { msg('name-msg', d.error, 'err'); }
- }
- catch (e) { }
- }
- async function startOTA() {
- var f = document.getElementById('fw-file').files[0]; if (!f) return;
- document.getElementById('ota-btn').disabled = true; msg('ota-msg', '正在上传...', 'ok');
- try { await fetch('/api/ota/update', { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: f }); msg('ota-msg', '完成!正在重启...', 'ok'); }
- catch (e) { }
- }
- async function restart() {
- if (!confirm('确认重启设备?')) return;
- try { await fetch('/api/system/restart', { method: 'POST' }); } catch (e) { }
- }
- async function loadInfo() {
- try {
- var r = await fetch('/api/system/info'); var d = await r.json();
- if (d.success) {
- var i = d.info;
- document.getElementById('dash-net').textContent = i.wifi_connected ? i.ip : (i.eth_connected ? i.ip : '离线');
- document.getElementById('info-ip').textContent = i.ip || '-';
- document.getElementById('info-name').textContent = i.device_name || '-';
- document.getElementById('info-heap').textContent = Math.round(i.free_heap / 1024) + ' KB';
- document.getElementById('info-fw').textContent = i.firmware_version || '-';
- document.getElementById('dev-name').placeholder = i.device_name || '';
- if (i.device_name) document.getElementById('brand-name').textContent = i.device_name.toUpperCase();
- }
- } catch (e) { }
- }
- window.onload = function () {
- document.getElementById('fw-file').onchange = function (e) {
- var f = e.target.files[0];
- document.getElementById('fw-name').textContent = f ? f.name : '未选择文件';
- document.getElementById('ota-btn').disabled = !f;
- };
- loadInfo();
- fetchStatus();
- setInterval(fetchStatus, 2000);
- setInterval(loadInfo, 10000);
- };
- </script>
- </body>
- </html>
|