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.
116 lines
5.1 KiB
116 lines
5.1 KiB
|
1 day ago
|
/* ---- 页内批量编辑(batchEdit:原子批任一失败零落库;字段 null=不改;脱敏形态(含*)提交=不改) ---- */
|
||
|
|
function enterListEdit() {
|
||
|
|
C.editing = true; C.dirtyRows = {};
|
||
|
|
C.editSnapshot = {};
|
||
|
|
C.list.forEach(c => { C.editSnapshot[c.id] = JSON.parse(JSON.stringify(c)); });
|
||
|
|
renderContactsList();
|
||
|
|
toast('已进入页内编辑:可修改姓名/职务/电话/关键/来源;保存走 batchEdit(原子批)');
|
||
|
|
}
|
||
|
|
function listDirtyCount() { return Object.keys(C.dirtyRows).length; }
|
||
|
|
function doExitListEdit() { C.editing = false; C.dirtyRows = {}; renderContactsList(); }
|
||
|
|
function resetListEditSilent() {
|
||
|
|
Object.keys(C.editSnapshot).forEach(id => {
|
||
|
|
const c = cById(id);
|
||
|
|
if (c) Object.assign(c, C.editSnapshot[id]);
|
||
|
|
});
|
||
|
|
C.dirtyRows = {};
|
||
|
|
}
|
||
|
|
function btnResetListClick() {
|
||
|
|
gConfirm('放弃修改', '确定放弃本次全部修改吗?数据将恢复到进入编辑模式时的状态。', () => {
|
||
|
|
resetListEditSilent(); doExitListEdit(); toast('已放弃未保存修改');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function editListToggle() { if (C.editing) exitListEditAttempt(); else enterListEdit(); }
|
||
|
|
function exitListEditAttempt(after) {
|
||
|
|
if (!listDirtyCount()) { doExitListEdit(); if (after) after(); return; }
|
||
|
|
gUnsavedDlg({
|
||
|
|
discard: () => { resetListEditSilent(); doExitListEdit(); if (after) after(); toast('已放弃未保存修改'); },
|
||
|
|
save: () => { saveListAll().then(ok => { if (ok) { doExitListEdit(); if (after) after(); } }); }
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/* 编辑态单元格 → 数据 + dirty 标记(input 不重渲染以保焦点)。
|
||
|
|
修复原型潜伏 bug:职务下拉 value=职务 code,须按「类别下的职务 code」反查(原型按类别 code 反查恒 miss →
|
||
|
|
页内改职务静默失效)。D1:职务变更 → 档位字典 level 跟随重算(levelSource=1 由服务端落库)。 */
|
||
|
|
function applyCellEdit(input, isInput) {
|
||
|
|
const id = input.dataset.id, f = input.dataset.f, c = cById(id);
|
||
|
|
if (!c) return;
|
||
|
|
const v = input.value;
|
||
|
|
if (f === 'jobFull') {
|
||
|
|
const grp = JOB_DICT.find(g => (g.jobs || []).some(j => j.code === v));
|
||
|
|
const job = grp && grp.jobs.find(j => j.code === v);
|
||
|
|
if (grp && job) {
|
||
|
|
c._catCode = grp.code; c._jobCode = v;
|
||
|
|
c.jobTitleName = job.name; c.jobTitleCategory = grp.cat; c.jobTitleLevel = job.level;
|
||
|
|
}
|
||
|
|
} else if (f === 'isKeyContact') {
|
||
|
|
c[f] = Number(v);
|
||
|
|
} else {
|
||
|
|
c[f] = v;
|
||
|
|
}
|
||
|
|
C.dirtyRows[id] = true;
|
||
|
|
const tr = input.closest('tr');
|
||
|
|
tr.classList.add('dirty-row');
|
||
|
|
if (!tr.querySelector('.undo-link')) {
|
||
|
|
const td = tr.lastElementChild;
|
||
|
|
const s = document.createElement('span');
|
||
|
|
s.className = 'undo-link'; s.dataset.op = 'undo'; s.dataset.id = id; s.textContent = '撤销修改';
|
||
|
|
td.appendChild(s);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function undoRow(id) {
|
||
|
|
const snap = C.editSnapshot[id], c = cById(id);
|
||
|
|
if (snap && c) Object.assign(c, snap);
|
||
|
|
delete C.dirtyRows[id];
|
||
|
|
renderContactsList();
|
||
|
|
toast('已撤销该行修改');
|
||
|
|
}
|
||
|
|
async function saveListAll() {
|
||
|
|
const dirtyIds = Object.keys(C.dirtyRows);
|
||
|
|
if (!dirtyIds.length) { toast('无修改行'); return true; }
|
||
|
|
let firstErr = null;
|
||
|
|
for (const id of dirtyIds) {
|
||
|
|
const c = cById(id);
|
||
|
|
if (!c) continue;
|
||
|
|
let msg = null;
|
||
|
|
if (!c.name || !c.name.trim()) msg = '姓名必填';
|
||
|
|
else if (!(c._jobCode || jobCodeOfName(c.jobTitleName))) msg = '职务必填';
|
||
|
|
else if (c.phone && c.phone.indexOf('*') < 0 && !/^1\d{10}$/.test(c.phone)) msg = '电话须为 11 位手机号(脱敏回显=未修改可放行)';
|
||
|
|
if (msg && !firstErr) firstErr = { id: id, msg: msg };
|
||
|
|
}
|
||
|
|
if (firstErr) {
|
||
|
|
renderContactsList();
|
||
|
|
const tr = $('contactTbody').querySelector('tr[data-id="' + firstErr.id + '"]');
|
||
|
|
if (tr) { tr.classList.add('err-row'); tr.scrollIntoView({ block: 'center' }); }
|
||
|
|
const c = cById(firstErr.id);
|
||
|
|
toast('校验失败:' + (c ? c.name + ' — ' : '') + firstErr.msg + '(已定位首个错误行,不提交任何行)', 'err');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const form = new URLSearchParams();
|
||
|
|
dirtyIds.forEach((id, i) => {
|
||
|
|
const c = cById(id);
|
||
|
|
form.set('rows[' + i + '].id', c.id);
|
||
|
|
form.set('rows[' + i + '].name', c.name.trim());
|
||
|
|
form.set('rows[' + i + '].jobTitleCode', c._jobCode || jobCodeOfName(c.jobTitleName));
|
||
|
|
form.set('rows[' + i + '].phone', c.phone || '');
|
||
|
|
form.set('rows[' + i + '].source', c.source || '');
|
||
|
|
form.set('rows[' + i + '].isKeyContact', Number(c.isKeyContact) ? '1' : '0');
|
||
|
|
});
|
||
|
|
try {
|
||
|
|
const ret = await api('/api/customer/contact/batchEdit', { method: 'POST', body: form.toString() });
|
||
|
|
if (ret && ret.failedRows && ret.failedRows.length) {
|
||
|
|
const first = ret.failedRows[0];
|
||
|
|
const c = cById(dirtyIds[first.rowIndex]);
|
||
|
|
toast('保存失败(原子批零落库):' + (c ? c.name + ' — ' : '') + (first.reason || ''), 'err');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
await Promise.all([loadContacts(), loadGraph()]);
|
||
|
|
C.dirtyRows = {};
|
||
|
|
addLog('数据', '页内编辑保存全部:共 ' + dirtyIds.length + ' 行(batchEdit 原子批)');
|
||
|
|
toast('保存全部成功', 'ok');
|
||
|
|
layoutGraph(); renderContactsAll();
|
||
|
|
return true;
|
||
|
|
} catch (err) {
|
||
|
|
toast('保存失败(' + err.code + '):' + err.message, 'err');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|