fix(cablemanager): 修复线缆分组详情编辑时成员被自动刷新打回且保存无效的问题
根因:
- cablemanager 每 900ms scheduleAutoReload() 通过 WS state_update 触发 reloadAll({silent:true}),之前 reloadAll 在 detail-view 场景下每次都会无条件把 detailTempMembers 从服务器读回、selectedAvailable/selectedMembers 清空,所以用户刚把某站台 » 加进成员,下一次被动刷新到了就立刻还原(视觉上就是「点过去又跳回来」)
- 保存 saveDetail() PUT 本身是能成功的(server PUT /api/audio-cable-groups/:id 的 stationNames 接口契约没问题),但保存之后 loadGroups() 拉完数据又立刻触发 broadcastUpdate()→WS state_update→scheduleAutoReload→又一次 reloadAll()→把刚保存的 detailTempMembers 用服务器值覆盖掉,用户看起来就是「保存无效」
修复 (cablemanager.html reloadAll):
1. 保存 refresh 前后的 prevGroups/prevStations 快照,只有 name/color/description 或站台列表真的变了(groupListChanged/ stationListChanged)才重绘列表和快选,避免心跳、lastSeen、tokenHolder 这些纯瞬时字段反复重绘
2. 区分 silent (被动自动刷) vs 非 silent (用户手动点刷新):
- 非 silent:保持原语义,完全用服务器值覆盖所有本地编辑(用户主动点刷新就是要回到当前后台状态)
- silent:
* 只在 metaChanged 时同步 name/description/color(并且不抢焦点的 input,避免正在打字被重置)
* 只在 membersChanged(后台 stationNames 真的跟上次不一样)时才同步成员到 detailTempMembers,否则保持用户未保存的成员编辑不变;同时清理 selected 中已不存在的名字
* renderDualLists 仅在 membersChanged || stationListChanged || metaChanged 才调用,其余时间完全不重绘详情
3. 服务器 PUT 端再验证过 stationNames 接口契约没问题:前端 saveDetail 发 {name,color,description,stationNames} 对得上 server PUT handler,保存完 /api/audio-cable-groups 读回 stationNames 正确
APP_VERSION_TEXT bump 到 v1.0.111
This commit is contained in:
@@ -1519,16 +1519,65 @@
|
|||||||
async function reloadAll(options) {
|
async function reloadAll(options) {
|
||||||
const silent = !!(options && options.silent);
|
const silent = !!(options && options.silent);
|
||||||
try {
|
try {
|
||||||
|
const prevGroups = audioCableGroups.slice();
|
||||||
|
const prevStations = stations.slice();
|
||||||
await Promise.all([loadGroups(), loadStations()]);
|
await Promise.all([loadGroups(), loadStations()]);
|
||||||
renderGroupList();
|
const groupListChanged = JSON.stringify(prevGroups.map(g => ({ id: g.id, name: g.name, color: g.color, description: g.description })))
|
||||||
renderQuickSelects();
|
!== JSON.stringify(audioCableGroups.map(g => ({ id: g.id, name: g.name, color: g.color, description: g.description })));
|
||||||
|
const stationListChanged = prevStations.length !== stations.length
|
||||||
|
|| prevStations.some((s, i) => stations[i] && (s.name !== stations[i].name || s.code !== stations[i].code));
|
||||||
|
if (groupListChanged || stationListChanged) {
|
||||||
|
renderGroupList();
|
||||||
|
renderQuickSelects();
|
||||||
|
}
|
||||||
if (currentGroupId) {
|
if (currentGroupId) {
|
||||||
const g = audioCableGroups.find(g => g.id === currentGroupId);
|
const g = audioCableGroups.find(g => g.id === currentGroupId);
|
||||||
if (g) {
|
if (!g) {
|
||||||
detailTempMembers = [...(g.stationNames || [])];
|
currentGroupId = null;
|
||||||
selectedAvailable.clear();
|
backToList();
|
||||||
selectedMembers.clear();
|
} else {
|
||||||
renderDualLists();
|
const pg = prevGroups.find(x => x.id === currentGroupId) || null;
|
||||||
|
const membersChanged = !pg || JSON.stringify(pg.stationNames || []) !== JSON.stringify(g.stationNames || []);
|
||||||
|
const metaChanged = !pg || pg.name !== g.name || pg.color !== g.color || pg.description !== g.description;
|
||||||
|
|
||||||
|
if (silent) {
|
||||||
|
// 被动自动刷新:同步名字/描述/颜色的标题区,只在后台成员真的变了才同步成员(否则保留用户未保存的编辑)
|
||||||
|
const detailNameEl = document.getElementById('detail-name');
|
||||||
|
const detailDescEl = document.getElementById('detail-description');
|
||||||
|
const detailTitleEl = document.getElementById('detail-title');
|
||||||
|
if (metaChanged) {
|
||||||
|
if (detailTitleEl && detailNameEl && !detailNameEl.matches(':focus')) {
|
||||||
|
detailNameEl.value = g.name || '';
|
||||||
|
detailTitleEl.textContent = g.name || '';
|
||||||
|
}
|
||||||
|
if (detailDescEl && !detailDescEl.matches(':focus')) {
|
||||||
|
detailDescEl.value = g.description || '';
|
||||||
|
}
|
||||||
|
if (detailSelectedColor !== (g.color || PRESET_COLORS[0])) {
|
||||||
|
detailSelectedColor = g.color || PRESET_COLORS[0];
|
||||||
|
document.getElementById('detail-color').style.background = detailSelectedColor;
|
||||||
|
_rebuildDetailColorPicker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (membersChanged) {
|
||||||
|
// 后台分组成员已经变了(例如被别的端删除/改了分组)—— 同步掉成员,避免用户编辑的是已经不存在的状态
|
||||||
|
detailTempMembers = [...(g.stationNames || [])];
|
||||||
|
// 清理选择集中已经不存在的名字,保留仍然存在的
|
||||||
|
for (const n of Array.from(selectedAvailable)) {
|
||||||
|
if (!stations.some(s => s.name === n)) selectedAvailable.delete(n);
|
||||||
|
}
|
||||||
|
for (const n of Array.from(selectedMembers)) {
|
||||||
|
if (!detailTempMembers.includes(n)) selectedMembers.delete(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (membersChanged || stationListChanged || metaChanged) renderDualLists();
|
||||||
|
} else {
|
||||||
|
// 用户主动点刷新:全同步(包括成员)
|
||||||
|
detailTempMembers = [...(g.stationNames || [])];
|
||||||
|
selectedAvailable.clear();
|
||||||
|
selectedMembers.clear();
|
||||||
|
renderDualLists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!silent) showToast('成功', '数据已刷新', 'success');
|
if (!silent) showToast('成功', '数据已刷新', 'success');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
-- 1. Configuration & Constants
|
-- 1. Configuration & Constants
|
||||||
local APP_VERSION_TEXT = "v1.0.110"
|
local APP_VERSION_TEXT = "v1.0.111"
|
||||||
|
|
||||||
local CONFIG = {
|
local CONFIG = {
|
||||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||||
|
|||||||
Reference in New Issue
Block a user