/* 定级/撤级统一入口(修订⑦+⑧):有上级边禁调级;有下级边可调但须高于最高下级(公司边不计入);取消等级仍须无任何边 */ function tryLevelDrop(id, target) { const c = byId(id); if (!c) return; const newLv = target.kind === 'unrec' ? null : target.lv; if (newLv === c.level) return; const eff = effectiveEdges(); const parentEdge = eff.find(e => e.child === id); const childEdges = eff.filter(e => e.parent === id && e.child !== COMPANY_ID); if (parentEdge) { toast('「' + c.name + '」有上级连线(' + cname(parentEdge.parent) + '),请先取消上级连线后再调整等级', 'err'); layoutGraph(); renderEdgesOnly(); return; } if (newLv == null) { if (childEdges.length) { toast('「' + c.name + '」存在连线,请先取消所有连线后再取消等级', 'err'); layoutGraph(); renderEdgesOnly(); return; } } else if (childEdges.length) { const maxChild = Math.max.apply(null, childEdges.map(e => byId(e.child).level)); const childNames = childEdges.filter(e => byId(e.child).level === maxChild).map(e => cname(e.child)).join('、'); if (newLv <= maxChild) { toast('「' + c.name + '」最高下级为等级 ' + maxChild + '(' + childNames + '),新等级须高于它', 'err'); layoutGraph(); renderEdgesOnly(); return; } } const from = c.level == null ? '未设置' : c.level; c.level = newLv; delete G.cardPos[id]; sanitizeManualEdges(); layoutGraph(); if (newLv == null) { addLog('数据', '「' + c.name + '」等级已取消(拖回未设置栏,原等级:' + from + ')'); toast('已取消「' + c.name + '」的等级,移入未设置栏'); } else { addLog('数据', '为「' + c.name + '」设定等级 ' + newLv + '(拖入等级格子,原等级:' + from + ')'); toast('已为「' + c.name + '」设定等级 ' + newLv); } renderGraph(); renderGraphState(); } /* 强制按等级归行:等级 10 在公司上方,等级 9..1 在公司下方(9 邻近公司),未定级固定右侧栏 */ function layoutGraph() { canvasInner.style.width = '2400px'; canvasInner.style.height = '1400px'; renderLevelRows(); const top = [], byLv = {}, unrec = []; G.contacts.forEach(c => { if (c.level == null) unrec.push(c); else if (c.level === 10) top.push(c); else (byLv[c.level] = byLv[c.level] || []).push(c); }); const placeRow = (arr, y) => { const L = ROW_X + 100, R = ROW_X + ROW_W; const total = arr.length * CARD_W + Math.max(0, arr.length - 1) * 44; let x = Math.max(L, (L + R - total) / 2); arr.forEach(c => { G.cardPos[c.id] = { x: x, y: y }; x += CARD_W + 44; }); }; placeRow(top, LV10_Y); const lvs = Object.keys(byLv).map(Number).sort((a, b) => b - a); lvs.forEach(lv => placeRow(byLv[lv], lvRowY(lv))); const zone = $('unrecZone'); zone.style.left = UNREC_X + 'px'; zone.style.top = UNREC_Y + 'px'; zone.style.width = UNREC_W + 'px'; zone.style.height = UNREC_H + 'px'; unrec.forEach((c, i) => { G.cardPos[c.id] = { x: UNREC_X + 16 + (i % 2) * 200, y: UNREC_Y + 52 + Math.floor(i / 2) * 130 }; }); } /* 10 个等级格子横条 + 标签(公司行不画格子) */ function renderLevelRows() { canvasInner.querySelectorAll('.lv-row').forEach(el => el.remove()); ROWS.forEach(r => { const el = document.createElement('div'); el.className = 'lv-row'; el.dataset.lv = r.lv; el.style.left = ROW_X + 'px'; el.style.top = r.y + 'px'; el.style.width = ROW_W + 'px'; el.style.height = CARD_H + 'px'; el.innerHTML = '等级 ' + r.lv + (r.lv === 10 ? ' · 顶层' : '') + ''; canvasInner.insertBefore(el, $('unrecZone')); }); }