diff --git a/backend/pipeline/dataset_api.py b/backend/pipeline/dataset_api.py index e41211c..8bacae8 100644 --- a/backend/pipeline/dataset_api.py +++ b/backend/pipeline/dataset_api.py @@ -1,4 +1,4 @@ -"""库内数据浏览 API:排序、价格、类目(§5 矩阵)与店铺名筛选。""" +"""库内数据浏览 API:排序、价格、类目(§5 矩阵)与店铺(精确)筛选。""" from __future__ import annotations from typing import Any @@ -61,9 +61,9 @@ def report_group_from_request(request: Request) -> str: return (request.query_params.get("report_group") or "").strip() -def shop_q_from_request(request: Request) -> str: - """店铺名模糊匹配;查询参数 ``shop_q``。""" - return (request.query_params.get("shop_q") or "").strip() +def shop_from_request(request: Request) -> str: + """店铺名精确匹配(与摘要 ``shop_options`` 中某项一致);查询参数 ``shop``。""" + return (request.query_params.get("shop") or "").strip() def detail_category_q_from_request(request: Request) -> str: @@ -73,7 +73,7 @@ def detail_category_q_from_request(request: Request) -> str: def filter_echo( *, report_group: str, - shop_q: str, + shop: str, price_min: float | None, price_max: float | None, detail_category_q: str, @@ -82,7 +82,7 @@ def filter_echo( ) -> dict[str, Any]: return { "report_group": report_group or None, - "shop_q": shop_q or None, + "shop": shop or None, "price_min": price_min, "price_max": price_max, "detail_category_q": detail_category_q or None, @@ -95,9 +95,9 @@ def apply_search_filters(qs: QuerySet, request: Request) -> QuerySet: rg = report_group_from_request(request) if rg: qs = qs.filter(Q(matrix_group_label=rg) | Q(leaf_category=rg)) - sq = shop_q_from_request(request) - if sq: - qs = qs.filter(shop_name__icontains=sq) + sp = shop_from_request(request) + if sp: + qs = qs.filter(shop_name=sp) pmin, pmax = price_bounds_from_request(request) if pmin is not None: qs = qs.filter(price_value__gte=pmin) @@ -131,9 +131,9 @@ def apply_detail_filters(qs: QuerySet, request: Request) -> QuerySet: rg = report_group_from_request(request) if rg: qs = qs.filter(matrix_group_label=rg) - sq = shop_q_from_request(request) - if sq: - qs = qs.filter(detail_shop_name__icontains=sq) + sp = shop_from_request(request) + if sp: + qs = qs.filter(detail_shop_name=sp) q = detail_category_q_from_request(request) if q: qs = qs.filter(detail_category_path__icontains=q) @@ -170,11 +170,9 @@ def apply_merged_filters(qs: QuerySet, request: Request) -> QuerySet: rg = report_group_from_request(request) if rg: qs = qs.filter(matrix_group_label=rg) - sq = shop_q_from_request(request) - if sq: - qs = qs.filter( - Q(shop_name__icontains=sq) | Q(detail_shop_name__icontains=sq) - ) + sp = shop_from_request(request) + if sp: + qs = qs.filter(Q(shop_name=sp) | Q(detail_shop_name=sp)) q = detail_category_q_from_request(request) if q: qs = qs.filter(detail_category_path__icontains=q) diff --git a/backend/pipeline/views.py b/backend/pipeline/views.py index cebe406..dbf620b 100644 --- a/backend/pipeline/views.py +++ b/backend/pipeline/views.py @@ -32,7 +32,7 @@ from .dataset_api import ( parse_sort_meta, price_bounds_from_request, report_group_from_request, - shop_q_from_request, + shop_from_request, ) from .dataset_nonempty import ( comment_columns_for_api, @@ -777,6 +777,48 @@ def _detail_category_path_options(job: PipelineJob) -> list[str]: ) +def _shop_options_for_job(job: PipelineJob) -> list[str]: + """任务内各表出现的店铺名去重排序(搜索 shop_name、商详/宽表店铺列)。""" + names: set[str] = set() + for v in ( + JdJobSearchRow.objects.filter(job=job) + .exclude(shop_name="") + .values_list("shop_name", flat=True) + .distinct() + ): + t = str(v).strip() + if t: + names.add(t) + for v in ( + JdJobDetailRow.objects.filter(job=job) + .exclude(detail_shop_name="") + .values_list("detail_shop_name", flat=True) + .distinct() + ): + t = str(v).strip() + if t: + names.add(t) + for v in ( + JdJobMergedRow.objects.filter(job=job) + .exclude(shop_name="") + .values_list("shop_name", flat=True) + .distinct() + ): + t = str(v).strip() + if t: + names.add(t) + for v in ( + JdJobMergedRow.objects.filter(job=job) + .exclude(detail_shop_name="") + .values_list("detail_shop_name", flat=True) + .distinct() + ): + t = str(v).strip() + if t: + names.add(t) + return sorted(names) + + class JobDatasetSummaryView(APIView): """任务在库中的搜索/详情/评价行数(入库后可用)。""" @@ -796,6 +838,7 @@ class JobDatasetSummaryView(APIView): "comment_columns": comment_columns_for_api(job), "merged_columns": merged_columns_for_api(job), "category_options": _report_group_options_for_job(job), + "shop_options": _shop_options_for_job(job), "detail_category_path_options": _detail_category_path_options(job), "dataset_sort_help": { "search": sorted(SEARCH_SORT_FIELDS), @@ -814,7 +857,7 @@ class JobDatasetSearchView(APIView): sort, desc = parse_sort_meta(request) sort_eff = sort if sort in SEARCH_SORT_FIELDS else "row_index" rg = report_group_from_request(request) - sq = shop_q_from_request(request) + sp = shop_from_request(request) pmin, pmax = price_bounds_from_request(request) dcq = detail_category_q_from_request(request) qs = JdJobSearchRow.objects.filter(job=job) @@ -830,7 +873,7 @@ class JobDatasetSearchView(APIView): "page_size": page_size, "filters": filter_echo( report_group=rg, - shop_q=sq, + shop=sp, price_min=pmin, price_max=pmax, detail_category_q=dcq, @@ -849,7 +892,7 @@ class JobDatasetDetailView(APIView): sort, desc = parse_sort_meta(request) sort_eff = sort if sort in DETAIL_SORT_FIELDS else "row_index" rg = report_group_from_request(request) - sq = shop_q_from_request(request) + sp = shop_from_request(request) pmin, pmax = price_bounds_from_request(request) dcq = detail_category_q_from_request(request) qs = JdJobDetailRow.objects.filter(job=job) @@ -865,7 +908,7 @@ class JobDatasetDetailView(APIView): "page_size": page_size, "filters": filter_echo( report_group=rg, - shop_q=sq, + shop=sp, price_min=pmin, price_max=pmax, detail_category_q=dcq, @@ -906,7 +949,7 @@ class JobDatasetMergedView(APIView): sort, desc = parse_sort_meta(request) sort_eff = sort if sort in MERGED_SORT_FIELDS else "row_index" rg = report_group_from_request(request) - sq = shop_q_from_request(request) + sp = shop_from_request(request) pmin, pmax = price_bounds_from_request(request) dcq = detail_category_q_from_request(request) qs = JdJobMergedRow.objects.filter(job=job) @@ -922,7 +965,7 @@ class JobDatasetMergedView(APIView): "page_size": page_size, "filters": filter_echo( report_group=rg, - shop_q=sq, + shop=sp, price_min=pmin, price_max=pmax, detail_category_q=dcq, diff --git a/frontend/src/components/JobDatasetModal.vue b/frontend/src/components/JobDatasetModal.vue index 47ffaaf..5c6a89f 100644 --- a/frontend/src/components/JobDatasetModal.vue +++ b/frontend/src/components/JobDatasetModal.vue @@ -42,7 +42,8 @@ const sortField = ref('row_index') const sortOrder = ref('asc') /** 类目(§5 矩阵),对应接口参数 report_group */ const reportGroup = ref('') -const shopQ = ref('') +/** 店铺名精确筛选,对应接口参数 shop;选项来自摘要 shop_options */ +const selectedShop = ref('') const priceMin = ref('') const priceMax = ref('') const detailCategoryQ = ref('') @@ -72,6 +73,7 @@ const sortOptions = computed(() => { }) const categoryOptions = computed(() => summary.value?.category_options || []) +const shopOptions = computed(() => summary.value?.shop_options || []) const displayColumns = computed(() => { const s = summary.value @@ -163,7 +165,7 @@ async function refreshList() { sort: sortField.value, order: sortOrder.value, reportGroup: reportGroup.value.trim(), - shopQ: shopQ.value.trim(), + shop: selectedShop.value.trim(), priceMin: priceMin.value, priceMax: priceMax.value, detailCategoryQ: detailCategoryQ.value.trim(), @@ -198,7 +200,7 @@ watch( sortField.value = 'row_index' sortOrder.value = 'asc' reportGroup.value = '' - shopQ.value = '' + selectedShop.value = '' priceMin.value = '' priceMax.value = '' detailCategoryQ.value = '' @@ -217,7 +219,7 @@ watch(tab, () => { sortField.value = 'row_index' sortOrder.value = 'asc' reportGroup.value = '' - shopQ.value = '' + selectedShop.value = '' priceMin.value = '' priceMax.value = '' detailCategoryQ.value = '' @@ -228,7 +230,7 @@ watch( sortField, sortOrder, reportGroup, - shopQ, + selectedShop, priceMin, priceMax, detailCategoryQ, @@ -248,7 +250,7 @@ watch( sortField, sortOrder, reportGroup, - shopQ, + selectedShop, priceMin, priceMax, detailCategoryQ, @@ -434,20 +436,18 @@ async function runExport(format) {