2025-05-27 15:46:31 +08:00

869 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

window.onload = function () {
function getStatus() {
return new Promise((resolve, reject) => {
fetch("/get_status")
.then((response) => {
if (!response.ok) {
reject(new Error("Network response was not ok"));
}
return response.json();
})
.then((data) => resolve(data)) // 如果成功,返回数据
.catch((error) => {
console.error("There was a problem with the fetch operation:", error);
reject(error); // 如果发生错误,返回错误
});
});
}
// 虚拟模式
const developBtn = document.getElementById("develop-btn");
const modeRealText = document.getElementById("mode-real-text");
const changePower = document.getElementById("change-power");
const changePowerText = document.getElementById("change-power-text");
const loadBtn = document.getElementById("load-btn");
const loadText = document.getElementById("load-text");
// 虚拟模式显示
modeRealText.innerText =
parseInt(localStorage.getItem("modeReal")) === 0 ? "开" : "关";
// 力控方式按钮显示
changePower.style.display =
parseInt(localStorage.getItem("modeReal")) === 0 ? "flex" : "none";
loadText.innerText =
parseInt(localStorage.getItem("loadMode")) === 1 ? "开" : "关";
developBtn.addEventListener("click", function () {
getStatus()
.then(async (data) => {
if (data.is_massaging || data.is_pause) {
let stopPleaseText = await getPopupText("stopPleaseText");
showPopup(stopPleaseText, { confirm: true, cancel: false });
return;
} else {
if (parseInt(localStorage.getItem("loadMode")) === 1) {
let noToggleText = await getPopupText("noToggleText");
showPopup(noToggleText);
return;
} else {
const modeReal =
parseInt(localStorage.getItem("modeReal")) === 0 ? 0 : 1;
if (modeReal === 1) {
localStorage.setItem("modeReal", 0);
modeRealText.innerText = "开";
changePower.style.display = "flex";
changePowerText.innerText = localStorage.getItem("powerControl")
? localStorage.getItem("powerControl")
: "导纳";
} else {
localStorage.setItem("modeReal", 1);
modeRealText.innerText = "关";
changePower.style.display = "none";
}
}
}
})
.catch((error) => {
console.error("Error:", error);
showPopup(error, { confirm: true, cancel: false });
});
});
loadBtn.addEventListener("click", function () {
getStatus()
.then((data) => {
if (data.is_massaging || data.is_pause) {
showPopup("请先停止按摩...", { confirm: true, cancel: false });
return;
} else {
if (parseInt(localStorage.getItem("modeReal")) === 0) {
showPopup("循环模式下和虚拟模式无法同时开启", { confirm: true, cancel: false });
return;
} else {
const loadMode =
parseInt(localStorage.getItem("loadMode")) === 1 ? 1 : 0;
if (loadMode === 0) {
localStorage.setItem("loadMode", 1);
loadText.innerText = "开";
} else {
localStorage.setItem("loadMode", 0);
loadText.innerText = "关";
}
}
}
})
.catch((error) => {
console.error("Error:", error);
showPopup(error, { confirm: true, cancel: false });
});
});
const changePowerBtn = document.getElementById("change-power-btn");
changePowerText.innerText = localStorage.getItem("powerControl")
? localStorage.getItem("powerControl")
: "导纳";
changePowerBtn.addEventListener("click", function () {
let powerControl = localStorage.getItem("powerControl");
if (powerControl === "导纳" || !powerControl) {
localStorage.setItem("powerControl", "力位混合");
changePowerText.innerText = "力位混合";
} else {
localStorage.setItem("powerControl", "导纳");
changePowerText.innerText = "导纳";
}
});
// 机械臂打包按钮
const robotPackBtn = document.getElementById("robot-pack-btn");
robotPackBtn.addEventListener("click", function () {
getStatus()
.then((data) => {
if (data.is_massaging || data.is_pause) {
showPopup("请先停止按摩...", { confirm: true, cancel: false });
return;
} else {
showPopup("准备运动到打包位置,请确定是否已经取下按摩头!").then(
(confirm) => {
if (confirm) {
fetch("/pack_mode", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded", // 表单数据类型
},
});
localStorage.setItem("modeReal", 1);
modeRealText.innerText = "关";
changePower.style.display = "none";
localStorage.setItem("loadMode", 0);
loadText.innerText = "关";
}
}
);
}
})
.catch((error) => {
console.error("Error:", error);
showPopup(error, { confirm: true, cancel: false });
});
});
let jumpModeInterval = null;
const massageLog = document.getElementById("massage-log-btn");
const uiLog = document.getElementById("ui-log-btn");
const langLog = document.getElementById("language-log-btn");
massageLog.addEventListener("click", function () {
loadLogData("show", "massage");
// showLogModal();
})
uiLog.addEventListener("click", function () {
loadLogData("show", "ui");
// showLogModal();
})
langLog.addEventListener("click", function () {
loadLogData("show", "language");
// showLogModal();
})
const logModal = document.getElementById("log-modal");
const logBg = document.getElementById("log-bg");
const logInput = document.getElementById("log-input");
const logSearch = document.getElementById("log-search");
const logRefresh = document.getElementById("log-refresh");
const logText = document.getElementById("log-text");
logBg.addEventListener("click", function () {
logModal.style.display = "none";
logInput.value = "";
logText.innerHTML = "";
});
// 自定义日志高亮规则
Prism.languages.log = {
'info': /INFO/,
'error': /ERROR/,
'warning': /WARNING/,
'timestamp': /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/
};
let currentPage = 1; // 当前页码
let totalPages = 1; // 总页数
let currentLogType = ""; // 当前日志类型
let currentKeyword = ""; // 当前搜索关键字
let isLoading = false; // 是否正在加载数据
// 加载日志数据
function loadLogData(clickType, type, keyword = "", page = 1) {
if (isLoading) return; // 如果正在加载,则退出
isLoading = true;
currentLogType = type;
currentKeyword = keyword;
currentPage = page;
// 显示加载指示器
document.getElementById("loading-indicator").style.display = "block";
getLog(type, keyword, page)
.then((data) => {
if (clickType === 'show') {
showLogModal();
}
// 如果是第一页,清空内容
if (page === 1) {
logText.innerHTML = `<pre><code class="language-log">${data.data.join("\n")}</code></pre>`;
} else {
// 追加新内容
const existingContent = logText.querySelector("code").textContent;
logText.innerHTML = `<pre><code class="language-log">${existingContent}\n${data.data.join("\n")}</code></pre>`;
}
// 调用 Prism.js 高亮
Prism.highlightAll();
// 更新总页数
totalPages = Math.ceil(data.total / data.pageSize);
// 隐藏加载指示器
document.getElementById("loading-indicator").style.display = "none";
isLoading = false;
})
.catch((error) => {
console.error("加载日志失败:", error);
document.getElementById("loading-indicator").style.display = "none";
isLoading = false;
});
}
logText.addEventListener("scroll", () => {
if (
logText.scrollTop + logText.clientHeight >=
logText.scrollHeight - 10 // 接近底部时触发
) {
if (currentPage < totalPages) {
loadLogData('scroll', currentLogType, currentKeyword, currentPage + 1);
}
}
});
logSearch.addEventListener("click", () => {
const keyword = logInput.value.trim();
loadLogData('search', currentLogType, keyword, 1); // 搜索时重置到第一页
});
logRefresh.addEventListener("click", () => {
logInput.value = currentKeyword;
loadLogData('refresh', currentLogType, currentKeyword, currentPage);
});
logInput.addEventListener("keyup", (event) => {
if (event.keyCode === 13) {
const keyword = logInput.value.trim();
loadLogData('search', currentLogType, keyword, 1); // 按下回车时搜索
}
});
// 显示日志弹窗
function showLogModal() {
logModal.style.display = "block";
}
function getLog(type, keyword = "", page = 1, pageSize = 400) {
return new Promise((resolve, reject) => {
fetch(`/get_log?type=${type}&keyword=${keyword}&page=${page}&pageSize=${pageSize}`)
.then((response) => response.json())
.then((data) => {
if (data.error) {
showPopup(data.error, { confirm: true, cancel: false });
reject(data.error); // 如果有错误,拒绝 Promise
} else {
resolve(data); // 如果成功,返回数据
}
})
.catch((error) => {
console.error(error);
showPopup(error, { confirm: true, cancel: false });
reject(error); // 捕获错误并拒绝 Promise
});
});
}
// 获取按摩头配置
function loadMassageHeads() {
fetch('/get_massage_heads')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
updateHeadCheckboxes(data.data);
} else {
showPopup('获取按摩头配置失败: ' + data.message, {confirm: true, cancel: false});
}
})
.catch(error => {
showPopup('请求出错: ' + error, {confirm: true, cancel: false});
});
}
const saveBtn = document.getElementById('save-heads-btn');
document.querySelectorAll('input[name="head-option"]').forEach(checkbox => {
checkbox.addEventListener('change', updateSaveButtonState);
});
// 更新保存按钮状态
function updateSaveButtonState() {
const hasSelected = Array.from(document.querySelectorAll('input[name="head-option"]:checked')).length > 0;
if (!hasSelected) {
saveBtn.disabled = true;
saveBtn.style.opacity = '0.4';
showPopup('请至少选择一个按摩头', { confirm: true, cancel: false })
} else {
saveBtn.disabled = false;
saveBtn.style.opacity = '1';
}
}
// 保存按摩头配置
function saveMassageHeads() {
if(saveBtn.style.opacity == '0.4') {
return;
}
getStatus()
.then((data) => {
if (data.is_massaging || data.is_pause) {
showPopup("按摩中无法设置", { confirm: true, cancel: false });
return;
} else {
const headsConfig = {};
document.querySelectorAll('input[name="head-option"]').forEach(checkbox => {
const headId = checkbox.value;
headsConfig[headId] = {
display: checkbox.checked,
name: document.querySelector(`label[for="head-${headId}"]`).textContent
};
});
fetch('/set_massage_heads', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ heads_config: headsConfig })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showPopup('按摩头设置已保存', {confirm: true, cancel: false});
} else {
showPopup('保存失败: ' + data.message, {confirm: true, cancel: false});
}
})
.catch(error => {
showPopup('保存出错: ' + error, {confirm: true, cancel: false});
});
}
})
.catch((error) => {
console.error("Error:", error);
showPopup(error, { confirm: true, cancel: false });
});
}
async function getJumpMode() {
try {
const response = await fetch('/get_jump_mode');
const { data } = await response.json();
document.getElementById('jump-text').textContent =
data.enabled ? '已开启' : '已关闭';
jumpModeInterval = data.enabled;
} catch (error) {
console.error('获取跳跃模式失败:', error);
showPopup('获取跳跃模式失败');
}
}
// 切换跳跃模式
document.getElementById('jump-toggle-btn').addEventListener('click', async () => {
getStatus()
.then(async (data) => {
if (data.is_massaging || data.is_pause) {
showPopup("请先停止按摩...", { confirm: true, cancel: false });
return;
} else {
if(jumpModeInterval === null || jumpModeInterval === undefined) {
try {
const current = await fetch('/get_jump_mode').then(res => res.json());
const newState = !current.data.enabled;
toggleJumpMode(newState);
} catch (error) {
console.error('切换跳跃模式失败:', error);
showPopup('切换跳跃模式失败');
}
} else {
const newState = !jumpModeInterval;
toggleJumpMode(newState);
}
}
})
.catch((error) => {
console.error("Error:", error);
showPopup(error, { confirm: true, cancel: false });
});
});
async function toggleJumpMode(newState) {
// 设置新状态
const response = await fetch('/set_jump_mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: newState })
});
const result = await response.json();
if (result.status === 'success') {
document.getElementById('jump-text').textContent = newState ? '已开启' : '已关闭';
jumpModeInterval = newState;
showPopup(`跳跃模式已${newState ? '开启' : '关闭'}`);
} else {
showPopup('操作失败: ' + result.message);
}
}
// 更新复选框状态
function updateHeadCheckboxes(headsConfig) {
for (const [headId, config] of Object.entries(headsConfig)) {
const checkbox = document.getElementById(`head-${headId}`);
if (checkbox) {
checkbox.checked = config.display;
}
}
}
// 绑定保存按钮事件
saveBtn.addEventListener('click', saveMassageHeads);
loadMassageHeads();
getJumpMode();
};
const wifiModal = document.getElementById("wifi-modal");
function getWifiStatus() {
fetch("/is_connected")
.then((response) => response.json())
.then((data) => {
const myWifi = document.getElementById("my-wifi");
if (data.is_connected) {
myWifi.style.display = "flex";
getWifiInfo();
} else {
myWifi.style.display = "none";
showPopup("星耀按摩机器人未连接网络,请检查网络连接后重试。", {confirm: true, cancel: false});
}
});
}
const wifiName = document.getElementById("wifi-name");
function getWifiInfo() {
fetch("/get_current_connection")
.then((response) => response.json())
.then((data) => {
const wifiTxt = document.getElementById("wifi-txt");
if (data.connection_info[0].name) {
wifiTxt.innerText = data.connection_info[0].name;
wifiName.innerText = data.connection_info[0].name;
} else {
wifiTxt.innerText = "未知网络或未连接";
wifiTxt.style.color = "rgba(145, 66, 197, 1)";
}
});
}
let isAllowClick = true;
let selectedSsid = ""; // 全局变量,用于存储当前选择的 SSID
let selectedWifiImg = ""; // 全局变量,用于存储当前选择的 Wi-Fi 信号强度图片
let selectedWifiImgElement = null;
function scanWifi() {
const wifiList = document.getElementById("wifi-list");
wifiList.innerHTML = `<div class="loading-box">
<img
class="loading-img"
src="/static/images/setting/loading.gif"
alt=""
/>
</div>`;
fetch("/scan_wifi")
.then((response) => response.json())
.then((data) => {
// 先清空列表
wifiList.innerHTML = "";
if (data.wifi_networks.length === 0) {
wifiList.innerHTML = `扫描不到wifi`;
} else {
// 再添加扫描到的wifi
data.wifi_networks.forEach((network) => {
if (network.SSID !== wifiName.innerText) {
let wifiImg =
network.Signal <= 33
? "/static/images/setting/wifi-small.png"
: network.Signal <= 66
? "/static/images/setting/wifi-medium.png"
: "/static/images/setting/wifi.png";
wifiList.innerHTML += `<div class="wifi-item" data-ssid="${network.SSID}">
<div class="wifi-is-connected">
<div class="wifi-type">
<img
src="${wifiImg}"
alt=""
class="wifi-signal-img"
/>
</div>
</div>
<div class="wifi-name">${network.SSID}</div>
<div class="right-content">
<div class="is-locked">
<img
src="/static/images/setting/lock.png"
alt=""
/>
</div>
</div>
</div>`;
}
});
// 为每个 wifi-item 添加点击事件监听器
const wifiItems = document.querySelectorAll(".wifi-item");
wifiItems.forEach((item) => {
item.addEventListener("click", function () {
if (isAllowClick === true) {
selectedSsid = this.getAttribute("data-ssid"); // 存储当前选中的 SSID
console.log("点击了 Wi-Fi 网络:", selectedSsid);
// 获取当前点击项中的图片元素,并保存它
selectedWifiImgElement = this.querySelector(".wifi-signal-img");
selectedWifiImg = selectedWifiImgElement.src;
// 显示密码框
showPwdModal().then((confirm) => {
if (confirm) {
wifiPwdCheck();
}
});
} else {
return;
}
});
});
}
});
}
const pwdModal = document.getElementById("pwd-modal");
const pwdInput = document.getElementById("pwd-input");
let pwdResolve;
function showPwdModal() {
pwdModal.style.display = "flex";
pwdInput.value = "";
pwdInput.focus();
// 返回Promise等待用户选择
return new Promise((resolve) => {
pwdResolve = resolve; // 保存 resolve 函数
});
}
// 绑定 keydown 事件
pwdInput.addEventListener('keydown', function (event) {
// 检查按下的键是否是 Enter 键
if (event.key === 'Enter' || event.keyCode === 13) {
// 回车键按下时的逻辑
pwdConfirm()
}
});
function pwdConfirm() {
pwdModal.style.display = "none";
pwdResolve(true);
}
function pwdCancel() {
pwdModal.style.display = "none";
pwdResolve(false);
}
function wifiPwdCheck() {
selectedWifiImgElement.src = "/static/images/setting/loading.gif"; // 更换为加载中图片
isAllowClick = false;
if (pwdInput.value === "") {
showPopup("密码不能为空。", {confirm: true, cancel: false});
isAllowClick = true;
// 恢复之前选择的 Wi-Fi 图标
if (selectedWifiImgElement) {
selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标
}
return;
} else if (pwdInput.value.length < 8) {
showPopup("密码长度必须大于等于8位。", {confirm: true, cancel: false});
isAllowClick = true;
// 恢复之前选择的 Wi-Fi 图标
if (selectedWifiImgElement) {
selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标
}
return;
} else {
let timeout = false; // 是否超时的标志
// 设置 20 秒超时计时器
const timer = setTimeout(() => {
timeout = true; // 标记超时
showPopup(
`连接成功请将平板的WI-FI连接到与按摩机器人同一网络${selectedSsid}再手动重启APP。`,
{ confirm: false, cancel: false }
);
}, 20000); // 20秒后触发超时逻辑
fetch("/connect_wifi", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
ssid: selectedSsid,
password: pwdInput.value,
}),
})
.then((response) => response.json())
.then((data) => {
let num = 20;
let interval = setInterval(() => {
if (timeout) {
clearInterval(interval); // 如果超时,则停止轮询
return;
}
if (data.message.status === "error") {
showPopup(data.message.message, { confirm: true });
isAllowClick = true;
// 恢复之前选择的 Wi-Fi 图标
if (selectedWifiImgElement) {
selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标
}
clearInterval(interval); // 错误时停止轮询
clearTimeout(timer); // 停止超时计时器
}
if (num === 0) {
clearInterval(interval); // 如果 20 秒后还没有返回错误,则显示成功信息
showPopup(
`连接成功请将平板的WI-FI连接到与按摩机器人同一网络${selectedSsid}再手动重启APP。`,
{ confirm: false, cancel: false }
);
clearTimeout(timer); // 结束超时计时器
}
num--;
}, 1000); // 每秒检查一次
})
.catch(() => {
isAllowClick = true;
clearTimeout(timer); // 如果请求出错,取消超时计时器
});
}
}
getWifiStatus();
scanWifi();
document.getElementById("scan-btn").addEventListener("click", function () {
wifiModal.style.display = "flex";
scanWifi();
});
document.getElementById("rescan-btn").addEventListener("click", function () {
scanWifi();
});
document
.getElementById("modal-level")
.addEventListener("click", function (e) {
if (isAllowClick === true) {
wifiModal.style.display = "none";
} else {
return;
}
});
const showBoardBtn = document.getElementById("show-board-btn");
const boardImg = document.getElementById("board-img");
const calibrationControls = document.getElementById("calibration-controls");
const startCalibrationBtn = document.getElementById("start-calibration-btn");
const calibrationResultModal = document.getElementById("calibration-result-modal");
const rotationMatrixResult = document.getElementById("rotation-matrix-result");
const translationVectorResult = document.getElementById("translation-vector-result");
const intrinsicsResult = document.getElementById("intrinsics-result");
// 存储最新的标定结果
let currentCalibrationResults = null;
// 使用已存在的socket连接
if (typeof socket === 'undefined') {
console.warn('Socket.IO connection not found, creating new one');
window.socket = io();
}
// 格式化矩阵显示
function formatMatrix(matrix) {
// 处理内参字典格式
if (typeof matrix === 'object' && matrix !== null && !Array.isArray(matrix) && 'fx' in matrix) {
let result = '';
// 添加主要参数
result += `fx: ${matrix.fx.toFixed(6)}\n`;
result += `fy: ${matrix.fy.toFixed(6)}\n`;
result += `cx: ${matrix.cx.toFixed(6)}\n`;
result += `cy: ${matrix.cy.toFixed(6)}\n`;
// 添加畸变系数
if (matrix.distortion_coeffs) {
result += `畸变系数:\n`;
const distCoeffs = matrix.distortion_coeffs;
result += ` k1: ${distCoeffs.k1.toFixed(6)}\n`;
result += ` k2: ${distCoeffs.k2.toFixed(6)}\n`;
result += ` p1: ${distCoeffs.p1.toFixed(6)}\n`;
result += ` p2: ${distCoeffs.p2.toFixed(6)}\n`;
result += ` k3: ${distCoeffs.k3.toFixed(6)}`;
}
return result;
} else if (!Array.isArray(matrix)) {
return JSON.stringify(matrix);
}
return matrix.map(row =>
Array.isArray(row)
? row.map(v => v.toFixed(6)).join(' ')
: row.toFixed(6) // 直接对非数组的 row 使用 toFixed(6)
).join('\n');
}
// 监听标定状态更新
socket.on('calibration_status', function (data) {
console.log('Received calibration status:', data);
switch (data.status) {
case 'running':
startCalibrationBtn.disabled = true;
startCalibrationBtn.textContent = '标定中...';
break;
case 'collecting':
startCalibrationBtn.textContent = '收集数据...';
break;
case 'calibrating':
startCalibrationBtn.textContent = '计算参数...';
break;
case 'completed':
startCalibrationBtn.disabled = false;
startCalibrationBtn.textContent = '开始标定';
// 保存当前标定结果
currentCalibrationResults = data.results;
// 格式化并显示标定结果
console.log(data.results.rotation_matrix)
console.log(data.results.translation_vector)
console.log(data.results.intrinsics)
rotationMatrixResult.textContent = formatMatrix(data.results.rotation_matrix);
translationVectorResult.textContent = formatMatrix(data.results.translation_vector);
intrinsicsResult.textContent = formatMatrix(data.results.intrinsics);
calibrationResultModal.style.display = "block";
break;
case 'error':
startCalibrationBtn.disabled = false;
startCalibrationBtn.textContent = '开始标定';
showPopup('标定失败:' + data.message);
break;
}
});
showBoardBtn.addEventListener("click", function () {
console.log('Showing board image');
boardImg.style.display = "block";
calibrationControls.style.display = "block";
});
boardImg.addEventListener("dblclick", function () {
console.log('Hiding board image');
boardImg.style.display = "none";
calibrationControls.style.display = "none";
calibrationResultModal.style.display = "none";
});
startCalibrationBtn.addEventListener("click", function () {
console.log('Starting calibration');
fetch('/start_calibration', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.status === 'error') {
showPopup(data.message);
}
})
.catch(error => {
console.error('Error:', error);
showPopup('启动标定失败:' + error.message);
});
});
function saveCalibrationResult() {
if (!currentCalibrationResults) {
showPopup('没有可保存的标定结果');
return;
}
fetch('/save_calibration', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
rotation_matrix: currentCalibrationResults.rotation_matrix,
translation_vector: currentCalibrationResults.translation_vector,
intrinsics: currentCalibrationResults.intrinsics
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showPopup('标定结果保存成功');
calibrationResultModal.style.display = "none";
boardImg.style.display = "none";
calibrationControls.style.display = "none";
} else {
showPopup('保存失败:' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
showPopup('保存失败:' + error.message);
});
}
function cancelCalibrationResult() {
calibrationResultModal.style.display = "none";
currentCalibrationResults = null;
}