- 列表全选复选框从工具栏移到表格表头,事件委托处理动态元素 - 重复ASIN Tab 标题加总数(XX 为重复 ASIN 数量) - 重复ASIN 筛选独立:ASIN/店铺/国家/上架时间(开始+结束),与原店铺数据筛选互不影响 - 后端支持 asin/shop_name/country/date_from/date_to 参数: 行级筛选命中 ASIN 后展示完整记录(含其他店铺,跨店语义更直观) - 日期文本归一化(支持 2026年8月19日 上午4:34 中文格式) - 按 ASIN 分组表格展示:ASIN/店铺数/记录条数/品牌/对应店铺/国家/上架时间/操作, 详情展开显示每条记录的店铺/分组/国家/日期/价格/品牌 - 修复 _shop_data_crawl_fetch_result_bytes 兼容 result_id 列别名(生产首个真实调用报 KeyError) - 新增单测:筛选语义、日期归一化、record_count
This commit is contained in:
+115
-25
@@ -1784,7 +1784,7 @@
|
||||
}
|
||||
return '<table>' +
|
||||
'<thead><tr>' +
|
||||
'<th style="width:36px;"></th>' +
|
||||
'<th style="width:36px;"><input type="checkbox" id="shopDataTaskSelectAll"></th>' +
|
||||
'<th style="width:18%;">店铺</th>' +
|
||||
'<th style="width:14%;">分组</th>' +
|
||||
'<th style="width:16%;">国家</th>' +
|
||||
@@ -1870,11 +1870,11 @@
|
||||
params.set('page', String(page || 1));
|
||||
params.set('page_size', String(shopDataDuplicatePageSize));
|
||||
var values = {
|
||||
shop_name: document.getElementById('shopDataTaskFilterShop').value.trim(),
|
||||
group_name: document.getElementById('shopDataTaskFilterGroup').value.trim(),
|
||||
country: document.getElementById('shopDataTaskFilterCountry').value.trim(),
|
||||
created_from: document.getElementById('shopDataTaskFilterFrom').value,
|
||||
created_to: document.getElementById('shopDataTaskFilterTo').value
|
||||
asin: document.getElementById('shopDataDupFilterAsin').value.trim(),
|
||||
shop_name: document.getElementById('shopDataDupFilterShop').value.trim(),
|
||||
country: document.getElementById('shopDataDupFilterCountry').value.trim(),
|
||||
date_from: document.getElementById('shopDataDupFilterDateFrom').value,
|
||||
date_to: document.getElementById('shopDataDupFilterDateTo').value
|
||||
};
|
||||
Object.keys(values).forEach(function (key) {
|
||||
if (values[key]) params.set(key, values[key]);
|
||||
@@ -1924,10 +1924,69 @@
|
||||
function renderShopDataDuplicateList() {
|
||||
var list = document.getElementById('shopDataDuplicateList');
|
||||
if (!shopDataDuplicateItems.length) {
|
||||
list.innerHTML = '<div class="shop-data-empty-hint">暂无重复 ASIN。请在左侧筛选条件后点击"查询"生效范围,再点击"重新分析"。</div>';
|
||||
list.innerHTML = '<div class="shop-data-empty-hint">暂无重复 ASIN。请在筛选条件后点击"查询",或"重新分析"。' +
|
||||
'<span id="dupEmptyHint"></span></div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = shopDataDuplicateItems.map(renderShopDataDuplicateCard).join('');
|
||||
var header = [
|
||||
{ label: 'ASIN', width: '14%' },
|
||||
{ label: '店铺数', width: '8%' },
|
||||
{ label: '记录条数', width: '10%' },
|
||||
{ label: '品牌', width: '12%' },
|
||||
{ label: '对应店铺', width: '22%' },
|
||||
{ label: '国家', width: '14%' },
|
||||
{ label: '上架时间', width: '12%' },
|
||||
{ label: '操作', width: '8%' }
|
||||
];
|
||||
var tbody = shopDataDuplicateItems.map(function (item) {
|
||||
return renderShopDataDuplicateRow(item);
|
||||
}).join('');
|
||||
list.innerHTML = '<table>' + shopDataDuplicateHeader(header) + '<tbody>' + tbody + '</tbody></table>';
|
||||
}
|
||||
|
||||
function renderShopDataDuplicateRow(item) {
|
||||
var occurrences = Array.isArray(item.occurrences) ? item.occurrences : [];
|
||||
var brand = '';
|
||||
var countries = {};
|
||||
var shops = {};
|
||||
occurrences.forEach(function (occ) {
|
||||
if (occ.brand && !brand) brand = occ.brand;
|
||||
(occ.country_codes || []).forEach(function (c) { countries[c] = true; });
|
||||
shops[occ.shop_name || '-'] = true;
|
||||
});
|
||||
var countryLabel = countryListLabel(Object.keys(countries));
|
||||
var shopList = Object.keys(shops).join('、');
|
||||
var dateList = occurrences.map(function (occ) { return occ.date; })
|
||||
.filter(function (d) { return d; }).join('、');
|
||||
return '<tr data-duplicate-asin="' + escapeHtml(item.asin) + '">' +
|
||||
'<td><span class="dup-asin">' + escapeHtml(item.asin) + '</span></td>' +
|
||||
'<td><span class="dup-count">' + (item.shop_count || 1) + ' 家</span></td>' +
|
||||
'<td>' + (item.record_count || occurrences.length) + ' 条</td>' +
|
||||
'<td>' + escapeHtml(brand || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(shopList || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(countryLabel || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(dateList || '-') + '</td>' +
|
||||
'<td><button class="btn btn-sm" type="button" data-dup-toggle>详情</button></td>' +
|
||||
'</tr>' + renderShopDataDuplicateDetailRows(item);
|
||||
}
|
||||
|
||||
function renderShopDataDuplicateDetailRows(item) {
|
||||
var occurrences = Array.isArray(item.occurrences) ? item.occurrences : [];
|
||||
if (!occurrences.length) return '';
|
||||
var rows = occurrences.map(function (occ) {
|
||||
var countries = countryListLabel(occ.country_codes);
|
||||
return '<tr class="dup-detail-row" style="display:none;">' +
|
||||
'<td></td>' +
|
||||
'<td class="dup-shop">' + escapeHtml(occ.shop_name || '-') + '</td>' +
|
||||
'<td class="dup-country">' + escapeHtml(occ.group_name || '-') + '</td>' +
|
||||
'<td class="dup-country">' + escapeHtml(countries) + '</td>' +
|
||||
'<td class="dup-date">' + escapeHtml(occ.date || '-') + '</td>' +
|
||||
'<td class="dup-date">' + escapeHtml(occ.price || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(occ.brand || '-') + '</td>' +
|
||||
'<td></td>' +
|
||||
'</tr>';
|
||||
}).join('');
|
||||
return rows;
|
||||
}
|
||||
|
||||
function loadShopDataDuplicateAsins(page) {
|
||||
@@ -1950,6 +2009,8 @@
|
||||
shopDataDuplicateAnalyzed.resultCount = Number(res.analyzed_result_count) || 0;
|
||||
var totalEl = document.getElementById('shopDataDuplicateTotal');
|
||||
totalEl.textContent = '共 ' + shopDataDuplicateTotal + ' 个重复 ASIN · 已分析 ' + shopDataDuplicateAnalyzed.shopCount + ' 家店铺';
|
||||
var tabCount = document.getElementById('shopDataDuplicateTabCount');
|
||||
if (tabCount) tabCount.textContent = shopDataDuplicateTotal;
|
||||
renderShopDataDuplicateList();
|
||||
renderPagination('shopDataDuplicatePagination', shopDataDuplicateTotal, shopDataDuplicatePage, shopDataDuplicatePageSize, loadShopDataDuplicateAsins);
|
||||
})
|
||||
@@ -2177,24 +2238,32 @@
|
||||
document.getElementById('shopDataSubTabRecords').onclick = function () { switchShopDataSubTab('records'); };
|
||||
document.getElementById('shopDataSubTabDuplicates').onclick = function () { switchShopDataSubTab('duplicates'); };
|
||||
document.getElementById('btnRefreshShopDataDuplicates').onclick = function () { loadShopDataDuplicateAsins(1); };
|
||||
document.getElementById('shopDataTaskSelectAll').onchange = function (event) {
|
||||
shopDataTasks.forEach(function (item) {
|
||||
var resultId = shopDataResultId(item);
|
||||
if (!item.file_ready || !resultId) return;
|
||||
if (event.target.checked) selectedShopDataResultIds.add(resultId);
|
||||
else selectedShopDataResultIds.delete(resultId);
|
||||
});
|
||||
syncShopDataSelectionUi();
|
||||
document.getElementById('btnFilterShopDataDuplicates').onclick = function () { loadShopDataDuplicateAsins(1); };
|
||||
document.getElementById('btnResetShopDataDuplicates').onclick = function () {
|
||||
['shopDataDupFilterAsin', 'shopDataDupFilterShop', 'shopDataDupFilterCountry', 'shopDataDupFilterDateFrom', 'shopDataDupFilterDateTo']
|
||||
.forEach(function (id) { document.getElementById(id).value = ''; });
|
||||
loadShopDataDuplicateAsins(1);
|
||||
};
|
||||
document.getElementById('btnBatchDownloadShopDataTasks').onclick = downloadShopDataTasksZip;
|
||||
// 全选移到表格表头后为动态元素,用事件委托
|
||||
document.getElementById('shopDataTaskGrid').onchange = function (event) {
|
||||
var checkbox = event.target.closest('[data-shop-data-select]');
|
||||
if (!checkbox) return;
|
||||
var resultId = Number(checkbox.dataset.shopDataSelect);
|
||||
if (!resultId) return;
|
||||
if (checkbox.checked) selectedShopDataResultIds.add(resultId);
|
||||
else selectedShopDataResultIds.delete(resultId);
|
||||
syncShopDataSelectionUi();
|
||||
if (checkbox) {
|
||||
var resultId = Number(checkbox.dataset.shopDataSelect);
|
||||
if (!resultId) return;
|
||||
if (checkbox.checked) selectedShopDataResultIds.add(resultId);
|
||||
else selectedShopDataResultIds.delete(resultId);
|
||||
syncShopDataSelectionUi();
|
||||
return;
|
||||
}
|
||||
if (event.target.closest('#shopDataTaskSelectAll')) {
|
||||
shopDataTasks.forEach(function (item) {
|
||||
var sid = shopDataResultId(item);
|
||||
if (!item.file_ready || !sid) return;
|
||||
if (event.target.checked) selectedShopDataResultIds.add(sid);
|
||||
else selectedShopDataResultIds.delete(sid);
|
||||
});
|
||||
syncShopDataSelectionUi();
|
||||
}
|
||||
};
|
||||
document.getElementById('shopDataTaskGrid').onclick = function (event) {
|
||||
var downloadButton = event.target.closest('[data-shop-data-download]');
|
||||
@@ -2209,6 +2278,29 @@
|
||||
deleteShopDataTask(deleteItem);
|
||||
}
|
||||
};
|
||||
document.getElementById('btnBatchDownloadShopDataTasks').onclick = downloadShopDataTasksZip;
|
||||
document.getElementById('shopDataDuplicateList').onclick = function (event) {
|
||||
var toggle = event.target.closest('[data-dup-toggle]');
|
||||
if (!toggle) return;
|
||||
var row = toggle.closest('tr');
|
||||
var allDetailRows = document.querySelectorAll('.dup-detail-row');
|
||||
// 若当前 ASIN 的详情行已展开,则本次点击收起
|
||||
var cur = row ? row.nextElementSibling : null;
|
||||
var wasExpanded = false;
|
||||
while (cur && cur.classList && cur.classList.contains('dup-detail-row')) {
|
||||
if (cur.style.display !== 'none') { wasExpanded = true; break; }
|
||||
cur = cur.nextElementSibling;
|
||||
}
|
||||
allDetailRows.forEach(function (dr) { dr.style.display = 'none'; });
|
||||
if (row && !wasExpanded) {
|
||||
// 展开当前 ASIN 的详情行(紧随其后)
|
||||
cur = row.nextElementSibling;
|
||||
while (cur && cur.classList && cur.classList.contains('dup-detail-row')) {
|
||||
cur.style.display = '';
|
||||
cur = cur.nextElementSibling;
|
||||
}
|
||||
}
|
||||
};
|
||||
document.getElementById('btnOpenShopDataTaskPermissions').onclick = openShopDataTaskPermissions;
|
||||
document.getElementById('btnCloseShopDataTaskPermissions').onclick = closeShopDataTaskPermissions;
|
||||
document.getElementById('btnCancelShopDataTaskPermissions').onclick = closeShopDataTaskPermissions;
|
||||
@@ -6408,8 +6500,6 @@
|
||||
currentUserRole = item.role || currentUserRole;
|
||||
currentUserUsername = item.username || currentUserUsername;
|
||||
nameEl.textContent = item.username || '管理员';
|
||||
var avatarEl = document.getElementById('adminUserAvatar');
|
||||
if (avatarEl) avatarEl.textContent = (item.username || '管').charAt(0).toUpperCase();
|
||||
if (item.role) {
|
||||
roleEl.textContent = item.role === 'super_admin'
|
||||
? '超级管理员'
|
||||
|
||||
Reference in New Issue
Block a user