/* ---- 快速添加(quickAdd 多行/查重/确认知悉;部分成功:合法行落库非法行明细) ---- */ function emptyQaRow() { return { name: '', cat: '', job: '', phone: '', key: false, source: '', ack: false }; } async function openQa() { await ensureDicts(); qaRows = [emptyQaRow(), emptyQaRow()]; $('qaCustName').textContent = custName(); renderQa(); $('qaOverlay').classList.add('show'); } function qaClose() { $('qaOverlay').classList.remove('show'); } function qaAddRow() { qaRows.push(emptyQaRow()); renderQa(); } function renderQa() { $('qaTbody').innerHTML = qaRows.map((r, i) => { const catOpts = '' + JOB_DICT.map(g => '').join(''); const grp = JOB_DICT.find(g => g.code === r.cat); const jobOpts = grp ? grp.jobs.map(j => '').join('') : ''; return '' + '' + (i + 1) + '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join(''); } function qaRowIsEmpty(r) { return !r.name && !r.cat && !r.job && !r.phone && !r.source && !r.key; } function qaDupReason(r, batch) { if (r.phone) { if (C.list.some(c => c.phone && maskPhone(r.phone) === c.phone)) return '疑似重复:手机号已存在(' + maskPhone(r.phone) + ')'; if (batch && batch.some(o => o !== r && o.phone && o.phone === r.phone)) return '疑似重复:与本批另一行手机号重复'; } else if (r.name && r.job) { const grp = JOB_DICT.find(g => g.code === r.cat); const jobName = grp && (grp.jobs.find(j => j.code === r.job) || {}).name; if (jobName && C.list.some(c => c.name === r.name.trim() && c.jobTitleName === jobName)) return '疑似重复:同客户已有「' + r.name.trim() + ' + ' + jobName + '」'; } return null; } function qaTableChange(e) { const tr = e.target.closest('tr'); if (!tr || tr.dataset.qa == null) return; const r = qaRows[Number(tr.dataset.qa)]; if (!r || !e.target.dataset.qf) return; const f = e.target.dataset.qf; if (e.target.type === 'checkbox') r[f] = e.target.checked; else r[f] = e.target.value; if (f === 'cat') { r.job = ''; renderQa(); } // 类别 → 职务二级联动 } function qaTableInput(e) { const tr = e.target.closest('tr'); if (!tr || tr.dataset.qa == null) return; const r = qaRows[Number(tr.dataset.qa)]; if (r && e.target.dataset.qf && e.target.type !== 'checkbox') r[e.target.dataset.qf] = e.target.value; } async function qaSubmit() { const rows = qaRows.filter(r => !qaRowIsEmpty(r)); if (!rows.length) { toast('请至少填写一行内容(空行不提交)', 'err'); return; } const errs = []; let firstErrIdx = -1; rows.forEach(r => { const idx = qaRows.indexOf(r); let msg = null; if (!r.name.trim()) msg = '姓名必填'; else if (!r.cat || !r.job) msg = '职务必填(先选类别再选职务)'; else if (r.phone && !/^1\d{10}$/.test(r.phone)) msg = '电话须为 11 位手机号'; if (!msg) { const dup = qaDupReason(r, rows); if (dup && !r.ack) msg = dup + ',请勾选「确认知悉」'; } if (msg) { errs.push({ idx: idx, msg: msg }); if (firstErrIdx < 0) firstErrIdx = idx; } }); $('qaTbody').querySelectorAll('tr').forEach(tr => { tr.classList.remove('qa-err', 'qa-dup'); const h = tr.querySelector('.qa-hint'); if (h) h.textContent = ''; }); if (errs.length) { errs.forEach(er => { const tr = $('qaTbody').querySelector('tr[data-qa="' + er.idx + '"]'); if (tr) { tr.classList.add(er.msg.indexOf('疑似重复') >= 0 ? 'qa-dup' : 'qa-err'); const h = tr.querySelector('.qa-hint'); if (h) h.textContent = er.msg; } }); const tr = $('qaTbody').querySelector('tr[data-qa="' + firstErrIdx + '"]'); if (tr) tr.scrollIntoView({ block: 'center' }); toast('提交失败:' + errs.length + ' 行有误(已保留全部内容并定位首错行)', 'err'); return; } const form = new URLSearchParams(); form.set('customerId', D.id); rows.forEach((r, i) => { form.set('rows[' + i + '].name', r.name.trim()); form.set('rows[' + i + '].jobTitleCode', r.job); form.set('rows[' + i + '].phone', r.phone.trim()); form.set('rows[' + i + '].source', r.source); form.set('rows[' + i + '].isKeyContact', r.key ? '1' : '0'); }); try { const ret = await api('/api/customer/contact/quickAdd', { method: 'POST', body: form.toString() }); const failed = (ret && ret.failedRows) || []; if (failed.length) { failed.forEach(f => { const idx = qaRows.indexOf(rows[f.rowIndex]); const tr = $('qaTbody').querySelector('tr[data-qa="' + idx + '"]'); if (tr) { tr.classList.add('qa-err'); const h = tr.querySelector('.qa-hint'); if (h) h.textContent = f.reason; } }); toast(((ret && ret.successCount) || 0) + ' 行成功,' + failed.length + ' 行失败(失败行保留待改,可重提)', 'err'); return; } qaClose(); await Promise.all([loadContacts(), loadGraph()]); layoutGraph(); addLog('数据', '快速添加:' + ((ret && ret.successCount) || rows.length) + ' 位联系人'); toast('成功添加 ' + ((ret && ret.successCount) || rows.length) + ' 位联系人,列表与图谱已刷新', 'ok'); renderContactsAll(); } catch (err) { toast('提交失败(' + err.code + '):' + err.message, 'err'); } }