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.
42 lines
3.2 KiB
42 lines
3.2 KiB
/* ================= 联系人图谱/页签增强(票02 收编自 customer-contact-graph demo 修订⑧版) ================= */
|
|
/* 修订⑧冻结口径:等级 1-10(10=顶层)、只有手动边(无自动生成/否决)、公司边 = level==10 前端派生视图不可删、
|
|
布局纯由 level 推导零坐标、graph/save 全量快照 + version CAS(67017 冲突退出编辑并重拉)。
|
|
数据分工:列表(ContactDTO 全字段)= 旧端点 /api/customer/contacts;图谱(脱敏+等级+边)= graph/detail。 */
|
|
const COMPANY_ID = '__company__';
|
|
const CARD_W = 150, CARD_H = 104;
|
|
const COMPANY_POS = { x: 1080, y: 184 };
|
|
const ROW_X = 20, ROW_W = 1910;
|
|
const UNREC_X = 1960, UNREC_W = 420, UNREC_Y = 40, UNREC_H = 1320;
|
|
const LV10_Y = 40, LV9_Y = 328, ROW_STEP = 116;
|
|
function lvRowY(lv) { return LV9_Y + (9 - lv) * ROW_STEP; }
|
|
const ROWS = [];
|
|
for (let lv = 10; lv >= 1; lv--) ROWS.push({ lv: lv, y: lv === 10 ? LV10_Y : lvRowY(lv) });
|
|
/* G = 图谱态;C = 列表态(列表数据源 = 旧端点 ContactDTO 全字段) */
|
|
const G = { contacts: [], edges: [], version: 0, editing: false, pendingAdds: [], pendingDeletes: [], editSnapshot: null, cardPos: {}, cycleHighlight: null, logs: [], view: { zoom: 0.62, panX: 0, panY: 0 }, fitOnce: false, curView: 'list' };
|
|
const C = { list: [], query: { keyword: '', internal: '' }, checked: {}, editing: false, editSnapshot: {}, dirtyRows: {}, revealAll: false };
|
|
let revealed = {}; // id -> true(单点临时明文;列表/图谱共享)
|
|
let JOB_DICT = []; // [{cat, code, jobs:[{name, code, level}]}]
|
|
let SOURCE_DICT = []; // [{code, name}]
|
|
let qaRows = [];
|
|
const wrap = $('graphWrap'), canvasInner = $('canvasInner'), edgeSvg = $('edgeSvg');
|
|
const custName = () => D.cname || ('客户 ' + (D.id || ''));
|
|
const opName = () => { const m = ME(); return (m && (m.userName || m.nickName)) || '当前用户'; };
|
|
function nowStr() { return new Date().toTimeString().slice(0, 8); }
|
|
function addLog(type, text) { G.logs.push({ time: nowStr(), type: type, text: text }); renderGraphLogs(); }
|
|
function maskPhone(p) { return p ? p.slice(0, 3) + '****' + p.slice(-4) : '--'; }
|
|
function byId(id) { return G.contacts.find(c => c.id === id); } // 图谱节点
|
|
function cById(id) { return C.list.find(c => String(c.id) === String(id)); } // 列表行
|
|
function cname(id) { return id === COMPANY_ID ? custName() : (byId(id) || { name: id }).name; }
|
|
function jobCodeOfName(name) { for (const g of JOB_DICT) { const j = (g.jobs || []).find(x => x.name === name); if (j) return j.code; } return ''; }
|
|
function sourceName(code) { const s = SOURCE_DICT.find(x => x.code === code); return s ? s.name : (code || '--'); }
|
|
let dictLoaded = false;
|
|
async function ensureDicts() {
|
|
if (dictLoaded) return;
|
|
const pair = await Promise.all([
|
|
api('/api/dict/item/tree?groupCode=job_title').catch(() => []),
|
|
api('/api/dict/item/enabled-list?groupCode=contact_source').catch(() => [])
|
|
]);
|
|
JOB_DICT = (pair[0] || []).map(g => ({ cat: g.name, code: g.code, jobs: (g.children || []).map(j => ({ name: j.name, code: j.code, level: j.level == null ? null : Number(j.level) })) }));
|
|
SOURCE_DICT = (pair[1] || []).map(s => ({ code: s.code, name: s.name }));
|
|
dictLoaded = true;
|
|
}
|