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 =>
'').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 || '--') + '(只读)'
];
}