function edgeChip(e, cls) { return '' + esc(cname(e.parent)) + ' → ' + esc(cname(e.child)) + ''; } function renderGraphState() { const delSet = new Set(G.pendingDeletes); const delEdges = G.edges.filter(e => delSet.has(e.id)); $('graphStateBox').innerHTML = '
committed 手动边(' + G.edges.length + '):' + (G.edges.length ? G.edges.map(e => edgeChip(e, 'manual')).join('') : '无') + '
' + '
pending 新增(' + G.pendingAdds.length + '):' + (G.pendingAdds.length ? G.pendingAdds.map(a => edgeChip({ parent: a.parent, child: a.child }, 'pending')).join('') : '无') + '
' + '
pending 删除(' + G.pendingDeletes.length + '):' + (delEdges.length ? delEdges.map(e => edgeChip(e, 'veto')).join('') : '无') + '
' + '
公司派生边(等级10→公司,' + derivedCompanyEdges().length + '):' + (derivedCompanyEdges().length ? derivedCompanyEdges().map(e => edgeChip(e, 'company')).join('') : '无') + '
' + '
图谱乐观锁版本:' + G.version + (G.editing ? '(编辑中)' : '') + '
'; } function renderGraphLogs() { const box = $('graphLogBox'); if (!box) return; box.innerHTML = G.logs.length ? G.logs.slice().reverse().map(l => '
' + l.time + ' [' + l.type + '] ' + esc(l.text) + '
').join('') : '暂无日志(保存关系 / 查看明文号码后出现在此)'; } /* 列表视图渲染(数据源 = C.list 全字段;脱敏/reveal 与图谱共享 revealed) */ function filteredContacts() { const kw = (C.query.keyword || '').trim(), internal = C.query.internal; return C.list.filter(c => { if (internal !== '' && String(Number(c.isInternal) || 0) !== internal) return false; if (!kw) return true; return (c.name || '').includes(kw) || (c.jobTitleName || '').includes(kw) || (c.phone || '').includes(kw); }); } function jobSelectHtml(c) { const cur = c._jobCode || jobCodeOfName(c.jobTitleName); return JOB_DICT.map(g => '' + g.jobs.map(j => '').join('') + '').join(''); } function ynSel(c, f) { const v = Number(c[f]); return ''; } function renderContactsList() { const tbody = $('contactTbody'); const list = filteredContacts(); $('contactCount').textContent = '共 ' + C.list.length + ' 位联系人'; tbody.innerHTML = list.length ? list.map((c, i) => { const cid = String(c.id); const dirty = !!C.dirtyRows[cid]; const opBtns = C.editing ? (dirty ? '撤销修改' : '') : '编辑' + '查重' + '删除'; const show = revealed[cid] || C.revealAll; const phoneCell = show ? '' + esc(c.phone || '--') + '' : '' + maskPhone(c.phone) + ''; const cells = C.editing ? editRowCells(c) : [ esc(c.name || '--'), esc(c.jobTitleName || '--'), phoneCell, Number(c.isKeyContact) ? '是' : '否', Number(c.isInternal) ? '是' : '否', esc(sourceName(c.source)), esc(c.giftRemark || '--') ]; return '' + '' + '' + (i + 1) + '' + cells.map(t => '' + t + '').join('') + '' + opBtns + ''; }).join('') : '无联系人'; const n = Object.keys(C.checked).filter(k => C.checked[k] && list.some(c => String(c.id) === k)).length; $('selInfo').textContent = n ? '已选择 ' + n + ' 个联系人' : ''; $('chkAll').checked = list.length > 0 && list.every(c => C.checked[String(c.id)]); $('btnEditList').textContent = C.editing ? '退出编辑' : '批量编辑'; $('btnSaveList').style.display = C.editing ? '' : 'none'; $('btnResetList').style.display = C.editing ? '' : 'none'; } /* 编辑态:单元格 → 行内控件(姓名/职务二级/电话/关键/来源;内线/礼品 batchEdit 不提交 → 只读展示) */ function editRowCells(c) { return [ '', '', '', ynSel(c, 'isKeyContact'), '' + (Number(c.isInternal) ? '是' : '否') + '(只读)', '', '' + esc(c.giftRemark || '--') + '(只读)' ]; }