1344 lines
57 KiB
PHP
1344 lines
57 KiB
PHP
@php
|
||
try {
|
||
app(\App\Support\CourierQuoteSignatureService::class)->refreshPending();
|
||
} catch (\Throwable $exception) {
|
||
report($exception);
|
||
}
|
||
$quoteSnapshot = app(\App\Support\QuoteManagementSnapshot::class)->load();
|
||
$quotes = $quoteSnapshot['quotes'];
|
||
$quoteCompanies = collect($quotes)
|
||
->pluck('company_name')
|
||
->filter()
|
||
->unique()
|
||
->sort()
|
||
->values()
|
||
->all();
|
||
$quoteTypes = collect($quotes)->pluck('quote_type')->unique()->sort()->values();
|
||
$quoteView = in_array(request('quote_view'), ['warehouse', 'reverse-logistics'], true)
|
||
? request('quote_view')
|
||
: 'courier';
|
||
@endphp
|
||
|
||
<section class="quote-management" aria-label="报价管理" data-quote-management>
|
||
@if(isset($errors) && ($errors->has('end_date') || $errors->has('reason') || $errors->has('courier_name')))
|
||
<div role="alert" style="padding:12px;margin-bottom:12px;border:1px solid #D8D2C7;border-radius:8px;background:#F7F0DE;color:#B80000">{{ $errors->first() }}</div>
|
||
@endif
|
||
<style>
|
||
.quote-management {
|
||
--quote-accent: var(--vip-content-accent-dark, #856632);
|
||
--quote-accent-soft: var(--vip-content-accent-soft, #F7F0DE);
|
||
--quote-border: var(--vip-border, #D8D2C7);
|
||
--quote-ink: var(--vip-content-ink, #171717);
|
||
--quote-muted: var(--vip-content-muted, #595959);
|
||
display: grid;
|
||
width: 100%;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
gap: 1rem;
|
||
margin-top: 1.5rem;
|
||
color: var(--quote-ink);
|
||
font-family: JetBrains Mono, "Microsoft YaHei", "PingFang SC", monospace;
|
||
font-size: 0.8125rem;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.quote-management *,
|
||
.quote-management *::before,
|
||
.quote-management *::after {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.quote-local-tabs {
|
||
display: flex;
|
||
min-height: 2.75rem;
|
||
align-items: center;
|
||
gap: 1.5rem;
|
||
overflow-x: auto;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-local-tab {
|
||
position: relative;
|
||
display: inline-flex;
|
||
min-height: 2.75rem;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
gap: .4rem;
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--quote-muted);
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-local-tab .material-icons-outlined { font-size: 17px; }
|
||
.quote-local-tab.is-active { color: var(--quote-accent); }
|
||
.quote-local-tab.is-active::after { position:absolute; right:0; bottom:0; left:0; height:2px; background:var(--quote-accent); content:''; }
|
||
.quote-local-tab:focus-visible { outline:2px solid var(--quote-accent); outline-offset:2px; }
|
||
.quote-local-panel { display:grid; min-width:0; gap:1rem; }
|
||
|
||
.quote-summary,
|
||
.quote-workspace {
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
width: 100%;
|
||
border: 1px solid var(--quote-border);
|
||
border-radius: 0.75rem;
|
||
background: #FFFFFF;
|
||
}
|
||
|
||
.quote-summary {
|
||
display: grid;
|
||
grid-template-columns: minmax(13rem, 1.4fr) repeat(4, minmax(7rem, 0.65fr));
|
||
align-items: stretch;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.quote-source,
|
||
.quote-metric {
|
||
min-width: 0;
|
||
padding: 0.875rem 1rem;
|
||
}
|
||
|
||
.quote-source {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.quote-source__icon {
|
||
display: grid;
|
||
flex: 0 0 34px;
|
||
width: 34px;
|
||
height: 34px;
|
||
place-items: center;
|
||
border-radius: 8px;
|
||
background: var(--quote-accent-soft);
|
||
color: var(--quote-accent);
|
||
}
|
||
|
||
.quote-source__icon .material-icons-outlined {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.quote-source__body {
|
||
min-width: 0;
|
||
}
|
||
|
||
.quote-source__file {
|
||
overflow: hidden;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.quote-source__state,
|
||
.quote-metric__label,
|
||
.quote-table__secondary,
|
||
.quote-detail-meta dt {
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.quote-metric {
|
||
border-left: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-metric__value {
|
||
margin-top: 0.2rem;
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.quote-toolbar {
|
||
min-width: 0;
|
||
display: grid;
|
||
grid-template-columns: minmax(13rem, 1.2fr) repeat(3, minmax(9rem, 0.65fr)) auto;
|
||
gap: 0.75rem;
|
||
align-items: end;
|
||
padding: 1rem;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-field {
|
||
display: grid;
|
||
gap: 0.35rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.quote-field__label {
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.quote-control,
|
||
.quote-icon-button,
|
||
.quote-button {
|
||
height: 34px;
|
||
border: 1px solid var(--quote-border);
|
||
border-radius: 8px;
|
||
background: #FFFFFF;
|
||
color: var(--quote-ink);
|
||
font: inherit;
|
||
}
|
||
|
||
.quote-control {
|
||
width: 100%;
|
||
min-width: 0;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.quote-control:focus,
|
||
.quote-icon-button:focus-visible,
|
||
.quote-button:focus-visible,
|
||
.quote-action:focus-visible,
|
||
.quote-modal__close:focus-visible {
|
||
border-color: var(--quote-accent);
|
||
outline: none;
|
||
box-shadow: 0 0 0 3px rgba(133, 102, 50, 0.20);
|
||
}
|
||
|
||
.quote-toolbar__actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.quote-icon-button,
|
||
.quote-button,
|
||
.quote-action,
|
||
.quote-modal__close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.quote-icon-button {
|
||
width: 34px;
|
||
padding: 0;
|
||
}
|
||
|
||
.quote-icon-button .material-icons-outlined,
|
||
.quote-button .material-icons-outlined {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.quote-button {
|
||
gap: 0.4rem;
|
||
padding: 0 14px;
|
||
}
|
||
|
||
.quote-button--primary {
|
||
border-color: var(--quote-accent);
|
||
background: var(--quote-accent);
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.quote-button--primary:hover {
|
||
border-color: #6A5129;
|
||
background: #6A5129;
|
||
}
|
||
|
||
.quote-button[disabled] {
|
||
cursor: not-allowed;
|
||
opacity: 0.58;
|
||
}
|
||
|
||
.quote-results {
|
||
min-width: 0;
|
||
display: flex;
|
||
min-height: 2.75rem;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 0.65rem 1rem;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-results__count {
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.quote-results__notice {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
color: var(--quote-accent);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.quote-results__notice .material-icons-outlined {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.quote-table-wrap,
|
||
.quote-detail-table-wrap {
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
width: 100%;
|
||
overflow: auto;
|
||
scrollbar-color: #B9954F #F2EFE8;
|
||
scrollbar-width: thin;
|
||
}
|
||
|
||
.quote-table-wrap::-webkit-scrollbar,
|
||
.quote-detail-table-wrap::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
.quote-table-wrap::-webkit-scrollbar-thumb,
|
||
.quote-detail-table-wrap::-webkit-scrollbar-thumb {
|
||
border-radius: 4px;
|
||
background: #B9954F;
|
||
}
|
||
|
||
.quote-table-wrap::-webkit-scrollbar-track,
|
||
.quote-detail-table-wrap::-webkit-scrollbar-track {
|
||
background: #F2EFE8;
|
||
}
|
||
|
||
.quote-table,
|
||
.quote-detail-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.78125rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.quote-table {
|
||
min-width: 67rem;
|
||
}
|
||
|
||
.quote-table th,
|
||
.quote-table td,
|
||
.quote-detail-table th,
|
||
.quote-detail-table td {
|
||
border-bottom: 1px solid var(--quote-border);
|
||
padding: 0.65rem 0.75rem;
|
||
text-align: left;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.quote-table th,
|
||
.quote-detail-table th {
|
||
background: var(--quote-accent-soft);
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.quote-table tbody tr:hover td {
|
||
background: #FCFAF5;
|
||
}
|
||
|
||
.quote-table__name {
|
||
display: block;
|
||
max-width: 17rem;
|
||
overflow: hidden;
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.quote-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 24px;
|
||
border: 1px solid var(--quote-border);
|
||
border-radius: 999px;
|
||
padding: 0.15rem 0.55rem;
|
||
background: #FFFFFF;
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.quote-badge--ready {
|
||
border-color: #A9C49A;
|
||
background: #F2F7EF;
|
||
color: #53793E;
|
||
}
|
||
|
||
.quote-badge--muted {
|
||
border-color: #DDD8CE;
|
||
background: #F4F2EE;
|
||
color: #8A8478;
|
||
}
|
||
|
||
.quote-badge--attention {
|
||
border-color: #D7B77D;
|
||
background: #FFF8E9;
|
||
color: #935F07;
|
||
}
|
||
|
||
.quote-signed {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
margin: 0.75rem 1rem 0;
|
||
padding: 0.6rem 0.8rem;
|
||
border: 1px solid #CFE3C8;
|
||
border-radius: 0.5rem;
|
||
background: #F1F8EE;
|
||
color: #2F6B2A;
|
||
}
|
||
|
||
.quote-modal__panel > .quote-detail-table-wrap {
|
||
grid-row: 5;
|
||
}
|
||
|
||
.quote-signed[hidden],
|
||
.quote-signed [hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.quote-signed__text {
|
||
margin-right: auto;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-action {
|
||
min-width: 3.2rem;
|
||
border: 0;
|
||
padding: 0.25rem 0.4rem;
|
||
background: transparent;
|
||
color: var(--quote-accent);
|
||
font: inherit;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-action:hover {
|
||
background: var(--quote-accent-soft);
|
||
}
|
||
|
||
.quote-action[disabled] {
|
||
cursor: not-allowed;
|
||
opacity: 0.48;
|
||
}
|
||
|
||
.quote-action[disabled]:hover {
|
||
background: transparent;
|
||
}
|
||
|
||
.quote-empty {
|
||
display: grid;
|
||
min-height: 15rem;
|
||
place-items: center;
|
||
padding: 2rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.quote-empty__body {
|
||
display: grid;
|
||
justify-items: center;
|
||
gap: 0.45rem;
|
||
max-width: 30rem;
|
||
}
|
||
|
||
.quote-empty .material-icons-outlined {
|
||
color: var(--quote-accent);
|
||
font-size: 32px;
|
||
}
|
||
|
||
.quote-empty__title {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-empty__text {
|
||
color: var(--quote-muted);
|
||
}
|
||
|
||
.quote-pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
min-height: 3.5rem;
|
||
padding: 0.65rem 1rem;
|
||
}
|
||
|
||
.quote-pagination__label {
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.quote-pagination__buttons {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.quote-modal {
|
||
position: fixed;
|
||
z-index: 1200;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: stretch;
|
||
justify-content: flex-end;
|
||
background: rgba(5, 5, 6, 0.52);
|
||
}
|
||
|
||
.quote-modal.is-open {
|
||
display: flex;
|
||
}
|
||
|
||
.quote-modal__panel {
|
||
display: grid;
|
||
grid-template-rows: auto auto auto auto minmax(0, 1fr);
|
||
width: min(78rem, calc(100vw - 3rem));
|
||
height: 100%;
|
||
background: #FFFFFF;
|
||
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.10);
|
||
}
|
||
|
||
.quote-modal__header {
|
||
display: flex;
|
||
min-width: 0;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 1rem 1.25rem;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-modal__title-wrap {
|
||
min-width: 0;
|
||
}
|
||
|
||
.quote-modal__title {
|
||
overflow-wrap: anywhere;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-modal__subtitle {
|
||
margin-top: 0.2rem;
|
||
color: var(--quote-muted);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.quote-modal__close {
|
||
flex: 0 0 34px;
|
||
width: 34px;
|
||
height: 34px;
|
||
border: 1px solid var(--quote-border);
|
||
border-radius: 8px;
|
||
background: #FFFFFF;
|
||
color: var(--quote-ink);
|
||
}
|
||
|
||
.quote-detail-meta {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||
margin: 0;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-detail-meta div {
|
||
min-width: 0;
|
||
padding: 0.75rem 1rem;
|
||
border-right: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-detail-meta div:last-child {
|
||
border-right: 0;
|
||
}
|
||
|
||
.quote-detail-meta dd {
|
||
margin: 0.2rem 0 0;
|
||
overflow-wrap: anywhere;
|
||
font-size: 0.78125rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.quote-detail-table-wrap {
|
||
min-height: 0;
|
||
padding: 1rem 1.25rem 1.25rem;
|
||
}
|
||
|
||
.quote-detail-table {
|
||
min-width: max-content;
|
||
border-top: 1px solid var(--quote-border);
|
||
border-left: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-detail-table th,
|
||
.quote-detail-table td {
|
||
min-width: 8rem;
|
||
max-width: 24rem;
|
||
border-right: 1px solid var(--quote-border);
|
||
white-space: pre-wrap;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.quote-detail-table .quote-detail-table__row-number {
|
||
min-width: 3.75rem;
|
||
width: 3.75rem;
|
||
background: #F8F6F1;
|
||
color: var(--quote-muted);
|
||
text-align: center;
|
||
}
|
||
|
||
.quote-detail-table__formula {
|
||
display: block;
|
||
margin-top: 0.25rem;
|
||
color: var(--quote-accent);
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
[hidden] {
|
||
display: none !important;
|
||
}
|
||
|
||
@media (max-width: 1050px) {
|
||
.quote-summary {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
|
||
.quote-source {
|
||
grid-column: 1 / -1;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-metric:first-of-type {
|
||
border-left: 0;
|
||
}
|
||
|
||
.quote-toolbar {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.quote-toolbar__actions {
|
||
grid-column: 1 / -1;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.quote-detail-meta {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
|
||
.quote-detail-meta div:nth-child(3) {
|
||
border-right: 0;
|
||
}
|
||
|
||
.quote-detail-meta div:nth-child(-n + 3) {
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.quote-management {
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.quote-summary,
|
||
.quote-toolbar,
|
||
.quote-detail-meta {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.quote-source,
|
||
.quote-metric,
|
||
.quote-detail-meta div,
|
||
.quote-detail-meta div:nth-child(3) {
|
||
grid-column: auto;
|
||
border-right: 0;
|
||
border-bottom: 1px solid var(--quote-border);
|
||
}
|
||
|
||
.quote-metric:last-child,
|
||
.quote-detail-meta div:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.quote-toolbar__actions {
|
||
grid-column: auto;
|
||
justify-content: stretch;
|
||
}
|
||
|
||
.quote-toolbar__actions .quote-button {
|
||
flex: 1;
|
||
}
|
||
|
||
.quote-results,
|
||
.quote-pagination {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.quote-pagination__buttons {
|
||
width: 100%;
|
||
}
|
||
|
||
.quote-pagination__buttons .quote-button {
|
||
flex: 1;
|
||
}
|
||
|
||
.quote-modal__panel {
|
||
width: 100%;
|
||
}
|
||
|
||
}
|
||
</style>
|
||
|
||
<nav class="quote-local-tabs" role="tablist" aria-label="报价管理分类">
|
||
<button type="button" role="tab" @class(['quote-local-tab', 'is-active' => $quoteView === 'courier']) data-quote-view="courier" aria-controls="quote-courier-panel" aria-selected="{{ $quoteView === 'courier' ? 'true' : 'false' }}">
|
||
<span class="material-icons-outlined" aria-hidden="true">local_shipping</span><span>快递报价</span>
|
||
</button>
|
||
<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>
|
||
|
||
<div class="quote-summary" aria-label="报价数据概览">
|
||
<div class="quote-source">
|
||
<span class="quote-source__icon" aria-hidden="true">
|
||
<span class="material-icons-outlined">description</span>
|
||
</span>
|
||
<div class="quote-source__body">
|
||
<div class="quote-source__file" title="{{ $quoteSnapshot['source_file'] }}">{{ $quoteSnapshot['source_file'] }}</div>
|
||
<div class="quote-source__state">本机私有报价源 · 原始工作簿未修改</div>
|
||
</div>
|
||
</div>
|
||
<div class="quote-metric">
|
||
<div class="quote-metric__label">报价版本</div>
|
||
<div class="quote-metric__value" data-summary="quotes">{{ $quoteSnapshot['summary']['quote_count'] }}</div>
|
||
</div>
|
||
<div class="quote-metric">
|
||
<div class="quote-metric__label">快递品牌</div>
|
||
<div class="quote-metric__value">{{ $quoteSnapshot['summary']['carrier_count'] }}</div>
|
||
</div>
|
||
<div class="quote-metric">
|
||
<div class="quote-metric__label">已标执行日期</div>
|
||
<div class="quote-metric__value">{{ $quoteSnapshot['summary']['dated_count'] }}</div>
|
||
</div>
|
||
<div class="quote-metric">
|
||
<div class="quote-metric__label">原表公式单元格</div>
|
||
<div class="quote-metric__value">{{ $quoteSnapshot['summary']['formula_count'] }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quote-workspace">
|
||
<div class="quote-toolbar" aria-label="报价筛选">
|
||
<label class="quote-field">
|
||
<span class="quote-field__label">报价名称</span>
|
||
<input class="quote-control" type="search" name="keyword" placeholder="搜索快递公司或版本号" autocomplete="off" data-quote-filter="keyword">
|
||
</label>
|
||
<label class="quote-field">
|
||
<span class="quote-field__label">快递公司</span>
|
||
<select class="quote-control" name="carrier" data-quote-filter="carrier">
|
||
<option value="">全部公司</option>
|
||
@foreach ($quoteCompanies as $companyName)
|
||
<option value="{{ $companyName }}">{{ $companyName }}</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
<label class="quote-field">
|
||
<span class="quote-field__label">报价类型</span>
|
||
<select class="quote-control" name="type" data-quote-filter="type">
|
||
<option value="">全部类型</option>
|
||
@foreach ($quoteTypes as $type)
|
||
<option value="{{ $type }}">{{ $type }}</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
<label class="quote-field">
|
||
<span class="quote-field__label">资料状态</span>
|
||
<select class="quote-control" name="status" data-quote-filter="status">
|
||
<option value="">全部状态</option>
|
||
<option value="已标执行日期">已标执行日期</option>
|
||
<option value="未标执行日期">未标执行日期</option>
|
||
</select>
|
||
</label>
|
||
<div class="quote-toolbar__actions">
|
||
<a class="quote-button" href="{{ route('business.courier-stock-quotes.create') }}">
|
||
<span class="material-icons-outlined" aria-hidden="true">upload_file</span><span>录入存量报价</span>
|
||
</a>
|
||
<button class="quote-icon-button" type="button" title="重置筛选" aria-label="重置筛选" data-quote-reset>
|
||
<span class="material-icons-outlined" aria-hidden="true">filter_alt_off</span>
|
||
</button>
|
||
@if ($quoteSnapshot['available'])
|
||
<a class="quote-button" href="{{ route('business.courier-quote-signatures.export') }}" title="把当前筛选结果里已签署的回签单 PDF 和签署完成证书打包下载" data-quote-export>
|
||
<span class="material-icons-outlined" aria-hidden="true">download</span>
|
||
<span data-quote-export-label>导出回签单</span>
|
||
</a>
|
||
@endif
|
||
<a class="quote-button" href="{{ route('business.courier-quote-changes.index') }}" title="查看报价变更草稿、审核状态和新版本">
|
||
<span class="material-icons-outlined" aria-hidden="true">history</span>
|
||
<span>报价历史</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
@if ($quoteSnapshot['available'])
|
||
<div class="quote-results">
|
||
<span class="quote-results__count" data-quote-result-count>共 {{ count($quotes) }} 个报价版本</span>
|
||
<span class="quote-results__notice">
|
||
<span class="material-icons-outlined" aria-hidden="true">verified_user</span>
|
||
<span>原始报价规则保留,当前页面不重新计算金额</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="quote-table-wrap">
|
||
<table class="quote-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col" data-finance-column-key="报价版本_0">报价版本</th>
|
||
<th scope="col" data-finance-column-key="快递公司_1">快递公司</th>
|
||
<th scope="col" data-finance-column-key="报价类型_2">报价类型</th>
|
||
<th scope="col" data-finance-column-key="原始范围_3">原始范围</th>
|
||
<th scope="col" data-finance-column-key="数据规模_4">数据规模</th>
|
||
<th scope="col" data-finance-column-key="执行日期_5">执行日期</th>
|
||
<th scope="col" data-finance-column-key="签章_6">签章</th>
|
||
<th scope="col" data-finance-column-key="资料状态_7">资料状态</th>
|
||
<th scope="col" data-finance-column-key="操作_8">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody data-quote-table-body></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="quote-empty" data-quote-filter-empty hidden>
|
||
<div class="quote-empty__body">
|
||
<span class="material-icons-outlined" aria-hidden="true">filter_alt_off</span>
|
||
<div class="quote-empty__title">没有符合当前条件的报价</div>
|
||
<div class="quote-empty__text">重置筛选后可查看全部已导入版本。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quote-pagination" data-quote-pagination>
|
||
<span class="quote-pagination__label" data-quote-page-label></span>
|
||
<span class="quote-pagination__buttons">
|
||
<button class="quote-button" type="button" data-quote-page="previous">
|
||
<span class="material-icons-outlined" aria-hidden="true">chevron_left</span>
|
||
<span>上一页</span>
|
||
</button>
|
||
<button class="quote-button" type="button" data-quote-page="next">
|
||
<span>下一页</span>
|
||
<span class="material-icons-outlined" aria-hidden="true">chevron_right</span>
|
||
</button>
|
||
</span>
|
||
</div>
|
||
@else
|
||
<div class="quote-empty" role="status">
|
||
<div class="quote-empty__body">
|
||
<span class="material-icons-outlined" aria-hidden="true">file_present</span>
|
||
<div class="quote-empty__title">{{ $quoteSnapshot['message'] }}</div>
|
||
<div class="quote-empty__text">报价页面仍可访问,重新导入后会显示工作表列表。</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
@if ($quoteSnapshot['available'])
|
||
<div class="quote-modal" role="dialog" aria-modal="true" aria-labelledby="quote-detail-title" data-quote-modal>
|
||
<div class="quote-modal__panel">
|
||
<header class="quote-modal__header">
|
||
<div class="quote-modal__title-wrap">
|
||
<div class="quote-modal__title" id="quote-detail-title" data-quote-detail="title"></div>
|
||
<div class="quote-modal__subtitle">原工作表内容 · 只读</div>
|
||
</div>
|
||
<div data-stock-original-links style="display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-left:auto"></div>
|
||
<button class="quote-modal__close" type="button" aria-label="关闭报价明细" title="关闭" data-quote-close>
|
||
<span class="material-icons-outlined" aria-hidden="true">close</span>
|
||
</button>
|
||
</header>
|
||
<div style="padding:10px 20px;border-bottom:1px solid var(--quote-border)"><label>报价时期 <select data-quote-version-picker class="quote-control" style="max-width:100%"></select></label></div>
|
||
<dl class="quote-detail-meta">
|
||
<div><dt>版本号</dt><dd data-quote-detail="id"></dd></div>
|
||
<div><dt>快递公司</dt><dd data-quote-detail="carrier"></dd></div>
|
||
<div><dt>报价类型</dt><dd data-quote-detail="type"></dd></div>
|
||
<div><dt>原始范围</dt><dd data-quote-detail="range"></dd></div>
|
||
<div><dt>执行日期</dt><dd data-quote-detail="date"></dd></div>
|
||
<div><dt>公式单元格</dt><dd data-quote-detail="formulas"></dd></div>
|
||
<div><dt>系统计费规则</dt><dd data-quote-detail="billing"></dd></div>
|
||
</dl>
|
||
<div class="quote-signed" data-quote-signed hidden>
|
||
<span class="material-icons-outlined" aria-hidden="true">task_alt</span>
|
||
<span class="quote-signed__text" data-quote-signed-text>已签署回传</span>
|
||
<a class="quote-button" data-quote-signed-link="signed"><span class="material-icons-outlined" aria-hidden="true">picture_as_pdf</span><span>回签单 PDF</span></a>
|
||
<a class="quote-button" data-quote-signed-link="certificate"><span class="material-icons-outlined" aria-hidden="true">verified</span><span>签署完成证书</span></a>
|
||
<a class="quote-button" data-quote-signed-link="export" title="导出报价表(xlsx,WPS / Excel 可打开),只含省份价格"><span class="material-icons-outlined" aria-hidden="true">table_view</span><span>导出电子档</span></a>
|
||
</div>
|
||
<div class="quote-detail-table-wrap">
|
||
<table class="quote-detail-table" data-quote-detail-table></table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<dialog data-period-dialog style="max-width:480px;width:calc(100% - 32px);padding:20px;border:1px solid #D8D2C7;border-radius:12px;color:#171717;background:#fff">
|
||
<form method="POST" data-period-form>@csrf
|
||
<h3 style="font-size:14px;margin:0 0 12px">设置存量报价截止日期</h3>
|
||
<p data-period-title></p>
|
||
<label style="display:grid;gap:6px;margin-bottom:12px">截止日期(当天仍适用)<input class="quote-control" type="date" name="end_date" required></label>
|
||
<label style="display:grid;gap:6px;margin-bottom:12px">调整依据<textarea class="quote-control" name="reason" minlength="2" maxlength="1000" required></textarea></label>
|
||
<p style="font-size:12px;color:#595959">仅更新执行期间并保留操作记录,不修改报价原文或签署原件。期间不得与其他已录入报价重叠。</p>
|
||
<div style="display:flex;gap:8px;justify-content:flex-end"><button type="button" class="quote-button" data-period-cancel>取消</button><button class="quote-button" type="submit">保存截止日期</button></div>
|
||
</form>
|
||
</dialog>
|
||
<script>
|
||
(function () {
|
||
var root = document.querySelector('[data-quote-management]');
|
||
|
||
if (!root || root.dataset.initialized === 'true') {
|
||
return;
|
||
}
|
||
|
||
root.dataset.initialized = 'true';
|
||
|
||
var quotes = @json($quotes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
|
||
var pageSize = 12;
|
||
var currentPage = 1;
|
||
var filteredQuotes = quotes.slice();
|
||
var body = root.querySelector('[data-quote-table-body]');
|
||
var empty = root.querySelector('[data-quote-filter-empty]');
|
||
var tableWrap = root.querySelector('.quote-table-wrap');
|
||
var pagination = root.querySelector('[data-quote-pagination]');
|
||
var modal = root.querySelector('[data-quote-modal]');
|
||
var exportBase = @json(route('business.courier-quote-signatures.export'));
|
||
var exportAnchor = root.querySelector('[data-quote-export]');
|
||
if (exportAnchor) {
|
||
exportAnchor.addEventListener('click', function (event) {
|
||
if (exportAnchor.getAttribute('aria-disabled') === 'true') {
|
||
event.preventDefault();
|
||
}
|
||
});
|
||
}
|
||
var lastFocusedElement = null;
|
||
var expandedGroups = new Set();
|
||
var asOf = @json(today()->toDateString());
|
||
var dateLabel = create('label', 'quote-filter-label', '查看日期 ');
|
||
var dateInput = create('input', 'quote-control');
|
||
dateInput.type = 'date'; dateInput.value = asOf;
|
||
dateInput.setAttribute('aria-label', '按日期查看报价版本');
|
||
dateLabel.appendChild(dateInput);
|
||
root.querySelector('[data-quote-result-count]').before(dateLabel);
|
||
dateInput.addEventListener('change', function () { if (dateInput.value) { asOf = dateInput.value; filterQuotes(true); } });
|
||
function groupKey(quote) { return quote.group_key || ('sheet:' + quote.id); }
|
||
function versionsFor(quote) { return quotes.filter(function (v) { return groupKey(v) === groupKey(quote); }).sort(function (a,b) { return (b.effective_date || '').localeCompare(a.effective_date || '') || b.id.localeCompare(a.id); }); }
|
||
function effectiveIn(versions) { return versions.filter(function (v) { return v.eligible && v.effective_date && v.effective_date <= asOf && (!v.end_date || v.end_date >= asOf); }).sort(function(a,b){return b.effective_date.localeCompare(a.effective_date) || b.id.localeCompare(a.id);})[0]; }
|
||
function atDate(quote) {
|
||
if (!quote.eligible) return quote;
|
||
var copy = Object.assign({}, quote);
|
||
if (asOf !== @json(today()->toDateString())) { delete copy.status_note; delete copy.superseded_by; }
|
||
var current = effectiveIn(versionsFor(quote));
|
||
if (quote.effective_date > asOf) copy.status = '待生效';
|
||
else if (quote.end_date && quote.end_date < asOf) copy.status = '已到期';
|
||
else if (current && current.id !== quote.id) copy.status = current.end_date && !current.is_stock ? '暂停中' : '已失效';
|
||
else copy.status = quote.end_date && !quote.is_stock ? '生效中(临时)' : '生效中';
|
||
return copy;
|
||
}
|
||
var fields = {
|
||
keyword: root.querySelector('[data-quote-filter="keyword"]'),
|
||
carrier: root.querySelector('[data-quote-filter="carrier"]'),
|
||
type: root.querySelector('[data-quote-filter="type"]'),
|
||
status: root.querySelector('[data-quote-filter="status"]')
|
||
};
|
||
['生效中','待生效','已到期','已失效','已作废','暂停中','生效中(临时)'].forEach(function(status){var option=create('option','',status);option.value=status;fields.status.appendChild(option);});
|
||
|
||
function text(value, fallback) {
|
||
if (value === null || value === undefined || value === '') {
|
||
return fallback || '';
|
||
}
|
||
|
||
return String(value);
|
||
}
|
||
|
||
function cellText(value) {
|
||
if (typeof value === 'boolean') {
|
||
return value ? 'TRUE' : 'FALSE';
|
||
}
|
||
|
||
return text(value, '');
|
||
}
|
||
|
||
function create(tag, className, value) {
|
||
var element = document.createElement(tag);
|
||
|
||
if (className) {
|
||
element.className = className;
|
||
}
|
||
|
||
if (value !== undefined) {
|
||
element.textContent = value;
|
||
}
|
||
|
||
return element;
|
||
}
|
||
|
||
function updateUrl() {
|
||
var url = new URL(window.location.href);
|
||
url.searchParams.set('quote_date', asOf);
|
||
|
||
Object.keys(fields).forEach(function (key) {
|
||
var value = fields[key].value.trim();
|
||
|
||
if (value) {
|
||
url.searchParams.set(key, value);
|
||
} else {
|
||
url.searchParams.delete(key);
|
||
}
|
||
});
|
||
|
||
if (currentPage > 1) {
|
||
url.searchParams.set('quote_page', String(currentPage));
|
||
} else {
|
||
url.searchParams.delete('quote_page');
|
||
}
|
||
|
||
window.history.replaceState({}, '', url);
|
||
}
|
||
|
||
function restoreUrlFilters() {
|
||
var params = new URL(window.location.href).searchParams;
|
||
var requestedDate=params.get('quote_date');
|
||
if (/^\d{4}-\d{2}-\d{2}$/.test(requestedDate || '')) { dateInput.value=requestedDate;if(dateInput.value)asOf=dateInput.value; }
|
||
|
||
Object.keys(fields).forEach(function (key) {
|
||
var value = params.get(key);
|
||
var field = fields[key];
|
||
|
||
if (value !== null && Array.from(field.options || []).some(function (option) { return option.value === value; })) {
|
||
field.value = value;
|
||
} else if (key === 'keyword' && value !== null) {
|
||
field.value = value;
|
||
}
|
||
});
|
||
|
||
currentPage = Math.max(parseInt(params.get('quote_page') || '1', 10) || 1, 1);
|
||
}
|
||
|
||
function appendCell(row, value, className) {
|
||
var cell = create('td', className || '', value);
|
||
row.appendChild(cell);
|
||
return cell;
|
||
}
|
||
|
||
function renderRow(quote) {
|
||
var row = create('tr');
|
||
var nameCell = appendCell(row, '');
|
||
nameCell.appendChild(create('span', 'quote-table__name', quote.id));
|
||
appendCell(row, quote.company_name + (quote.courier_expcode ? ' · ' + quote.courier_expcode : ''));
|
||
appendCell(row, quote.quote_type);
|
||
appendCell(row, text(quote.source_range, '-'));
|
||
appendCell(row, quote.row_count + ' 行 × ' + quote.column_count + ' 列');
|
||
appendCell(row, text(quote.period_label || quote.effective_date, '-'));
|
||
var signCell = appendCell(row, quote.signature_status, 'quote-table__secondary');
|
||
if (quote.signed_files && quote.signed_files.signed) {
|
||
signCell.appendChild(document.createElement('br'));
|
||
var signedLink = create('a', 'quote-action', quote.signature_status === '历史签署资料已归档' ? '历史签署资料' : '回签单');
|
||
signedLink.href = quote.signed_files.signed;
|
||
signedLink.title = '下载 ' + quote.id + ' 签署资料' + (quote.signed_at ? '(' + quote.signed_at + ' 签署)' : '');
|
||
signedLink.setAttribute('aria-label', '下载回签单 ' + quote.id);
|
||
signCell.appendChild(signedLink);
|
||
}
|
||
|
||
var statusCell = appendCell(row, '');
|
||
statusCell.appendChild(create(
|
||
'span',
|
||
'quote-badge ' + (['已失效', '已作废', '已到期', '暂停中'].indexOf(quote.status) !== -1 ? 'quote-badge--muted' : (quote.effective_date ? 'quote-badge--ready' : 'quote-badge--attention')),
|
||
quote.status
|
||
));
|
||
if (quote.void_reason) {
|
||
statusCell.appendChild(create('div', 'quote-table__secondary', '作废:' + quote.void_reason));
|
||
}
|
||
if (quote.superseded_by) {
|
||
statusCell.appendChild(create('div', 'quote-table__secondary', '被 ' + quote.superseded_by + ' 替代'));
|
||
}
|
||
if (quote.status_note) {
|
||
statusCell.appendChild(create('div', 'quote-table__secondary', quote.status_note));
|
||
}
|
||
|
||
var actionCell = appendCell(row, '');
|
||
var action = create('button', 'quote-action', '查看');
|
||
action.type = 'button';
|
||
action.dataset.quoteOpen = quote.id;
|
||
action.setAttribute('aria-label', '查看 ' + quote.sheet_name);
|
||
actionCell.appendChild(action);
|
||
if (quote.period_end_url) {
|
||
var periodButton = create('button', 'quote-action', '设置截止日期');
|
||
periodButton.type = 'button'; periodButton.dataset.periodEnd = quote.id;
|
||
actionCell.appendChild(periodButton);
|
||
}
|
||
var changeAction = create('a', 'quote-action', '变价');
|
||
changeAction.href = quote.requote_url || (@json(route('business.courier-quote-changes.start')) + '?source_quote_id=' + encodeURIComponent(quote.id));
|
||
changeAction.title = '发起 ' + quote.sheet_name + ' 的变更草稿';
|
||
changeAction.setAttribute('aria-label', '变价 ' + quote.sheet_name);
|
||
if (quote.status !== '已作废') {
|
||
actionCell.appendChild(changeAction);
|
||
}
|
||
// 动态重建行时按字段键对应当前表头,保留用户列顺序与隐藏设置。
|
||
var columnKeys = ['报价版本_0', '快递公司_1', '报价类型_2', '原始范围_3', '数据规模_4', '执行日期_5', '签章_6', '资料状态_7', '操作_8'];
|
||
var cellsByKey = {};
|
||
Array.from(row.children).forEach(function (cell, index) {
|
||
cell.dataset.financeColumnKey = columnKeys[index];
|
||
cellsByKey[columnKeys[index]] = cell;
|
||
});
|
||
body.closest('table').querySelectorAll('thead th[data-finance-column-key]').forEach(function (header) {
|
||
var cell = cellsByKey[header.dataset.financeColumnKey];
|
||
if (!cell) return;
|
||
cell.classList.toggle('is-column-hidden', header.classList.contains('is-column-hidden'));
|
||
row.appendChild(cell);
|
||
});
|
||
return row;
|
||
}
|
||
|
||
function render() {
|
||
var groups = [];
|
||
filteredQuotes.forEach(function (quote) { var group = groups.find(function (g) { return g.key === groupKey(quote); }); if (!group) { group = {key:groupKey(quote), versions:[]};groups.push(group); } group.versions.push(quote); });
|
||
var pageCount = Math.max(Math.ceil(groups.length / pageSize), 1);
|
||
currentPage = Math.min(currentPage, pageCount);
|
||
var start = (currentPage - 1) * pageSize;
|
||
var visibleGroups = groups.slice(start, start + pageSize);
|
||
body.replaceChildren();
|
||
visibleGroups.forEach(function (group) {
|
||
group.versions.sort(function(a,b){return (b.effective_date || '').localeCompare(a.effective_date || '') || b.id.localeCompare(a.id);});
|
||
group.versions.forEach(function (version) { body.appendChild(renderRow(version)); });
|
||
});
|
||
|
||
root.querySelector('[data-quote-result-count]').textContent = '共 ' + groups.length + ' 组快递报价 · ' + filteredQuotes.length + ' 个版本';
|
||
var exportLink = root.querySelector('[data-quote-export]');
|
||
if (exportLink) {
|
||
var signedCodes = filteredQuotes.filter(function (quote) { return quote.signed_files && quote.signed_files.export; })
|
||
.map(function (quote) { return quote.id; });
|
||
exportLink.href = exportBase + (signedCodes.length ? '?codes=' + encodeURIComponent(signedCodes.join(',')) : '');
|
||
exportLink.setAttribute('aria-disabled', signedCodes.length ? 'false' : 'true');
|
||
exportLink.style.opacity = signedCodes.length ? '' : '0.48';
|
||
exportLink.querySelector('[data-quote-export-label]').textContent = '导出回签单' + (signedCodes.length ? '(' + signedCodes.length + ')' : '');
|
||
}
|
||
root.querySelector('[data-quote-page-label]').textContent = filteredQuotes.length
|
||
? '共 ' + groups.length + ' 组 · 共 ' + pageCount + ' 页 · 当前第 ' + currentPage + ' 页'
|
||
: '共 0 条 · 共 0 页';
|
||
root.querySelector('[data-quote-page="previous"]').disabled = currentPage <= 1;
|
||
root.querySelector('[data-quote-page="next"]').disabled = currentPage >= pageCount;
|
||
tableWrap.hidden = filteredQuotes.length === 0;
|
||
empty.hidden = filteredQuotes.length !== 0;
|
||
pagination.hidden = false;
|
||
updateUrl();
|
||
}
|
||
|
||
function filterQuotes(resetPage) {
|
||
var keyword = fields.keyword.value.trim().toLocaleLowerCase('zh-CN');
|
||
|
||
filteredQuotes = quotes.map(atDate).filter(function (quote) {
|
||
var matchesKeyword = !keyword || (quote.company_name + ' ' + quote.sheet_name + ' ' + quote.id + ' ' + quote.carrier + ' ' + (quote.courier_expcode || ''))
|
||
.toLocaleLowerCase('zh-CN')
|
||
.includes(keyword);
|
||
|
||
return matchesKeyword
|
||
&& (!fields.carrier.value || quote.company_name === fields.carrier.value)
|
||
&& (!fields.type.value || quote.quote_type === fields.type.value)
|
||
&& (!fields.status.value || quote.status === fields.status.value);
|
||
});
|
||
|
||
if (resetPage) {
|
||
currentPage = 1;
|
||
}
|
||
|
||
render();
|
||
}
|
||
|
||
function setDetail(name, value) {
|
||
root.querySelector('[data-quote-detail="' + name + '"]').textContent = value;
|
||
}
|
||
|
||
function excelColumn(index) {
|
||
var label = '';
|
||
var value = index + 1;
|
||
|
||
while (value > 0) {
|
||
value--;
|
||
label = String.fromCharCode(65 + (value % 26)) + label;
|
||
value = Math.floor(value / 26);
|
||
}
|
||
|
||
return label;
|
||
}
|
||
|
||
function openDetail(quote, trigger) {
|
||
if (trigger) lastFocusedElement = trigger;
|
||
var picker = root.querySelector('[data-quote-version-picker]');
|
||
picker.replaceChildren();
|
||
versionsFor(quote).forEach(function (version) {var option=create('option','',version.id+' · '+(version.effective_date||'未标日期')+' 至 '+(version.end_date||'长期有效'));option.value=version.id;option.selected=version.id===quote.id;picker.appendChild(option);});
|
||
setDetail('title', quote.sheet_name);
|
||
setDetail('id', quote.id);
|
||
setDetail('carrier', quote.company_name + (quote.courier_expcode ? ' · ' + quote.courier_expcode : ''));
|
||
setDetail('type', quote.quote_type);
|
||
setDetail('range', text(quote.source_range, '-'));
|
||
setDetail('date', text(quote.period_label || quote.effective_date, 'Excel 未标注'));
|
||
setDetail('formulas', String(quote.formula_count));
|
||
setDetail('billing', text(quote.billing_summary, '未设置(运费需人工核算)'));
|
||
var originals = root.querySelector('[data-stock-original-links]');
|
||
originals.replaceChildren();
|
||
(quote.stock_originals || []).forEach(function (file, index, files) {
|
||
var link = create('a', 'quote-button', (file.previewable ? '预览签署原件' : '下载签署原件') + (files.length > 1 ? ' ' + (index + 1) : ''));
|
||
link.href = file.url;
|
||
link.title = file.name;
|
||
link.target = '_blank';
|
||
link.rel = 'noopener noreferrer';
|
||
originals.appendChild(link);
|
||
});
|
||
var signedBox = root.querySelector('[data-quote-signed]');
|
||
var files = quote.signed_files || {};
|
||
signedBox.hidden = !files.signed;
|
||
root.querySelector('[data-quote-signed-text]').textContent = '已签署回传' + (quote.signed_at ? ' · ' + quote.signed_at : '');
|
||
signedBox.querySelectorAll('[data-quote-signed-link]').forEach(function (link) {
|
||
var url = files[link.getAttribute('data-quote-signed-link')];
|
||
link.hidden = !url;
|
||
if (url) { link.href = url; } else { link.removeAttribute('href'); }
|
||
});
|
||
|
||
var table = root.querySelector('[data-quote-detail-table]');
|
||
var head = create('thead');
|
||
var headerRow = create('tr');
|
||
headerRow.appendChild(create('th', 'quote-detail-table__row-number', '序号'));
|
||
|
||
for (var column = 0; column < quote.column_count; column++) {
|
||
headerRow.appendChild(create('th', '', excelColumn(column)));
|
||
}
|
||
|
||
head.appendChild(headerRow);
|
||
var tableBody = create('tbody');
|
||
|
||
quote.values.forEach(function (values, rowIndex) {
|
||
var row = create('tr');
|
||
row.appendChild(create('th', 'quote-detail-table__row-number', String(rowIndex + 1)));
|
||
|
||
for (var columnIndex = 0; columnIndex < quote.column_count; columnIndex++) {
|
||
var cell = create('td', '', cellText(values[columnIndex]));
|
||
var formula = quote.formulas[rowIndex] && quote.formulas[rowIndex][columnIndex];
|
||
|
||
if (formula) {
|
||
cell.appendChild(create('span', 'quote-detail-table__formula', '=' + String(formula).replace(/^=/, '')));
|
||
}
|
||
|
||
row.appendChild(cell);
|
||
}
|
||
|
||
tableBody.appendChild(row);
|
||
});
|
||
|
||
table.replaceChildren(head, tableBody);
|
||
modal.classList.add('is-open');
|
||
document.body.style.overflow = 'hidden';
|
||
root.querySelector('[data-quote-close]').focus();
|
||
}
|
||
|
||
function closeDetail() {
|
||
modal.classList.remove('is-open');
|
||
document.body.style.overflow = '';
|
||
|
||
if (lastFocusedElement) {
|
||
lastFocusedElement.focus();
|
||
}
|
||
}
|
||
|
||
Object.keys(fields).forEach(function (key) {
|
||
fields[key].addEventListener(key === 'keyword' ? 'input' : 'change', function () {
|
||
filterQuotes(true);
|
||
});
|
||
});
|
||
|
||
root.querySelector('[data-quote-reset]').addEventListener('click', function () {
|
||
Object.keys(fields).forEach(function (key) { fields[key].value = ''; });
|
||
filterQuotes(true);
|
||
fields.keyword.focus();
|
||
});
|
||
|
||
root.querySelector('[data-quote-page="previous"]').addEventListener('click', function () {
|
||
currentPage = Math.max(currentPage - 1, 1);
|
||
render();
|
||
});
|
||
|
||
root.querySelector('[data-quote-page="next"]').addEventListener('click', function () {
|
||
currentPage++;
|
||
render();
|
||
});
|
||
|
||
body.addEventListener('click', function (event) {
|
||
var period=event.target.closest('[data-period-end]');
|
||
if(period){var item=quotes.find(function(q){return q.id===period.dataset.periodEnd;});var form=root.querySelector('[data-period-form]');form.action=item.period_end_url;form.elements.end_date.value=item.end_date||'';form.elements.end_date.min=item.effective_date;form.elements.reason.value='';root.querySelector('[data-period-title]').textContent=item.company_name+' · '+item.id+' · '+item.effective_date+' 起';root.querySelector('[data-period-dialog]').showModal();return;}
|
||
var trigger = event.target.closest('[data-quote-open]');
|
||
if (!trigger) {
|
||
return;
|
||
}
|
||
|
||
var quote = quotes.find(function (item) { return item.id === trigger.dataset.quoteOpen; });
|
||
|
||
if (quote) {
|
||
openDetail(quote, trigger);
|
||
}
|
||
});
|
||
|
||
root.querySelector('[data-quote-close]').addEventListener('click', closeDetail);
|
||
root.querySelector('[data-quote-version-picker]').addEventListener('change',function(event){var quote=quotes.find(function(q){return q.id===event.target.value;});if(quote)openDetail(quote);});
|
||
root.querySelector('[data-period-cancel]').addEventListener('click',function(){root.querySelector('[data-period-dialog]').close();});
|
||
modal.addEventListener('click', function (event) {
|
||
if (event.target === modal) {
|
||
closeDetail();
|
||
}
|
||
});
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
|
||
closeDetail();
|
||
}
|
||
|
||
});
|
||
|
||
restoreUrlFilters();
|
||
filterQuotes(false);
|
||
}());
|
||
</script>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="quote-local-panel" id="quote-warehouse-panel" role="tabpanel" data-quote-panel="warehouse" @if ($quoteView !== 'warehouse') hidden @endif>
|
||
<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]');
|
||
if (!root || root.dataset.localTabsReady === 'true') return;
|
||
root.dataset.localTabsReady = 'true';
|
||
|
||
const tabs = [...root.querySelectorAll('[data-quote-view]')];
|
||
const panels = [...root.querySelectorAll('[data-quote-panel]')];
|
||
const activate = (view, updateUrl = true) => {
|
||
tabs.forEach((tab) => {
|
||
const active = tab.dataset.quoteView === view;
|
||
tab.classList.toggle('is-active', active);
|
||
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
||
});
|
||
panels.forEach((panel) => { panel.hidden = panel.dataset.quotePanel !== view; });
|
||
if (updateUrl) {
|
||
const url = new URL(window.location.href);
|
||
if (view === 'courier') url.searchParams.delete('quote_view');
|
||
else url.searchParams.set('quote_view', view);
|
||
history.replaceState({}, '', url);
|
||
}
|
||
};
|
||
|
||
tabs.forEach((tab) => tab.addEventListener('click', () => activate(tab.dataset.quoteView)));
|
||
})();
|
||
</script>
|
||
</section>
|