Compare commits

...

1 Commits

Author SHA1 Message Date
8e6797a0b9 聚水潭订单标签明细:店铺/标签筛选改为可搜索多选
- 页面:引入项目已有的「可搜索多选」组件(resources/views/business/pages/partials/multi-select.blade.php),
  店铺名称、标签由原生单选 select 改为可打字搜索、可全选/清空、可一条或多条勾选;勾选结果按 shop_name[]/label[] 随表单提交
- 控制器:jstOrderLabelFilters 支持数组参数并与旧单值参数(?shop_name=A&label=B)兼容;
  店铺用 whereIn 精确匹配,多个标签按「任一包含命中」OR 组合;导出/分页沿用同一份筛选
- 验证(临时容器 + 生产数据,只读):多选店铺 951=951、多选标签 39575=39575、组合 0=0、旧单值 92=92 / 374=374 全部与独立 SQL 计数一致;Blade 编译通过
2026-10-07 10:53:56 +08:00
2 changed files with 70 additions and 25 deletions

View File

@ -1801,7 +1801,11 @@ class Pages extends Controller
$limit = min(max((int) setting('default.list_limit', 25), 10), 100);
$page = max((int) $request->query('page', 1), 1);
$hasFilters = collect($filters)->contains(static fn (string $value): bool => $value !== '');
$hasFilters = $filters['date_from'] !== ''
|| $filters['date_to'] !== ''
|| $filters['shop_names'] !== []
|| $filters['labels'] !== []
|| $filters['keyword'] !== '';
// 无筛选时复用缓存总数,避免每次翻页都在 233 万行上重跑 COUNT(*)。
$total = $hasFilters
? (clone $query)->count()
@ -1826,19 +1830,39 @@ class Pages extends Controller
];
}
/** @return array<string,string> */
/** @return array{date_from:string,date_to:string,shop_names:list<string>,labels:list<string>,keyword:string} */
private function jstOrderLabelFilters(Request $request): array
{
return [
'date_from' => $this->dateFilter($request->query('date_from')),
'date_to' => $this->dateFilter($request->query('date_to')),
'shop_name' => mb_substr(trim((string) $request->query('shop_name')), 0, 191),
'label' => mb_substr(trim((string) $request->query('label')), 0, 191),
'shop_names' => $this->filterValues($request->query('shop_name'), 191),
'labels' => $this->filterValues($request->query('label'), 191),
'keyword' => mb_substr(trim((string) $request->query('keyword')), 0, 100),
];
}
/** @param array<string,string> $filters */
/**
* 多选筛选值归一化:兼容旧的单值参数(?shop_name=A)与新的数组参数(?shop_name[]=A&shop_name[]=B)。
*
* @return list<string>
*/
private function filterValues(mixed $raw, int $maxLength, int $limit = 500): array
{
$list = is_array($raw) ? $raw : [$raw];
$values = [];
foreach ($list as $value) {
$value = mb_substr(trim((string) $value), 0, $maxLength);
if ($value !== '' && ! in_array($value, $values, true)) {
$values[] = $value;
}
}
return array_slice($values, 0, $limit);
}
/** @param array{date_from:string,date_to:string,shop_names:list<string>,labels:list<string>,keyword:string} $filters */
private function jstOrderLabelQuery(array $filters): \Illuminate\Database\Eloquent\Builder
{
$query = \App\Models\Business\JstOrderLabel::query()->where('company_id', company_id());
@ -1849,11 +1873,16 @@ class Pages extends Controller
if ($filters['date_to'] !== '') {
$query->where('ordered_at', '<', date('Y-m-d', strtotime($filters['date_to'] . ' +1 day')) . ' 00:00:00');
}
if ($filters['shop_name'] !== '') {
$query->where('shop_name', $filters['shop_name']);
if ($filters['shop_names'] !== []) {
$query->whereIn('shop_name', $filters['shop_names']);
}
if ($filters['label'] !== '') {
$query->where('label', 'like', '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $filters['label']) . '%');
if ($filters['labels'] !== []) {
// 多选标签按「任一命中」过滤,保持与单值时一致的包含匹配语义
$query->where(function ($sub) use ($filters): void {
foreach ($filters['labels'] as $label) {
$sub->orWhere('label', 'like', '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $label) . '%');
}
});
}
if ($filters['keyword'] !== '') {
$query->where('order_no', 'like', '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $filters['keyword']) . '%');

View File

@ -7,6 +7,7 @@
<div class="upstream-data-page upstream-data-page--spaced" data-jst-order-labels="true">
@include('business.pages.partials.upstream-data-styles')
@include('business.pages.partials.multi-select')
<style>
.jst-order-labels-table {
@ -75,23 +76,25 @@
<button class="jst-order-labels-clear" type="button" data-jst-labels-clear="jst-label-date-to" title="清除结束日期" aria-label="清除结束日期">×</button>
</span>
</div>
<div class="upstream-data-field">
<label for="jst-label-shop">店铺名称</label>
<select id="jst-label-shop" name="shop_name">
<option value="">全部店铺</option>
@foreach ($filterOptions['shops'] as $shop)
<option value="{{ $shop }}" @selected(($filters['shop_name'] ?? '') === $shop)>{{ $shop }}</option>
@endforeach
</select>
<div class="upstream-data-field cw-ms" id="jst-label-shop" data-name="shop_name" data-initial='@json($filters['shop_names'] ?? [])'>
<span>店铺名称</span>
<span class="cw-ms__box">
<button type="button" class="cw-ms__btn" data-role="btn" aria-expanded="false">
<span class="cw-ms__label">全部店铺</span><span class="cw-ms__caret">▾</span>
</button>
<span class="cw-ms__panel" data-role="panel" hidden></span>
<span class="cw-ms__values" data-role="values"></span>
</span>
</div>
<div class="upstream-data-field">
<label for="jst-label-label">标签</label>
<select id="jst-label-label" name="label">
<option value="">全部标签</option>
@foreach ($filterOptions['labels'] as $label)
<option value="{{ $label }}" @selected(($filters['label'] ?? '') === $label)>{{ $label }}</option>
@endforeach
</select>
<div class="upstream-data-field cw-ms" id="jst-label-label" data-name="label" data-initial='@json($filters['labels'] ?? [])'>
<span>标签</span>
<span class="cw-ms__box">
<button type="button" class="cw-ms__btn" data-role="btn" aria-expanded="false">
<span class="cw-ms__label">全部标签</span><span class="cw-ms__caret">▾</span>
</button>
<span class="cw-ms__panel" data-role="panel" hidden></span>
<span class="cw-ms__values" data-role="values"></span>
</span>
</div>
<div class="upstream-data-field">
<label for="jst-label-keyword">线上订单号</label>
@ -209,5 +212,18 @@
form.submit();
});
});
if (window.CaiWuMultiSelect) {
window.CaiWuMultiSelect.create(document.getElementById('jst-label-shop'), {
options: @json($filterOptions['shops'] ?? []),
searchable: true,
searchPlaceholder: '打字找店铺',
});
window.CaiWuMultiSelect.create(document.getElementById('jst-label-label'), {
options: @json($filterOptions['labels'] ?? []),
searchable: true,
searchPlaceholder: '打字找标签',
});
}
})();
</script>