// 部门匹配独立规则库,不对业务数据执行回写。
var departmentMatchingState = {rows: [], kind: 'platform', page: 1, search: ''};
var departmentMatchingKinds = {platform: '平台层规则', brand: '品牌层规则', nonbrand: '非品牌清单', shop: '店铺归属对照表'};
var departmentMatchingFields = {
platform: [['department', '归属部门(必填)'], ['shop_keywords', '店铺关键词(逗号分隔)'], ['source_keywords', '空店铺来源(逗号分隔,精确匹配)']],
brand: [['keyword', '品牌关键词(必填)'], ['department', '默认归属'], ['mode', '匹配方式(必填)'], ['pdd_department', '拼多多归属'], ['overseas_department', '京东海外/国际归属'], ['notes', '判定说明']],
nonbrand: [['name', '名称(必填)'], ['reason', '为什么不是品牌'], ['guidance', '判定说明']],
shop: [['shop', '店铺(必填)'], ['department', '预期归属(必填)'], ['layer', '参考命中层级']]
};
var departmentMatchingModes = {fixed: '固定归属(全平台)', split: '按平台分配归属', pdd_only: '拼多多专属,其他回落平台层'};
async function departmentMatchingRequest(path, data) {
var response = await fetch('/api/department-matching/' + path, data === undefined ? {} : {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)
});
var result = await response.json();
if (!response.ok || result.code !== 0) throw new Error(result.msg || '请求失败');
return result;
}
async function loadDepartmentMatchingPage(container) {
container.innerHTML = `
非品牌清单用于解释排除项;归属通过平台规则判定。平台层先比较关键词长度,长度相同时优先级数字越小越优先。
正在加载…
`;
try {
var result = await departmentMatchingRequest('records');
departmentMatchingState.rows = result.data;
renderDepartmentMatching();
} catch (err) { document.getElementById('dmTable').textContent = err.message; }
}
function renderDepartmentMatching() {
var state = departmentMatchingState;
var tabs = document.getElementById('dmTabs');
if (!tabs) return;
tabs.innerHTML = Object.keys(departmentMatchingKinds).map(function(kind) {
return '';
}).join('');
document.getElementById('dmCreate').style.display = window._hasPagePerm('department_matching', 'create') ? '' : 'none';
document.getElementById('dmSearch').value = state.search;
var rows = state.rows.filter(function(row) {
return row.kind === state.kind && (!state.search || departmentMatchingFields[row.kind].map(function(f){return row.payload[f[0]] || '';}).join(' ').toLowerCase().includes(state.search.toLowerCase()));
});
var pages = Math.max(1, Math.ceil(rows.length / 20));
state.page = Math.min(Math.max(state.page, 1), pages);
var fields = departmentMatchingFields[state.kind];
var canEdit = window._hasPagePerm('department_matching', 'edit');
var canDelete = window._hasPagePerm('department_matching', 'delete');
var rule = state.kind === 'platform' || state.kind === 'brand';
var showPriority = state.kind === 'platform';
var heads = '序号 | ' + fields.map(function(f){return '' + escHtml(f[1].replace('(必填)','')) + ' | ';}).join('') + (showPriority ? '优先级 | ' : '') + (rule ? '状态 | ' : '') + (canEdit || canDelete ? '操作 | ' : '');
var body = rows.slice((state.page - 1) * 20, state.page * 20).map(function(row, i) {
return '| ' + ((state.page - 1) * 20 + i + 1) + ' | ' + fields.map(function(f) {
var value = row.payload[f[0]] || '';
if (f[0] === 'mode') value = departmentMatchingModes[value] || value;
return '' + escHtml(String(value)) + ' | ';
}).join('') + (showPriority ? '' + row.priority + ' | ' : '') + (rule ? '' + (row.enabled ? '启用' : '停用') + ' | ' : '') + (canEdit || canDelete ? '' +
(canEdit ? ' ' : '') +
(canDelete ? '' : '') + ' | ' : '') + '
';
}).join('');
document.getElementById('dmTable').innerHTML = '' + heads + '
' + (body || '| 暂无记录 |
') + '
';
document.getElementById('dmPager').innerHTML = '共 ' + rows.length + ' 条,每页20条' + state.page + ' / ' + pages + '';
}
function selectDepartmentMatchingTab(kind) { departmentMatchingState.kind=kind; departmentMatchingState.page=1; renderDepartmentMatching(); }
function searchDepartmentMatching(reset) { departmentMatchingState.search=reset?'':document.getElementById('dmSearch').value.trim(); departmentMatchingState.page=1; renderDepartmentMatching(); }
function pageDepartmentMatching(delta) {
departmentMatchingState.page = delta ? departmentMatchingState.page + delta : (parseInt(document.getElementById('dmJump').value,10) || 1);
renderDepartmentMatching();
}
function editDepartmentMatching(id) {
var row = departmentMatchingState.rows.find(function(r){return r.id === id;}) || {id:0, kind:departmentMatchingState.kind, payload:{}, priority:100, enabled:1};
document.getElementById('dmEditModal')?.remove();
var modal = document.createElement('div'); modal.id='dmEditModal'; modal.className='modal-overlay'; modal.style.cssText='display:flex;z-index:15000';
var fields = departmentMatchingFields[row.kind].map(function(f) {
var value = row.payload[f[0]] || '';
var control;
if (f[0] === 'mode') control = '';
else control = '';
return '' + control + '
';
}).join('');
var rule = row.kind === 'brand' || row.kind === 'platform';
modal.innerHTML = '';
if (row.kind === 'brand') modal.querySelector('#dmPriority').parentElement.remove();
document.body.appendChild(modal);
document.getElementById('dmEditForm').onsubmit = async function(event) {
event.preventDefault();
var payload=Object.assign({},row.payload); modal.querySelectorAll('[data-dm-field]').forEach(function(el){payload[el.dataset.dmField]=el.value;});
var button=document.getElementById('dmSave'); button.disabled=true;
try {
await departmentMatchingRequest('save', {id:row.id, kind:row.kind, payload:payload, priority:row.kind==='platform'?Number(document.getElementById('dmPriority').value):row.priority, enabled:rule?Number(document.getElementById('dmEnabled').value):1});
departmentMatchingState.rows=(await departmentMatchingRequest('records')).data;
modal.remove(); renderDepartmentMatching();
} catch(err) { document.getElementById('dmEditError').textContent=err.message; button.disabled=false; }
};
}
async function deleteDepartmentMatching(id) {
if (!confirm('确认删除此记录?删除规则会影响后续匹配测试结果。')) return;
try { await departmentMatchingRequest('delete',{id:id}); departmentMatchingState.rows=(await departmentMatchingRequest('records')).data; renderDepartmentMatching(); }
catch(err) { alert(err.message); }
}
async function testDepartmentMatching() {
var el=document.getElementById('dmResult');
try {
var data=(await departmentMatchingRequest('test',{shop:document.getElementById('dmShop').value, source:document.getElementById('dmSource').value})).data;
el.textContent='归属:' + (data.department || '未匹配') + ' | ' + data.layer + ' | ' + data.reason;
} catch(err) { el.textContent=err.message; }
}
async function checkDepartmentMatching() {
var el=document.getElementById('dmResult'); el.textContent='正在核验…';
try {
var data=(await departmentMatchingRequest('check',{})).data;
el.textContent='核验完成:' + data.passed + ' / ' + data.total + ' 条归属一致。';
if (data.mismatches.length) el.innerHTML += '| 店铺 | 预期归属 | 实际归属 | 原因 |
' + data.mismatches.map(function(r){return '| ' + escHtml(r.shop) + ' | ' + escHtml(r.expected) + ' | ' + escHtml(r.actual || '未匹配') + ' | ' + escHtml(r.reason) + ' |
';}).join('') + '
';
} catch(err) { el.textContent=err.message; }
}