chenxi 26556a9169 Add warehouse short names to inventory report
Restore warehouse name mapping ledger and inventory short-name column.
2026-09-28 16:33:38 +08:00

1108 lines
53 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@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) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[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>