fix: 营销账户账单启用日期范围筛选 #674

Merged
qiuxiang merged 1 commits from fix/qiuxiang-marketing-account-date-filters into main 2026-10-27 11:11:39 +08:00
9 changed files with 211 additions and 43 deletions

View File

@ -65,10 +65,30 @@
}
}
function setupBillDatePickers(scope) {
if (!scope || !window.CaiWuDatePicker) {
return;
}
scope.querySelectorAll('[data-marketing-bill-date-filter]').forEach((form) => {
window.CaiWuDatePicker.setup(form);
const from = form.elements.namedItem('from');
const to = form.elements.namedItem('to');
if (from && to) {
[from, to].forEach((input) => {
input.addEventListener('change', () => to.setCustomValidity(''));
});
}
});
}
function initialiseInsertedContent(workspace) {
if (window.Alpine && typeof window.Alpine.initTree === 'function') {
window.Alpine.initTree(workspace);
}
setupBillDatePickers(workspace);
}
async function navigate(url, options) {
@ -176,6 +196,27 @@
}, true);
document.addEventListener('submit', (event) => {
const workspace = currentWorkspace();
const dateFilter = workspace
? event.target.closest(`${workspaceSelector} [data-marketing-bill-date-filter]`)
: null;
if (dateFilter) {
const from = dateFilter.elements.namedItem('from');
const to = dateFilter.elements.namedItem('to');
if (from && to) {
to.setCustomValidity('');
if (String(from.value || '') !== ''
&& String(to.value || '') !== ''
&& String(from.value) > String(to.value)) {
event.preventDefault();
to.setCustomValidity('结束日期不能早于开始日期');
to.reportValidity();
return;
}
}
}
const form = event.target.closest(`${workspaceSelector} [data-jd-wallet-filter]`);
if (!form) {
@ -198,6 +239,12 @@
navigate(target.href, { focus: '#jd-filter-store' });
}, true);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setupBillDatePickers(document));
} else {
setupBillDatePickers(document);
}
window.addEventListener('popstate', () => {
if (currentWorkspace() && isSupportedUrl(window.location.href)) {
navigate(window.location.href, { pushState: false });

View File

@ -69,7 +69,7 @@
@endforeach
</div>
<form method="GET" action="{{ request()->url() }}" class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
<form method="GET" action="{{ request()->url() }}" class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3" data-marketing-bill-date-filter>
<div class="flex flex-wrap items-end gap-4">
<div>
<label for="alibaba-filter-shop" class="mb-1 block text-xs font-medium text-gray-600">店铺名称</label>
@ -89,14 +89,12 @@
@endforeach
</select>
</div>
<div>
<label for="alibaba-filter-from" class="mb-1 block text-xs font-medium text-gray-600">交易时间 起</label>
<input id="alibaba-filter-from" type="date" name="from" value="{{ $filters['from'] ?? '' }}" class="alibaba-bill-control" />
</div>
<div>
<label for="alibaba-filter-to" class="mb-1 block text-xs font-medium text-gray-600">交易时间 止</label>
<input id="alibaba-filter-to" type="date" name="to" value="{{ $filters['to'] ?? '' }}" class="alibaba-bill-control" />
</div>
@include('business.pages.partials.marketing-bill-date-range', [
'idPrefix' => 'alibaba-filter',
'fieldLabel' => '交易时间',
'fromValue' => $filters['from'] ?? '',
'toValue' => $filters['to'] ?? '',
])
<div class="flex items-end gap-2">
<button type="submit" class="alibaba-bill-primary"><span class="material-icons text-base" aria-hidden="true">search</span>查询</button>
<a href="{{ request()->url() }}" class="alibaba-bill-secondary"><span class="material-icons text-base" aria-hidden="true">clear</span>清除</a>

View File

@ -98,7 +98,7 @@
</div>
@endif
<form method="GET" action="{{ $currentUrl }}" class="jd-wallet-filter" data-jd-wallet-filter>
<form method="GET" action="{{ $currentUrl }}" class="jd-wallet-filter" data-jd-wallet-filter data-marketing-bill-date-filter>
<input type="hidden" name="jd_ledger" value="{{ $ledger }}" />
<div class="flex flex-wrap items-end gap-4">
<div>
@ -110,14 +110,12 @@
@endforeach
</select>
</div>
<div>
<label for="jd-filter-from" class="mb-1 block text-xs font-medium text-gray-600">账单日期 起</label>
<input id="jd-filter-from" type="date" name="from" value="{{ $filters['from'] ?? '' }}" class="jd-wallet-control" />
</div>
<div>
<label for="jd-filter-to" class="mb-1 block text-xs font-medium text-gray-600">账单日期 止</label>
<input id="jd-filter-to" type="date" name="to" value="{{ $filters['to'] ?? '' }}" class="jd-wallet-control" />
</div>
@include('business.pages.partials.marketing-bill-date-range', [
'idPrefix' => 'jd-filter',
'fieldLabel' => '账单日期',
'fromValue' => $filters['from'] ?? '',
'toValue' => $filters['to'] ?? '',
])
<div class="flex items-end gap-2">
<button type="submit" class="jd-wallet-button jd-wallet-button-primary"><span class="material-icons text-base" aria-hidden="true">search</span>查询</button>
<a href="{{ $currentUrl }}?jd_ledger={{ $ledger }}" data-jd-wallet-navigation class="jd-wallet-button jd-wallet-button-secondary"><span class="material-icons text-base" aria-hidden="true">clear</span>清除</a>

View File

@ -96,7 +96,8 @@
], static fn ($value) => $value !== '');
@endphp
<form method="GET" action="{{ request()->url() }}"
class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3"
data-marketing-bill-date-filter>
<div class="flex flex-wrap items-end gap-4">
<div>
<label for="pdd-filter-store" class="mb-1 block text-xs font-medium text-gray-600">店铺名称</label>
@ -117,16 +118,12 @@
<option value="expense" @selected($currentFlowType === 'expense')>支出</option>
</select>
</div>
<div>
<label for="pdd-filter-from" class="mb-1 block text-xs font-medium text-gray-600">费用时间 起</label>
<input id="pdd-filter-from" type="date" name="from" value="{{ $currentFrom }}"
class="bill-filter-control" />
</div>
<div>
<label for="pdd-filter-to" class="mb-1 block text-xs font-medium text-gray-600">费用时间 止</label>
<input id="pdd-filter-to" type="date" name="to" value="{{ $currentTo }}"
class="bill-filter-control" />
</div>
@include('business.pages.partials.marketing-bill-date-range', [
'idPrefix' => 'pdd-filter',
'fieldLabel' => '费用时间',
'fromValue' => $currentFrom,
'toValue' => $currentTo,
])
<div class="flex items-end gap-2">
<button type="submit"
class="bill-filter-primary">

View File

@ -88,7 +88,8 @@
], static fn ($value) => $value !== '');
@endphp
<form method="GET" action="{{ request()->url() }}"
class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
class="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3"
data-marketing-bill-date-filter>
<div class="flex flex-wrap items-end gap-4">
<div>
<label for="tb-filter-store" class="mb-1 block text-xs font-medium text-gray-600">店铺名称</label>
@ -109,16 +110,12 @@
<option value="expense" @selected($currentFlowType === 'expense')>支出</option>
</select>
</div>
<div>
<label for="tb-filter-from" class="mb-1 block text-xs font-medium text-gray-600">交易日期 起</label>
<input id="tb-filter-from" type="date" name="from" value="{{ $currentFrom }}"
class="bill-filter-control" />
</div>
<div>
<label for="tb-filter-to" class="mb-1 block text-xs font-medium text-gray-600">交易日期 止</label>
<input id="tb-filter-to" type="date" name="to" value="{{ $currentTo }}"
class="bill-filter-control" />
</div>
@include('business.pages.partials.marketing-bill-date-range', [
'idPrefix' => 'tb-filter',
'fieldLabel' => '交易日期',
'fromValue' => $currentFrom,
'toValue' => $currentTo,
])
<div class="flex items-end gap-2">
<button type="submit" class="bill-filter-primary">
<span class="material-icons text-base" aria-hidden="true">search</span>

