/* ---- 数据加载(列表=旧端点 contacts 全字段;图谱=graph/detail;互不吞数据) ---- */ async function loadContacts() { const d = await api('/api/customer/contacts?customerId=' + D.id); C.list = (Array.isArray(d) ? d : ((d && d.content) || [])).map(c => Object.assign({}, c, { _jobCode: '', _catCode: '' })); C.checked = {}; revealed = {}; C.dirtyRows = {}; C.revealAll = false; $('btnRevealAll').textContent = '明文查看'; window._contacts = C.list; // dlgContact/dlgCheckPhone 旧入口兼容 C.list.forEach(c => { c._jobCode = jobCodeOfName(c.jobTitleName); }); } function loadGraph() { return api('/api/customer/contact/graph/detail?customerId=' + D.id).then(applyGraph); } function applyGraph(dto) { G.version = Number(dto.version) || 0; G.contacts = (dto.nodes || []).map(n => ({ id: String(n.contactId), name: n.name || '', cat: n.jobTitleCategory || '', job: n.jobTitleName || '', level: n.level == null ? null : Number(n.level), levelSource: n.levelSource == null ? null : Number(n.levelSource), phone: n.phoneMasked || '', key: Number(n.isKeyContact) || 0, internal: null })); G.edges = (dto.edges || []).map(e => ({ id: String(e.parentId) + '>' + String(e.childId), parent: String(e.parentId), child: String(e.childId) })); G.cardPos = {}; G.pendingAdds = []; G.pendingDeletes = []; G.cycleHighlight = null; G.editing = false; document.body.classList.remove('graph-edit'); paintGraphEditButtons(); } /* 联系人子页签入口(switchSub('contacts') 调;双加载后首渲染) */ async function openContactsTab() { await ensureDicts(); try { await Promise.all([loadContacts(), loadGraph()]); } catch (e) { $('contactTbody').innerHTML = '
' + esc(e.message) + codeSuffix(e) + '
'; return; } layoutGraph(); renderContactsAll(); if (G.curView === 'graph') { if (!G.fitOnce) { fitView(); G.fitOnce = true; } applyTransform(); } } function renderContactsAll() { renderContactsList(); renderGraph(); renderGraphState(); } /* ---- 脱敏明文(GET /reveal 服务端记埋点;双写列表行与图谱节点) ---- */ async function revealOne(id) { if (revealed[id]) return; try { const plain = await api('/api/customer/contact/reveal?id=' + encodeURIComponent(id)); revealed[id] = true; const lc = cById(id); if (lc) lc.phone = plain; const gc = byId(id); if (gc) gc.phone = plain; addLog('埋点', opName() + ' 于 ' + nowStr() + ' 查看明文号码:' + (gc ? gc.name : id) + '(' + maskPhone(plain) + ')'); } catch (err) { toast('明文获取失败(' + err.code + '):' + err.message, 'err'); } } async function toggleRevealAll() { C.revealAll = !C.revealAll; $('btnRevealAll').textContent = C.revealAll ? '隐藏明文' : '明文查看'; if (C.revealAll) { addLog('埋点', opName() + ' 于 ' + nowStr() + ' 批量显示当前过滤结果完整号码(共 ' + filteredContacts().length + ' 位)'); for (const c of filteredContacts()) { if (!revealed[c.id]) await revealOne(c.id); } } renderContactsAll(); } /* ---- 查询(页内过滤;勾选与临时明文重置) ---- */ function queryContacts() { C.query = { keyword: $('qKeyword').value, internal: $('qInternal').value }; C.checked = {}; revealed = {}; C.revealAll = false; $('btnRevealAll').textContent = '明文查看'; renderContactsList(); } function resetContactsContact() { $('qKeyword').value = ''; $('qInternal').value = ''; C.query = { keyword: '', internal: '' }; C.checked = {}; revealed = {}; C.revealAll = false; $('btnRevealAll').textContent = '明文查看'; renderContactsList(); } function chkAllChange(el) { filteredContacts().forEach(c => { const k = String(c.id); if (el.checked) C.checked[k] = true; else delete C.checked[k]; }); renderContactsList(); }