317 lines
23 KiB
PHP
317 lines
23 KiB
PHP
@php
|
|
$filters = $shippingFilters ?? [];
|
|
$sync = $shippingSync ?? ['code' => 'unavailable', 'last_succeeded_at' => null];
|
|
$statusLabels = ['succeeded' => '数据新鲜', 'partial' => !empty($sync['freight_pending']) ? '部分数据已同步 · 运费待补' : '部分数据已同步 · 商品或金额待核对', 'running' => '同步中', 'pending' => '等待同步', 'failed' => '同步失败', 'unavailable' => '尚未同步'];
|
|
|
|
// 按工作平台发货数据界面顺序的 47 列: [列key, 表头, 对齐, 类型]
|
|
$columns = [
|
|
['express_no','快递单号','left','mono'],
|
|
['trade_no','交易单号','left','mono'],
|
|
['item_id','商品ID','left','mono'],
|
|
['barcode','条形码','left','text'],
|
|
['product_name','商品名称','left','text'],
|
|
['qty','数量','right','int'],
|
|
['pkg_count','件数','center','text'],
|
|
['receiver_state','省','left','text'],
|
|
['buyer_nick','买家昵称','left','text'],
|
|
['receiver_name','收货姓名','left','text'],
|
|
['shop_name','店铺名称','left','text'],
|
|
['express_name1','快递名称1','left','text'],
|
|
['warehouse_name','仓库','left','text'],
|
|
['ship_time','发货时间','left','text'],
|
|
['freight','运费','right','money'],
|
|
['online_weight','线上重量','right','num'],
|
|
['actual_weight','实称重量','right','num'],
|
|
['length_cm','长(cm)','right','num'],
|
|
['width_cm','宽(cm)','right','num'],
|
|
['height_cm','高(cm)','right','num'],
|
|
['volume_cm3','体积(cm³)','right','num'],
|
|
['receiver_city','市','left','text'],
|
|
['receiver_district','区','left','text'],
|
|
['supplier_name','供应商','left','text'],
|
|
['receiver_address','收货详细地址','left','text'],
|
|
['receiver_mobile','手机号码','left','text'],
|
|
['receiver_phone','座机号码','left','text'],
|
|
['express_name2','快递名称2','left','text'],
|
|
['platform','平台','left','text'],
|
|
['buyer_memo','买家留言','left','text'],
|
|
['seller_memo','卖家备注','left','text'],
|
|
['package_no','配货单号','left','mono'],
|
|
['amount','金额','right','money'],
|
|
['product_code','货号','left','text'],
|
|
['download_time','下载时间','left','text'],
|
|
['pay_time','付款时间','left','text'],
|
|
['freight_rate','费率','right','num'],
|
|
['freight_formula','公式','left','text'],
|
|
['weight_kg','重量(kg)','right','num'],
|
|
['settle_weight_kg','结算重量(kg)','right','num'],
|
|
['settle_volume_cm3','结算体积(cm³)','right','num'],
|
|
['recon_weight_kg','对账重量(kg)','right','num'],
|
|
['recon_volume_cm3','对账体积(cm³)','right','num'],
|
|
['finished_barcode','成品条码','left','text'],
|
|
['finished_name','成品名称','left','text'],
|
|
['finished_supplier','成品供应商','left','text'],
|
|
['settle_freight','结算运费','right','money'],
|
|
['front_no','换单前单号','left','mono'],
|
|
];
|
|
|
|
$fmt = function ($v, $type) {
|
|
if ($v === null || $v === '') return '';
|
|
return match ($type) {
|
|
'money' => number_format((float) $v, 2),
|
|
'num' => rtrim(rtrim(number_format((float) $v, 4, '.', ''), '0'), '.'),
|
|
'int' => (string) (int) $v,
|
|
default => (string) $v,
|
|
};
|
|
};
|
|
@endphp
|
|
|
|
<div class="shipping-data-page">
|
|
<style>
|
|
.shipping-data-page { color: #29251f; }
|
|
.shipping-data-page .sd-control { width: 100%; height: 2.125rem; border: 1px solid #d8d2c7; border-radius: .5rem; padding: 0 .625rem; font-size: .8125rem; background: #fff; color: #171717; outline: none; }
|
|
.shipping-data-page .sd-control:focus { border-color: #b18a4a; box-shadow: 0 0 0 2px rgba(177,138,74,.15); }
|
|
.shipping-data-page .sd-filter { min-width: 10rem; flex: 1 1 10rem; }
|
|
.shipping-data-page .sd-filter--wide { min-width: 12rem; flex-basis: 12rem; }
|
|
.shipping-data-page .sd-date-picker { position: relative; display: block; }
|
|
.shipping-data-page .sd-date-picker .sd-control { padding-right: 2.5rem; }
|
|
.shipping-data-page .sd-date-picker button { position: absolute; top: 0; right: 0; width: 2.125rem; height: 2.125rem; border: 0; border-left: 1px solid #d8d2c7; background: transparent; color: #856632; cursor: pointer; }
|
|
.shipping-data-page .sd-button { display: inline-flex; height: 2.125rem; align-items: center; gap: .375rem; border-radius: .5rem; padding: 0 .875rem; font-size: .8125rem; font-weight: 500; white-space: nowrap; }
|
|
.shipping-data-page .sd-button--primary { background: #856632; color: #fff; }
|
|
.shipping-data-page .sd-button--primary:hover { background: #6a5129; }
|
|
.shipping-data-page .sd-button--secondary { border: 1px solid #d8d2c7; color: #171717; background: #fff; }
|
|
.shipping-data-page .sd-button .material-icons-outlined { font-size: 1rem; }
|
|
.shipping-data-page .sd-table th { background: #f5f1ea; color: #6f675c; font-weight: 600; position: sticky; top: 0; }
|
|
.shipping-data-page .sd-table td, .shipping-data-page .sd-table th { border-bottom: 1px solid #eee8de; border-right: 1px solid #f1ece3; padding: .5rem .6rem; white-space: nowrap; font-size: .78rem; }
|
|
.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; }
|
|
.shipping-data-page .sd-dd { position: relative; }
|
|
.shipping-data-page .sd-dd-toggle { display: flex; align-items: center; justify-content: space-between; gap: .25rem; width: 100%; height: 2.125rem; border: 1px solid #d8d2c7; border-radius: .5rem; padding: 0 .35rem 0 .625rem; background: #fff; color: #171717; font-size: .8125rem; cursor: pointer; text-align: left; }
|
|
.shipping-data-page .sd-dd.is-open .sd-dd-toggle, .shipping-data-page .sd-dd-toggle:focus { outline: none; border-color: #b18a4a; box-shadow: 0 0 0 2px rgba(177,138,74,.15); }
|
|
.shipping-data-page .sd-dd-summary { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.shipping-data-page .sd-dd-summary.is-empty { color: #9a9182; }
|
|
.shipping-data-page .sd-dd-caret { font-size: 1.1rem; color: #856632; flex: none; }
|
|
.shipping-data-page .sd-dd-panel { position: absolute; top: 100%; left: 0; z-index: 50; margin-top: 2px; min-width: 100%; width: max-content; max-width: 20rem; background: #fff; border: 1px solid #d8d2c7; border-radius: .5rem; box-shadow: 0 8px 24px rgba(0,0,0,.12); padding: 6px; }
|
|
.shipping-data-page .sd-dd-search { display: flex; gap: 4px; margin-bottom: 4px; }
|
|
.shipping-data-page .sd-dd-q { flex: 1; height: 1.9rem; border: 1px solid #d8d2c7; border-radius: .375rem; padding: 0 .5rem; font-size: .8rem; outline: none; }
|
|
.shipping-data-page .sd-dd-q:focus { border-color: #b18a4a; }
|
|
.shipping-data-page .sd-dd-clear { border: 1px solid #d8d2c7; border-radius: .375rem; background: #f7f3ec; color: #4b3a24; font-size: .75rem; padding: 0 .6rem; cursor: pointer; white-space: nowrap; }
|
|
.shipping-data-page .sd-dd-clear:hover { background: #efe7d8; }
|
|
.shipping-data-page .sd-dd-head { display: flex; align-items: center; gap: .4rem; padding: .3rem .4rem; border-bottom: 1px solid #eee8de; font-size: .78rem; font-weight: 600; color: #6f675c; cursor: pointer; }
|
|
.shipping-data-page .sd-dd-list { list-style: none; margin: 0; padding: 2px 0 0; max-height: 14rem; overflow-y: auto; }
|
|
.shipping-data-page .sd-dd-opt { display: flex; align-items: center; gap: .4rem; padding: .3rem .4rem; font-size: .8rem; color: #29251f; border-radius: .375rem; cursor: pointer; white-space: nowrap; }
|
|
.shipping-data-page .sd-dd-opt:hover { background: #f3ece0; }
|
|
.shipping-data-page .sd-dd-opt > span { overflow: hidden; text-overflow: ellipsis; }
|
|
.shipping-data-page .sd-dd-empty { padding: .4rem; color: #9a9182; font-size: .75rem; }
|
|
.shipping-data-page .sd-tab { padding: .3rem .7rem; border: 1px solid #d8d2c7; border-radius: 999px; color: #6f675c; background: #fff; }
|
|
.shipping-data-page .sd-tab--on { background: #856632; color: #fff; border-color: #856632; }
|
|
</style>
|
|
|
|
<div class="flex flex-col gap-4 mb-5 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<h1 class="text-xl font-semibold text-gray-800">{{ $page['title'] ?? '发货数据' }}</h1>
|
|
<p class="mt-1 text-xs text-gray-500">数据来源:聚水潭(聚石塔/奇门)· 销售出库单 · caiwu 直连拉取 · 用于快递运费核算</p>
|
|
</div>
|
|
<div class="flex items-center gap-2 text-xs">
|
|
<span class="rounded-full border border-[#d9d0c2] bg-white px-3 py-1.5 text-[#6f675c]">{{ $statusLabels[$sync['code'] ?? 'unavailable'] ?? '状态未知' }}</span>
|
|
@if (!empty($sync['last_succeeded_at'])) <span class="text-gray-500">{{ ($sync['code'] ?? '') === 'partial' ? '最近成功同步' : '最近同步' }} {{ $sync['last_succeeded_at'] }}</span> @endif
|
|
</div>
|
|
</div>
|
|
|
|
@if (!empty($sync['warning']))
|
|
<div class="mb-4 rounded border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">{{ $sync['warning'] }}</div>
|
|
@endif
|
|
|
|
@if (!empty($shippingError))
|
|
<div class="mb-4 rounded border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">{{ $shippingError }}</div>
|
|
@endif
|
|
|
|
<div class="mb-5 rounded-lg border border-[#e7e0d5] bg-white p-4 shadow-sm">
|
|
@php $mode = $shippingMode ?? 'merged'; $modeQuery = array_merge(request()->query(), []); @endphp
|
|
<div class="mb-3 flex items-center gap-2 text-xs">
|
|
<span class="text-gray-500">口径:</span>
|
|
<a href="{{ url()->current() }}?{{ http_build_query(array_merge(request()->except('page','mode'), ['mode'=>'merged'])) }}"
|
|
class="sd-tab {{ $mode==='merged' ? 'sd-tab--on' : '' }}">合并(一个快递单号一行)</a>
|
|
<a href="{{ url()->current() }}?{{ http_build_query(array_merge(request()->except('page','mode'), ['mode'=>'detail'])) }}"
|
|
class="sd-tab {{ $mode==='detail' ? 'sd-tab--on' : '' }}">明细(每件商品一行)</a>
|
|
</div>
|
|
<form method="GET" class="flex flex-wrap items-end gap-3" aria-label="发货数据筛选" data-suggest-url="{{ route('business.courier-payables.shipping-data.suggest') }}">
|
|
<input type="hidden" name="mode" value="{{ $shippingMode ?? 'merged' }}">
|
|
<label class="sd-filter sd-filter--wide flex flex-col gap-1 text-xs text-gray-500" for="shipping-express-no"><span>快递单号</span><input id="shipping-express-no" class="sd-control" type="text" name="express_no" value="{{ $filters['express_no'] ?? '' }}" placeholder="精确匹配"></label>
|
|
<label class="sd-filter sd-filter--wide flex flex-col gap-1 text-xs text-gray-500" for="shipping-trade-no"><span>交易单号</span><input id="shipping-trade-no" class="sd-control" type="text" name="sourceno" value="{{ $filters['sourceno'] ?? '' }}" placeholder="精确匹配"></label>
|
|
<label class="sd-filter flex flex-col gap-1 text-xs text-gray-500" for="shipping-barcode"><span>条形码</span><input id="shipping-barcode" class="sd-control" type="text" name="barcode" value="{{ $filters['barcode'] ?? '' }}" placeholder="条形码关键词"></label>
|
|
@php
|
|
$msFields = [
|
|
['supplier_name', '供应商'],
|
|
['shop_name', '店铺名称'],
|
|
['warehouse_name', '仓库'],
|
|
['express_company', '快递公司'],
|
|
];
|
|
$msVal = static function ($key) use ($filters) {
|
|
$v = $filters[$key] ?? [];
|
|
if (is_string($v)) { $v = $v === '' ? [] : [$v]; }
|
|
return array_values(array_filter(array_map('strval', (array) $v), static fn ($x) => $x !== ''));
|
|
};
|
|
@endphp
|
|
@foreach ($msFields as [$msKey, $msLabel])
|
|
<div class="sd-filter sd-dd-field flex flex-col gap-1 text-xs text-gray-500"><span>{{ $msLabel }}</span>
|
|
<div class="sd-dd" data-suggest="{{ $msKey }}" data-name="{{ $msKey }}" data-ph="选择{{ $msLabel }}">
|
|
<button type="button" class="sd-dd-toggle"><span class="sd-dd-summary"></span><span class="sd-dd-caret material-icons-outlined" aria-hidden="true">expand_more</span></button>
|
|
<div class="sd-dd-panel" hidden>
|
|
<div class="sd-dd-search"><input type="text" class="sd-dd-q" placeholder="回车检索..."><button type="button" class="sd-dd-clear">清除</button></div>
|
|
<label class="sd-dd-head"><input type="checkbox" class="sd-dd-all"><span>{{ $msLabel }}</span></label>
|
|
<ul class="sd-dd-list"></ul>
|
|
</div>
|
|
<span class="sd-dd-hidden">
|
|
@foreach ($msVal($msKey) as $chip)
|
|
<input type="hidden" name="{{ $msKey }}[]" value="{{ $chip }}">
|
|
@endforeach
|
|
</span>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
<label class="sd-filter flex flex-col gap-1 text-xs text-gray-500" for="shipping-from"><span>发货时间起</span><span class="sd-date-picker"><input id="shipping-from" class="sd-control" type="date" name="ship_from" value="{{ $filters['ship_from'] ?? '' }}"><button type="button" data-date-picker-input="shipping-from" aria-label="选择发货开始日期" title="选择发货开始日期"><span class="material-icons-outlined" aria-hidden="true">calendar_today</span></button></span></label>
|
|
<label class="sd-filter flex flex-col gap-1 text-xs text-gray-500" for="shipping-to"><span>发货时间止</span><span class="sd-date-picker"><input id="shipping-to" class="sd-control" type="date" name="ship_to" value="{{ $filters['ship_to'] ?? '' }}"><button type="button" data-date-picker-input="shipping-to" aria-label="选择发货结束日期" title="选择发货结束日期"><span class="material-icons-outlined" aria-hidden="true">calendar_today</span></button></span></label>
|
|
<div class="flex gap-2">
|
|
<button class="sd-button sd-button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">search</span><span>查询</span></button>
|
|
@php $expBase = ($shippingExportUrl ?? route('business.courier-payables.shipping-data.export')) . '?' . http_build_query(array_filter($filters, static fn ($value) => is_array($value) ? $value !== [] : $value !== '')); @endphp
|
|
<a class="sd-button sd-button--secondary" href="{{ $expBase }}&mode=merged" title="导出合并(一个快递单号一行)"><span class="material-icons-outlined" aria-hidden="true">download</span><span>导出</span></a>
|
|
<a class="sd-button sd-button--secondary" href="{{ $expBase }}&mode=detail" title="导出明细(每件商品一行)"><span class="material-icons-outlined" aria-hidden="true">list</span><span>导出明细</span></a>
|
|
<a class="sd-button sd-button--secondary" href="{{ url()->current() }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span><span>重置</span></a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="overflow-hidden rounded-lg border border-[#e7e0d5] bg-white shadow-sm">
|
|
<div class="sd-scroll">
|
|
<table class="sd-table min-w-full">
|
|
<thead><tr>
|
|
@foreach ($columns as $c)
|
|
<th class="text-{{ $c[2] }}">{{ $c[1] }}</th>
|
|
@endforeach
|
|
</tr></thead>
|
|
<tbody>
|
|
@forelse ($shippingRows ?? [] as $row)
|
|
<tr>
|
|
@foreach ($columns as $c)
|
|
@php $val = $fmt($row[$c[0]] ?? null, $c[3]); @endphp
|
|
<td class="text-{{ $c[2] }} {{ $c[3] === 'mono' ? 'sd-mono' : '' }} {{ in_array($c[3], ['num','int','money']) ? 'tabular-nums' : '' }}">{{ $val === '' ? '-' : $val }}</td>
|
|
@endforeach
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="{{ count($columns) }}" class="px-4 py-16 text-center text-sm text-gray-500">暂无发货数据(填好聚水潭凭证并运行同步后显示;或先运行演示数据 Seeder 查看界面)</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@if (($shippingRows ?? null) instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator && $shippingRows->hasPages())
|
|
<div class="flex items-center justify-between border-t border-[#eee8de] px-4 py-3 text-sm text-gray-500"><span>共 {{ $shippingRows->total() }} 条记录</span><div>{{ $shippingRows->onEachSide(1)->links() }}</div></div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
// 日期选择触发
|
|
document.querySelectorAll('.shipping-data-page [data-date-picker-input]').forEach((trigger) => {
|
|
if (trigger.dataset.datePickerBound === 'true') return;
|
|
const input = document.getElementById(trigger.dataset.datePickerInput || '');
|
|
if (!input) return;
|
|
trigger.dataset.datePickerBound = 'true';
|
|
trigger.addEventListener('click', () => {
|
|
input.focus();
|
|
if (typeof input.showPicker === 'function') input.showPicker();
|
|
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('.sd-dd').forEach((dd) => {
|
|
const field = dd.dataset.suggest;
|
|
const name = dd.dataset.name;
|
|
const ph = dd.dataset.ph || '请选择';
|
|
const toggle = dd.querySelector('.sd-dd-toggle');
|
|
const summary = dd.querySelector('.sd-dd-summary');
|
|
const panel = dd.querySelector('.sd-dd-panel');
|
|
const q = dd.querySelector('.sd-dd-q');
|
|
const clearBtn = dd.querySelector('.sd-dd-clear');
|
|
const allBox = dd.querySelector('.sd-dd-all');
|
|
const listEl = dd.querySelector('.sd-dd-list');
|
|
const hiddenWrap = dd.querySelector('.sd-dd-hidden');
|
|
let timer = null, seq = 0;
|
|
const selected = new Set([...hiddenWrap.querySelectorAll('input')].map((i) => i.value));
|
|
|
|
const syncHidden = () => {
|
|
hiddenWrap.innerHTML = '';
|
|
selected.forEach((v) => {
|
|
const h = document.createElement('input');
|
|
h.type = 'hidden'; h.name = name + '[]'; h.value = v;
|
|
hiddenWrap.appendChild(h);
|
|
});
|
|
};
|
|
const syncSummary = () => {
|
|
if (selected.size === 0) { summary.textContent = ph; summary.classList.add('is-empty'); }
|
|
else if (selected.size === 1) { summary.textContent = [...selected][0]; summary.classList.remove('is-empty'); }
|
|
else { summary.textContent = '已选 ' + selected.size + ' 项'; summary.classList.remove('is-empty'); }
|
|
};
|
|
const syncAll = () => {
|
|
const boxes = [...listEl.querySelectorAll('.sd-dd-cb')];
|
|
allBox.checked = boxes.length > 0 && boxes.every((b) => b.checked);
|
|
allBox.indeterminate = !allBox.checked && boxes.some((b) => b.checked);
|
|
};
|
|
const render = (items) => {
|
|
listEl.innerHTML = '';
|
|
if (!items.length) { listEl.innerHTML = '<li class="sd-dd-empty">无匹配项</li>'; syncAll(); return; }
|
|
items.forEach((it) => {
|
|
const li = document.createElement('li');
|
|
li.className = 'sd-dd-opt';
|
|
const cb = document.createElement('input');
|
|
cb.type = 'checkbox'; cb.className = 'sd-dd-cb'; cb.value = it; cb.checked = selected.has(it);
|
|
const sp = document.createElement('span'); sp.textContent = it; sp.title = it;
|
|
cb.addEventListener('change', () => {
|
|
if (cb.checked) selected.add(it); else selected.delete(it);
|
|
syncHidden(); syncSummary(); syncAll();
|
|
});
|
|
li.addEventListener('click', (e) => { if (e.target !== cb) { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); } });
|
|
li.append(cb, sp);
|
|
listEl.appendChild(li);
|
|
});
|
|
syncAll();
|
|
};
|
|
const load = () => {
|
|
const my = ++seq;
|
|
const kw = q.value.trim();
|
|
fetch(url + '?field=' + encodeURIComponent(field) + '&q=' + encodeURIComponent(kw) + '&limit=300', { 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) render([]); });
|
|
};
|
|
const open = () => { if (!panel.hidden) return; dd.classList.add('is-open'); panel.hidden = false; load(); setTimeout(() => q.focus(), 0); };
|
|
const close = () => { panel.hidden = true; dd.classList.remove('is-open'); };
|
|
|
|
toggle.addEventListener('click', () => { panel.hidden ? open() : close(); });
|
|
q.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(load, 250); });
|
|
q.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); clearTimeout(timer); load(); } });
|
|
clearBtn.addEventListener('click', () => { q.value = ''; load(); q.focus(); });
|
|
allBox.addEventListener('change', () => {
|
|
const boxes = [...listEl.querySelectorAll('.sd-dd-cb')];
|
|
boxes.forEach((cb) => { cb.checked = allBox.checked; if (allBox.checked) selected.add(cb.value); else selected.delete(cb.value); });
|
|
syncHidden(); syncSummary(); syncAll();
|
|
});
|
|
document.addEventListener('click', (e) => { if (!dd.contains(e.target)) close(); });
|
|
|
|
syncSummary();
|
|
});
|
|
})();
|
|
</script>
|