diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 8b94b681..7b1f00c0 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -824,6 +824,25 @@ class Pages extends Controller ]); } + public function suggestShippingData(Request $request): \Illuminate\Http\JsonResponse + { + $allowed = ['supplier_name', 'shop_name', 'warehouse_name', 'express_company']; + $field = (string) $request->query('field', ''); + if (! in_array($field, $allowed, true)) { + return response()->json(['items' => []]); + } + $keyword = mb_substr(trim((string) $request->query('q', '')), 0, 64); + + try { + $items = $this->shippingOrders->distinctValues($field, $keyword, 20); + } catch (Throwable $exception) { + report($exception); + $items = []; + } + + return response()->json(['items' => array_values($items)]); + } + /** @return array */ private function shippingDataFilters(Request $request): array { diff --git a/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php b/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php index e2605f67..78495956 100644 --- a/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php +++ b/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php @@ -114,6 +114,47 @@ class ShippingOrderRepository return '%' . str_replace(['!', '%', '_'], ['!!', '!%', '!_'], trim($value)) . '%'; } + /** + * 取某字段去重候选值(供筛选框自动补全)。 + * @return array + */ + public function distinctValues(string $field, string $keyword, int $limit = 20): array + { + $map = [ + 'supplier_name' => ['supplier_name'], + 'shop_name' => ['shop_name'], + 'warehouse_name' => ['warehouse_name'], + 'express_company' => ['express_name1', 'express_name2'], + ]; + if (! isset($map[$field])) { + return []; + } + $limit = min(max($limit, 1), 50); + $like = $this->contains($keyword); + $values = []; + foreach ($map[$field] as $col) { + $rows = $this->db->table('jst_oms_shipping') + ->selectRaw($col . ' as v') + ->whereRaw($col . " LIKE ? ESCAPE '!'", [$like]) + ->whereNotNull($col) + ->where($col, '<>', '') + ->groupBy($col) + ->orderBy($col) + ->limit($limit) + ->pluck('v'); + foreach ($rows as $v) { + $v = trim((string) $v); + if ($v !== '') { + $values[$v] = true; + } + } + } + $values = array_keys($values); + sort($values, SORT_FLAG_CASE | SORT_STRING); + + return array_slice($values, 0, $limit); + } + /** 同步状态(供页面显示同步中/最近完成)。 */ public function syncStatus(): array { diff --git a/resources/views/business/pages/courier-payables/shipping-data.blade.php b/resources/views/business/pages/courier-payables/shipping-data.blade.php index 75344ff6..66744cd0 100644 --- a/resources/views/business/pages/courier-payables/shipping-data.blade.php +++ b/resources/views/business/pages/courier-payables/shipping-data.blade.php @@ -85,6 +85,11 @@ .shipping-data-page .sd-table tbody tr:hover { background: #fcfaf7; } .shipping-data-page .sd-mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .72rem; color: #4b3a24; } .shipping-data-page .sd-scroll { overflow-x: auto; max-width: 100%; } + .shipping-data-page .sd-ac-field { position: relative; } + .shipping-data-page .sd-ac-list { position: absolute; top: 100%; left: 0; right: 0; z-index: 40; margin-top: 2px; max-height: 15rem; overflow-y: auto; list-style: none; padding: 4px; background: #fff; border: 1px solid #d8d2c7; border-radius: .5rem; box-shadow: 0 8px 24px rgba(0,0,0,.10); } + .shipping-data-page .sd-ac-item { padding: .35rem .5rem; font-size: .8rem; border-radius: .375rem; cursor: pointer; color: #29251f; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .shipping-data-page .sd-ac-item:hover, .shipping-data-page .sd-ac-item.is-active { background: #f3ece0; } + .shipping-data-page .sd-ac-empty { padding: .35rem .5rem; font-size: .75rem; color: #9a9182; }
@@ -103,14 +108,14 @@ @endif
-
+ - - - - + + + +
@@ -163,5 +168,72 @@ else input.click(); }); }); + + // 筛选框自动补全:输入关键词弹下拉,点选或键盘选择填入。 + (() => { + const form = document.querySelector('.shipping-data-page form[aria-label="发货数据筛选"]'); + const url = form ? form.dataset.suggestUrl : ''; + if (!form || !url) return; + + form.querySelectorAll('input[data-suggest]').forEach((input) => { + const label = input.closest('label'); + if (!label) return; + label.classList.add('sd-ac-field'); + + const list = document.createElement('ul'); + list.className = 'sd-ac-list'; + list.hidden = true; + label.appendChild(list); + + let timer = null; + let active = -1; + let seq = 0; + + const hide = () => { list.hidden = true; list.innerHTML = ''; active = -1; }; + + const render = (items) => { + list.innerHTML = ''; + active = -1; + if (!items.length) { hide(); return; } + items.forEach((it) => { + const li = document.createElement('li'); + li.className = 'sd-ac-item'; + li.textContent = it; + li.title = it; + li.addEventListener('mousedown', (e) => { e.preventDefault(); input.value = it; hide(); }); + list.appendChild(li); + }); + list.hidden = false; + }; + + const load = () => { + const my = ++seq; + const q = input.value.trim(); + const u = url + '?field=' + encodeURIComponent(input.dataset.suggest) + '&q=' + encodeURIComponent(q); + fetch(u, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }) + .then((r) => r.ok ? r.json() : { items: [] }) + .then((d) => { if (my === seq) render(Array.isArray(d.items) ? d.items : []); }) + .catch(() => { if (my === seq) hide(); }); + }; + + const schedule = (delay) => { clearTimeout(timer); timer = setTimeout(load, delay); }; + + input.addEventListener('input', () => schedule(200)); + input.addEventListener('focus', () => schedule(120)); + input.addEventListener('blur', () => setTimeout(hide, 150)); + input.addEventListener('keydown', (e) => { + if (list.hidden) return; + const items = list.querySelectorAll('.sd-ac-item'); + if (!items.length) return; + if (e.key === 'ArrowDown') { e.preventDefault(); active = Math.min(active + 1, items.length - 1); } + else if (e.key === 'ArrowUp') { e.preventDefault(); active = Math.max(active - 1, 0); } + else if (e.key === 'Enter') { if (active >= 0) { e.preventDefault(); input.value = items[active].textContent; hide(); } return; } + else if (e.key === 'Escape') { hide(); return; } + else return; + items.forEach((li, i) => li.classList.toggle('is-active', i === active)); + items[active].scrollIntoView({ block: 'nearest' }); + }); + }); + })(); })(); diff --git a/routes/admin.php b/routes/admin.php index 69e90c4f..99151e5d 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -147,6 +147,10 @@ Route::get('workspace/courier-payables/shipping-data/export', 'Business\Pages@ex ->middleware('business.page:courier-payables,shipping-data') ->name('business.courier-payables.shipping-data.export'); +Route::get('workspace/courier-payables/shipping-data/suggest', 'Business\\Pages@suggestShippingData') + ->middleware('business.page:courier-payables,shipping-data') + ->name('business.courier-payables.shipping-data.suggest'); + Route::get('workspace/courier-payables/third-party-warehouse-storage/export', 'Business\Pages@exportThirdPartyWarehouseStorage') ->middleware('business.page:courier-payables,third-party-warehouse-storage') ->name('business.courier-payables.third-party-warehouse-storage.export');