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
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' };
|
|
}
|