You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
5.1 KiB

1 day ago
/* ---- 图谱编辑模式(进入=快照基准;保存成功后基准刷新为已保存状态) ---- */
function snapshotGraph() {
return { edges: JSON.parse(JSON.stringify(G.edges)), pos: JSON.parse(JSON.stringify(G.cardPos)),
version: G.version, levels: Object.fromEntries(G.contacts.map(c => [c.id, c.level])) };
}
function paintGraphEditButtons() {
$('btnGraphEdit').textContent = G.editing ? '退出编辑' : '编辑图谱';
$('btnGraphSave').style.display = G.editing ? '' : 'none';
$('btnGraphReset').style.display = G.editing ? '' : 'none';
$('hintBar').classList.toggle('show', G.editing);
wrap.classList.toggle('editing', G.editing);
}
function enterEdit() {
G.editing = true;
G.pendingAdds = []; G.pendingDeletes = []; G.cycleHighlight = null;
G.editSnapshot = snapshotGraph();
document.body.classList.add('graph-edit');
paintGraphEditButtons();
renderGraph(); renderGraphState();
toast('已进入图谱编辑:拖卡片定级/改级,拖圆点连线,悬停连线删边');
}
function doExitEdit() {
G.editing = false;
G.pendingAdds = []; G.pendingDeletes = []; G.cycleHighlight = null;
document.body.classList.remove('graph-edit');
paintGraphEditButtons();
hideBubble();
renderGraph(); renderGraphState();
}
function restoreLevels() {
const lv = (G.editSnapshot && G.editSnapshot.levels) || {};
G.contacts.forEach(c => { if (c.id in lv) c.level = lv[c.id]; });
}
function discardGraphEdit() {
if (G.editSnapshot) {
G.edges = JSON.parse(JSON.stringify(G.editSnapshot.edges));
G.cardPos = JSON.parse(JSON.stringify(G.editSnapshot.pos));
G.version = G.editSnapshot.version;
restoreLevels();
}
doExitEdit();
toast('已放弃本次修改,恢复到进入编辑时的状态');
}
function graphEditToggle() { if (G.editing) exitEditAttempt(); else enterEdit(); }
function exitEditAttempt(after) {
const dirty = G.pendingAdds.length || G.pendingDeletes.length;
if (!dirty) { doExitEdit(); if (after) after(); return; }
gUnsavedDlg({
discard: () => { discardGraphEdit(); if (after) after(); },
save: () => { saveGraph().then(ok => { if (ok) { doExitEdit(); if (after) after(); } }); }
});
}
/* 全量快照 POST graph/save(版本 CAS):nodes 全员等级终态(含未定级空值=撤级)+ 手动边终态集;
公司边派生不入快照(D8)。67017 冲突 → 退出编辑态并重拉图谱(比原型更稳,原型保留编辑态易叠脏改)。 */
async function saveGraph() {
const list = effectiveEdges();
const cyc = findCycle(list);
if (cyc) {
G.cycleHighlight = cyc;
renderGraph();
toast('检测到循环关系(' + cyc.slice(0, -1).map(cname).join(' → ') + '),已阻止保存,请修改环边(红色高亮)', 'err');
return false;
}
const adds = G.pendingAdds.slice();
const delEdges = G.edges.filter(e => G.pendingDeletes.includes(e.id));
const nextEdges = G.edges.filter(e => !G.pendingDeletes.includes(e.id))
.concat(adds.map(a => ({ id: a.parent + '>' + a.child, parent: a.parent, child: a.child })));
const form = new URLSearchParams();
form.set('customerId', D.id);
form.set('version', String(G.version));
G.contacts.forEach((c, i) => {
form.set('nodes[' + i + '].contactId', c.id);
form.set('nodes[' + i + '].level', c.level == null ? '' : String(c.level));
});
nextEdges.forEach((e, i) => {
form.set('edges[' + i + '].parentId', e.parent);
form.set('edges[' + i + '].childId', e.child);
});
try {
const newVer = await api('/api/customer/contact/graph/save', { method: 'POST', body: form.toString() });
G.version = Number(newVer) || G.version + 1;
G.edges = nextEdges;
adds.forEach(a => addLog('关系', '新增手动连线:' + cname(a.parent) + ' → ' + cname(a.child)));
delEdges.forEach(e => addLog('关系', '删除连线:' + cname(e.parent) + ' → ' + cname(e.child) + '(手动边)'));
G.pendingAdds = []; G.pendingDeletes = []; G.cycleHighlight = null;
G.editSnapshot = snapshotGraph();
toast('保存成功(版本 ' + G.version + '),已记录关系变更日志', 'ok');
renderContactsList(); renderGraph(); renderGraphState();
return true;
} catch (err) {
toast('保存失败(' + err.code + '):' + err.message + (err.code === 67017 ? ',版本已被他人推进,已退出编辑并重拉图谱' : ''), 'err');
if (err.code === 67017) { doExitEdit(); loadGraph().then(() => { layoutGraph(); renderContactsList(); renderGraph(); renderGraphState(); }); }
return false;
}
}
function resetGraphEdit() {
const dirty = G.pendingAdds.length || G.pendingDeletes.length;
const doReset = () => {
if (G.editSnapshot) {
G.edges = JSON.parse(JSON.stringify(G.editSnapshot.edges));
G.cardPos = JSON.parse(JSON.stringify(G.editSnapshot.pos));
G.version = G.editSnapshot.version;
restoreLevels();
}
G.pendingAdds = []; G.pendingDeletes = []; G.cycleHighlight = null;
toast('已重置:恢复到进入编辑时的初始状态');
renderGraph(); renderGraphState();
};
if (dirty) gConfirm('重置', '确定重置图谱布局吗?所有未保存的调整将恢复到初始状态。', doReset);
else doReset();
}