View File

@ -192,6 +192,8 @@
}
function setupDatePicker(picker) {
if (picker.dataset.datePickerReady === 'true') { return; }
picker.dataset.datePickerReady = 'true';
var input = picker.querySelector('[data-date-input]');
var panel = picker.querySelector('[data-date-calendar]');
var toggle = picker.querySelector('[data-date-toggle]');

View File

@ -0,0 +1,91 @@
@php
$idPrefix = $idPrefix ?? 'marketing-bill';
$fromId = $idPrefix . '-from';
$toId = $idPrefix . '-to';
$fromName = $fromName ?? 'from';
$toName = $toName ?? 'to';
$fieldLabel = $fieldLabel ?? '交易日期';
$fromLabel = $fromLabel ?? ($fieldLabel . ' 起');
$toLabel = $toLabel ?? ($fieldLabel . ' 止');
$fromValue = (string) ($fromValue ?? '');
$toValue = (string) ($toValue ?? '');
@endphp
<style>
.marketing-bill-date-range {
flex: 1 1 22rem;
min-width: 18rem;
}
.marketing-bill-date-range__label {
display: block;
margin-bottom: 0.25rem;
color: #595959;
font-size: 0.75rem;
font-weight: 500;
}
.marketing-bill-date-range .upstream-data-date-range {
width: 100%;
}
.marketing-bill-date-range .upstream-data-date input {
height: 34px;
width: 100%;
border: 1px solid var(--vip-border, #d8d2c7);
background: #ffffff;
color: #171717;
font-size: 0.8125rem;
}
.marketing-bill-date-range .upstream-data-date__pick {
height: 34px;
}
</style>
<div class="marketing-bill-date-range">
<span class="marketing-bill-date-range__label">{{ $fieldLabel }}</span>
<div class="upstream-data-date-range">
<span class="upstream-data-date" data-date-picker>
<input
id="{{ $fromId }}"
type="text"
name="{{ $fromName }}"
value="{{ $fromValue }}"
readonly
autocomplete="off"
placeholder="开始日期"
aria-label="{{ $fromLabel }}"
data-date-input
>
<button type="button" class="upstream-data-date__clear" data-date-clear hidden aria-label="清除{{ $fromLabel }}">
<span class="material-icons-outlined" aria-hidden="true">close</span>
</button>
<button type="button" class="upstream-data-date__pick" data-date-toggle aria-label="选择{{ $fromLabel }}">
<span class="material-icons-outlined" aria-hidden="true">calendar_today</span>
</button>
<div class="upstream-data-calendar" role="dialog" aria-label="{{ $fromLabel }}日历" data-date-calendar hidden></div>
</span>
<b class="upstream-data-date-range__sep">至</b>
<span class="upstream-data-date" data-date-picker>
<input
id="{{ $toId }}"
type="text"
name="{{ $toName }}"
value="{{ $toValue }}"
readonly
autocomplete="off"
placeholder="结束日期"
aria-label="{{ $toLabel }}"
data-date-input
>
<button type="button" class="upstream-data-date__clear" data-date-clear hidden aria-label="清除{{ $toLabel }}">
<span class="material-icons-outlined" aria-hidden="true">close</span>
</button>
<button type="button" class="upstream-data-date__pick" data-date-toggle aria-label="选择{{ $toLabel }}">
<span class="material-icons-outlined" aria-hidden="true">calendar_today</span>
</button>
<div class="upstream-data-calendar" role="dialog" aria-label="{{ $toLabel }}日历" data-date-calendar hidden></div>
</span>
</div>
</div>

View File

@ -1022,6 +1022,9 @@
@elseif ($sectionView)
@include($sectionView)
@elseif (!empty($content) && !empty($content['view']))
@if ($isMarketingAccountBillsPage)
@include('business.pages.partials.date-range-picker')
@endif
<section class="business-content-section" aria-label="{{ $activeContentTitle }}">
@include($content['view'], $content)
</section>

View File

@ -354,7 +354,12 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase
->assertOk()
->assertDontSeeText('抖音账户')
->assertDontSeeText('得物账户')
->assertDontSeeText('唯品会账户');
->assertDontSeeText('唯品会账户')
->assertSee('data-marketing-bill-date-filter', false)
->assertSee('data-date-picker', false)
->assertSee('id="tb-filter-from"', false)
->assertSee('name="from"', false)
->assertSeeText('开始日期');
$page = collect(config('business_navigation'))
->firstWhere('slug', 'billing-transactions')['pages'];
@ -400,7 +405,10 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase
->assertOk()
->assertSeeText('导出')
->assertSee(e(route('business.pages.export', $parameters)), false)
->assertSee('aria-label="导出当前筛选结果"', false);
->assertSee('aria-label="导出当前筛选结果"', false)
->assertSee('data-marketing-bill-date-filter', false)
->assertSee('id="pdd-filter-from"', false)
->assertSee('data-date-picker', false);
}
public function testPinduoduoBillsExportUsesTheCurrentMirrorFilters(): void
@ -595,7 +603,10 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase
->assertSee('aria-label="导出当前筛选结果"', false)
->assertDontSeeText('投放日期')
->assertSee('data-jd-wallet-panel', false)
->assertSee('data-jd-wallet-navigation', false);
->assertSee('data-jd-wallet-navigation', false)
->assertSee('data-marketing-bill-date-filter', false)
->assertSee('id="jd-filter-from"', false)
->assertSee('data-date-picker', false);
$this->loginAs()
->get(route('business.pages.show', array_merge($base, ['jd_ledger' => 'consumption'])))
@ -608,6 +619,30 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase
->assertDontSeeText('充值时间');
}
public function testMarketingAccountBillFormsUseTheSharedCalendarDateRange(): void
{
foreach ([
'banking/marketing_taobao_alipay_bills/partials/embedded-table.blade.php',
'banking/marketing_alibaba_bills/partials/embedded-table.blade.php',
'banking/marketing_pinduoduo_bills/partials/embedded-table.blade.php',
'banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php',
] as $view) {
$this->assertStringContainsString(
"@include('business.pages.partials.marketing-bill-date-range'",
file_get_contents(resource_path('views/' . $view)),
);
}
$this->assertStringContainsString(
"@include('business.pages.partials.date-range-picker')",
file_get_contents(resource_path('views/business/pages/show.blade.php')),
);
$this->assertStringContainsString(
'setupBillDatePickers(workspace);',
file_get_contents(resource_path('assets/js/views/business/marketing-account-bills.js')),
);
}
public function testJdWalletColdStartRegistersEveryRequiredSnapshotScope(): void
{
$this->allowMarketingAccountBillsPage();