+
-
@@ -954,9 +1011,6 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(id).classList.add('active');
- if (id === 'tab-nvs') {
- loadNvsConfig();
- }
}
async function fetchStatus() {
@@ -2080,148 +2134,132 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
}, 3000);
}
- async function loadNvsConfig() {
+ // Config Management: Export Keymap
+ async function exportKeymapConfig() {
try {
- const res = await fetch('/api/nvs');
- const json = await res.json();
- const editor = document.getElementById('nvs-editor');
- editor.value = JSON.stringify(json, null, 2);
- validateNvsJson();
- showToast('NVS 配置已从 Flash 加载');
+ const res = await fetch('/api/keymap');
+ const data = await res.json();
+ const jsonStr = JSON.stringify(data, null, 2);
+ const blob = new Blob([jsonStr], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ const now = new Date();
+ const pad = n => String(n).padStart(2, '0');
+ const dateStr = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
+ a.href = url;
+ a.download = `RemoteMapper_Keymap_${dateStr}.json`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ showToast('按键与层级配置已成功导出!');
} catch(e) {
- showToast('读取 NVS 失败: ' + e.message, true);
+ showToast('导出配置失败: ' + e.message, true);
}
}
- function validateNvsJson() {
- const editor = document.getElementById('nvs-editor');
- const badge = document.getElementById('nvs-json-status');
- if (!editor || !badge) return false;
- try {
- JSON.parse(editor.value);
- badge.innerHTML = 'JSON 格式有效';
- badge.style.background = 'rgba(16,185,129,0.2)';
- badge.style.color = '#34d399';
- badge.style.borderColor = 'rgba(16,185,129,0.4)';
- return true;
- } catch(e) {
- badge.innerHTML = e.message;
- badge.style.background = 'rgba(239,68,68,0.2)';
- badge.style.color = '#f87171';
- badge.style.borderColor = 'rgba(239,68,68,0.4)';
- return false;
- }
- }
-
- function formatNvsJson() {
- const editor = document.getElementById('nvs-editor');
- try {
- const obj = JSON.parse(editor.value);
- editor.value = JSON.stringify(obj, null, 2);
- validateNvsJson();
- showToast('JSON 格式化完成');
- } catch(e) {
- showToast('无法格式化,当前 JSON 存在语法错误: ' + e.message, true);
- }
- }
-
- function copyNvsJson() {
- const editor = document.getElementById('nvs-editor');
- navigator.clipboard.writeText(editor.value).then(() => {
- showToast('已复制 NVS JSON 到剪贴板');
- }).catch(() => {
- editor.select();
- document.execCommand('copy');
- showToast('已复制到剪贴板');
- });
- }
-
- function exportNvsJson() {
- const editor = document.getElementById('nvs-editor');
- const blob = new Blob([editor.value], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- const timeStr = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
- a.href = url;
- a.download = `RemoteMapper_NVS_Backup_${timeStr}.json`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- showToast('NVS 备份文件已导出下载');
- }
-
- function importNvsJson(e) {
+ // Config Management: Import Keymap
+ function importKeymapConfig(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
- reader.onload = function(evt) {
+ reader.onload = async function(evt) {
try {
- const obj = JSON.parse(evt.target.result);
- document.getElementById('nvs-editor').value = JSON.stringify(obj, null, 2);
- validateNvsJson();
- showToast('文件导入成功,请点击“保存写入 NVS”生效');
+ const data = JSON.parse(evt.target.result);
+ if (!data || !Array.isArray(data.layers)) {
+ throw new Error('无效的按键配置格式(必须包含 layers 数组)');
+ }
+ if (!confirm(`确定要导入按键配置文件「${file.name}」吗?\n\n导入后将覆盖所有按键映射与层级设置,保留现有的 Wi-Fi 与蓝牙配置。`)) {
+ return;
+ }
+ const res = await fetch('/api/keymap/save', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+ const ret = await res.json();
+ if (res.ok) {
+ showToast('按键配置导入成功并已保存!');
+ await loadKeymap();
+ } else {
+ showToast('导入失败: ' + (ret.error || '服务器拒绝该配置'), true);
+ }
} catch(err) {
- showToast('导入文件解析失败: ' + err.message, true);
+ showToast('导入解析失败: ' + err.message, true);
+ } finally {
+ e.target.value = '';
}
};
reader.readAsText(file);
- e.target.value = '';
}
- async function saveNvsConfig() {
- const editor = document.getElementById('nvs-editor');
- if (!validateNvsJson()) {
- showToast('当前 JSON 存在语法错误,无法保存!', true);
- return;
- }
- if (!confirm('确定要将当前编辑的 JSON 写入 ESP32 Flash NVS 吗?\n写入后参数将立即更新。')) {
+ // Config Management: Reset Keymap Only (preserve WiFi & BLE)
+ async function resetKeymapOnly() {
+ if (!confirm('确定要清空所有按键映射与层级设置吗?\n\n所有按键将恢复为出厂默认映射规则。\n蓝牙遥控器配对与 Wi-Fi 网络参数将完全保留!')) {
return;
}
try {
- const res = await fetch('/api/nvs/save', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: editor.value
- });
- const ret = await res.json();
+ const res = await fetch('/api/keymap/reset', { method: 'POST' });
if (res.ok) {
- showToast(ret.message || 'NVS 配置已成功保存到 Flash!');
- loadKeymap();
+ await loadKeymap();
+ showToast('按键与层级数据已恢复出厂默认(蓝牙与Wi-Fi配置已保留)');
} else {
- showToast('保存失败: ' + (ret.error || '未知错误'), true);
+ showToast('重置按键映射失败', true);
}
} catch(e) {
- showToast('保存请求失败: ' + e.message, true);
+ showToast('请求失败: ' + e.message, true);
}
}
+ // Config Management: Factory Reset (wipe all NVS)
async function resetNvsFactory() {
- if (!confirm('警告:此操作将清空 ESP32 内部的所有 NVS 持久化数据(包括 Wi-Fi 密码、遥控器绑定、自定义按键),并自动重启!\n\n确定要执行出厂重置吗?')) {
+ if (!confirm('⚠️ 严重警告:此操作将清空 ESP32 内部的全部 NVS 持久化数据!\n\n包括:\n• 所有 Wi-Fi 密码与 AP 热点设置\n• 绑定的蓝牙遥控器配对信息\n• 所有自定义按键与多层级映射\n\n设备将自动重启并完全恢复出厂设置!是否继续?')) {
return;
}
try {
const res = await fetch('/api/nvs/reset', { method: 'POST' });
const ret = await res.json();
- showToast(ret.message || 'NVS 已清空,设备重启中...');
- setTimeout(() => location.reload(), 3000);
+ showToast(ret.message || '全部 NVS 已清空,设备重启中...');
+ setTimeout(() => location.reload(), 3500);
} catch(e) {
showToast('操作失败: ' + e.message, true);
}
}
- function handleNvsEditorKey(e) {
- if (e.key === 'Tab') {
- e.preventDefault();
- const textarea = e.target;
- const start = textarea.selectionStart;
- const end = textarea.selectionEnd;
- textarea.value = textarea.value.substring(0, start) + ' ' + textarea.value.substring(end);
- textarea.selectionStart = textarea.selectionEnd = start + 2;
- validateNvsJson();
+ // Config Management: Read-Only NVS Debug Viewer
+ function onNvsDebugToggle(detailsEl) {
+ const arrow = document.getElementById('nvs-details-arrow');
+ if (arrow) arrow.innerText = detailsEl.open ? '▼' : '▶';
+ if (detailsEl.open) {
+ loadNvsReadOnly(false);
}
}
+ async function loadNvsReadOnly(forceToast = false) {
+ const viewer = document.getElementById('nvs-readonly-viewer');
+ if (!viewer) return;
+ if (forceToast) viewer.textContent = '正在刷新 NVS 内部数据...';
+ try {
+ const res = await fetch('/api/nvs');
+ const json = await res.json();
+ viewer.textContent = JSON.stringify(json, null, 2);
+ if (forceToast) showToast('NVS 内部数据已刷新');
+ } catch(e) {
+ viewer.textContent = '读取 NVS 数据失败: ' + e.message;
+ if (forceToast) showToast('读取 NVS 失败: ' + e.message, true);
+ }
+ }
+
+ function copyNvsReadOnly() {
+ const viewer = document.getElementById('nvs-readonly-viewer');
+ if (!viewer) return;
+ navigator.clipboard.writeText(viewer.textContent).then(() => {
+ showToast('已复制 NVS 只读数据到剪贴板');
+ }).catch(() => {
+ showToast('复制失败,请手动选取文本');
+ });
+ }
+
// Periodic background pollers
setInterval(fetchKeyTelemetry, 100);
setInterval(fetchStatus, 3000);