520 lines
32 KiB
PHP
520 lines
32 KiB
PHP
@php
|
|
$rows = $thirdPartyWarehouseRows ?? null;
|
|
$rawFilters = is_array($thirdPartyWarehouseFilters ?? null) ? $thirdPartyWarehouseFilters : [];
|
|
$dimension = in_array(($rawFilters['dimension'] ?? 'month'), ['month', 'day'], true)
|
|
? $rawFilters['dimension']
|
|
: 'month';
|
|
$period = trim((string) ($rawFilters['period'] ?? (
|
|
$dimension === 'day' ? ($rawFilters['date'] ?? '') : ($rawFilters['month'] ?? '')
|
|
)));
|
|
$filters = [
|
|
'dimension' => $dimension,
|
|
'period' => $period,
|
|
'sku_code' => trim((string) ($rawFilters['sku_code'] ?? '')),
|
|
'product_name' => trim((string) ($rawFilters['product_name'] ?? '')),
|
|
'supplier' => trim((string) ($rawFilters['supplier'] ?? '')),
|
|
'category' => trim((string) ($rawFilters['category'] ?? '')),
|
|
];
|
|
$summary = $thirdPartyWarehouseSummary ?? [];
|
|
$sync = array_replace([
|
|
'code' => 'unavailable',
|
|
'last_succeeded_at' => null,
|
|
'lag_minutes' => null,
|
|
'message' => null,
|
|
], $thirdPartyWarehouseSync ?? []);
|
|
$error = trim((string) ($thirdPartyWarehouseError ?? ''));
|
|
$pending = (bool) ($thirdPartyWarehousePending ?? false);
|
|
$exportUrl = trim((string) ($thirdPartyWarehouseExportUrl ?? ''));
|
|
|
|
$columns = [
|
|
['key' => 'owner', 'label' => '货主'],
|
|
['key' => 'date', 'label' => '日期'],
|
|
['key' => 'sku_code', 'label' => '商品编码', 'type' => 'code'],
|
|
['key' => 'image_url', 'label' => '图片', 'type' => 'image'],
|
|
['key' => 'product_name', 'label' => '商品名称', 'type' => 'wide'],
|
|
['key' => 'short_name', 'label' => '商品简称'],
|
|
['key' => 'supplier', 'label' => '供应商', 'type' => 'wide'],
|
|
['key' => 'category', 'label' => '分类'],
|
|
['key' => 'virtual_category', 'label' => '虚拟分类'],
|
|
['key' => 'supplier_style_no', 'label' => '供应商款号', 'type' => 'code'],
|
|
['key' => 'color_spec', 'label' => '颜色规格'],
|
|
['key' => 'brand', 'label' => '品牌'],
|
|
['key' => 'unit', 'label' => '单位'],
|
|
['key' => 'opening_qty', 'label' => '期初数量', 'type' => 'number'],
|
|
['key' => 'closing_qty', 'label' => '期末数量', 'type' => 'number'],
|
|
['key' => 'processing_in', 'label' => '加工进仓', 'type' => 'number'],
|
|
['key' => 'processing_out', 'label' => '加工出仓', 'type' => 'number'],
|
|
['key' => 'purchase_in', 'label' => '采购进仓', 'type' => 'number'],
|
|
['key' => 'purchase_return', 'label' => '采购退货', 'type' => 'number'],
|
|
['key' => 'other_in', 'label' => '其它进仓', 'type' => 'number'],
|
|
['key' => 'other_return', 'label' => '其它退货', 'type' => 'number'],
|
|
['key' => 'sales_out', 'label' => '销售出仓', 'type' => 'number'],
|
|
['key' => 'sales_return', 'label' => '销售退货', 'type' => 'number'],
|
|
['key' => 'stocktake', 'label' => '盘点', 'type' => 'number'],
|
|
['key' => 'transfer_out', 'label' => '调拨出', 'type' => 'number'],
|
|
['key' => 'transfer_in', 'label' => '调拨入', 'type' => 'number'],
|
|
['key' => 'opening_aux', 'label' => '期初', 'type' => 'number'],
|
|
['key' => 'other_out', 'label' => '其它出库', 'type' => 'number'],
|
|
];
|
|
|
|
$statusLabels = [
|
|
'healthy' => '同步正常',
|
|
'fresh' => '同步正常',
|
|
'syncing' => '同步中,显示最近数据',
|
|
'reconciling' => '全量校准中,显示最近数据',
|
|
'stale' => '同步延迟,显示最近数据',
|
|
'delayed' => '同步延迟,显示最近数据',
|
|
'failed' => '同步失败,显示最近数据',
|
|
'initializing' => '首次同步中',
|
|
'dependency_pending' => '商品资料同步中',
|
|
'pending' => '等待首次同步',
|
|
'initial_failed' => '首次同步失败',
|
|
'unavailable' => '镜像不可用',
|
|
];
|
|
$statusCode = (string) ($sync['code'] ?? 'unavailable');
|
|
$statusLabel = $statusLabels[$statusCode] ?? '同步状态未知';
|
|
$statusTone = match ($statusCode) {
|
|
'failed', 'initial_failed', 'unavailable' => 'danger',
|
|
'stale', 'delayed', 'initializing', 'dependency_pending', 'pending' => 'warning',
|
|
'syncing', 'reconciling' => 'info',
|
|
default => 'success',
|
|
};
|
|
$rowTotal = is_object($rows) && method_exists($rows, 'total')
|
|
? (int) $rows->total()
|
|
: (is_countable($rows) ? count($rows) : 0);
|
|
$rowCount = is_object($rows) && method_exists($rows, 'count')
|
|
? (int) $rows->count()
|
|
: (is_countable($rows) ? count($rows) : 0);
|
|
$summaryReady = ! $pending && collect(['count', 'opening_qty', 'closing_qty'])
|
|
->every(static fn (string $key): bool => array_key_exists($key, $summary));
|
|
$showingLastVersion = $rowTotal > 0 && ($error !== '' || in_array($statusCode, [
|
|
'syncing', 'reconciling', 'stale', 'delayed', 'failed',
|
|
], true));
|
|
$canExport = $exportUrl !== '' && $rowTotal > 0 && ! $pending;
|
|
$activeFilters = array_filter(
|
|
$filters,
|
|
static fn (mixed $value): bool => $value !== null && $value !== ''
|
|
);
|
|
if (is_object($rows) && method_exists($rows, 'appends')) {
|
|
$rows->appends($activeFilters);
|
|
}
|
|
$fallbackExportUrl = $exportUrl;
|
|
|
|
if ($exportUrl !== '' && $activeFilters !== []) {
|
|
$fallbackExportUrl .= (str_contains($exportUrl, '?') ? '&' : '?') . http_build_query($activeFilters);
|
|
}
|
|
|
|
$text = static function (mixed $value): string {
|
|
if ($value === null || $value === '') {
|
|
return '-';
|
|
}
|
|
|
|
if (is_bool($value)) {
|
|
return $value ? '是' : '否';
|
|
}
|
|
|
|
if (is_scalar($value)) {
|
|
return (string) $value;
|
|
}
|
|
|
|
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '-';
|
|
};
|
|
$number = static function (mixed $value) use ($text): string {
|
|
if ($value === null || $value === '') {
|
|
return '-';
|
|
}
|
|
|
|
if (! is_numeric($value)) {
|
|
return $text($value);
|
|
}
|
|
|
|
return rtrim(rtrim(number_format((float) $value, 4, '.', ','), '0'), '.');
|
|
};
|
|
$money = static function (mixed $value) use ($text): string {
|
|
if ($value === null || $value === '') {
|
|
return '-';
|
|
}
|
|
|
|
return is_numeric($value) ? number_format((float) $value, 2, '.', ',') : $text($value);
|
|
};
|
|
$imageUrl = static function (mixed $value): ?string {
|
|
if (is_array($value)) {
|
|
$value = $value['url'] ?? $value['src'] ?? reset($value);
|
|
}
|
|
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
|
|
$candidate = trim($value);
|
|
|
|
if (str_starts_with($candidate, '[') || str_starts_with($candidate, '{')) {
|
|
$decoded = json_decode($candidate, true);
|
|
|
|
if (is_array($decoded)) {
|
|
$candidate = trim((string) ($decoded['url'] ?? $decoded['src'] ?? reset($decoded)));
|
|
}
|
|
}
|
|
|
|
$scheme = strtolower((string) parse_url($candidate, PHP_URL_SCHEME));
|
|
|
|
return filter_var($candidate, FILTER_VALIDATE_URL) && in_array($scheme, ['http', 'https'], true)
|
|
? $candidate
|
|
: null;
|
|
};
|
|
$lagMinutes = is_numeric($sync['lag_minutes'] ?? null) ? max(0, (int) $sync['lag_minutes']) : null;
|
|
$lagLabel = $lagMinutes === null
|
|
? null
|
|
: ($lagMinutes < 60 ? $lagMinutes . ' 分钟' : floor($lagMinutes / 60) . ' 小时 ' . ($lagMinutes % 60) . ' 分钟');
|
|
@endphp
|
|
|
|
<div class="third-party-warehouse-page" data-finance-table-surface>
|
|
<style>
|
|
.third-party-warehouse-page {
|
|
--warehouse-border: var(--vip-border, #D8D2C7);
|
|
--warehouse-ink: var(--vip-content-ink, #171717);
|
|
--warehouse-muted: var(--vip-content-muted, #595959);
|
|
--warehouse-accent: var(--vip-content-accent-dark, #856632);
|
|
--warehouse-soft: var(--vip-content-accent-soft, #F7F0DE);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: .75rem;
|
|
margin: 1.5rem 0;
|
|
color: var(--warehouse-ink);
|
|
font-size: .8125rem;
|
|
}
|
|
.third-party-warehouse-status { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:.75rem 1rem; min-height:2.625rem; padding:.5rem .75rem; border:1px solid var(--warehouse-border); border-radius:.5rem; background:var(--warehouse-soft); }
|
|
.third-party-warehouse-status__primary,.third-party-warehouse-status__meta { display:flex; flex-wrap:wrap; align-items:center; gap:.5rem .75rem; }
|
|
.third-party-warehouse-status__primary { font-weight:600; }
|
|
.third-party-warehouse-status__meta { color:var(--warehouse-muted); font-size:.75rem; }
|
|
.third-party-warehouse-status__dot { width:.5rem; height:.5rem; flex:0 0 auto; border-radius:50%; background:var(--vip-success,#53793E); }
|
|
.third-party-warehouse-status__dot.is-warning { background:var(--vip-warning,#935F07); }
|
|
.third-party-warehouse-status__dot.is-danger { background:var(--vip-danger,#B80000); }
|
|
.third-party-warehouse-status__dot.is-info { background:var(--vip-info,#006395); }
|
|
.third-party-warehouse-alert { display:flex; align-items:flex-start; gap:.5rem; padding:.625rem .75rem; border:1px solid rgba(184,0,0,.35); border-radius:.5rem; background:#fff5f5; color:var(--vip-danger,#B80000); }
|
|
.third-party-warehouse-notice { display:flex; align-items:flex-start; gap:.5rem; padding:.625rem .75rem; border:1px solid rgba(147,95,7,.35); border-radius:.5rem; background:#fffaf0; color:var(--vip-warning,#935F07); }
|
|
.third-party-warehouse-alert .material-icons-outlined,.third-party-warehouse-notice .material-icons-outlined { margin-top:.05rem; font-size:1.1rem; }
|
|
.third-party-warehouse-filters { display:flex; flex-wrap:wrap; align-items:flex-end; gap:.65rem; padding:.75rem; border:1px solid var(--warehouse-border); border-radius:.5rem; background:var(--vip-surface,#fff); }
|
|
.third-party-warehouse-field { display:flex; min-width:8.75rem; flex:1 1 10rem; flex-direction:column; gap:.3rem; }
|
|
.third-party-warehouse-field:first-of-type { flex:0 1 9.5rem; }
|
|
.third-party-warehouse-field label { color:var(--warehouse-muted); font-size:.75rem; font-weight:600; }
|
|
.third-party-warehouse-field input { width:100%; height:2.125rem; padding:0 .625rem; border:1px solid var(--warehouse-border); border-radius:.5rem; background:#fff; color:var(--warehouse-ink); font:inherit; }
|
|
.third-party-warehouse-field input:focus { border-color:var(--warehouse-accent); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
|
|
.third-party-warehouse-dimension { display:flex; min-width:10.75rem; flex:0 0 auto; flex-direction:column; gap:.3rem; margin:0; padding:0; border:0; }
|
|
.third-party-warehouse-dimension__label { color:var(--warehouse-muted); font-size:.75rem; font-weight:600; }
|
|
.third-party-warehouse-segmented { display:inline-grid; height:2.125rem; grid-template-columns:repeat(2,1fr); overflow:hidden; border:1px solid var(--warehouse-border); border-radius:.5rem; background:#fff; }
|
|
.third-party-warehouse-segmented label { position:relative; display:flex; min-width:5rem; align-items:center; justify-content:center; padding:0 .65rem; color:var(--warehouse-muted); cursor:pointer; font-size:.75rem; font-weight:600; }
|
|
.third-party-warehouse-segmented label + label { border-left:1px solid var(--warehouse-border); }
|
|
.third-party-warehouse-segmented input { position:absolute; width:1px; height:1px; overflow:hidden; opacity:0; pointer-events:none; }
|
|
.third-party-warehouse-segmented label:has(input:checked) { background:var(--warehouse-accent); color:#fff; }
|
|
.third-party-warehouse-segmented label:has(input:focus-visible) { outline:2px solid var(--warehouse-accent); outline-offset:-3px; }
|
|
.third-party-warehouse-actions { display:flex; flex:1 1 29rem; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:.4rem; }
|
|
.third-party-warehouse-button,.third-party-warehouse-actions [data-finance-server-export] { display:inline-flex; height:2.125rem; align-items:center; justify-content:center; gap:.3rem; padding:0 .7rem; border:1px solid var(--warehouse-border); border-radius:.5rem; background:#fff; color:var(--warehouse-ink); cursor:pointer; font:inherit; font-weight:600; line-height:1; text-decoration:none; white-space:nowrap; }
|
|
.third-party-warehouse-button:hover,.third-party-warehouse-actions [data-finance-server-export]:hover { border-color:var(--warehouse-accent); background:var(--warehouse-soft); }
|
|
.third-party-warehouse-button:focus-visible,.third-party-warehouse-actions [data-finance-server-export]:focus-visible { border-color:var(--warehouse-accent); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.2); }
|
|
.third-party-warehouse-button.is-primary { border-color:var(--warehouse-accent); background:var(--warehouse-accent); color:#fff; }
|
|
.third-party-warehouse-button.is-primary:hover { background:#6A5129; }
|
|
.third-party-warehouse-button.is-disabled,.third-party-warehouse-actions [data-finance-server-export][disabled] { opacity:.5; cursor:not-allowed; pointer-events:none; }
|
|
.third-party-warehouse-button .material-icons-outlined,.third-party-warehouse-actions .material-icons-outlined { font-size:1rem; }
|
|
.third-party-warehouse-actions [data-finance-server-export] { display:none; }
|
|
.third-party-warehouse-page[data-finance-enhanced] .third-party-warehouse-actions [data-finance-server-export] { display:inline-flex; }
|
|
.third-party-warehouse-page .finance-table-field-picker__panel { right:0; left:auto; }
|
|
.third-party-warehouse-summary { display:grid; grid-template-columns:repeat(3,minmax(8rem,1fr)); overflow:hidden; border:1px solid var(--warehouse-border); border-radius:.5rem; background:#fff; }
|
|
.third-party-warehouse-summary__item { min-width:0; padding:.625rem .75rem; border-right:1px solid var(--warehouse-border); }
|
|
.third-party-warehouse-summary__item:last-child { border-right:0; }
|
|
.third-party-warehouse-summary__label { display:block; color:var(--warehouse-muted); font-size:.7rem; }
|
|
.third-party-warehouse-summary__value { display:block; margin-top:.15rem; overflow:hidden; color:#4B3A24; font-size:.9375rem; font-variant-numeric:tabular-nums; font-weight:700; text-overflow:ellipsis; white-space:nowrap; }
|
|
.third-party-warehouse-table-panel { overflow:hidden; border:1px solid var(--warehouse-border); border-radius:.5rem; background:#fff; }
|
|
.third-party-warehouse-table-scroll { width:100%; height:min(56vh,42rem); min-height:16rem; overflow:auto; overscroll-behavior:contain; scrollbar-color:var(--vip-content-accent,#B9954F) #EFEDE8; scrollbar-gutter:stable both-edges; scrollbar-width:thin; }
|
|
.third-party-warehouse-table-scroll::-webkit-scrollbar { width:.5rem; height:.5rem; }
|
|
.third-party-warehouse-table-scroll::-webkit-scrollbar-thumb { border-radius:.25rem; background:var(--vip-content-accent,#B9954F); }
|
|
.third-party-warehouse-table-scroll::-webkit-scrollbar-track { background:#EFEDE8; }
|
|
.third-party-warehouse-table { width:max-content; min-width:100%; border-collapse:separate; border-spacing:0; font-size:.78125rem; }
|
|
.third-party-warehouse-table th,.third-party-warehouse-table td { min-width:7.25rem; max-width:15rem; padding:.5rem .625rem; border-right:1px solid var(--warehouse-border); border-bottom:1px solid #EEEAE3; background:#fff; line-height:1.45; text-align:left; vertical-align:middle; white-space:nowrap; }
|
|
.third-party-warehouse-table th { position:sticky; top:0; z-index:4; border-bottom-color:var(--warehouse-border); background:var(--warehouse-soft); color:#4F4A43; font-size:.75rem; font-weight:600; }
|
|
.third-party-warehouse-table tbody tr:hover td { background:#FCFAF5; }
|
|
.third-party-warehouse-table .is-number { text-align:right; font-variant-numeric:tabular-nums; }
|
|
.third-party-warehouse-table .is-wide { min-width:14rem; max-width:20rem; overflow:hidden; text-overflow:ellipsis; }
|
|
.third-party-warehouse-table .is-code { min-width:10rem; font-variant-numeric:tabular-nums; }
|
|
.third-party-warehouse-table .is-image { min-width:4.75rem; width:4.75rem; text-align:center; }
|
|
.third-party-warehouse-thumb { display:block; width:2.5rem; height:2.5rem; margin:auto; border:1px solid var(--warehouse-border); border-radius:.25rem; background:#F8F6F1; object-fit:cover; }
|
|
.third-party-warehouse-empty { height:15rem; color:var(--warehouse-muted); text-align:center !important; white-space:normal !important; }
|
|
.third-party-warehouse-pagination { display:flex; min-height:2.75rem; align-items:center; justify-content:space-between; gap:.75rem; padding:.5rem .75rem; border-top:1px solid var(--warehouse-border); color:var(--warehouse-muted); font-size:.75rem; }
|
|
.third-party-warehouse-pagination__controls { display:flex; align-items:center; gap:.4rem; }
|
|
@media (max-width: 960px) {
|
|
.third-party-warehouse-summary { grid-template-columns:repeat(2,minmax(0,1fr)); }
|
|
.third-party-warehouse-summary__item { border-bottom:1px solid var(--warehouse-border); }
|
|
.third-party-warehouse-actions { justify-content:flex-start; }
|
|
}
|
|
@media (max-width: 640px) {
|
|
.third-party-warehouse-field { flex-basis:100%; }
|
|
.third-party-warehouse-actions { flex-basis:100%; }
|
|
.third-party-warehouse-summary { grid-template-columns:1fr; }
|
|
.third-party-warehouse-summary__item { border-right:0; }
|
|
.third-party-warehouse-table-scroll { height:52vh; min-height:14rem; }
|
|
.third-party-warehouse-pagination { align-items:flex-start; flex-direction:column; }
|
|
.third-party-warehouse-page .finance-table-field-picker__panel { right:auto; left:0; }
|
|
}
|
|
</style>
|
|
|
|
<div class="third-party-warehouse-status" role="status" aria-live="polite">
|
|
<div class="third-party-warehouse-status__primary">
|
|
<span @class(['third-party-warehouse-status__dot', 'is-' . $statusTone]) aria-hidden="true"></span>
|
|
<span>{{ $statusLabel }}</span>
|
|
@if ($showingLastVersion)
|
|
<span>· 当前展示最近一次成功同步的数据</span>
|
|
@endif
|
|
</div>
|
|
<div class="third-party-warehouse-status__meta">
|
|
<span>三方-南昌樵舍仓 · 查询维度:商品 + 按天</span>
|
|
<span>每日 00:00 自动同步</span>
|
|
@if (! empty($sync['last_succeeded_at']))
|
|
<span>最近成功同步 {{ $sync['last_succeeded_at'] }}</span>
|
|
@endif
|
|
@if ($lagLabel !== null)
|
|
<span>延迟 {{ $lagLabel }}</span>
|
|
@endif
|
|
@if ($error === '' && ! empty($sync['message']))
|
|
<span>{{ $sync['message'] }}</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
@if ($error !== '')
|
|
<div class="third-party-warehouse-alert" role="alert">
|
|
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
|
<span>
|
|
@if ($showingLastVersion)
|
|
本次同步或读取异常,已保留上一版数据:{{ $error }}
|
|
@elseif ($pending)
|
|
首次同步未完成:{{ $error }}
|
|
@else
|
|
数据暂时无法读取:{{ $error }}
|
|
@endif
|
|
</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($pending)
|
|
<div class="third-party-warehouse-notice" role="status">
|
|
<span class="material-icons-outlined" aria-hidden="true">schedule</span>
|
|
<span>首次同步尚未完成,当前没有可展示的正式镜像数据;同步成功后页面会自动使用镜像数据。</span>
|
|
</div>
|
|
@endif
|
|
|
|
<form method="GET" class="third-party-warehouse-filters" aria-label="三方仓进销存筛选" data-finance-controls>
|
|
<fieldset class="third-party-warehouse-dimension">
|
|
<legend class="third-party-warehouse-dimension__label">查询维度</legend>
|
|
<div class="third-party-warehouse-segmented" data-warehouse-dimension>
|
|
<label>
|
|
<input type="radio" name="dimension" value="month" @checked($dimension === 'month')>
|
|
<span>按月</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="dimension" value="day" @checked($dimension === 'day')>
|
|
<span>按天</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
<div class="third-party-warehouse-field">
|
|
<label for="warehouse-period" data-warehouse-period-label>{{ $dimension === 'day' ? '日期' : '月份' }}</label>
|
|
<input
|
|
id="warehouse-period"
|
|
type="{{ $dimension === 'day' ? 'date' : 'month' }}"
|
|
name="period"
|
|
value="{{ $filters['period'] }}"
|
|
data-warehouse-period
|
|
>
|
|
</div>
|
|
<div class="third-party-warehouse-field">
|
|
<label for="warehouse-sku-code">商品编码</label>
|
|
<input id="warehouse-sku-code" type="search" name="sku_code" value="{{ $filters['sku_code'] }}" placeholder="输入商品编码">
|
|
</div>
|
|
<div class="third-party-warehouse-field">
|
|
<label for="warehouse-product-name">商品名称</label>
|
|
<input id="warehouse-product-name" type="search" name="product_name" value="{{ $filters['product_name'] }}" placeholder="支持关键词筛选">
|
|
</div>
|
|
<div class="third-party-warehouse-field">
|
|
<label for="warehouse-supplier">供应商</label>
|
|
<input id="warehouse-supplier" type="search" name="supplier" value="{{ $filters['supplier'] }}" placeholder="支持关键词筛选">
|
|
</div>
|
|
<div class="third-party-warehouse-field">
|
|
<label for="warehouse-category">分类</label>
|
|
<input id="warehouse-category" type="search" name="category" value="{{ $filters['category'] }}" placeholder="支持关键词筛选">
|
|
</div>
|
|
<div class="third-party-warehouse-actions">
|
|
<button class="third-party-warehouse-button is-primary" type="submit">
|
|
<span class="material-icons-outlined" aria-hidden="true">search</span><span>筛选</span>
|
|
</button>
|
|
<a class="third-party-warehouse-button" href="{{ url()->current() }}">
|
|
<span class="material-icons-outlined" aria-hidden="true">restart_alt</span><span>重置</span>
|
|
</a>
|
|
<a
|
|
@class(['third-party-warehouse-button', 'finance-table-fallback-export', 'is-disabled' => ! $canExport])
|
|
data-finance-fallback-export
|
|
href="{{ $canExport ? $fallbackExportUrl : '#' }}"
|
|
@if (! $canExport) aria-disabled="true" @endif
|
|
>
|
|
<span class="material-icons-outlined" aria-hidden="true">download</span><span>导出筛选结果</span>
|
|
</a>
|
|
<button type="button" data-finance-server-export="visible" @disabled(! $canExport)>
|
|
<span class="material-icons-outlined" aria-hidden="true">download</span><span>导出显示列</span>
|
|
</button>
|
|
<button type="button" data-finance-server-export="all" @disabled(! $canExport)>
|
|
<span class="material-icons-outlined" aria-hidden="true">download</span><span>导出全部列</span>
|
|
</button>
|
|
<div class="finance-table-field-picker" data-finance-field-picker>
|
|
<button type="button" class="third-party-warehouse-button" aria-expanded="false">
|
|
<span class="material-icons-outlined" aria-hidden="true">view_column</span><span>列设置</span>
|
|
</button>
|
|
<div class="finance-table-field-picker__panel" role="group" aria-label="选择展示字段" hidden></div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="third-party-warehouse-summary" aria-label="当前筛选汇总">
|
|
@foreach ([
|
|
['key' => 'count', 'label' => '明细记录数', 'type' => 'count'],
|
|
['key' => 'opening_qty', 'label' => '期初数量合计', 'type' => 'number'],
|
|
['key' => 'closing_qty', 'label' => '期末数量合计', 'type' => 'number'],
|
|
] as $metric)
|
|
@php
|
|
$metricValue = $summaryReady ? ($summary[$metric['key']] ?? null) : null;
|
|
$metricDisplay = match ($metric['type']) {
|
|
'count' => $metricValue === null ? '—' : number_format((int) $metricValue),
|
|
default => $metricValue === null ? '—' : $number($metricValue),
|
|
};
|
|
@endphp
|
|
<div class="third-party-warehouse-summary__item">
|
|
<span class="third-party-warehouse-summary__label">{{ $metric['label'] }}</span>
|
|
<span class="third-party-warehouse-summary__value">{{ $metricDisplay }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<div class="third-party-warehouse-table-panel">
|
|
<div class="third-party-warehouse-table-scroll finance-table-scroll-owner">
|
|
<table
|
|
class="third-party-warehouse-table"
|
|
aria-label="三方仓进销存"
|
|
data-finance-table
|
|
@if ($exportUrl !== '') data-finance-export-base="{{ $exportUrl }}" @endif
|
|
data-finance-export-query="{{ json_encode($activeFilters, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}"
|
|
data-finance-export-disabled="{{ $canExport ? 'false' : 'true' }}"
|
|
>
|
|
<thead>
|
|
<tr>
|
|
@foreach ($columns as $column)
|
|
<th
|
|
scope="col"
|
|
data-finance-column-key="{{ $column['key'] }}"
|
|
data-export-field="{{ $column['key'] }}"
|
|
@class([
|
|
'is-number' => in_array($column['type'] ?? null, ['number', 'money'], true),
|
|
'is-image' => ($column['type'] ?? null) === 'image',
|
|
'is-wide' => ($column['type'] ?? null) === 'wide',
|
|
'is-code' => ($column['type'] ?? null) === 'code',
|
|
])
|
|
>{{ $column['label'] }}</th>
|
|
@endforeach
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse ($rows ?? [] as $row)
|
|
<tr>
|
|
@foreach ($columns as $column)
|
|
@php
|
|
$value = data_get($row, $column['key']);
|
|
$type = $column['type'] ?? 'text';
|
|
$displayValue = match ($type) {
|
|
'number' => $number($value),
|
|
'money' => $money($value),
|
|
default => $text($value),
|
|
};
|
|
@endphp
|
|
<td
|
|
@class([
|
|
'is-number' => in_array($type, ['number', 'money'], true),
|
|
'is-image' => $type === 'image',
|
|
'is-wide' => $type === 'wide',
|
|
'is-code' => $type === 'code',
|
|
])
|
|
@if ($type === 'wide' && $displayValue !== '-') title="{{ $displayValue }}" @endif
|
|
@if ($type === 'image') data-export-value="{{ $text($value) }}" @endif
|
|
>
|
|
@if ($type === 'image')
|
|
@php($safeImageUrl = $imageUrl($value))
|
|
@if ($safeImageUrl !== null)
|
|
<a href="{{ $safeImageUrl }}" target="_blank" rel="noopener noreferrer" aria-label="查看{{ $text(data_get($row, 'product_name')) }}图片">
|
|
<img class="third-party-warehouse-thumb" src="{{ $safeImageUrl }}" alt="" width="40" height="40" loading="lazy" decoding="async" referrerpolicy="no-referrer">
|
|
</a>
|
|
@else
|
|
-
|
|
@endif
|
|
@else
|
|
{{ $displayValue }}
|
|
@endif
|
|
</td>
|
|
@endforeach
|
|
</tr>
|
|
@empty
|
|
<tr class="is-empty">
|
|
<td class="third-party-warehouse-empty" colspan="{{ count($columns) }}">
|
|
@if ($pending)
|
|
首次同步尚未完成,当前不会显示空白数据冒充正式结果。
|
|
@elseif ($error !== '')
|
|
镜像数据读取失败,当前没有可展示的数据。
|
|
@elseif (in_array($statusCode, ['initial_failed', 'unavailable'], true))
|
|
镜像尚未准备好,请等待同步恢复后再查看。
|
|
@else
|
|
当前期间及筛选条件下没有数据,请调整条件或重置筛选。
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="third-party-warehouse-pagination">
|
|
<span>共 {{ number_format($rowTotal) }} 条记录,本页 {{ number_format($rowCount) }} 条</span>
|
|
@if (is_object($rows) && method_exists($rows, 'currentPage'))
|
|
<div class="third-party-warehouse-pagination__controls" aria-label="三方仓进销存分页">
|
|
<a
|
|
@class(['third-party-warehouse-button', 'is-disabled' => $rows->onFirstPage()])
|
|
href="{{ $rows->previousPageUrl() ?: '#' }}"
|
|
aria-label="上一页"
|
|
@if ($rows->onFirstPage()) aria-disabled="true" @endif
|
|
><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></a>
|
|
<span>第 {{ $rows->currentPage() }} / {{ max($rows->lastPage(), 1) }} 页</span>
|
|
<a
|
|
@class(['third-party-warehouse-button', 'is-disabled' => ! $rows->hasMorePages()])
|
|
href="{{ $rows->nextPageUrl() ?: '#' }}"
|
|
aria-label="下一页"
|
|
@if (! $rows->hasMorePages()) aria-disabled="true" @endif
|
|
><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
const page = document.currentScript.closest('.third-party-warehouse-page');
|
|
const period = page?.querySelector('[data-warehouse-period]');
|
|
const periodLabel = page?.querySelector('[data-warehouse-period-label]');
|
|
|
|
if (!period || !periodLabel) return;
|
|
|
|
page.querySelectorAll('[data-warehouse-dimension] input[name="dimension"]').forEach((control) => {
|
|
control.addEventListener('change', () => {
|
|
const nextType = control.value === 'day' ? 'date' : 'month';
|
|
if (period.type !== nextType) period.value = '';
|
|
period.type = nextType;
|
|
periodLabel.textContent = nextType === 'date' ? '日期' : '月份';
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
</div>
|