// 部门匹配独立规则库,不对业务数据执行回写。 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 '';}).join('') + '
店铺预期归属实际归属原因
' + escHtml(r.shop) + '' + escHtml(r.expected) + '' + escHtml(r.actual || '未匹配') + '' + escHtml(r.reason) + '
'; } catch(err) { el.textContent=err.message; } }