- 生成入驻二维码新增 11 个必填项(快递方看不到,放在入驻流程下方单独一块):快递类型、快递合作方式、快递属性、扣款方式、单号类型、快递邮箱、 是否需要核对物流点、快递发货方式、是否已有工作平台账号、快递网点代码、网点所在地区;入驻列表、入驻详情可查看和修改 - 快递资料:扣款方式对齐到「扣款方式」列(工作平台没填时用入驻的),其余字段接在入驻信息列里;网点代码/地区进网点明细;导出同步 - 不再依赖钉钉「新建快递」流程字段,ERP 开通改为手动补录 ERP账户ID - 快递资料「自定义3」列、筛选、编辑和导出表头改名为「扣款方式」(字段仍是 undefined3) - 报价管理工具栏、报价历史页去掉「发起报价变更」:它默认拿第一份报价走老的改格子流程,对入驻快递会绕开后台变价; 变价统一从报价列表每一行的「变价」进入;老报价编辑页新增 …/changes/new 入口,避免套餐限制中间件把 GET …/create 跳到用户页 - 新增迁移 2026_10_14_300000:business_courier_onboardings 加 11 个可空字段 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019gzfsnV3fJ89CoUy1D9V9g
220 lines
19 KiB
PHP
220 lines
19 KiB
PHP
@php
|
||
$ready = (bool) ($onboardingReady ?? false);
|
||
$rows = $onboardingRows ?? null;
|
||
$group = $onboardingGroup ?? 'pending';
|
||
$groups = $onboardingGroups ?? [];
|
||
$counts = $onboardingCounts ?? [];
|
||
$search = $onboardingSearch ?? '';
|
||
$groupUrl = static fn (string $key): string => route('business.pages.show', array_filter([
|
||
'section' => 'courier-payables',
|
||
'page' => 'courier-onboarding',
|
||
'group' => $key,
|
||
'search' => $search !== '' ? $search : null,
|
||
]));
|
||
$badge = static fn (string $status): string => match ($status) {
|
||
'submitted', 'first_approved', 'approved' => 'is-pending',
|
||
'returned' => 'is-warning',
|
||
'activated' => 'is-success',
|
||
'rejected', 'cancelled' => 'is-muted',
|
||
default => 'is-info',
|
||
};
|
||
@endphp
|
||
|
||
<section class="co-page" aria-label="快递入驻">
|
||
<style>
|
||
.co-page { --co-ink:var(--vip-content-ink,#171717); --co-muted:var(--vip-content-muted,#595959); --co-accent:var(--vip-content-accent-dark,#856632); --co-soft:var(--vip-content-accent-soft,#F7F0DE); --co-border:var(--vip-border,#D8D2C7); display:grid; gap:1rem; color:var(--co-ink); font-family:JetBrains Mono,"Microsoft YaHei","PingFang SC",monospace; font-size:.8125rem; }
|
||
.co-page *, .co-page *::before, .co-page *::after { box-sizing:border-box; }
|
||
.co-notice { display:flex; align-items:flex-start; gap:.6rem; padding:.75rem 1rem; border:1px solid #d7c39d; border-radius:.65rem; background:#fffaf0; color:#6a5129; line-height:1.55; }
|
||
.co-notice .material-icons-outlined { color:var(--co-accent); font-size:1.15rem; }
|
||
.co-notice.is-error { border-color:#e3b0b0; background:#fdf1f1; color:#7a1010; } .co-notice.is-error .material-icons-outlined { color:#b80000; }
|
||
.co-hero { display:grid; grid-template-columns:minmax(0,1.4fr) minmax(18rem,1fr); gap:1rem; align-items:start; }
|
||
.co-card { overflow:hidden; border:1px solid var(--co-border); border-radius:.75rem; background:#fff; }
|
||
.co-card__head { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:.5rem 1rem; padding:.85rem 1.15rem; border-bottom:1px solid var(--co-border); }
|
||
.co-card__title { display:flex; align-items:center; gap:.45rem; margin:0; font-size:.9rem; font-weight:700; } .co-card__title .material-icons-outlined { color:var(--co-accent); font-size:1.1rem; }
|
||
.co-card__hint { color:var(--co-muted); font-size:.72rem; }
|
||
.co-flow { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:0; margin:0; padding:1rem 1.15rem 1.15rem; list-style:none; counter-reset:step; }
|
||
.co-flow li { position:relative; display:grid; align-content:start; gap:.25rem; padding-right:.75rem; counter-increment:step; }
|
||
.co-flow li::before { display:grid; width:1.6rem; height:1.6rem; place-items:center; border-radius:50%; background:var(--co-soft); color:var(--co-accent); font-weight:700; font-size:.72rem; content:counter(step); }
|
||
.co-flow li:not(:last-child)::after { position:absolute; top:.8rem; left:2rem; right:.5rem; height:1px; background:var(--co-border); content:''; }
|
||
.co-flow strong { font-size:.78rem; } .co-flow span { color:var(--co-muted); font-size:.7rem; line-height:1.5; }
|
||
.co-invite { display:grid; gap:.7rem; padding:1rem 1.15rem 1.15rem; }
|
||
.co-field { display:grid; gap:.3rem; min-width:0; } .co-field label { color:var(--co-muted); font-size:.72rem; font-weight:600; }
|
||
.co-field input, .co-field select { width:100%; height:2.2rem; padding:0 .6rem; border:1px solid var(--co-border); border-radius:.45rem; background:#fff; color:var(--co-ink); font:inherit; }
|
||
.co-field input:focus, .co-field select:focus { border-color:var(--co-accent); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.18); }
|
||
.co-help { color:var(--co-muted); font-size:.68rem; line-height:1.5; } .co-error { color:#b80000; font-size:.72rem; }
|
||
.co-invite__row { display:grid; grid-template-columns:minmax(0,1fr) 7rem; gap:.6rem; }
|
||
.co-hero__main { display:grid; gap:1rem; align-content:start; min-width:0; }
|
||
.co-profile-grid { display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:.7rem .8rem; padding:1rem 1.15rem 1.15rem; }
|
||
@media (max-width: 1100px) { .co-profile-grid { grid-template-columns:repeat(2, minmax(0,1fr)); } }
|
||
@media (max-width: 560px) { .co-profile-grid { grid-template-columns:1fr; } }
|
||
.co-button { display:inline-flex; align-items:center; justify-content:center; gap:.35rem; height:2.2rem; padding:0 .8rem; border:1px solid var(--co-border); border-radius:.45rem; background:#fff; color:var(--co-ink); font:inherit; font-weight:600; text-decoration:none; white-space:nowrap; cursor:pointer; }
|
||
.co-button .material-icons-outlined { font-size:1rem; } .co-button--primary { border-color:var(--co-accent); background:var(--co-accent); color:#fff; } .co-button--primary:hover { background:#6a5129; }
|
||
.co-toolbar { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:.75rem; padding:.75rem 1.15rem; border-bottom:1px solid var(--co-border); background:#fffdf8; }
|
||
.co-tabs { display:flex; flex-wrap:wrap; gap:1.1rem; }
|
||
.co-tab { display:inline-flex; align-items:center; gap:.35rem; min-height:2.1rem; padding:0 .15rem; border-bottom:2px solid transparent; color:var(--co-muted); font-weight:600; text-decoration:none; }
|
||
.co-tab.is-active { border-color:var(--co-accent); color:var(--co-accent); }
|
||
.co-tab__count { min-width:1.4rem; padding:.05rem .4rem; border-radius:999px; background:#f1ede4; color:var(--co-muted); font-size:.68rem; text-align:center; font-variant-numeric:tabular-nums; }
|
||
.co-tab.is-active .co-tab__count { background:var(--co-soft); color:var(--co-accent); }
|
||
.co-search { display:flex; gap:.45rem; } .co-search input { width:16rem; height:2.2rem; padding:0 .6rem; border:1px solid var(--co-border); border-radius:.45rem; font:inherit; }
|
||
.co-table-wrap { overflow:auto; }
|
||
.co-table { width:100%; min-width:62rem; border-collapse:separate; border-spacing:0; font-size:.75rem; }
|
||
.co-table th, .co-table td { padding:.62rem .7rem; border-bottom:1px solid var(--co-border); text-align:left; vertical-align:middle; white-space:nowrap; }
|
||
.co-table th { background:var(--co-soft); font-weight:700; }
|
||
.co-table tbody tr:hover td { background:#fcfaf4; }
|
||
.co-table .is-code { font-variant-numeric:tabular-nums; }
|
||
.co-table .is-clamp { max-width:16rem; overflow:hidden; text-overflow:ellipsis; }
|
||
.co-table a { color:var(--co-accent); font-weight:600; text-decoration:none; }
|
||
.co-badge { display:inline-flex; align-items:center; padding:.15rem .5rem; border-radius:999px; font-size:.7rem; font-weight:700; }
|
||
.co-badge.is-pending { background:#fff4df; color:#935f07; } .co-badge.is-warning { background:#fde9d4; color:#8a4a06; } .co-badge.is-success { background:#edf5e8; color:#53793e; } .co-badge.is-muted { background:#f1efea; color:#595959; } .co-badge.is-info { background:#e8f2f8; color:#006395; }
|
||
.co-empty { padding:3rem 1rem; text-align:center; color:var(--co-muted); }
|
||
.co-footer { display:flex; align-items:center; justify-content:space-between; padding:.65rem 1.15rem; color:var(--co-muted); font-size:.72rem; }
|
||
.co-footer nav { display:flex; gap:.35rem; } .co-footer a { display:inline-grid; width:2rem; height:2rem; place-items:center; border:1px solid var(--co-border); border-radius:.4rem; color:var(--co-ink); } .co-footer a.is-disabled { pointer-events:none; opacity:.4; }
|
||
@media (max-width: 1100px) { .co-hero { grid-template-columns:1fr; } .co-flow { grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:1rem; } .co-flow li::after { display:none; } }
|
||
</style>
|
||
|
||
@if (! $ready)
|
||
<div class="co-notice is-error" role="alert">
|
||
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
||
<span>快递入驻数据表尚未完成迁移,请联系管理员执行数据库迁移后再使用。</span>
|
||
</div>
|
||
@else
|
||
@if (session('success'))
|
||
<div class="co-notice" role="status"><span class="material-icons-outlined" aria-hidden="true">check_circle</span><span>{{ session('success') }}</span></div>
|
||
@endif
|
||
|
||
<div class="co-hero">
|
||
<div class="co-hero__main">
|
||
<div class="co-card">
|
||
<div class="co-card__head">
|
||
<h2 class="co-card__title"><span class="material-icons-outlined" aria-hidden="true">route</span>入驻流程</h2>
|
||
<span class="co-card__hint">快递方无需账号,扫码即可填写</span>
|
||
</div>
|
||
<ol class="co-flow">
|
||
<li><strong>生成二维码</strong><span>填快递公司代码、系统快递名称和快递资料,生成后发给快递方</span></li>
|
||
<li><strong>快递扫码填写</strong><span>主体、联系人、结算账户、报价、资质照片</span></li>
|
||
<li><strong>对接人初审</strong><span>资料不全可退回,快递用原码补件</span></li>
|
||
<li><strong>财务复审</strong><span>确认系统快递名称,首版报价进报价管理</span></li>
|
||
<li><strong>签署并开通</strong><span>协议签署后开通,信息对齐到快递资料</span></li>
|
||
</ol>
|
||
</div>
|
||
<section class="co-card" aria-labelledby="co-profile-title">
|
||
<div class="co-card__head">
|
||
<h2 class="co-card__title" id="co-profile-title"><span class="material-icons-outlined" aria-hidden="true">local_shipping</span>快递资料</h2>
|
||
<span class="co-card__hint">全部必填 · 我方填写,快递方看不到;生成后对齐到快递资料,入驻详情里也能改</span>
|
||
</div>
|
||
<div class="co-profile-grid">
|
||
@foreach (\App\Models\Business\CourierOnboarding::PROFILE_FIELDS as $field => [$label, $options])
|
||
<div class="co-field">
|
||
<label for="co-{{ $field }}">{{ $label }} *</label>
|
||
@if ($options !== null)
|
||
<select id="co-{{ $field }}" name="{{ $field }}" form="invite" required>
|
||
<option value=""></option>
|
||
@foreach ($options as $option)
|
||
<option value="{{ $option }}" @selected(old($field) === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
@else
|
||
<input id="co-{{ $field }}" name="{{ $field }}" form="invite" required type="{{ $field === 'courier_email' ? 'email' : 'text' }}" maxlength="{{ $field === 'network_code' ? 64 : 191 }}" value="{{ old($field) }}" placeholder="{{ ['courier_email' => '如:kefu@example.com', 'network_code' => '如:519001', 'network_region' => '如:江苏省常州市天宁区'][$field] ?? '' }}" autocomplete="off">
|
||
@endif
|
||
@error($field)<span class="co-error">{{ $message }}</span>@enderror
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<form class="co-card" id="invite" method="POST" action="{{ route('business.courier-onboardings.store') }}">
|
||
@csrf
|
||
<div class="co-card__head">
|
||
<h2 class="co-card__title"><span class="material-icons-outlined" aria-hidden="true">qr_code_2</span>生成入驻二维码</h2>
|
||
</div>
|
||
<div class="co-invite">
|
||
<div class="co-field">
|
||
<label for="co-expcode">快递公司代码 *</label>
|
||
<input id="co-expcode" name="workplatform_expcode" type="text" maxlength="64" required value="{{ old('workplatform_expcode', request('expcode')) }}" placeholder="如:SQZTKY" autocomplete="off">
|
||
<span class="co-help">与快递资料「快递公司代码」一致,入驻信息按这个代码对齐到快递资料。</span>
|
||
@error('workplatform_expcode')<span class="co-error">{{ $message }}</span>@enderror
|
||
</div>
|
||
<div class="co-field">
|
||
<label for="co-courier-name">系统快递名称 *</label>
|
||
<input id="co-courier-name" name="courier_name" type="text" maxlength="100" required value="{{ old('courier_name', request('courier_name')) }}" placeholder="如:新北中通、小件宿迁圆通">
|
||
<span class="co-help">我方规定的名称,需与报价名、发货数据里的快递名称完全一致;快递方看不到。</span>
|
||
@error('courier_name')<span class="co-error">{{ $message }}</span>@enderror
|
||
</div>
|
||
<div class="co-invite__row">
|
||
<div class="co-field">
|
||
<label for="co-invite-label">备注(快递方也能看到)</label>
|
||
<input id="co-invite-label" name="invite_label" type="text" maxlength="100" value="{{ old('invite_label') }}" placeholder="如:新北中通 · 张经理">
|
||
</div>
|
||
<div class="co-field">
|
||
<label for="co-valid-days">有效天数</label>
|
||
<select id="co-valid-days" name="valid_days">
|
||
@foreach ([7, 15, 30, 60, 90] as $days)
|
||
<option value="{{ $days }}" @selected((int) old('valid_days', 30) === $days)>{{ $days }} 天</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="co-button co-button--primary"><span class="material-icons-outlined" aria-hidden="true">qr_code_2</span>生成二维码</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="co-card">
|
||
<div class="co-toolbar">
|
||
<nav class="co-tabs" aria-label="入驻状态">
|
||
@foreach ($groups as $key => $config)
|
||
<a class="co-tab {{ $group === $key ? 'is-active' : '' }}" href="{{ $groupUrl($key) }}" @if ($group === $key) aria-current="page" @endif>
|
||
{{ $config['label'] }}<span class="co-tab__count">{{ number_format($counts[$key] ?? 0) }}</span>
|
||
</a>
|
||
@endforeach
|
||
</nav>
|
||
<form class="co-search" method="GET" role="search">
|
||
<input type="hidden" name="group" value="{{ $group }}">
|
||
<input type="search" name="search" value="{{ $search }}" placeholder="单号 / 公司 / 网点 / 对接人 / 电话" aria-label="搜索入驻申请">
|
||
<button type="submit" class="co-button"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button>
|
||
</form>
|
||
</div>
|
||
<div class="co-table-wrap">
|
||
<table class="co-table" aria-label="入驻申请">
|
||
<thead>
|
||
<tr><th>入驻单号</th><th>状态</th><th>备注</th><th>类型</th><th>公司 / 姓名</th><th>网点 / 品牌</th><th>快递公司代码</th>@foreach (\App\Models\Business\CourierOnboarding::PROFILE_FIELDS as [$profileLabel])<th>{{ $profileLabel }}</th>@endforeach<th>对接人</th><th>系统快递名称</th><th>附件</th><th>提交时间</th><th>最近更新</th><th>操作</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($rows ?? [] as $row)
|
||
<tr>
|
||
<td class="is-code"><a href="{{ route('business.courier-onboardings.show', $row) }}">{{ $row->number }}</a></td>
|
||
<td><span class="co-badge {{ $badge($row->status) }}">{{ $row->linkExpired() && in_array($row->status, ['invited', 'returned'], true) ? '链接已过期' : $row->statusLabel() }}</span></td>
|
||
<td class="is-clamp" title="{{ $row->invite_label }}">{{ $row->invite_label ?: '—' }}</td>
|
||
<td>{{ $row->company_full_name ? $row->entityLabel() : '—' }}</td>
|
||
<td class="is-clamp" title="{{ $row->company_full_name }}">{{ $row->company_full_name ?: '—' }}</td>
|
||
<td>{{ $row->network_name ?: '—' }}{{ $row->carrier_brand ? ' · ' . $row->carrier_brand : '' }}</td>
|
||
<td class="is-code">{{ $row->workplatform_expcode ?: '—' }}</td>
|
||
@foreach (array_keys(\App\Models\Business\CourierOnboarding::PROFILE_FIELDS) as $profileField)
|
||
<td class="is-clamp" title="{{ $row->{$profileField} }}">{{ $row->{$profileField} ?: '—' }}</td>
|
||
@endforeach
|
||
<td>{{ $row->contact_name ?: '—' }}{{ $row->contact_phone ? ' ' . $row->contact_phone : '' }}</td>
|
||
<td>{{ $row->courier_name ?: '—' }}</td>
|
||
<td class="is-code">{{ $row->files_count }}</td>
|
||
<td class="is-code">{{ $row->submitted_at?->format('Y-m-d H:i') ?? '—' }}</td>
|
||
<td class="is-code">{{ $row->updated_at?->format('Y-m-d H:i') ?? '—' }}</td>
|
||
<td><a href="{{ route('business.courier-onboardings.show', $row) }}">{{ in_array($row->status, ['submitted', 'first_approved', 'approved'], true) ? '去处理' : '查看' }}</a></td>
|
||
</tr>
|
||
@empty
|
||
<tr><td colspan="24" class="co-empty">{{ $search !== '' ? '没有符合条件的入驻申请。' : '这里还没有入驻申请,先在上方生成一个二维码发给快递方。' }}</td></tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if ($rows && $rows->total() > 0)
|
||
<div class="co-footer">
|
||
<span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页,共 {{ number_format($rows->total()) }} 条</span>
|
||
<nav aria-label="分页">
|
||
<a class="{{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->previousPageUrl() ?: '#' }}" aria-label="上一页"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a>
|
||
<a class="{{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->nextPageUrl() ?: '#' }}" aria-label="下一页"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a>
|
||
</nav>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
</section>
|