From f21bfbe02bf29f998f4e1b9498722d16917d11c2 Mon Sep 17 00:00:00 2001
From: hub-gif <2487812171@qq.com>
Date: Thu, 16 Apr 2026 10:08:06 +0800
Subject: [PATCH] =?UTF-8?q?feat(dataset):=20=E5=BA=97=E9=93=BA=E6=94=B9?=
=?UTF-8?q?=E4=B8=BA=E4=B8=8B=E6=8B=89=E9=80=89=E6=8B=A9=EF=BC=8C=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=20shop=20=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
摘要增加 shop_options;筛选参数由 shop_q 改为 shop;合并表匹配 shop_name 或 detail_shop_name。
Made-with: Cursor
---
backend/pipeline/dataset_api.py | 32 ++++++------
backend/pipeline/views.py | 57 ++++++++++++++++++---
frontend/src/components/JobDatasetModal.vue | 28 +++++-----
frontend/src/composables/useJobs.js | 6 +--
4 files changed, 82 insertions(+), 41 deletions(-)
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) {