style: 统一流程明细页面与分页 #641

Merged
yueyang merged 1 commits from fix/yueyang-process-details-polish into main 2026-10-26 19:48:51 +08:00
2 changed files with 319 additions and 39 deletions

View File

@ -1,5 +1,8 @@
<style>
.process-details-page {
display: flex;
flex-direction: column;
gap: 1rem;
color: var(--vip-content-ink, #171717);
font-size: .8125rem;
}
@ -8,27 +11,35 @@
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .4rem .9rem;
margin-bottom: .85rem;
gap: .4rem 1.25rem;
padding: .65rem 1rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .75rem;
background: #faf7f1;
color: var(--vip-content-muted, #595959);
font-size: .75rem;
line-height: 1.6;
}
.process-details-meta span {
display: inline-flex;
align-items: center;
gap: .25rem;
}
.process-details-meta strong {
color: var(--vip-content-ink, #171717);
font-weight: 600;
}
.process-details-toolbar {
display: flex;
flex-wrap: wrap;
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
align-items: flex-end;
gap: .75rem;
gap: .8rem 1rem;
padding: 1rem;
margin-bottom: 1rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .5rem;
border-radius: .75rem;
background: var(--vip-surface, #fff);
}
@ -36,11 +47,16 @@
display: flex;
flex-direction: column;
gap: .35rem;
min-width: 10.5rem;
min-width: 0;
grid-column: span 2;
}
.process-details-field.is-wide {
min-width: 13.5rem;
grid-column: span 3;
}
.process-details-field.is-keyword {
grid-column: span 8;
}
.process-details-field label {
@ -51,13 +67,15 @@
.process-details-field input,
.process-details-field select {
height: 2.25rem;
width: 100%;
height: 2.125rem;
padding: 0 .65rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .5rem;
background: #fff;
color: var(--vip-content-ink, #171717);
font: inherit;
font-size: .8125rem;
}
.process-details-field input:focus,
@ -71,13 +89,16 @@
display: flex;
flex-wrap: wrap;
gap: .5rem;
grid-column: span 4;
align-self: end;
justify-content: flex-end;
}
.process-details-button {
display: inline-flex;
align-items: center;
gap: .35rem;
height: 2.25rem;
height: 2.125rem;
padding: 0 .85rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .5rem;
@ -116,44 +137,55 @@
}
.process-details-summary {
display: flex;
flex-wrap: wrap;
gap: 1rem 2rem;
padding: .8rem 1rem;
margin-bottom: 1rem;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
overflow: hidden;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .5rem;
border-radius: .75rem;
background: var(--vip-content-accent-soft, #F7F0DE);
}
.process-details-summary .item {
display: flex;
flex-direction: column;
gap: .15rem;
gap: .2rem;
min-width: 0;
padding: .8rem 1rem;
border-right: 1px solid rgba(185, 149, 79, .24);
}
.process-details-summary .item:last-child {
border-right: 0;
}
.process-details-summary .item span {
color: var(--vip-content-muted, #595959);
font-size: .7rem;
font-size: .72rem;
}
.process-details-summary .item b {
font-size: .95rem;
color: var(--vip-content-ink, #171717);
font-size: 1rem;
font-variant-numeric: tabular-nums;
}
.process-details-note {
margin-bottom: .75rem;
margin: -.2rem .25rem 0;
color: var(--vip-content-muted, #595959);
font-size: .75rem;
line-height: 1.6;
}
.process-details-table-card {
overflow: hidden;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .75rem;
background: #fff;
}
.process-details-table-wrap {
overflow: auto;
max-height: min(68vh, 46rem);
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .5rem;
background: #fff;
}
@ -185,10 +217,29 @@
text-align: left;
}
.process-details-table th:first-child,
.process-details-table td:first-child {
position: sticky;
left: 0;
}
.process-details-table th:first-child {
z-index: 4;
}
.process-details-table td:first-child {
z-index: 1;
background: #fff;
}
.process-details-table tr:hover td {
background: #fcfaf4;
}
.process-details-table tr:hover td:first-child {
background: #fcfaf4;
}
.process-details-table .num {
text-align: right;
font-variant-numeric: tabular-nums;
@ -251,29 +302,196 @@
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: .75rem;
padding: .8rem 1rem;
gap: .6rem 1rem;
min-height: 3.25rem;
padding: .65rem 1rem;
border-top: 1px solid var(--vip-border, #D8D2C7);
background: #fffdf8;
color: var(--vip-content-muted, #595959);
font-size: .75rem;
}
.process-details-footer .pagination {
.process-details-footer__summary {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .35rem 1rem;
}
.process-details-footer__summary b {
color: var(--vip-content-ink, #171717);
font-variant-numeric: tabular-nums;
}
.process-details-pagination {
display: inline-flex;
align-items: center;
gap: .3rem;
margin: 0;
}
.process-details-page-link,
.process-details-page-current,
.process-details-page-link.is-disabled {
display: inline-flex;
width: 2rem;
height: 2rem;
align-items: center;
justify-content: center;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .45rem;
background: #fff;
color: var(--vip-content-accent-dark, #856632);
text-decoration: none;
}
.process-details-page-link:hover {
border-color: var(--vip-content-accent, #B9954F);
background: var(--vip-content-accent-soft, #F7F0DE);
color: var(--vip-content-accent-dark, #856632);
text-decoration: none;
}
.process-details-page-link.is-disabled {
border-color: #ece7dc;
background: #faf9f6;
color: #c0b9ad;
cursor: not-allowed;
}
.process-details-page-link .material-icons-outlined {
font-size: 1.05rem;
}
.process-details-page-current {
width: auto;
min-width: 4.5rem;
padding: 0 .6rem;
border-color: var(--vip-content-accent-dark, #856632);
background: var(--vip-content-accent-dark, #856632);
color: #fff;
font-variant-numeric: tabular-nums;
}
.process-details-page-jump {
display: inline-flex;
align-items: center;
gap: .35rem;
margin-left: .25rem;
}
.process-details-page-jump input {
width: 4.25rem;
height: 2rem;
padding: 0 .45rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .45rem;
background: #fff;
color: var(--vip-content-ink, #171717);
font: inherit;
font-size: .75rem;
text-align: center;
}
.process-details-page-jump button {
height: 2rem;
padding: 0 .65rem;
border: 1px solid var(--vip-border, #D8D2C7);
border-radius: .45rem;
background: #fff;
color: var(--vip-content-ink, #171717);
font: inherit;
font-size: .75rem;
cursor: pointer;
}
.process-details-page-jump button:hover {
border-color: var(--vip-content-accent, #B9954F);
color: var(--vip-content-accent-dark, #856632);
}
@media (max-width: 900px) {
.process-details-toolbar {
align-items: stretch;
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.process-details-field {
grid-column: span 2;
}
.process-details-field,
.process-details-field.is-wide {
min-width: 100%;
grid-column: span 3;
}
.process-details-field.is-keyword,
.process-details-actions {
grid-column: span 6;
}
.process-details-actions {
justify-content: flex-start;
}
.process-details-summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.process-details-summary .item:nth-child(2) {
border-right: 0;
}
.process-details-summary .item:nth-child(-n+2) {
border-bottom: 1px solid rgba(185, 149, 79, .24);
}
.process-details-footer {
align-items: flex-start;
}
}
@media (max-width: 640px) {
.process-details-toolbar {
grid-template-columns: 1fr;
}
.process-details-field,
.process-details-field.is-wide,
.process-details-field.is-keyword,
.process-details-actions {
grid-column: 1;
}
.process-details-actions .process-details-button {
flex: 1 1 auto;
justify-content: center;
}
.process-details-summary {
grid-template-columns: 1fr;
}
.process-details-summary .item {
border-right: 0;
border-bottom: 1px solid rgba(185, 149, 79, .24);
}
.process-details-summary .item:last-child {
border-bottom: 0;
}
.process-details-footer,
.process-details-pagination,
.process-details-page-jump {
width: 100%;
}
.process-details-pagination {
flex-wrap: wrap;
}
.process-details-page-jump {
margin-left: 0;
}
}
</style>
@ -321,7 +539,7 @@
<label for="process-details-originator">发起人</label>
<input id="process-details-originator" type="search" name="originator_name" value="{{ $filters['originator_name'] ?? '' }}" placeholder="支持包含查询">
</div>
<div class="process-details-field is-wide">
<div class="process-details-field is-keyword">
<label for="process-details-keyword">用途/摘要</label>
<input id="process-details-keyword" type="search" name="keyword" value="{{ $filters['keyword'] ?? '' }}" placeholder="支持包含查询">
</div>
@ -350,8 +568,9 @@
企业级全量数据待领导开通 OA 权限后切换。
</p>
<div class="process-details-table-wrap">
<table class="process-details-table">
<div class="process-details-table-card">
<div class="process-details-table-wrap">
<table class="process-details-table">
<thead>
<tr>
<th>序号</th>
@ -400,13 +619,70 @@
</tr>
@endforelse
</tbody>
</table>
</div>
</table>
</div>
<div class="process-details-footer">
<span>
第 {{ $processRows->currentPage() }} / {{ max($processRows->lastPage(), 1) }} 页,共 {{ number_format($processRows->total()) }} 行
</span>
<div>{{ $processRows->links() }}</div>
@php
$processDetailsLastPage = max($processRows->lastPage(), 1);
$processDetailsCurrentPage = min(max($processRows->currentPage(), 1), $processDetailsLastPage);
@endphp
<footer class="process-details-footer">
<div class="process-details-footer__summary">
<span>共 <b>{{ number_format($processRows->total()) }}</b> 行</span>
<span>第 <b>{{ number_format($processDetailsCurrentPage) }}</b> / {{ number_format($processDetailsLastPage) }} 页</span>
<span>本页 <b>{{ number_format($processRows->count()) }}</b> 行</span>
</div>
<nav class="process-details-pagination" aria-label="流程明细分页">
@if ($processRows->onFirstPage())
<span class="process-details-page-link is-disabled" aria-disabled="true" title="首页">
<span class="material-icons-outlined" aria-hidden="true">first_page</span>
</span>
<span class="process-details-page-link is-disabled" aria-disabled="true" title="上一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_left</span>
</span>
@else
<a class="process-details-page-link" href="{{ $processRows->url(1) }}" title="首页" aria-label="首页">
<span class="material-icons-outlined" aria-hidden="true">first_page</span>
</a>
<a class="process-details-page-link" href="{{ $processRows->previousPageUrl() }}" title="上一页" aria-label="上一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_left</span>
</a>
@endif
<span class="process-details-page-current" aria-current="page">
{{ number_format($processDetailsCurrentPage) }} / {{ number_format($processDetailsLastPage) }}
</span>
@if ($processRows->hasMorePages())
<a class="process-details-page-link" href="{{ $processRows->nextPageUrl() }}" title="下一页" aria-label="下一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_right</span>
</a>
<a class="process-details-page-link" href="{{ $processRows->url($processDetailsLastPage) }}" title="末页" aria-label="末页">
<span class="material-icons-outlined" aria-hidden="true">last_page</span>
</a>
@else
<span class="process-details-page-link is-disabled" aria-disabled="true" title="下一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_right</span>
</span>
<span class="process-details-page-link is-disabled" aria-disabled="true" title="末页">
<span class="material-icons-outlined" aria-hidden="true">last_page</span>
</span>
@endif
@if ($processDetailsLastPage > 1)
<form class="process-details-page-jump" method="GET" action="{{ $pageUrl }}">
@foreach ($filters as $filterName => $filterValue)
@if ($filterValue !== '')
<input type="hidden" name="{{ $filterName }}" value="{{ $filterValue }}">
@endif
@endforeach
<label for="process-details-page">跳到</label>
<input id="process-details-page" type="number" name="page" min="1" max="{{ $processDetailsLastPage }}" value="{{ $processDetailsCurrentPage }}" inputmode="numeric" aria-label="跳到第几页">
<button type="submit">确定</button>
</form>
@endif
</nav>
</footer>
</div>
</div>

View File

@ -27,6 +27,10 @@ class ProcessDetailsPageDataTest extends TestCase
);
$this->assertStringNotContainsString('<x-layouts.admin>', $content);
$this->assertStringContainsString('class="process-details-page"', $content);
$this->assertStringContainsString('class="process-details-table-card"', $content);
$this->assertStringContainsString('class="process-details-pagination"', $content);
$this->assertStringContainsString('class="process-details-page-jump"', $content);
$this->assertStringNotContainsString('$processRows->links()', $content);
}
public function test_page_data_contains_real_paginator_filters_and_summary(): void