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
114 lines
6.2 KiB
/* ---- 快速添加(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');
|
|
}
|
|
}
|