fix: 第三轮修复 — 前端Session切换、DevTools UI刷新保持、头像背景替换
1. 修复前端清空对话无反应 (clearMainSessionMessages 链路) 2. 修复清除所有对话后侧边栏残留 + 重复新增按钮 3. 修复侧边栏点击无法切换会话 (Zustand 竞态 + URL hash) 4. 修复 URL 不显示 session ID (hash 同步链) 5. DevTools 会话监看刷新保持展开/折叠状态 6. 首页性能仪表盘去重 + 资源使用卡片 60s sparkline 7. DevTools 全局刷新改为 DOM 局部增量更新 8. 替换前端昔涟头像、聊天背景、用户头像为实际图片 9. 修复图片文件名 (双.png + 目录拼写)
This commit is contained in:
+131
-10
@@ -35,19 +35,21 @@ var IOT_COLOR_OPTIONS = [
|
||||
];
|
||||
|
||||
async function renderIoTPanel() {
|
||||
var result = await fetchIoTDevices();
|
||||
var panel = document.getElementById('panel-iot');
|
||||
|
||||
// 更新操作栏 (只更新时间戳文本)
|
||||
document.getElementById('panel-actions').innerHTML =
|
||||
'<button class="btn btn-sm" onclick="renderIoTPanel()" id="iot-refresh-btn">🔄 刷新</button>' +
|
||||
'<span class="iot-last-update">⏱ 每3秒自动刷新 · 最后更新: ' + new Date().toLocaleTimeString('zh-CN', {hour12: false}) + '</span>';
|
||||
|
||||
var result = await fetchIoTDevices();
|
||||
var panel = document.getElementById('panel-iot');
|
||||
|
||||
if (result.error) {
|
||||
var hint = '';
|
||||
if (result.error.errorType === 'iot_not_running') {
|
||||
hint = '<br><span style="font-size:11px">💡 提示: 请先在「服务管理」面板中启动 IoT Debug 服务</span>';
|
||||
}
|
||||
panel.innerHTML = '<div class="empty-state"><div class="icon">⚠️</div>' + escHtml(result.error.error) + hint + '</div>';
|
||||
STATE.iotInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,14 +70,133 @@ async function renderIoTPanel() {
|
||||
badge.style.display = devices.length > 0 ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
var html = '<div class="iot-refresh-bar">' +
|
||||
'<span style="font-weight:600;font-size:14px">📡 模拟 IoT 设备 (' + devices.length + ')</span>' +
|
||||
'<span style="font-size:11px;color:var(--text2)">通过 IoT 调试服务 (端口 8083) 管理</span>' +
|
||||
'</div><div class="iot-device-grid" id="iot-device-grid">' +
|
||||
devices.map(function(d) { return renderIoTDeviceCard(d); }).join('') +
|
||||
'</div>';
|
||||
// Bug 7: 增量更新 — 首次渲染完整 DOM,后续只更新设备属性值
|
||||
var grid = document.getElementById('iot-device-grid');
|
||||
var firstRender = !STATE.iotInitialized;
|
||||
|
||||
panel.innerHTML = html;
|
||||
if (firstRender || !grid) {
|
||||
// 首次渲染: 创建完整结构
|
||||
var html = '<div class="iot-refresh-bar">' +
|
||||
'<span style="font-weight:600;font-size:14px" id="iot-device-count">📡 模拟 IoT 设备 (' + devices.length + ')</span>' +
|
||||
'<span style="font-size:11px;color:var(--text2)">通过 IoT 调试服务 (端口 8083) 管理</span>' +
|
||||
'</div><div class="iot-device-grid" id="iot-device-grid">' +
|
||||
devices.map(function(d) { return renderIoTDeviceCard(d); }).join('') +
|
||||
'</div>';
|
||||
panel.innerHTML = html;
|
||||
STATE.iotInitialized = true;
|
||||
} else {
|
||||
// 增量更新: 只更新设备数量、最后更新时间
|
||||
var countEl = document.getElementById('iot-device-count');
|
||||
if (countEl) countEl.textContent = '📡 模拟 IoT 设备 (' + devices.length + ')';
|
||||
|
||||
// 对每个已有设备卡片做增量更新
|
||||
devices.forEach(function(device) {
|
||||
updateIoTDeviceCardInPlace(device);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Bug 7 helper: 增量更新单个设备卡片的属性值,不重建 DOM
|
||||
function updateIoTDeviceCardInPlace(device) {
|
||||
var card = document.getElementById('iot-card-' + device.id);
|
||||
if (!card) return;
|
||||
|
||||
var isOn = device.status === 'on';
|
||||
|
||||
// 更新卡片 class (on/off 边框)
|
||||
card.className = 'iot-device-card ' + (isOn ? 'on' : 'off');
|
||||
|
||||
// 更新状态圆点和文字
|
||||
var statusDot = card.querySelector('.iot-status-dot');
|
||||
if (statusDot) statusDot.className = 'iot-status-dot ' + (isOn ? 'on' : 'off');
|
||||
var statusText = card.querySelector('.iot-device-status span:last-child');
|
||||
if (statusText) {
|
||||
statusText.textContent = isOn ? '开启' : '关闭';
|
||||
statusText.style.color = isOn ? 'var(--green)' : 'var(--text3)';
|
||||
}
|
||||
|
||||
// 更新开关按钮
|
||||
var toggleBtn = card.querySelector('.iot-toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.className = 'iot-toggle-btn ' + (isOn ? 'on' : 'off');
|
||||
toggleBtn.textContent = isOn ? '⏻ 关闭' : '⏻ 开启';
|
||||
}
|
||||
|
||||
// 更新设备属性值 (温度、亮度、位置等)
|
||||
var propValues = card.querySelectorAll('.iot-prop-value');
|
||||
var type = device.type;
|
||||
|
||||
if (type === 'ac') {
|
||||
// AC: 温度 slider + 模式按钮
|
||||
var tempSlider = card.querySelector('.iot-prop-control input[type="range"]');
|
||||
if (tempSlider) {
|
||||
tempSlider.value = device.temperature || 26;
|
||||
var tempVal = tempSlider.nextElementSibling;
|
||||
if (tempVal) tempVal.textContent = (device.temperature || 26) + '°C';
|
||||
}
|
||||
// 更新模式按钮
|
||||
var modeBtns = card.querySelectorAll('.iot-mode-btn');
|
||||
modeBtns.forEach(function(btn) {
|
||||
var btnMode = btn.textContent.trim();
|
||||
if (btnMode === (device.mode || 'cool')) {
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
} else if (type === 'light') {
|
||||
var brightSlider = card.querySelector('.iot-prop-control input[type="range"]');
|
||||
if (brightSlider) {
|
||||
brightSlider.value = device.brightness || 80;
|
||||
var brightVal = brightSlider.nextElementSibling;
|
||||
if (brightVal) brightVal.textContent = (device.brightness || 80) + '%';
|
||||
}
|
||||
// 更新颜色按钮
|
||||
var colorBtns = card.querySelectorAll('.iot-color-btn');
|
||||
colorBtns.forEach(function(btn) {
|
||||
// 颜色值从 onclick 属性解析
|
||||
var onclick = btn.getAttribute('onclick') || '';
|
||||
var match = onclick.match(/iotSetProperty\('[^']+',\s*'color',\s*'([^']+)'\)/);
|
||||
if (match && match[1] === (device.color || 'warm_white')) {
|
||||
btn.classList.add('active');
|
||||
} else if (!match || match[1] !== (device.color || 'warm_white')) {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
} else if (type === 'curtain') {
|
||||
var posSlider = card.querySelector('.iot-prop-control input[type="range"]');
|
||||
if (posSlider) {
|
||||
posSlider.value = device.position != null ? device.position : 100;
|
||||
var posVal = posSlider.nextElementSibling;
|
||||
if (posVal) posVal.textContent = (device.position != null ? device.position : 100) + '%';
|
||||
}
|
||||
} else if (device.temperature != null) {
|
||||
// sensor/thermostat: 只读温度
|
||||
if (propValues.length > 0) propValues[0].textContent = device.temperature + (device.unit || '°C');
|
||||
}
|
||||
|
||||
// 更新电量
|
||||
var batteryEls = card.querySelectorAll('.iot-prop-value');
|
||||
batteryEls.forEach(function(el) {
|
||||
if (el.parentElement && el.parentElement.querySelector('.iot-prop-label') &&
|
||||
el.parentElement.querySelector('.iot-prop-label').textContent.indexOf('🔋') !== -1) {
|
||||
if (device.battery != null) el.textContent = device.battery + '%';
|
||||
}
|
||||
});
|
||||
|
||||
// 更新 AC 温度按钮的 onclick 引用
|
||||
if (type === 'ac') {
|
||||
var acBtns = card.querySelectorAll('.iot-device-actions .btn-xs');
|
||||
acBtns.forEach(function(btn) {
|
||||
var text = btn.textContent.trim();
|
||||
var currentTemp = device.temperature || 26;
|
||||
if (text === '⬇ -2°C') {
|
||||
btn.setAttribute('onclick', "iotSetProperty('" + device.id + "', 'temperature', " + (currentTemp - 2) + ");refreshIoTDeviceCard('" + device.id + "')");
|
||||
} else if (text === '⬆ +2°C') {
|
||||
btn.setAttribute('onclick', "iotSetProperty('" + device.id + "', 'temperature', " + (currentTemp + 2) + ");refreshIoTDeviceCard('" + device.id + "')");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderIoTDeviceCard(device) {
|
||||
|
||||
Reference in New Issue
Block a user