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.

114 lines
6.2 KiB

1 day ago
/* ---- 快速添加(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 = '<option value="">--选择类别--</option>' + JOB_DICT.map(g => '<option value="' + esc(g.code) + '"' + (r.cat === g.code ? ' selected' : '') + '>' + esc(g.cat) + '</option>').join('');
const grp = JOB_DICT.find(g => g.code === r.cat);
const jobOpts = grp
? grp.jobs.map(j => '<option value="' + esc(j.code) + '"' + (r.job === j.code ? ' selected' : '') + '>' + esc(j.name) + '</option>').join('')
: '<option value="">--先选类别--</option>';
return '<tr data-qa="' + i + '">' +
'<td>' + (i + 1) + '</td>' +
'<td><input type="text" data-qf="name" value="' + esc(r.name) + '"></td>' +
'<td><select data-qf="cat">' + catOpts + '</select></td>' +
'<td><select data-qf="job">' + jobOpts + '</select></td>' +
'<td><input type="text" data-qf="phone" value="' + esc(r.phone) + '" placeholder="选填,11 位手机号"></td>' +
'<td><input type="checkbox" data-qf="key"' + (r.key ? ' checked' : '') + '></td>' +
'<td><select data-qf="source"><option value="">--</option>' + SOURCE_DICT.map(s => '<option value="' + esc(s.code) + '"' + (r.source === s.code ? ' selected' : '') + '>' + esc(s.name) + '</option>').join('') + '</select></td>' +
'<td><input type="checkbox" data-qf="ack"' + (r.ack ? ' checked' : '') + '><span class="qa-hint" data-hint="' + i + '"></span></td>' +
'</tr>';
}).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');
}
}