/* ---- 一次性事件绑定(contactsShell 常驻 DOM → init 时绑一次;委托 contactTbody/qaTbody) ---- */ function bindContactsUI() { wrap.addEventListener('mousedown', graphMouseDown); document.addEventListener('mousemove', graphMouseMove); document.addEventListener('mouseup', graphMouseUp); wrap.addEventListener('wheel', graphWheel, { passive: false }); edgeSvg.addEventListener('click', edgeDeleteClick); canvasInner.addEventListener('click', cardPhoneClick); canvasInner.addEventListener('mouseover', cardBubbleOver); canvasInner.addEventListener('mouseout', e => { if (e.target.closest('.gcard')) hideBubble(); }); $('contactTbody').addEventListener('click', function (e) { const op = e.target.closest('[data-op]'); if (!op) return; if (op.dataset.op === 'reveal') { revealOne(op.dataset.id).then(renderContactsAll); return; } if (op.dataset.op === 'undo') { undoRow(op.dataset.id); return; } if (op.dataset.op === 'edit') { dlgContact(op.dataset.id); return; } // 旧 contact/edit 弹窗保留演示 if (op.dataset.op === 'check') { dlgCheckPhone(op.dataset.phone); return; } if (op.dataset.op === 'del') { delContact(op.dataset.id); return; } }); $('contactTbody').addEventListener('change', function (e) { if (e.target.classList.contains('row-chk')) { const k = e.target.dataset.id; if (e.target.checked) C.checked[k] = true; else delete C.checked[k]; renderContactsList(); return; } if (e.target.dataset.f && C.editing) applyCellEdit(e.target); }); $('contactTbody').addEventListener('input', function (e) { if (e.target.dataset.f && C.editing) applyCellEdit(e.target, true); }); $('qaTbody').addEventListener('change', qaTableChange); $('qaTbody').addEventListener('input', qaTableInput); }