Compare commits

...

2 Commits

Author SHA1 Message Date
ce0553e95b 供应商对账:表格改为卡片内横向滚动,修复宽表把页面撑开、滚动条丢失
宽表(退货 15 列)把 grid 卡片撑宽,横向滚动条落到整页最下方且页面被截断:卡片与滚动容器加 min-width:0 / max-width:100%,每个表格在自己的卡片内横向滚动。
2026-10-17 09:54:00 +08:00
c9f3e5b323 供应商对账:厂家上传的对账数据改为每页 15 行 + 页码翻页
上传的对账数据表原来一页 100 行太长:改为每页 15 行的短表,底部显示总行数/页码(首尾页+当前页附近页码、上一页/下一页),翻页保留所选厂家。
2026-10-17 09:47:26 +08:00
4 changed files with 58 additions and 8 deletions

View File

@ -15,7 +15,8 @@ use Modules\EcommerceFinance\Services\SupplierStatementStore;
*/
class SupplierReconciliationPageData
{
private const PER_PAGE = 100;
/** 厂家上传的对账数据表:每页行数(短表 + 翻页)。 */
private const PER_PAGE = 15;
public function __construct(
private readonly SupplierPayableOverrideStore $store,

View File

@ -1,7 +1,7 @@
<div class="supplier-reconciliation-page">
<style>
.supplier-reconciliation-page { display: grid; gap: 16px; }
.supplier-reconciliation-page .sr-card { padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: var(--vip-surface, #fff); display: grid; gap: 12px; }
.supplier-reconciliation-page .sr-card { min-width: 0; max-width: 100%; padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: var(--vip-surface, #fff); display: grid; gap: 12px; }
.supplier-reconciliation-page h2 { margin: 0; font-size: .875rem; font-weight: 600; color: var(--vip-content-ink, #171717); }
.supplier-reconciliation-page .sr-toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
.supplier-reconciliation-page .sr-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin: 0; }
@ -12,7 +12,7 @@
.supplier-reconciliation-page .sr-button { display: inline-flex; height: 36px; align-items: center; gap: 6px; padding: 0 14px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; color: var(--vip-content-ink, #171717); font-size: .8125rem; font-weight: 600; text-decoration: none; cursor: pointer; }
.supplier-reconciliation-page .sr-button.is-primary { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
.supplier-reconciliation-page .sr-hint { margin: 0; color: var(--vip-content-muted, #595959); font-size: .75rem; line-height: 1.7; }
.supplier-reconciliation-page .sr-scroll { overflow-x: auto; }
.supplier-reconciliation-page .sr-scroll { min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
.supplier-reconciliation-page table { width: 100%; min-width: 720px; border-collapse: collapse; font-size: .8125rem; }
.supplier-reconciliation-page th { padding: 8px 10px; background: var(--vip-content-accent-soft, #F7F0DE); border-bottom: 1px solid var(--vip-border, #D8D2C7); color: #5B5343; font-size: .75rem; font-weight: 600; text-align: left; white-space: nowrap; }
.supplier-reconciliation-page td { padding: 8px 10px; border-bottom: 1px solid #EFEAE0; vertical-align: middle; }

View File

@ -1,7 +1,7 @@
<div class="supplier-reconciliation-page">
<style>
.supplier-reconciliation-page { display: grid; gap: 16px; }
.supplier-reconciliation-page .sr-card { padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: var(--vip-surface, #fff); display: grid; gap: 12px; }
.supplier-reconciliation-page .sr-card { min-width: 0; max-width: 100%; padding: 16px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: var(--vip-surface, #fff); display: grid; gap: 12px; }
.supplier-reconciliation-page h2 { margin: 0; font-size: .875rem; font-weight: 600; color: var(--vip-content-ink, #171717); }
.supplier-reconciliation-page .sr-toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
.supplier-reconciliation-page .sr-field { display: grid; gap: 4px; }
@ -10,7 +10,7 @@
.supplier-reconciliation-page .sr-button { display: inline-flex; height: 36px; align-items: center; gap: 6px; padding: 0 14px; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; color: var(--vip-content-ink, #171717); font-size: .8125rem; font-weight: 600; text-decoration: none; cursor: pointer; }
.supplier-reconciliation-page .sr-button.is-primary { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
.supplier-reconciliation-page .sr-hint { margin: 0; color: var(--vip-content-muted, #595959); font-size: .75rem; line-height: 1.7; }
.supplier-reconciliation-page .sr-scroll { overflow-x: auto; }
.supplier-reconciliation-page .sr-scroll { min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
.supplier-reconciliation-page table { width: 100%; min-width: 720px; border-collapse: collapse; font-size: .8125rem; }
.supplier-reconciliation-page th { padding: 8px 10px; background: var(--vip-content-accent-soft, #F7F0DE); border-bottom: 1px solid var(--vip-border, #D8D2C7); color: #5B5343; font-size: .75rem; font-weight: 600; text-align: left; white-space: nowrap; }
.supplier-reconciliation-page td { padding: 8px 10px; border-bottom: 1px solid #EFEAE0; }
@ -132,13 +132,35 @@
</table>
</div>
@if ($statementTotal > $perPage)
@php
$pageCount = max((int) ceil($statementTotal / $perPage), 1);
$pageStart = max(1, min($rowsPage - 4, $pageCount - 8));
$pageEnd = min($pageCount, $pageStart + 8);
$pageUrl = fn (int $number): string => route('business.pages.show', [
'section' => $section['slug'], 'page' => $page['slug'],
'supplier' => $selectedSupplier, 'rows_page' => $number,
]);
@endphp
<div class="sr-pagination">
<span>第 {{ $rowsPage }} 页 / 共 {{ max((int) ceil($statementTotal / $perPage), 1) }} 页</span>
<span>共 {{ number_format($statementTotal) }} 行,每页 {{ $perPage }} 行 · 第 {{ $rowsPage }} / {{ $pageCount }} 页</span>
@if ($rowsPage > 1)
<a class="sr-button" href="{{ route('business.pages.show', ['section' => $section['slug'], 'page' => $page['slug'], 'supplier' => $selectedSupplier, 'rows_page' => $rowsPage - 1]) }}">上一页</a>
<a class="sr-button is-small" href="{{ $pageUrl($rowsPage - 1) }}">上一页</a>
@endif
@if ($pageStart > 1)
<a class="sr-button is-small" href="{{ $pageUrl(1) }}">1</a>
@if ($pageStart > 2) <span>…</span> @endif
@endif
@for ($number = $pageStart; $number <= $pageEnd; $number++)
<a class="sr-button is-small {{ $number === $rowsPage ? 'is-primary' : '' }}"
@if ($number === $rowsPage) aria-current="page" @endif
href="{{ $pageUrl($number) }}">{{ $number }}</a>
@endfor
@if ($pageEnd < $pageCount)
@if ($pageEnd < $pageCount - 1) <span>…</span> @endif
<a class="sr-button is-small" href="{{ $pageUrl($pageCount) }}">{{ $pageCount }}</a>
@endif
@if ($rowsPage * $perPage < $statementTotal)
<a class="sr-button" href="{{ route('business.pages.show', ['section' => $section['slug'], 'page' => $page['slug'], 'supplier' => $selectedSupplier, 'rows_page' => $rowsPage + 1]) }}">下一页</a>
<a class="sr-button is-small" href="{{ $pageUrl($rowsPage + 1) }}">下一页</a>
@endif
</div>
@endif

View File

@ -134,6 +134,33 @@ class SupplierReconciliationTest extends FeatureTestCase
->assertSeeText('ZSJ-1');
}
public function test_statement_rows_are_paginated_fifteen_per_page(): void
{
$rows = [];
for ($i = 1; $i <= 20; $i++) {
$rows[] = $this->statementRow(sprintf('ST-%02d', $i), '10.00', '1', '10.00');
}
$this->seedStatement('晨丰', $rows);
$pageUrl = fn (int $page): string => route('business.pages.show', [
'section' => 'supplier-reconciliation',
'page' => 'supplier-data',
'supplier' => '晨丰',
'rows_page' => $page,
]);
$this->loginAs()->get($pageUrl(1))
->assertOk()
->assertSeeText('每页 15 行')
->assertSeeText('ST-01')
->assertDontSeeText('ST-20');
$this->loginAs()->get($pageUrl(2))
->assertOk()
->assertSeeText('ST-20')
->assertDontSeeText('ST-01');
}
public function test_return_rows_can_be_confirmed_disputed_and_confirmed_all(): void
{
$store = app(SupplierPayableOverrideStore::class);