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.

85 lines
5.5 KiB

1 day ago
function edgeChip(e, cls) {
return '<span class="edge-chip ' + cls + '">' + esc(cname(e.parent)) + ' → ' + esc(cname(e.child)) + '</span>';
}
function renderGraphState() {
const delSet = new Set(G.pendingDeletes);
const delEdges = G.edges.filter(e => delSet.has(e.id));
$('graphStateBox').innerHTML =
'<div>committed 手动边(' + G.edges.length + '):' + (G.edges.length ? G.edges.map(e => edgeChip(e, 'manual')).join('') : '无') + '</div>' +
'<div>pending 新增(' + G.pendingAdds.length + '):' + (G.pendingAdds.length ? G.pendingAdds.map(a => edgeChip({ parent: a.parent, child: a.child }, 'pending')).join('') : '无') + '</div>' +
'<div>pending 删除(' + G.pendingDeletes.length + '):' + (delEdges.length ? delEdges.map(e => edgeChip(e, 'veto')).join('') : '无') + '</div>' +
'<div>公司派生边(等级10→公司,' + derivedCompanyEdges().length + '):' + (derivedCompanyEdges().length ? derivedCompanyEdges().map(e => edgeChip(e, 'company')).join('') : '无') + '</div>' +
'<div>图谱乐观锁版本:' + G.version + (G.editing ? '(编辑中)' : '') + '</div>';
}
function renderGraphLogs() {
const box = $('graphLogBox');
if (!box) return;
box.innerHTML = G.logs.length
? G.logs.slice().reverse().map(l => '<div style="border-left:2px solid #e5e7eb;padding:2px 0 2px 10px;margin-bottom:6px"><span class="hint">' + l.time + '</span> [' + l.type + '] ' + esc(l.text) + '</div>').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 =>
'<optgroup label="' + esc(g.cat) + '">' +
g.jobs.map(j => '<option value="' + esc(j.code) + '"' + (cur === j.code ? ' selected' : '') + '>' + esc(j.name) + '</option>').join('') +
'</optgroup>').join('');
}
function ynSel(c, f) {
const v = Number(c[f]);
return '<select data-f="' + f + '" data-id="' + c.id + '"><option value="0"' + (!v ? ' selected' : '') + '>否</option><option value="1"' + (v ? ' selected' : '') + '>是</option></select>';
}
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 ? '<span class="undo-link" data-op="undo" data-id="' + cid + '">撤销修改</span>' : '')
: '<span class="opt-link" data-op="edit" data-id="' + cid + '">编辑</span>' +
'<span class="opt-link" data-op="check" data-id="' + cid + '" data-phone="' + esc(c.phone || '') + '">查重</span>' +
'<span class="opt-link danger" data-op="del" data-id="' + cid + '">删除</span>';
const show = revealed[cid] || C.revealAll;
const phoneCell = show
? '<span class="phone-plain">' + esc(c.phone || '--') + '</span>'
: '<span class="phone-masked" data-op="reveal" data-id="' + cid + '">' + maskPhone(c.phone) + '</span>';
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 '<tr class="' + (dirty ? 'dirty-row' : '') + '" data-id="' + cid + '">' +
'<td><input type="checkbox" class="row-chk" data-id="' + cid + '"' + (C.checked[cid] ? ' checked' : '') + '></td>' +
'<td>' + (i + 1) + '</td>' + cells.map(t => '<td>' + t + '</td>').join('') +
'<td>' + opBtns + '</td></tr>';
}).join('') : '<tr><td colspan="10" class="empty">无联系人</td></tr>';
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 [
'<input type="text" data-f="name" data-id="' + c.id + '" value="' + esc(c.name || '') + '">',
'<select data-f="jobFull" data-id="' + c.id + '">' + jobSelectHtml(c) + '</select>',
'<input type="text" data-f="phone" data-id="' + c.id + '" value="' + esc(c.phone || '') + '">',
ynSel(c, 'isKeyContact'),
'<span class="hint">' + (Number(c.isInternal) ? '是' : '否') + '(只读)</span>',
'<select data-f="source" data-id="' + c.id + '"><option value="">--</option>' + SOURCE_DICT.map(s => '<option value="' + esc(s.code) + '"' + (c.source === s.code ? ' selected' : '') + '>' + esc(s.name) + '</option>').join('') + '</select>',
'<span class="hint" style="white-space:normal">' + esc(c.giftRemark || '--') + '(只读)</span>'
];
}