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.
 
 
 
 
 

56 lines
2.8 KiB

/* 层级变化后一致性清理(用户拍板:同级不应有线;方向规则:上级层级必须 > 下级):
committed 手动边与 pending 新增中,两端层级相同或父层级 <= 子层级的 → 移除并记日志(修订⑧:edges 全为手动边)。 */
function sanitizeManualEdges() {
const bad = e => {
const p = byId(e.parent), c = byId(e.child);
return p && c && p.level != null && c.level != null && p.level <= c.level;
};
G.pendingAdds = G.pendingAdds.filter(a => {
if (bad({ parent: a.parent, child: a.child })) {
addLog('关系', '清除未保存连线:' + cname(a.parent) + ' → ' + cname(a.child) + '(层级变化后不再满足上下级关系)');
return false;
}
return true;
});
const removed = G.edges.filter(bad);
if (removed.length) {
G.edges = G.edges.filter(e => !bad(e));
removed.forEach(e => addLog('关系', '删除连线:' + cname(e.parent) + ' → ' + cname(e.child) + '(两端层级变化后不再满足上下级关系,已清除)'));
}
}
/* 保存后有效边集 = edges(去 pendingDeletes) + pendingAdds(全部手动边,无 auto) */
function effectiveEdges() {
const del = new Set(G.pendingDeletes);
const list = G.edges.filter(e => !del.has(e.id)).map(e => ({ id: e.id, parent: e.parent, child: e.child }));
G.pendingAdds.forEach(a => list.push({ id: 'p_' + a.parent + '_' + a.child, parent: a.parent, child: a.child, pending: true }));
return list;
}
/* 修订⑧ D8:公司边 = 等级 10 → 公司 的派生视图(不入 edges/不入 save 快照,不可删;摘线 = 降级) */
function derivedCompanyEdges() {
return G.contacts.filter(c => c.level === 10).map(c => ({ id: 'C>' + c.id, parent: c.id, child: COMPANY_ID, company: true }));
}
function hasPair(list, p, c) { return list.some(e => e.parent === p && e.child === c); }
/* DFS 环检测:返回环节点序列([a,b,c,a])或 null;保存前兜底(连线双闸已挡大部分) */
function findCycle(list) {
const adj = {};
list.forEach(e => (adj[e.parent] = adj[e.parent] || []).push(e.child));
const color = {}, stack = [];
let cycle = null;
function dfs(u) {
color[u] = 1; stack.push(u);
for (const v of (adj[u] || [])) {
if (color[v] === 1) { cycle = stack.slice(stack.indexOf(v)).concat([v]); return true; }
if (!color[v] && dfs(v)) return true;
}
color[u] = 2; stack.pop(); return false;
}
for (const id of Object.keys(adj)) { if (!color[id] && dfs(id)) break; }
return cycle;
}
/* 画布坐标 → 落点区域(unrec 优先于 y 判定) */
function rowAt(x, y) {
if (x >= UNREC_X) return { kind: 'unrec' };
for (const r of ROWS) if (y >= r.y - 10 && y <= r.y + CARD_H + 10) return { kind: 'lv', lv: r.lv, y: r.y };
if (y >= COMPANY_POS.y - 10 && y <= COMPANY_POS.y + CARD_H + 10) return { kind: 'company' };
return { kind: 'none' };
}