快递应付:逆向物流报价页签 #252

Merged
zhoushengchen merged 4 commits from feature/zhoushengchen-reverse-logistics-quotes into main 2026-09-23 17:44:41 +08:00
3 changed files with 47 additions and 3 deletions

View File

@ -9,7 +9,9 @@
->values()
->all();
$quoteTypes = collect($quotes)->pluck('quote_type')->unique()->sort()->values();
$quoteView = request('quote_view') === 'warehouse' ? 'warehouse' : 'courier';
$quoteView = in_array(request('quote_view'), ['warehouse', 'reverse-logistics'], true)
? request('quote_view')
: 'courier';
@endphp
<section class="quote-management" aria-label="报价管理" data-quote-management>
@ -654,6 +656,9 @@
<button type="button" role="tab" @class(['quote-local-tab', 'is-active' => $quoteView === 'warehouse']) data-quote-view="warehouse" aria-controls="quote-warehouse-panel" aria-selected="{{ $quoteView === 'warehouse' ? 'true' : 'false' }}">
<span class="material-icons-outlined" aria-hidden="true">warehouse</span><span>仓储报价</span>
</button>
<button type="button" role="tab" @class(['quote-local-tab', 'is-active' => $quoteView === 'reverse-logistics']) data-quote-view="reverse-logistics" aria-controls="quote-reverse-logistics-panel" aria-selected="{{ $quoteView === 'reverse-logistics' ? 'true' : 'false' }}">
<span class="material-icons-outlined" aria-hidden="true">assignment_return</span><span>逆向物流报价</span>
</button>
</nav>
<div class="quote-local-panel" id="quote-courier-panel" role="tabpanel" data-quote-panel="courier" @if ($quoteView !== 'courier') hidden @endif>
@ -1127,6 +1132,10 @@
<livewire:business.third-party-warehouse-quotes />
</div>
<div class="quote-local-panel" id="quote-reverse-logistics-panel" role="tabpanel" data-quote-panel="reverse-logistics" @if ($quoteView !== 'reverse-logistics') hidden @endif>
@include('business.pages.courier-payables.reverse-logistics-quotes')
</div>
<script>
(() => {
const root = document.currentScript.closest('[data-quote-management]');
@ -1144,8 +1153,8 @@
panels.forEach((panel) => { panel.hidden = panel.dataset.quotePanel !== view; });
if (updateUrl) {
const url = new URL(window.location.href);
if (view === 'warehouse') url.searchParams.set('quote_view', 'warehouse');
else url.searchParams.delete('quote_view');
if (view === 'courier') url.searchParams.delete('quote_view');
else url.searchParams.set('quote_view', view);
history.replaceState({}, '', url);
}
};

View File

@ -0,0 +1,7 @@
<div class="quote-empty" role="status" data-reverse-logistics-quotes>
<div class="quote-empty__body">
<span class="material-icons-outlined" aria-hidden="true">request_quote</span>
<div class="quote-empty__title">逆向物流报价尚未维护</div>
<div class="quote-empty__text">确认报价字段和计费规则后,可在此建立报价。</div>
</div>
</div>

View File

@ -90,6 +90,9 @@ class QuoteManagementPageTest extends FeatureTestCase
]))
->assertOk()
->assertSeeText('报价管理')
->assertSee('data-quote-view="reverse-logistics"', false)
->assertSee('data-quote-panel="reverse-logistics"', false)
->assertSeeText('逆向物流报价尚未维护')
->assertSeeText('报价单合并.xlsx')
->assertSeeText('报价名称')
->assertSeeText('快递公司')
@ -122,6 +125,31 @@ class QuoteManagementPageTest extends FeatureTestCase
);
}
public function testReverseLogisticsQuoteTabCanBeOpenedDirectly(): void
{
$courierPayables = collect(config('business_navigation'))
->firstWhere('slug', 'courier-payables');
$this->assertIsArray($courierPayables);
$this->assertContains('quote-management', array_column($courierPayables['pages'], 'slug'));
$this->assertNotContains('reverse-logistics-quotes', array_column($courierPayables['pages'], 'slug'));
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'quote-management',
'quote_view' => 'reverse-logistics',
]))
->assertOk()
->assertSee('data-quote-view="reverse-logistics" aria-controls="quote-reverse-logistics-panel" aria-selected="true"', false)
->assertSee('id="quote-reverse-logistics-panel" role="tabpanel" data-quote-panel="reverse-logistics"', false)
->assertSeeText('逆向物流报价尚未维护');
}
public function testQuoteManagementPageKeepsTheShellWhenThePrivateSnapshotIsMissing(): void
{
app()->instance(