1108 lines
53 KiB
PHP
1108 lines
53 KiB
PHP
@php
|
||
$isMarketingAccountBillsPage = ($section['slug'] ?? null) === 'billing-transactions'
|
||
&& ($page['slug'] ?? null) === 'marketing-account-bills';
|
||
$isMarketingAccountBalancePage = ($section['slug'] ?? null) === 'billing-transactions'
|
||
&& ($page['slug'] ?? null) === 'marketing-account-balance';
|
||
$isStoreReceivingAccountsPage = ($section['slug'] ?? null) === 'fund-management'
|
||
&& ($page['slug'] ?? null) === 'store-receiving-accounts';
|
||
$isSupplierWorkspace = in_array($section['slug'] ?? null, ['supplier-payables', 'supplier-payables-final'], true);
|
||
$supplierWorkspaceSection = ($section['slug'] ?? null) === 'supplier-payables-final'
|
||
? collect(\App\Utilities\SidebarPermissions::navigationFor(user()))->firstWhere('slug', 'supplier-payables')
|
||
: $section;
|
||
$marketingAccountTabTitles = [
|
||
'taobao-wechat' => '阿里巴巴账户',
|
||
'jd-wallet' => '京东',
|
||
];
|
||
$activeContentTitle = $activeTab['title'] ?? ($page['title'] ?? $section['title']);
|
||
$directory = $page['directory'] ?? null;
|
||
|
||
if ($isMarketingAccountBillsPage && isset($marketingAccountTabTitles[$activeTab['slug'] ?? null])) {
|
||
$activeContentTitle = $marketingAccountTabTitles[$activeTab['slug']];
|
||
}
|
||
@endphp
|
||
|
||
<x-layouts.admin>
|
||
<x-slot name="title">
|
||
{{ $activeContentTitle }}
|
||
</x-slot>
|
||
|
||
<x-slot name="content">
|
||
<style>
|
||
#main-body > .container {
|
||
max-width: none;
|
||
width: auto;
|
||
margin-left: 1.5rem;
|
||
margin-right: 1.5rem;
|
||
}
|
||
|
||
.business-tab-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2.25rem;
|
||
min-height: 5rem;
|
||
padding: 0 2rem;
|
||
overflow-x: auto;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 1rem;
|
||
background: #ffffff;
|
||
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.06);
|
||
}
|
||
|
||
.business-tab-link {
|
||
position: relative;
|
||
display: inline-flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
gap: 0.65rem;
|
||
min-height: 5rem;
|
||
color: #4b5563;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
transition: color 150ms ease;
|
||
}
|
||
|
||
.business-tab-link:hover,
|
||
.business-tab-link.is-active {
|
||
color: var(--vip-content-accent);
|
||
}
|
||
|
||
.business-tab-link.is-active::after {
|
||
position: absolute;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
height: 3px;
|
||
border-radius: 999px 999px 0 0;
|
||
background: var(--vip-content-accent);
|
||
content: '';
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-tab-bar {
|
||
gap: 2rem;
|
||
border-color: var(--vip-border);
|
||
border-radius: 0.75rem;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-tab-link {
|
||
gap: 0.5rem;
|
||
color: #595959;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-tab-link:hover,
|
||
.business-page--marketing-account-bills .business-tab-link.is-active {
|
||
color: var(--vip-content-accent-dark);
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-tab-link.is-active::after {
|
||
background: var(--vip-content-accent);
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-tab-link:focus-visible {
|
||
outline: 2px solid var(--vip-content-accent-dark);
|
||
outline-offset: 3px;
|
||
}
|
||
|
||
.business-page--marketing-account-bills .business-content-section {
|
||
border-color: var(--vip-border);
|
||
border-radius: 0.75rem;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.biz-card {
|
||
background: #ffffff;
|
||
border: 1px solid #eef0f4;
|
||
border-radius: 1rem;
|
||
box-shadow: 0 6px 18px rgba(31, 41, 55, 0.04);
|
||
}
|
||
|
||
.biz-alert {
|
||
margin-bottom: 1rem;
|
||
padding: 0.75rem 1rem;
|
||
border: 1px solid #bbf7d0;
|
||
border-radius: 0.5rem;
|
||
background: #f0fdf4;
|
||
color: #166534;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.biz-alert-error {
|
||
border-color: #fecaca;
|
||
background: #fef2f2;
|
||
color: #991b1b;
|
||
}
|
||
|
||
.biz-import-form {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.biz-import-input {
|
||
max-width: 15rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.biz-filter-bar {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 0.75rem;
|
||
flex-wrap: wrap;
|
||
padding: 0.9rem 1.5rem;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
background: #fffdf8;
|
||
}
|
||
|
||
.biz-filter-field {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.biz-filter-field label {
|
||
color: #6b7280;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.biz-filter-input,
|
||
.biz-filter-select {
|
||
width: 10.5rem;
|
||
height: 2.15rem;
|
||
padding: 0.35rem 0.55rem;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 0.45rem;
|
||
background: #ffffff;
|
||
color: #111827;
|
||
font-size: 0.8125rem;
|
||
}
|
||
|
||
.biz-filter-select {
|
||
width: 8.25rem;
|
||
}
|
||
|
||
.biz-filter-input:focus,
|
||
.biz-filter-select:focus {
|
||
border-color: #a67c3d;
|
||
outline: 2px solid rgba(166, 124, 61, 0.18);
|
||
}
|
||
|
||
.biz-card + .biz-card {
|
||
margin-top: 1.25rem;
|
||
}
|
||
|
||
.installation-filter-bar {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(12rem, 1fr));
|
||
gap: 0.65rem 1rem;
|
||
padding: 1rem 1.5rem;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
background: #fcfbf8;
|
||
}
|
||
|
||
.installation-filter-field {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
min-width: 0;
|
||
color: #595959;
|
||
font-size: 0.75rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.installation-filter-field span {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.installation-filter-field input,
|
||
.installation-filter-field select {
|
||
min-width: 0;
|
||
width: 100%;
|
||
height: 2.1rem;
|
||
padding: 0 0.5rem;
|
||
border: 1px solid #d8d2c7;
|
||
border-radius: 0.375rem;
|
||
background: #ffffff;
|
||
color: #171717;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.installation-filter-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.installation-filter-bar { grid-template-columns: repeat(2, minmax(14rem, 1fr)); }
|
||
}
|
||
@media (max-width: 650px) {
|
||
.installation-filter-bar { grid-template-columns: minmax(0, 1fr); padding: 1rem; }
|
||
}
|
||
|
||
.biz-card-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 1rem 1.5rem;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
}
|
||
|
||
.biz-card-title {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.biz-card-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.biz-table-wrap {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.biz-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.biz-table thead th {
|
||
position: sticky;
|
||
top: 0;
|
||
background: #f9fafb;
|
||
color: #4b5563;
|
||
font-weight: 600;
|
||
text-align: left;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.biz-table tbody td {
|
||
padding: 0.65rem 1rem;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
color: #1f2937;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.biz-table tbody tr:hover td {
|
||
background: #f9fafb;
|
||
}
|
||
|
||
.installation-main-row {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.installation-expand {
|
||
width: 2rem;
|
||
height: 2rem;
|
||
padding: 0;
|
||
border: 1px solid #d8d2c7;
|
||
border-radius: 0.375rem;
|
||
background: #ffffff;
|
||
color: #856632;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.installation-detail-row td {
|
||
padding: 0.75rem 1rem 1rem 3rem;
|
||
background: #fbfaf7;
|
||
}
|
||
|
||
.installation-detail-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.installation-detail-table th,
|
||
.installation-detail-table td {
|
||
padding: 0.45rem 0.65rem;
|
||
border: 1px solid #e5e0d7;
|
||
white-space: nowrap;
|
||
text-align: left;
|
||
}
|
||
|
||
.installation-detail-table th {
|
||
background: #f3efe6;
|
||
color: #595959;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.installation-detail-empty,
|
||
.installation-detail-error {
|
||
color: #595959;
|
||
padding: 0.5rem 0;
|
||
}
|
||
|
||
.installation-detail-error {
|
||
color: #b80000;
|
||
}
|
||
|
||
.biz-table .num {
|
||
text-align: right;
|
||
font-variant-numeric: tabular-nums;
|
||
color: #111827;
|
||
}
|
||
|
||
.biz-table .actions-cell {
|
||
white-space: nowrap;
|
||
text-align: right;
|
||
}
|
||
|
||
.biz-empty {
|
||
padding: 4rem 1rem;
|
||
text-align: center;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.biz-empty .material-icons-outlined {
|
||
font-size: 3rem;
|
||
color: #d1d5db;
|
||
}
|
||
|
||
.biz-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0.4rem 0.85rem;
|
||
border-radius: 0.5rem;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
transition: background 150ms ease, color 150ms ease;
|
||
border: 1px solid transparent;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.biz-btn-primary {
|
||
background: #4f46e5;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.biz-btn-primary:hover {
|
||
background: #4338ca;
|
||
}
|
||
|
||
.biz-btn-ghost {
|
||
background: transparent;
|
||
color: #4b5563;
|
||
border-color: #e5e7eb;
|
||
}
|
||
|
||
.biz-btn-ghost:hover {
|
||
background: #f3f4f6;
|
||
}
|
||
|
||
.biz-btn-danger-ghost {
|
||
background: transparent;
|
||
color: #dc2626;
|
||
border-color: #fecaca;
|
||
}
|
||
|
||
.biz-btn-danger-ghost:hover {
|
||
background: #fef2f2;
|
||
}
|
||
|
||
.biz-badge {
|
||
display: inline-block;
|
||
padding: 0.15rem 0.55rem;
|
||
border-radius: 999px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.biz-badge-yes { background: #dcfce7; color: #15803d; }
|
||
.biz-badge-no { background: #f3f4f6; color: #6b7280; }
|
||
|
||
/* Modal */
|
||
.biz-modal {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(15, 23, 42, 0.45);
|
||
display: none;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
padding: 4rem 1rem;
|
||
z-index: 60;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.biz-modal.is-open {
|
||
display: flex;
|
||
}
|
||
|
||
.biz-modal-card {
|
||
background: #ffffff;
|
||
border-radius: 1rem;
|
||
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.25);
|
||
width: 100%;
|
||
max-width: 720px;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.biz-modal-title {
|
||
font-size: 1.125rem;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.biz-form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 0.85rem;
|
||
}
|
||
|
||
.biz-form-grid .full {
|
||
grid-column: span 2;
|
||
}
|
||
|
||
.biz-field-label {
|
||
display: block;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: #6b7280;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.biz-field-input,
|
||
.biz-field-select,
|
||
.biz-field-textarea {
|
||
width: 100%;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 0.5rem;
|
||
padding: 0.5rem 0.65rem;
|
||
font-size: 0.875rem;
|
||
color: #111827;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.biz-field-input:focus,
|
||
.biz-field-select:focus,
|
||
.biz-field-textarea:focus {
|
||
border-color: #6366f1;
|
||
outline: 2px solid rgba(99, 102, 241, 0.25);
|
||
}
|
||
|
||
.biz-modal-foot {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 0.5rem;
|
||
margin-top: 1.25rem;
|
||
}
|
||
|
||
.biz-pagination {
|
||
display: flex;
|
||
gap: 0.25rem;
|
||
padding: 0.75rem 1.5rem;
|
||
border-top: 1px solid #f3f4f6;
|
||
color: #4b5563;
|
||
font-size: 0.85rem;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.biz-pagination a,
|
||
.biz-pagination span {
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 0.375rem;
|
||
}
|
||
|
||
.biz-pagination a:hover {
|
||
background: #f3f4f6;
|
||
}
|
||
|
||
.business-content-section {
|
||
margin-top: 1.5rem;
|
||
padding: 1.5rem;
|
||
border: 1px solid #eef0f4;
|
||
border-radius: 1rem;
|
||
background: #ffffff;
|
||
}
|
||
|
||
[v-cloak] {
|
||
display: none;
|
||
}
|
||
|
||
@media (min-width: 1280px) {
|
||
#main-body > .container {
|
||
margin-left: 2.5rem;
|
||
margin-right: 2.5rem;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<div
|
||
@class([
|
||
'my-6',
|
||
'business-page--marketing-account-bills' => $isMarketingAccountBillsPage,
|
||
'business-page--marketing-account-balance' => $isMarketingAccountBalancePage,
|
||
])
|
||
@if ($isMarketingAccountBillsPage) data-marketing-account-bills-workspace @endif
|
||
@if ($isSupplierWorkspace) data-supplier-workspace @endif
|
||
>
|
||
@if ($isMarketingAccountBillsPage)
|
||
<p class="sr-only" role="status" aria-live="polite" data-marketing-account-bills-status></p>
|
||
<div class="biz-alert biz-alert-error" role="alert" data-marketing-account-bills-navigation-error hidden>
|
||
<span>账单内容局部加载失败,当前页面内容已保留。</span>
|
||
<a href="{{ request()->fullUrl() }}" data-marketing-account-bills-retry data-marketing-account-bills-full-page>重新加载</a>
|
||
</div>
|
||
@endif
|
||
@if (is_string(session('success')))
|
||
<div class="biz-alert" role="status">{{ session('success') }}</div>
|
||
@endif
|
||
|
||
@if (is_string(session('error')))
|
||
<div class="biz-alert biz-alert-error" role="alert">{{ session('error') }}</div>
|
||
@endif
|
||
|
||
@if ($errors->has('import'))
|
||
<div class="biz-alert biz-alert-error" role="alert">{{ $errors->first('import') }}</div>
|
||
@endif
|
||
|
||
@if (!empty($upstreamError))
|
||
<div class="biz-alert biz-alert-error" role="alert">{{ $upstreamError }}</div>
|
||
@endif
|
||
|
||
@if ($isSupplierWorkspace && $supplierWorkspaceSection)
|
||
@include('business.pages.supplier-payables.navigation', ['section' => $supplierWorkspaceSection])
|
||
@endif
|
||
|
||
@if ($tabs->isNotEmpty())
|
||
<nav class="business-tab-bar" aria-label="{{ $page['title'] }}">
|
||
@foreach ($tabs as $tab)
|
||
@php
|
||
$visibleTabTitle = $isMarketingAccountBillsPage
|
||
? ($marketingAccountTabTitles[$tab['slug']] ?? $tab['title'])
|
||
: $tab['title'];
|
||
@endphp
|
||
|
||
<a
|
||
href="{{ route('business.pages.show', [
|
||
'section' => $section['slug'],
|
||
'page' => $page['slug'],
|
||
'tab' => $tab['slug'],
|
||
]) }}"
|
||
@class([
|
||
'business-tab-link',
|
||
'is-active' => $activeTab['slug'] === $tab['slug'],
|
||
])
|
||
@if ($activeTab['slug'] === $tab['slug']) aria-current="page" @endif
|
||
>
|
||
<span class="material-icons-outlined text-xl" aria-hidden="true">
|
||
{{ $tab['icon'] ?? 'description' }}
|
||
</span>
|
||
<span>{{ $visibleTabTitle }}</span>
|
||
</a>
|
||
@endforeach
|
||
</nav>
|
||
@endif
|
||
|
||
@php
|
||
$pageView = null;
|
||
$sectionView = null;
|
||
|
||
if ($page) {
|
||
$viewName = 'business.pages.' . $section['slug'] . '.' . $page['slug'];
|
||
|
||
if ($tabs->isNotEmpty()) {
|
||
$viewName .= '.' . ($activeTab['slug'] ?? '');
|
||
}
|
||
|
||
if (view()->exists($viewName)) {
|
||
$pageView = $viewName;
|
||
}
|
||
} else {
|
||
$viewName = 'business.pages.' . $section['slug'] . '.index';
|
||
|
||
if (view()->exists($viewName)) {
|
||
$sectionView = $viewName;
|
||
}
|
||
}
|
||
@endphp
|
||
|
||
@if (in_array($section['slug'], ['courier-payables', 'courier-payables-final'], true) && empty($page))
|
||
@include('business.pages.courier-payables.final-workspace')
|
||
@elseif ($sectionView)
|
||
@include($sectionView)
|
||
@elseif (!empty($content) && !empty($content['view']))
|
||
<section class="business-content-section" aria-label="{{ $activeContentTitle }}">
|
||
@include($content['view'], $content)
|
||
</section>
|
||
@elseif ($directory)
|
||
@include('business.pages.partials.directory-table', [
|
||
'directory' => $directory,
|
||
'section' => $section,
|
||
'page' => $page,
|
||
'rows' => $rows,
|
||
'upstreamError' => $upstreamError,
|
||
'inventoryWarehouseMappings' => $inventoryWarehouseMappings ?? collect(),
|
||
])
|
||
@elseif ($pageView)
|
||
@include($pageView)
|
||
@elseif ($descriptor)
|
||
<div class="biz-card mt-6">
|
||
@if ($page['slug'] === 'installation-details')
|
||
<form method="GET" action="{{ request()->url() }}" class="installation-filter-bar" aria-label="安装明细筛选查询栏">
|
||
@php
|
||
$filterFields = [
|
||
['name' => 'created_from', 'label' => '创建日期:', 'type' => 'date'], ['name' => 'created_to', 'label' => '至:', 'type' => 'date'],
|
||
['name' => 'customer_review_from', 'label' => '客服审核时间:', 'type' => 'date'], ['name' => 'customer_review_to', 'label' => '至:', 'type' => 'date'],
|
||
['name' => 'finance_review_from', 'label' => '财务审核时间:', 'type' => 'date'], ['name' => 'finance_review_to', 'label' => '至:', 'type' => 'date'],
|
||
['name' => 'push_status', 'label' => '推送状态:'], ['name' => 'order_no', 'label' => '订单编号:'],
|
||
['name' => 'customer_review', 'label' => '客服审核:', 'options' => ['0'=>'未审核','1'=>'已审核']], ['name' => 'finance_review', 'label' => '财务审核:', 'options' => ['0'=>'未审核','1'=>'已审核']],
|
||
['name' => 'quote_status', 'label' => '报价状态:'], ['name' => 'arranged_status', 'label' => '是否安排:', 'options' => ['0'=>'未安排','1'=>'已安排']],
|
||
['name' => 'fee_payer', 'label' => '安装费承担方:'], ['name' => 'completed_status', 'label' => '是否完工:', 'options' => ['0'=>'未完工','1'=>'已完工']],
|
||
['name' => 'shop_name', 'label' => '选择店铺:'], ['name' => 'delete_status', 'label' => '选择删除:', 'options' => ['0'=>'未删除','1'=>'已删除']],
|
||
['name' => 'installation_company', 'label' => '安装公司:'], ['name' => 'installation_no', 'label' => '安装单号:'],
|
||
['name' => 'order_type', 'label' => '订单类型:'], ['name' => 'product_id', 'label' => '商品ID:'],
|
||
];
|
||
@endphp
|
||
@foreach ($filterFields as $filter)
|
||
<label class="installation-filter-field">
|
||
<span>{{ $filter['label'] }}</span>
|
||
@if (isset($filter['options']))
|
||
<select name="{{ $filter['name'] }}"><option value="">全部</option>@foreach ($filter['options'] as $value => $label)<option value="{{ $value }}" @selected((string) request($filter['name']) === (string) $value)>{{ $label }}</option>@endforeach</select>
|
||
@else
|
||
<input type="{{ $filter['type'] ?? 'text' }}" name="{{ $filter['name'] }}" value="{{ request($filter['name']) }}" @if (($filter['type'] ?? '') !== 'date') placeholder="按原值精确查询" @endif>
|
||
@endif
|
||
</label>
|
||
@endforeach
|
||
<div class="installation-filter-actions">
|
||
<button type="submit" class="biz-btn biz-btn-primary">查询</button>
|
||
<a href="{{ request()->url() }}" class="biz-btn biz-btn-ghost">清除</a>
|
||
<a href="{{ route('business.installation-details.export', request()->query()) }}" class="biz-btn biz-btn-ghost">导出</a>
|
||
</div>
|
||
</form>
|
||
@endif
|
||
<div class="biz-card-head">
|
||
<div class="biz-card-title">
|
||
<span class="material-icons-outlined" aria-hidden="true">table_view</span>
|
||
<span>{{ $activeTab['title'] ?? $page['title'] }} · 数据列表</span>
|
||
@if ($descriptor->readOnly)
|
||
<span class="biz-badge biz-badge-yes">工作平台数据库</span>
|
||
@endif
|
||
</div>
|
||
@unless ($descriptor->readOnly)
|
||
<div class="biz-card-actions">
|
||
@if ($isStoreReceivingAccountsPage)
|
||
<button type="button" class="biz-btn biz-btn-ghost" onclick="bizOpenImportModal()">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">upload_file</span>
|
||
<span>导入</span>
|
||
</button>
|
||
@else
|
||
<form
|
||
class="biz-import-form"
|
||
method="POST"
|
||
enctype="multipart/form-data"
|
||
action="{{ route('business.pages.import', [
|
||
'section' => $section['slug'],
|
||
'page' => $page['slug'],
|
||
'tab' => $activeTab['slug'] ?? null,
|
||
]) }}"
|
||
>
|
||
@csrf
|
||
<input class="biz-import-input" type="file" name="import" accept=".xlsx,.xls,.csv" required>
|
||
<button type="submit" class="biz-btn biz-btn-ghost" title="导入 Excel 或 CSV">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">upload_file</span>
|
||
<span>导入</span>
|
||
</button>
|
||
</form>
|
||
@endif
|
||
<button type="button" class="biz-btn biz-btn-primary" onclick="bizOpenCreateModal()">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">add</span>
|
||
<span>新增</span>
|
||
</button>
|
||
</div>
|
||
@endunless
|
||
</div>
|
||
|
||
@if ($isStoreReceivingAccountsPage)
|
||
<form class="biz-filter-bar" method="GET" action="{{ route('business.pages.show', [
|
||
'section' => $section['slug'], 'page' => $page['slug'], 'tab' => $activeTab['slug'] ?? null,
|
||
]) }}" aria-label="店铺收款账户筛选">
|
||
<div class="biz-filter-field">
|
||
<label for="filter-erp-store-name">ERP店铺名称</label>
|
||
<input id="filter-erp-store-name" class="biz-filter-input" type="search" name="erp_store_name" value="{{ request('erp_store_name') }}" placeholder="输入店铺名称">
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="filter-shop-name">店铺名称</label>
|
||
<input id="filter-shop-name" class="biz-filter-input" type="search" name="shop_name" value="{{ request('shop_name') }}" placeholder="输入店铺名称">
|
||
</div>
|
||
<div class="biz-filter-field">
|
||
<label for="filter-store-status">店铺状态</label>
|
||
<select id="filter-store-status" class="biz-filter-select" name="store_status">
|
||
<option value="">全部</option>
|
||
@foreach (['正常', '关店中', '已关店'] as $statusOption)
|
||
<option value="{{ $statusOption }}" @selected(request('store_status') === $statusOption)>{{ $statusOption }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<button type="submit" class="biz-btn biz-btn-primary">筛选</button>
|
||
@if (request()->hasAny(['erp_store_name', 'shop_name', 'store_status']))
|
||
<a class="biz-btn biz-btn-ghost" href="{{ route('business.pages.show', [
|
||
'section' => $section['slug'], 'page' => $page['slug'], 'tab' => $activeTab['slug'] ?? null,
|
||
]) }}">重置</a>
|
||
@endif
|
||
</form>
|
||
@endif
|
||
<div class="biz-table-wrap">
|
||
<table class="biz-table">
|
||
<thead>
|
||
<tr>
|
||
@if ($page['slug'] !== 'installation-details')<th>#</th>@endif
|
||
@foreach ($descriptor->fields as $field)
|
||
<th class="@if ($field['currency']) num @endif">{{ $field['label'] }}</th>
|
||
@endforeach
|
||
@unless ($descriptor->readOnly)
|
||
<th class="actions-cell" data-finance-fixed="end">操作</th>
|
||
@endunless
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($rows as $row)
|
||
<tr @if ($page['slug'] === 'installation-details') class="installation-main-row" data-installation-row data-guid="{{ $row->related_guid ?? '' }}" tabindex="0" aria-label="查看安装商品明细" @endif>
|
||
@if ($page['slug'] !== 'installation-details')<td>{{ $row->id }}</td>@endif
|
||
@foreach ($descriptor->fields as $field)
|
||
@php
|
||
$val = $row->{$field['name']};
|
||
@endphp
|
||
<td class="@if ($field['currency']) num @endif">
|
||
@if ($field['type'] === 'boolean')
|
||
@if ($val)
|
||
<span class="biz-badge biz-badge-yes">是</span>
|
||
@else
|
||
<span class="biz-badge biz-badge-no">否</span>
|
||
@endif
|
||
@elseif ($field['type'] === 'date' && $val)
|
||
{{ \Illuminate\Support\Carbon::parse($val)->format('Y-m-d') }}
|
||
@elseif ($field['currency'])
|
||
{{ number_format((float) $val, 2) }}
|
||
@else
|
||
{{ $val }}
|
||
@endif
|
||
</td>
|
||
@endforeach
|
||
@unless ($descriptor->readOnly)
|
||
<td class="actions-cell" data-finance-fixed="end">
|
||
<button type="button" class="biz-btn biz-btn-ghost" title="编辑" onclick='bizOpenEditModal({{ json_encode($row->only(array_merge(['id'], array_map(fn($f) => $f['name'], $descriptor->fields)))) }})'>
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">edit</span>
|
||
</button>
|
||
<form method="POST" action="{{ route('business.pages.destroy', [
|
||
'section' => $section['slug'],
|
||
'page' => $page['slug'],
|
||
'id' => $row->id,
|
||
'tab' => $activeTab['slug'] ?? null,
|
||
]) }}" style="display:inline" onsubmit="return confirm('确认删除这条记录?')">
|
||
@csrf
|
||
@method('DELETE')
|
||
<button type="submit" class="biz-btn biz-btn-danger-ghost">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">delete</span>
|
||
</button>
|
||
</form>
|
||
</td>
|
||
@endunless
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="{{ count($descriptor->fields) + ($page['slug'] === 'installation-details' ? 0 : 1) + ($descriptor->readOnly ? 0 : 1) }}">
|
||
<div class="biz-empty">
|
||
<span class="material-icons-outlined" aria-hidden="true">inbox</span>
|
||
<div>{{ $descriptor->readOnly ? '暂无工作平台数据' : '暂无数据,点击右上角"新增"开始录入' }}</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="biz-pagination">
|
||
<span>共 {{ $rows->total() }} 条 · 第 {{ $rows->currentPage() }} / {{ $rows->lastPage() }} 页</span>
|
||
<span>
|
||
@if ($rows->onFirstPage())
|
||
<span>‹ 上一页</span>
|
||
@else
|
||
<a href="{{ $rows->previousPageUrl() }}">‹ 上一页</a>
|
||
@endif
|
||
@if ($rows->hasMorePages())
|
||
<a href="{{ $rows->nextPageUrl() }}">下一页 ›</a>
|
||
@else
|
||
<span>下一页 ›</span>
|
||
@endif
|
||
</span>
|
||
</div>
|
||
|
||
@if ($page['slug'] === 'installation-details')
|
||
<section class="installation-detail-panel" data-installation-detail-panel aria-label="明细子表">
|
||
<div class="biz-card-head"><div class="biz-card-title">明细子表</div></div>
|
||
<div class="biz-table-wrap" data-installation-detail-content>
|
||
<div class="installation-detail-empty">请点击上方主表记录查看商品明细</div>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
|
||
@if ($page['slug'] === 'installation-details')
|
||
<script>
|
||
(() => {
|
||
const detailUrl = @json(route('business.installation-details.details', ['guid' => '__GUID__']));
|
||
const columns = [
|
||
['transaction_no', '交易单号'], ['product_id', '商品id'], ['id_owner', 'ID负责人'],
|
||
['department_owner', '部门负责人'], ['auxiliary_spu', '辅助码Spu'], ['product_store_code', '商品店铺编码'],
|
||
['item_no', '货号'], ['barcode', '条形码'], ['product_name', '商品名称'], ['supplier_name', '供应商名称'],
|
||
['detail_requested_amount', '申请明细金额'], ['detail_requested_quantity', '申请明细数量'],
|
||
['detail_settled_amount', '结算明细金额'], ['detail_received_amount', '收款明细金额'],
|
||
['change_reason', '变更原因'], ['source_created_at', '创建时间'], ['source_modified_at', '修改时间'],
|
||
];
|
||
const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
||
const renderDetails = (items) => {
|
||
if (!items.length) return '<div class="installation-detail-empty">暂无商品明细</div>';
|
||
const head = columns.map(([, label]) => `<th>${escapeHtml(label)}</th>`).join('');
|
||
const body = items.map((item) => `<tr>${columns.map(([key]) => `<td>${escapeHtml(item[key])}</td>`).join('')}</tr>`).join('');
|
||
return `<table class="installation-detail-table"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`;
|
||
};
|
||
const detailPanel = document.querySelector('[data-installation-detail-panel]');
|
||
const detailContent = detailPanel?.querySelector('[data-installation-detail-content]');
|
||
let detailRequest = 0;
|
||
document.querySelectorAll('[data-installation-row]').forEach((row) => {
|
||
row.addEventListener('keydown', (event) => {
|
||
if (event.target === row && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); row.click(); }
|
||
});
|
||
row.addEventListener('click', async () => {
|
||
if (!detailContent) return;
|
||
const requestId = ++detailRequest;
|
||
detailContent.innerHTML = '<div>正在加载明细…</div>';
|
||
try {
|
||
const response = await fetch(detailUrl.replace('__GUID__', encodeURIComponent(row.dataset.guid)), {headers: {'Accept': 'application/json'}});
|
||
if (!response.ok) throw new Error('明细加载失败');
|
||
const payload = await response.json();
|
||
if (requestId !== detailRequest) return;
|
||
detailContent.innerHTML = renderDetails(payload.data || []);
|
||
if (payload.has_more) detailContent.insertAdjacentHTML('beforeend', '<div class="installation-detail-empty">当前仅展示前 100 条商品明细,不代表全部。</div>');
|
||
} catch (error) {
|
||
if (requestId !== detailRequest) return;
|
||
detailContent.innerHTML = '<div class="installation-detail-error">明细加载失败,请稍后重试</div>';
|
||
}
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
@endif
|
||
|
||
</div>
|
||
|
||
@unless ($descriptor->readOnly)
|
||
@if ($isStoreReceivingAccountsPage)
|
||
<div id="biz-import-modal" class="biz-modal" onclick="if(event.target===this)bizCloseImportModal()">
|
||
<div class="biz-modal-card" style="max-width: 560px">
|
||
<div class="biz-modal-title">导入{{ $activeTab['title'] }}店铺收款账户</div>
|
||
<p style="margin:0 0 1rem;color:#6b7280;font-size:.8125rem">请上传当前页签格式的 Excel 文件;空 ERP 店铺名称的示例行会自动跳过。</p>
|
||
<form method="POST" enctype="multipart/form-data" action="{{ route('business.pages.import', [
|
||
'section' => $section['slug'], 'page' => $page['slug'], 'tab' => $activeTab['slug'],
|
||
]) }}">
|
||
@csrf
|
||
<input class="biz-field-input" type="file" name="import" accept=".xlsx,.xls,.csv" required>
|
||
<div style="margin-top:.75rem">
|
||
<a class="biz-btn biz-btn-ghost" href="{{ route('business.pages.store-receiving-account-template', [
|
||
'section' => $section['slug'], 'page' => $page['slug'], 'tab' => $activeTab['slug'],
|
||
]) }}">
|
||
<span class="material-icons-outlined" style="font-size:1rem">download</span>
|
||
下载{{ $activeTab['title'] }}模板
|
||
</a>
|
||
</div>
|
||
<div class="biz-modal-foot">
|
||
<button type="button" class="biz-btn biz-btn-ghost" onclick="bizCloseImportModal()">取消</button>
|
||
<button type="submit" class="biz-btn biz-btn-primary">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">upload_file</span>
|
||
上传并导入
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
{{-- Generic create/edit modal --}}
|
||
<div id="biz-modal" class="biz-modal" onclick="if(event.target===this)bizCloseModal()">
|
||
<div class="biz-modal-card">
|
||
<div class="biz-modal-title" id="biz-modal-title">新增记录</div>
|
||
<form id="biz-form" method="POST" action="">
|
||
@csrf
|
||
<input type="hidden" name="_method" value="POST" id="biz-form-method">
|
||
<div class="biz-form-grid">
|
||
@foreach ($descriptor->fields as $field)
|
||
<div class="{{ $field['type'] === 'textarea' ? 'full' : '' }}">
|
||
<label class="biz-field-label">{{ $field['label'] }}</label>
|
||
@switch($field['type'])
|
||
@case('boolean')
|
||
<select class="biz-field-select" name="{{ $field['name'] }}" data-default="">
|
||
<option value="0">否</option>
|
||
<option value="1">是</option>
|
||
</select>
|
||
@break
|
||
@case('select')
|
||
<select class="biz-field-select" name="{{ $field['name'] }}" data-default="">
|
||
<option value="">— 请选择 —</option>
|
||
@foreach ($field['options'] ?? [] as $opt)
|
||
<option value="{{ $opt }}">{{ $opt }}</option>
|
||
@endforeach
|
||
</select>
|
||
@break
|
||
@case('date')
|
||
<input type="date" class="biz-field-input" name="{{ $field['name'] }}" data-default="">
|
||
@break
|
||
@case('currency')
|
||
@case('number')
|
||
<input type="number" step="0.01" class="biz-field-input" name="{{ $field['name'] }}" data-default="">
|
||
@break
|
||
@case('textarea')
|
||
<textarea class="biz-field-textarea" name="{{ $field['name'] }}" rows="3" data-default=""></textarea>
|
||
@break
|
||
@default
|
||
<input type="text" class="biz-field-input" name="{{ $field['name'] }}" data-default="">
|
||
@endswitch
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
<div class="biz-modal-foot">
|
||
<button type="button" class="biz-btn biz-btn-ghost" onclick="bizCloseModal()">取消</button>
|
||
<button type="submit" class="biz-btn biz-btn-primary">
|
||
<span class="material-icons-outlined" style="font-size:1.1rem">save</span>
|
||
<span>保存</span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
window.__bizDescriptor = {
|
||
section: {!! json_encode($section['slug']) !!},
|
||
page: {!! json_encode($page['slug']) !!},
|
||
tab: {!! json_encode($activeTab['slug'] ?? null) !!},
|
||
storeUrl: "{{ route('business.pages.store', ['section' => $section['slug'], 'page' => $page['slug'], 'tab' => $activeTab['slug'] ?? null]) }}",
|
||
updateBase: "{{ route('business.pages.update', ['section' => $section['slug'], 'page' => $page['slug'], 'id' => '__ID__', 'tab' => $activeTab['slug'] ?? null]) }}".replace('__ID__', ''),
|
||
fields: {!! json_encode($descriptor->fields) !!}
|
||
};
|
||
|
||
function bizOpenModal() {
|
||
var m = document.getElementById('biz-modal');
|
||
m.classList.add('is-open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function bizCloseModal() {
|
||
var m = document.getElementById('biz-modal');
|
||
m.classList.remove('is-open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
function bizOpenImportModal() {
|
||
var modal = document.getElementById('biz-import-modal');
|
||
if (!modal) return;
|
||
modal.classList.add('is-open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function bizCloseImportModal() {
|
||
var modal = document.getElementById('biz-import-modal');
|
||
if (!modal) return;
|
||
modal.classList.remove('is-open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
function bizResetForm() {
|
||
var form = document.getElementById('biz-form');
|
||
form.querySelectorAll('[data-default]').forEach(function (el) {
|
||
el.value = el.getAttribute('data-default') || '';
|
||
});
|
||
}
|
||
|
||
function bizOpenCreateModal() {
|
||
document.getElementById('biz-modal-title').textContent = '新增记录';
|
||
var form = document.getElementById('biz-form');
|
||
form.action = window.__bizDescriptor.storeUrl;
|
||
document.getElementById('biz-form-method').value = 'POST';
|
||
bizResetForm();
|
||
bizOpenModal();
|
||
}
|
||
|
||
function bizOpenEditModal(rowData) {
|
||
document.getElementById('biz-modal-title').textContent = '编辑记录 # ' + rowData.id;
|
||
var form = document.getElementById('biz-form');
|
||
form.action = window.__bizDescriptor.updateBase + rowData.id;
|
||
document.getElementById('biz-form-method').value = 'PATCH';
|
||
|
||
form.querySelectorAll('[data-default]').forEach(function (el) {
|
||
var name = el.getAttribute('name');
|
||
var val = rowData[name] === null ? '' : rowData[name];
|
||
// Format dates to YYYY-MM-DD for the date input
|
||
if (el.type === 'date' && val) {
|
||
val = String(val).substr(0, 10);
|
||
}
|
||
// Booleans -> "1" / "0"
|
||
if (typeof val === 'boolean') {
|
||
val = val ? '1' : '0';
|
||
}
|
||
el.value = val;
|
||
});
|
||
|
||
bizOpenModal();
|
||
}
|
||
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape') {
|
||
bizCloseModal();
|
||
bizCloseImportModal();
|
||
}
|
||
});
|
||
</script>
|
||
@endunless
|
||
@elseif (($section['slug'] ?? null) === 'fund-management' && ($page['slug'] ?? null) === 'store-receiving-accounts')
|
||
<section class="biz-card mt-6 business-blank-page" aria-label="店铺收款账户">
|
||
<div class="biz-empty">
|
||
<span class="material-icons-outlined" aria-hidden="true">inbox</span>
|
||
<div>店铺收款账户目录已清空,暂无页签和表格。</div>
|
||
</div>
|
||
</section>
|
||
@else
|
||
<section
|
||
@class([
|
||
'biz-card mt-6',
|
||
'business-blank-page' => ($page['slug'] ?? null) === 'store-receiving-bills',
|
||
])
|
||
@if (($page['slug'] ?? null) === 'store-receiving-bills') data-table-preferences-user="{{ user()->id }}" @endif
|
||
aria-label="{{ $activeTab['title'] ?? ($page['title'] ?? $section['title']) }}"
|
||
>
|
||
<div class="biz-empty">
|
||
<span class="material-icons-outlined" aria-hidden="true">description</span>
|
||
<div>该页面暂未配置数据表,敬请期待。</div>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
</div>
|
||
|
||
@include('business.pages.partials.finance-table-interactions')
|
||
|
||
@if ($isMarketingAccountBillsPage)
|
||
@push('scripts_start')
|
||
<script src="{{ asset('public/js/business/marketing-account-bills.min.js') }}?id={{ md5_file(public_path('js/business/marketing-account-bills.min.js')) }}"></script>
|
||
@endpush
|
||
@elseif (($page['slug'] ?? null) === 'store-receiving-bills')
|
||
@push('scripts_start')
|
||
<script src="{{ asset('public/js/business/pages.min.js') }}?id={{ md5_file(public_path('js/business/pages.min.js')) }}"></script>
|
||
@endpush
|
||
@endif
|
||
</x-slot>
|
||
</x-layouts.admin>
|