后台店铺数据重复ASIN:全选移至表头、Tab总数、独立筛选、按ASIN分组表格展示
Build Backend JAR / build (push) Has been cancelled

- 列表全选复选框从工具栏移到表格表头,事件委托处理动态元素
- 重复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:
2026-09-03 09:32:25 +08:00
parent 417a2bf831
commit 672b9f9b46
5 changed files with 263 additions and 115 deletions
@@ -36,6 +36,12 @@ class ShopDataDuplicateAsinTest(unittest.TestCase):
{'shop_name': 'Shop C', 'latest_created_at': datetime(2026, 8, 31, 1, 15, 3)},
]
# 每家店一个结果文件:Shop A 与 Shop B 共享 ASIN1Shop C 单独 ASIN3
# 国家码按店铺实际 sheetA=UK+DE、B=仅UK、C=FR
self.shop_country = {
'Shop A': '["UK","DE"]',
'Shop B': '["UK"]',
'Shop C': '["FR"]',
}
self.shop_files = {
'Shop A': _make_workbook({
'英国': [('2026-08-30', 'B0ABC111', 'GBP 9.99', 'BrandA'),
@@ -130,7 +136,8 @@ class ShopDataDuplicateAsinTest(unittest.TestCase):
def _run_request(self, query=''):
cursor = self._FakeCursor(
self.group_rows,
[self._result_row(i + 1, row['shop_name'], '["UK","DE"]') for i, row in enumerate(self.group_rows)],
[self._result_row(i + 1, row['shop_name'], self.shop_country[row['shop_name']])
for i, row in enumerate(self.group_rows)],
)
connection = self._FakeConnection(cursor)
with self.app.test_request_context('/api/admin/shop-data-crawl/duplicate-asins?' + query):
@@ -151,6 +158,7 @@ class ShopDataDuplicateAsinTest(unittest.TestCase):
item = body['items'][0]
self.assertEqual(item['asin'], 'B0ABC111')
self.assertEqual(item['shop_count'], 2)
self.assertEqual(item['record_count'], 3) # ShopA 英国/德国 + ShopB 英国 共 3 条
shops = {occ['shop_name'] for occ in item['occurrences']}
self.assertEqual(shops, {'Shop A', 'Shop B'})
# 国家与日期从行/表头正确映射
@@ -158,6 +166,38 @@ class ShopDataDuplicateAsinTest(unittest.TestCase):
self.assertIn('UK', shop_a['country_codes'])
self.assertEqual(shop_a['date'], '2026-08-30')
def test_filters_asin_and_date_range(self):
# asin 模糊:匹配 B0ABC222 的只有 Shop B 一家,不构成跨店重复 → total 0
response = self._run_request('page=1&page_size=10&asin=B0ABC222')
body = response.get_json()
self.assertEqual(body['total'], 0)
# 日期范围 08-31B0ABC111 在 Shop B 有该日记录 → 命中,展示完整记录(跨店仍 2 家)
response = self._run_request('page=1&page_size=10&date_from=2026-08-31&date_to=2026-08-31')
body = response.get_json()
self.assertEqual(body['total'], 1)
self.assertEqual(body['items'][0]['asin'], 'B0ABC111')
self.assertEqual(body['items'][0]['shop_count'], 2) # 完整记录仍跨店
# 日期范围 08-30:Shop A 两条命中,同样返回完整记录
response = self._run_request('page=1&page_size=10&date_from=2026-08-30T00:00&date_to=2026-08-30T23:59')
body = response.get_json()
self.assertEqual(body['total'], 1)
def test_filters_shop_and_country(self):
# 店铺过滤 Shop B:命中含 Shop B 记录的 ASIN → B0ABC111,展示完整记录跨店
response = self._run_request('page=1&page_size=10&shop_name=Shop+B')
body = response.get_json()
self.assertEqual(body['total'], 1)
self.assertEqual(body['items'][0]['asin'], 'B0ABC111')
# 国家过滤 FR:只有 Shop C 有 FR,其 ASIN 仅单店 → 不构成重复 → total 0
response = self._run_request('page=1&page_size=10&country=FR')
body = response.get_json()
self.assertEqual(body['total'], 0)
# 国家过滤 DEShop A 命中 → B0ABC111 完整记录 → total 1
response = self._run_request('page=1&page_size=10&country=DE')
body = response.get_json()
self.assertEqual(body['total'], 1)
self.assertEqual(body['items'][0]['asin'], 'B0ABC111')
def test_pagination_when_page_out_of_range(self):
response = self._run_request('page=2&page_size=10')
body = response.get_json()
@@ -173,6 +213,20 @@ class ShopDataDuplicateAsinTest(unittest.TestCase):
rows = admin_api._shop_data_crawl_parse_workbook(wb)
self.assertEqual([row['asin'] for row in rows], ['B0TEST01'])
def test_date_key_normalizes_chinese_datetime(self):
# 中文日期归一化为 ISO 供范围比较
cases = {
'2026年8月18日 上午4:34': '2026-08-18',
'2026年8月19日 05:48': '2026-08-19',
'2026-08-19': '2026-08-19',
'2026.8.19': '2026-08-19',
'2026/8/19': '2026-08-19',
'': '',
'abc': 'abc', # 无法识别时原样返回,范围比较自然失败
}
for raw, expected in cases.items():
self.assertEqual(admin_api._shop_data_date_key(raw), expected)
if __name__ == '__main__':
unittest.main()