feat: add order status filtering

This commit is contained in:
王源
2026-10-14 17:44:01 +08:00
parent 034ace13b5
commit 1d0b1c54b4
3 changed files with 2457 additions and 10 deletions
+8
View File
@@ -139,6 +139,7 @@ def parse_workbook(source: Path) -> tuple[list[dict[str, str]], dict[str, int]]:
def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: Path) -> dict[str, Any]: def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: Path) -> dict[str, Any]:
status_priority = {"投放中": 0, "暂停": 1, "未标注": 2, "其他/不可投": 3}
groups: dict[tuple[str, str], dict[str, Any]] = {} groups: dict[tuple[str, str], dict[str, Any]] = {}
for row in records: for row in records:
key = (row["pkg"].casefold(), row["country"]) key = (row["pkg"].casefold(), row["country"])
@@ -149,6 +150,7 @@ def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: P
"country": row["country"], "country": row["country"],
"products": set(), "products": set(),
"adpingids": set(), "adpingids": set(),
"order_statuses": Counter(),
"platforms": defaultdict( "platforms": defaultdict(
lambda: {"statuses": Counter(), "adpingids": set(), "order_count": 0} lambda: {"statuses": Counter(), "adpingids": set(), "order_count": 0}
), ),
@@ -156,6 +158,7 @@ def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: P
}, },
) )
group["products"].add(row["product"]) group["products"].add(row["product"])
group["order_statuses"][row["status"]] += 1
if row["adpingid"]: if row["adpingid"]:
group["adpingids"].add(row["adpingid"]) group["adpingids"].add(row["adpingid"])
detail = group["platforms"][row["platform"]] detail = group["platforms"][row["platform"]]
@@ -170,6 +173,9 @@ def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: P
platform_names = sorted(group["platforms"], key=lambda name: (name == TARGET_PLATFORM, name.casefold())) platform_names = sorted(group["platforms"], key=lambda name: (name == TARGET_PLATFORM, name.casefold()))
target_present = TARGET_PLATFORM in group["platforms"] target_present = TARGET_PLATFORM in group["platforms"]
other_platforms = [name for name in platform_names if name != TARGET_PLATFORM] other_platforms = [name for name in platform_names if name != TARGET_PLATFORM]
order_statuses = sorted(
group["order_statuses"], key=lambda status: (status_priority.get(status, 99), status)
)
platform_details = [] platform_details = []
for name in platform_names: for name in platform_names:
detail = group["platforms"][name] detail = group["platforms"][name]
@@ -187,6 +193,8 @@ def aggregate(records: list[dict[str, str]], counters: dict[str, int], source: P
"product": " / ".join(sorted(group["products"])), "product": " / ".join(sorted(group["products"])),
"pkg": group["pkg"], "pkg": group["pkg"],
"country": group["country"], "country": group["country"],
"order_statuses": order_statuses,
"order_status_counts": {status: group["order_statuses"][status] for status in order_statuses},
"platforms": platform_names, "platforms": platform_names,
"other_platforms": other_platforms, "other_platforms": other_platforms,
"platform_details": platform_details, "platform_details": platform_details,
+2419 -1
View File
File diff suppressed because it is too large Load Diff
+30 -9
View File
@@ -293,9 +293,17 @@
head.append(title, tabs); head.append(title, tabs);
const filters = el('div', 'filters'); const filters = el('div', 'filters');
filters.style.gridTemplateColumns = 'minmax(220px, 1.3fr) repeat(4, minmax(125px, .55fr)) auto';
const search = el('input', 'control'); const search = el('input', 'control');
search.id = 'search'; search.type = 'search'; search.placeholder = '搜索 Pkg 或产品名'; search.autocomplete = 'off'; search.id = 'search'; search.type = 'search'; search.placeholder = '搜索 Pkg 或产品名'; search.autocomplete = 'off';
filters.append(search, selectControl('countryFilter', '全部国家'), selectControl('platformFilter', '全部平台'), selectControl('statusFilter', ''), el('button', 'button', '导出当前结果')); filters.append(
search,
selectControl('countryFilter', '全部国家'),
selectControl('platformFilter', '全部平台'),
selectControl('statusFilter', ''),
selectControl('orderStatusFilter', '全部订单状态'),
el('button', 'button', '导出当前结果')
);
filters.lastChild.id = 'exportButton'; filters.lastChild.type = 'button'; filters.lastChild.id = 'exportButton'; filters.lastChild.type = 'button';
header.append(head, filters); header.append(head, filters);
panel.append(header, el('div', 'result-meta'), el('div', 'table-wrap'), el('div', 'pagination')); panel.append(header, el('div', 'result-meta'), el('div', 'table-wrap'), el('div', 'pagination'));
@@ -317,10 +325,12 @@
const status = document.getElementById('statusFilter'); const status = document.getElementById('statusFilter');
[['missing','只看待搬单'], ['all','全部覆盖状态'], ['covered','只看已覆盖'], ['target-only','仅 TaurusxDSP']].forEach(([value,label]) => status.append(new Option(label, value))); [['missing','只看待搬单'], ['all','全部覆盖状态'], ['covered','只看已覆盖'], ['target-only','仅 TaurusxDSP']].forEach(([value,label]) => status.append(new Option(label, value)));
status.value = 'missing'; status.value = 'missing';
const orderStatus = document.getElementById('orderStatusFilter');
['投放中', '暂停', '未标注', '其他/不可投'].forEach(value => orderStatus.append(new Option(value, value)));
} }
function bindEvents() { function bindEvents() {
['search', 'countryFilter', 'platformFilter', 'statusFilter'].forEach(id => { ['search', 'countryFilter', 'platformFilter', 'statusFilter', 'orderStatusFilter'].forEach(id => {
document.getElementById(id).addEventListener(id === 'search' ? 'input' : 'change', () => { state.page = 1; applyFilters(); }); document.getElementById(id).addEventListener(id === 'search' ? 'input' : 'change', () => { state.page = 1; applyFilters(); });
}); });
document.getElementById('exportButton').addEventListener('click', exportCsv); document.getElementById('exportButton').addEventListener('click', exportCsv);
@@ -338,13 +348,15 @@
const country = document.getElementById('countryFilter').value; const country = document.getElementById('countryFilter').value;
const platform = document.getElementById('platformFilter').value; const platform = document.getElementById('platformFilter').value;
const status = document.getElementById('statusFilter').value; const status = document.getElementById('statusFilter').value;
const orderStatus = document.getElementById('orderStatusFilter').value;
state.filtered = state.data.records.filter(row => { state.filtered = state.data.records.filter(row => {
const recordStatus = statusOf(row).key; const recordStatus = statusOf(row).key;
const searchable = `${row.adpingids.join(' ')} ${row.pkg} ${row.product}`.toLocaleLowerCase(); const searchable = `${row.adpingids.join(' ')} ${row.pkg} ${row.product}`.toLocaleLowerCase();
return (!query || searchable.includes(query)) return (!query || searchable.includes(query))
&& (country === 'all' || row.country === country) && (country === 'all' || row.country === country)
&& (platform === 'all' || row.platforms.includes(platform)) && (platform === 'all' || row.platforms.includes(platform))
&& (status === 'all' || recordStatus === status); && (status === 'all' || recordStatus === status)
&& (orderStatus === 'all' || row.order_statuses.includes(orderStatus));
}); });
renderResults(); renderResults();
} }
@@ -367,11 +379,11 @@
const table = el('table'); const table = el('table');
const thead = el('thead'); const thead = el('thead');
const header = el('tr'); const header = el('tr');
['搬单状态', 'Adpingid', '产品', 'Pkg', '国家', '已出现平台', '源订单行数'].forEach(label => header.append(el('th', '', label))); ['搬单状态', 'Adpingid', '产品', 'Pkg', '国家', '订单状态', '已出现平台', '源订单行数'].forEach(label => header.append(el('th', '', label)));
thead.append(header); table.append(thead); thead.append(header); table.append(thead);
const tbody = el('tbody'); const tbody = el('tbody');
if (!rows.length) { if (!rows.length) {
const cell = el('td', 'empty', '没有符合当前条件的记录'); cell.colSpan = 7; const cell = el('td', 'empty', '没有符合当前条件的记录'); cell.colSpan = 8;
const row = el('tr'); row.append(cell); tbody.append(row); const row = el('tr'); row.append(cell); tbody.append(row);
} }
rows.forEach(record => { rows.forEach(record => {
@@ -382,11 +394,15 @@
const product = el('td', 'product', record.product); const product = el('td', 'product', record.product);
const pkg = el('td', 'pkg', record.pkg); const pkg = el('td', 'pkg', record.pkg);
const country = el('td'); country.append(el('span', 'country-pill', record.country)); const country = el('td'); country.append(el('span', 'country-pill', record.country));
const orderStatuses = el('td');
const statusPills = el('div', 'platforms');
record.order_statuses.forEach(name => statusPills.append(el('span', 'platform-pill', `${name} ${record.order_status_counts[name]}`)));
orderStatuses.append(statusPills);
const platforms = el('td'); const platforms = el('td');
const pills = el('div', 'platforms'); const pills = el('div', 'platforms');
record.platforms.forEach(name => pills.append(el('span', `platform-pill ${name === state.data.meta.target_platform ? 'target' : ''}`.trim(), name))); record.platforms.forEach(name => pills.append(el('span', `platform-pill ${name === state.data.meta.target_platform ? 'target' : ''}`.trim(), name)));
platforms.append(pills); platforms.append(pills);
row.append(badgeCell, adpingids, product, pkg, country, platforms, el('td', '', nf.format(record.order_count))); row.append(badgeCell, adpingids, product, pkg, country, orderStatuses, platforms, el('td', '', nf.format(record.order_count)));
tbody.append(row); tbody.append(row);
}); });
table.append(tbody); wrap.append(table); table.append(tbody); wrap.append(table);
@@ -438,13 +454,14 @@
} }
function exportCsv() { function exportCsv() {
const headers = ['搬单状态', 'Adpingid', '产品', 'Pkg', '投放国家', '已出现平台', '是否有TaurusxDSP', '源订单行数']; const headers = ['搬单状态', 'Adpingid', '产品', 'Pkg', '投放国家', '订单状态', '已出现平台', '是否有TaurusxDSP', '源订单行数'];
const csvRows = [headers, ...state.filtered.map(record => [ const csvRows = [headers, ...state.filtered.map(record => [
statusOf(record).label, statusOf(record).label,
record.adpingids.join(' / '), record.adpingids.join(' / '),
record.product, record.product,
record.pkg, record.pkg,
record.country, record.country,
record.order_statuses.join(' / '),
record.platforms.join(' / '), record.platforms.join(' / '),
record.target_present ? '是' : '否', record.target_present ? '是' : '否',
record.order_count record.order_count
@@ -523,7 +540,7 @@
country.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); }); country.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); });
match.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); }); match.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); });
sort.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); }); sort.addEventListener('change', () => { mtgState.page = 1; applyMtgFilters(); });
['search', 'countryFilter', 'platformFilter', 'statusFilter'].forEach(id => { ['search', 'countryFilter', 'platformFilter', 'statusFilter', 'orderStatusFilter'].forEach(id => {
const control = document.getElementById(id); const control = document.getElementById(id);
control.addEventListener(id === 'search' ? 'input' : 'change', () => { control.addEventListener(id === 'search' ? 'input' : 'change', () => {
mtgState.page = 1; mtgState.page = 1;
@@ -545,6 +562,8 @@
const mainPlatform = document.getElementById('platformFilter').value; const mainPlatform = document.getElementById('platformFilter').value;
const mainStatusControl = document.getElementById('statusFilter'); const mainStatusControl = document.getElementById('statusFilter');
const mainStatus = mainStatusControl.value; const mainStatus = mainStatusControl.value;
const mainOrderStatusControl = document.getElementById('orderStatusFilter');
const mainOrderStatus = mainOrderStatusControl.value;
const coverageByKey = new Map( const coverageByKey = new Map(
state.data.records.map(row => [`${row.pkg.toLocaleLowerCase()}|${row.country}`, row]) state.data.records.map(row => [`${row.pkg.toLocaleLowerCase()}|${row.country}`, row])
); );
@@ -563,7 +582,8 @@
&& (!mainSearch || mainSearchable.includes(mainSearch)) && (!mainSearch || mainSearchable.includes(mainSearch))
&& (mainCountry === 'all' || row.country === mainCountry) && (mainCountry === 'all' || row.country === mainCountry)
&& (mainPlatform === 'all' || Boolean(coverage && coverage.platforms.includes(mainPlatform))) && (mainPlatform === 'all' || Boolean(coverage && coverage.platforms.includes(mainPlatform)))
&& (mainStatus === 'all' || coverageStatus === mainStatus); && (mainStatus === 'all' || coverageStatus === mainStatus)
&& (mainOrderStatus === 'all' || Boolean(coverage && coverage.order_statuses.includes(mainOrderStatus)));
}); });
const [field, direction] = sort.split('-'); const [field, direction] = sort.split('-');
@@ -583,6 +603,7 @@
mtgState.sortLabel = sortControl.options[sortControl.selectedIndex].text.replace('排序 · ', ''); mtgState.sortLabel = sortControl.options[sortControl.selectedIndex].text.replace('排序 · ', '');
const linked = [mainStatusControl.options[mainStatusControl.selectedIndex].text]; const linked = [mainStatusControl.options[mainStatusControl.selectedIndex].text];
if (mainOrderStatus !== 'all') linked.push(`订单状态 ${mainOrderStatus}`);
if (mainCountry !== 'all') linked.push(`国家 ${mainCountry}`); if (mainCountry !== 'all') linked.push(`国家 ${mainCountry}`);
if (mainPlatform !== 'all') linked.push(`平台 ${mainPlatform}`); if (mainPlatform !== 'all') linked.push(`平台 ${mainPlatform}`);
if (mainSearch) linked.push(`搜索 ${mainSearch}`); if (mainSearch) linked.push(`搜索 ${mainSearch}`);