2137 lines
125 KiB
PHP
2137 lines
125 KiB
PHP
@php
|
||
$directoryRows = $rows ?? collect();
|
||
$directoryFields = $directory['fields'] ?? [];
|
||
$isInventoryReportPage = ($page['slug'] ?? '') === 'inventory-report';
|
||
$inventoryWarehouseMappings = $isInventoryReportPage
|
||
? ($inventoryWarehouseMappings ?? collect())
|
||
: collect();
|
||
$warehouseMappingPreview = $isInventoryReportPage ? session('warehouse_mapping_preview') : null;
|
||
// “最近三个月销量”必须是三个独立的月份列;月份按当前月向前取三个完整月份。
|
||
// 保留原字段名作为兼容契约,渲染时展开为 *_1、*_2、*_3 三个值。
|
||
$directoryFields = collect($directoryFields)->flatMap(function (array $field): array {
|
||
if (($field['name'] ?? null) !== 'recent_three_month_sales') {
|
||
return [$field];
|
||
}
|
||
|
||
return collect(range(3, 1))->map(function (int $monthsAgo): array {
|
||
$month = now()->startOfMonth()->subMonthsNoOverflow($monthsAgo);
|
||
|
||
return [
|
||
'name' => 'recent_three_month_sales_' . (4 - $monthsAgo),
|
||
'label' => $month->format('n月') . '销量',
|
||
'kind' => 'number',
|
||
];
|
||
})->all();
|
||
})->values()->all();
|
||
$directoryIsPaginated = is_object($directoryRows) && method_exists($directoryRows, 'total');
|
||
$directoryTotal = $directoryIsPaginated ? $directoryRows->total() : (is_countable($directoryRows) ? count($directoryRows) : 0);
|
||
$directoryCurrentPage = $directoryIsPaginated ? $directoryRows->currentPage() : 1;
|
||
$directoryLastPage = $directoryIsPaginated ? $directoryRows->lastPage() : 1;
|
||
$directoryPerPage = $directoryIsPaginated ? $directoryRows->perPage() : 0;
|
||
// 页面配置在部分导航上下文中会省略父级 slug,优先使用当前路由参数避免功能开关失配。
|
||
$directoryRouteSection = request()->route('section') ?: request()->segment(3) ?: ($section['slug'] ?? '');
|
||
$directoryRoutePage = request()->route('page') ?: request()->segment(4) ?: ($page['slug'] ?? '');
|
||
$directoryPageKey = (string) $directoryRouteSection . '/' . (string) $directoryRoutePage;
|
||
$directoryUsesFieldQuery = in_array($directoryPageKey, [
|
||
'company-management/basic-information',
|
||
'store-management/basic-information',
|
||
'trademark-management/information',
|
||
'store-management/store-accounts',
|
||
'store-management/store-report',
|
||
'company-management/subject-operating-report',
|
||
'company-management/company-cost-arrangement',
|
||
'company-management/personnel-inventory',
|
||
], true);
|
||
$directoryQueryPageDescriptions = [
|
||
'company-management/basic-information' => '公司主体、状态和登记资料的查询与导出',
|
||
'store-management/basic-information' => '店铺主体、平台和经营资料的查询与导出',
|
||
'trademark-management/information' => '商标主体、状态和登记资料的查询与导出',
|
||
'store-management/store-accounts' => '店铺账号资料的查询与安全状态导出',
|
||
'store-management/store-report' => '按店铺、主体和月份销量查询经营数据并导出',
|
||
'company-management/subject-operating-report' => '按主体和收入月份查询经营汇总并导出',
|
||
'company-management/company-cost-arrangement' => '按公司类型和发票类型查询成本安排字段',
|
||
'company-management/personnel-inventory' => '按人员、职位和公司类型查询人员盘点字段',
|
||
];
|
||
@endphp
|
||
|
||
<style>
|
||
.directory-table-page {
|
||
margin-top: 1.5rem;
|
||
overflow: hidden;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.75rem;
|
||
background: var(--vip-surface, #ffffff);
|
||
color: var(--vip-content-ink, #171717);
|
||
}
|
||
|
||
.directory-table-page.has-field-query {
|
||
margin-top: 1.5rem;
|
||
overflow: visible;
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
}
|
||
|
||
.directory-query-page-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.directory-query-page-head strong {
|
||
display: block;
|
||
color: var(--vip-content-ink, #171717);
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
.directory-query-page-head p {
|
||
margin: 0.4rem 0 0;
|
||
color: var(--vip-content-muted, #595959);
|
||
font-size: 0.75rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.directory-table-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 1rem 1.25rem;
|
||
border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
}
|
||
|
||
.directory-table-title {
|
||
display: flex;
|
||
min-width: 0;
|
||
align-items: flex-start;
|
||
gap: 0.625rem;
|
||
}
|
||
|
||
.directory-table-title .material-icons-outlined {
|
||
flex: 0 0 auto;
|
||
margin-top: 0.05rem;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
font-size: 1.25rem;
|
||
}
|
||
|
||
.directory-table-title strong {
|
||
display: block;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.directory-table-description {
|
||
max-width: 72rem;
|
||
margin-top: 0.3rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
font-size: 0.75rem;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.directory-table-status {
|
||
flex: 0 0 auto;
|
||
padding: 0.25rem 0.55rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 999px;
|
||
background: var(--vip-content-accent-soft, #f7f0de);
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
font-size: 0.75rem;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.directory-table-notice {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.5rem;
|
||
margin: 1rem 1.25rem;
|
||
padding: 0.7rem 0.8rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
background: #fbfaf7;
|
||
color: var(--vip-content-muted, #595959);
|
||
font-size: 0.75rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.directory-table-notice .material-icons-outlined {
|
||
flex: 0 0 auto;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.directory-table-tools {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
background: var(--vip-content-bg, #f8f6f1);
|
||
}
|
||
|
||
.directory-query-bar {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(12rem, 1fr));
|
||
gap: 1rem 1.25rem;
|
||
margin: 0;
|
||
padding: 1.25rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
background: #fff;
|
||
}
|
||
.directory-query-heading { display: flex; grid-column: 1 / -1; align-items: center; justify-content: space-between; gap: 1rem; padding-bottom: 0.125rem; color: var(--vip-content-accent-dark, #856632); }
|
||
.directory-query-heading strong { font-size: 0.8125rem; }
|
||
.directory-query-heading span { color: var(--vip-content-muted, #595959); font-size: 0.6875rem; font-weight: 400; }
|
||
.directory-query-field { position: relative; display: flex; min-width: 0; flex-direction: column; align-items: stretch; gap: 0.45rem; color: var(--vip-content-muted, #595959); font-size: 0.75rem; }
|
||
.directory-query-field > span:first-child { overflow: hidden; color: var(--vip-content-ink, #171717); font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
||
.directory-query-field input, .directory-query-field select { width: 100%; min-width: 0; height: 2.125rem; padding: 0 0.625rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.375rem; background: #fff; color: var(--vip-content-ink, #171717); font-size: 0.75rem; }
|
||
.directory-query-field input:focus, .directory-query-field select:focus { border-color: var(--vip-content-accent, #b9954f); outline: 2px solid rgba(185, 149, 79, 0.16); }
|
||
.directory-query-dates { display: grid; flex: 1 1 auto; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 0.25rem; width: 100%; min-width: 0; }
|
||
.directory-query-dates input { min-width: 0; }
|
||
.directory-query-more { grid-column: 1 / -1; margin-top: 0.25rem; border-top: 1px solid #e8e3da; }
|
||
.directory-query-more > summary { width: fit-content; padding-top: 0.75rem; color: var(--vip-content-accent-dark, #856632); cursor: pointer; list-style: disclosure-closed; }
|
||
.directory-query-more[open] > summary { list-style: disclosure-open; }
|
||
.directory-query-more__fields { display: grid; grid-template-columns: repeat(4, minmax(12rem, 1fr)); gap: 1rem 1.25rem; margin-top: 1rem; }
|
||
.directory-query-suggestions { position: absolute; top: calc(100% + 0.25rem); right: 0; left: 0; z-index: 6; margin: 0; padding: 0.25rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.375rem; background: #fff; box-shadow: 0 5px 12px rgba(23,23,23,0.12); list-style: none; }
|
||
.directory-query-suggestions[hidden] { display: none; }
|
||
.directory-query-suggestions button { display: block; width: 100%; overflow: hidden; padding: 0.4rem 0.5rem; border: 0; border-radius: 0.25rem; background: transparent; color: var(--vip-content-ink, #171717); font: inherit; text-align: left; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
|
||
.directory-query-suggestions button:hover, .directory-query-suggestions button:focus { background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||
.directory-query-actions { display: flex; grid-column: 1 / -1; flex-wrap: wrap; align-items: flex-start; justify-content: flex-end; gap: 0.5rem; padding-top: 0.125rem; }
|
||
.directory-query-actions button { height: 2.1rem; padding: 0 0.8rem; border: 1px solid var(--vip-content-accent, #b9954f); border-radius: 0.375rem; background: #fff; color: var(--vip-content-accent-dark, #856632); cursor: pointer; }
|
||
.directory-query-actions .is-primary { background: var(--vip-content-accent-dark, #856632); color: #fff; }
|
||
.directory-query-picker, .directory-query-export { position: relative; }
|
||
.directory-query-picker summary, .directory-query-export summary { display: flex; height: 2.1rem; align-items: center; padding: 0 0.8rem; border: 1px solid var(--vip-content-accent, #b9954f); border-radius: 0.375rem; background: #fff; color: var(--vip-content-accent-dark, #856632); cursor: pointer; list-style: none; }
|
||
.directory-query-export summary { gap: 0.35rem; }
|
||
.directory-query-export summary .material-icons-outlined { font-size: 1rem; }
|
||
.directory-query-picker summary::-webkit-details-marker, .directory-query-export summary::-webkit-details-marker { display: none; }
|
||
.directory-query-picker__panel, .directory-query-export__panel { position: absolute; top: calc(100% + 0.35rem); right: 0; z-index: 8; display: grid; grid-template-columns: repeat(2, minmax(10rem, 1fr)); gap: 0.35rem 0.75rem; width: max-content; min-width: 24rem; max-width: min(34rem, calc(100vw - 2rem)); max-height: 22rem; overflow: auto; padding: 0.75rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.5rem; background: #fff; box-shadow: 0 4px 8px rgba(23, 23, 23, 0.10); }
|
||
.directory-query-picker__hint { grid-column: 1 / -1; color: var(--vip-content-muted, #595959); font-size: 0.6875rem; }
|
||
.directory-query-picker__option-form { min-width: 0; }
|
||
.directory-query-picker__option { display: flex; align-items: center; gap: 0.4rem; width: 100%; min-width: 0; padding: 0.25rem; border: 0; border-radius: 0.25rem; background: transparent; color: var(--vip-content-ink, #171717); font: inherit; font-size: 0.75rem; text-align: left; cursor: pointer; }
|
||
.directory-query-picker__option:hover, .directory-query-picker__option:focus { background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||
.directory-query-picker__option input { flex: 0 0 auto; width: 1rem; height: 1rem; margin: 0; accent-color: var(--vip-content-accent, #b9954f); }
|
||
.directory-query-export__option { display: flex; align-items: center; gap: 0.4rem; min-width: 0; color: var(--vip-content-ink, #171717); font-size: 0.75rem; cursor: pointer; }
|
||
.directory-query-export__option input { flex: 0 0 auto; width: 1rem; height: 1rem; margin: 0; accent-color: var(--vip-content-accent, #b9954f); }
|
||
.directory-query-export__button { grid-column: 1 / -1; justify-self: end; }
|
||
@media (max-width: 1100px) { .directory-query-bar { grid-template-columns: repeat(2, minmax(14rem, 1fr)); } }
|
||
@media (max-width: 1100px) { .directory-query-more__fields { grid-template-columns: repeat(2, minmax(14rem, 1fr)); } }
|
||
@media (max-width: 650px) {
|
||
.directory-query-page-head { margin-bottom: 1rem; }
|
||
.directory-query-bar { grid-template-columns: minmax(0, 1fr); padding: 1rem; }
|
||
.directory-query-more__fields { grid-template-columns: minmax(0, 1fr); }
|
||
.directory-query-heading { align-items: flex-start; flex-direction: column; gap: 0.3rem; }
|
||
.directory-query-actions { justify-content: flex-start; }
|
||
.directory-query-actions > * { flex: 1 1 calc(50% - 0.25rem); }
|
||
.directory-query-actions button,
|
||
.directory-query-picker summary,
|
||
.directory-query-export summary { justify-content: center; width: 100%; }
|
||
.directory-query-picker__panel, .directory-query-export__panel { right: auto; left: 0; grid-template-columns: minmax(0, 1fr); width: min(18rem, calc(100vw - 2rem)); min-width: 0; }
|
||
}
|
||
|
||
.directory-table-tools > [data-directory-search] {
|
||
flex: 1 1 16rem;
|
||
min-width: 12rem;
|
||
height: 2.25rem;
|
||
padding: 0 0.75rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.25rem;
|
||
background: #fff;
|
||
color: var(--vip-content-ink, #171717);
|
||
}
|
||
|
||
.directory-table-tools button {
|
||
height: 2.25rem;
|
||
padding: 0 0.875rem;
|
||
border: 1px solid var(--vip-content-accent, #b9954f);
|
||
border-radius: 0.25rem;
|
||
background: #fff;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.directory-table-tools button:disabled {
|
||
cursor: wait;
|
||
opacity: 0.65;
|
||
}
|
||
|
||
.directory-table-tools select {
|
||
height: 2.25rem;
|
||
padding: 0 0.5rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.25rem;
|
||
background: #fff;
|
||
color: var(--vip-content-ink, #171717);
|
||
}
|
||
|
||
.inventory-month-picker {
|
||
position: relative;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
|
||
.inventory-month-picker > input[type="hidden"] { display: none; }
|
||
|
||
.inventory-month-picker__trigger {
|
||
display: flex;
|
||
width: 100%;
|
||
height: 2.1rem;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
padding: 0 0.6rem 0 0.7rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.375rem;
|
||
background: var(--vip-surface, #fff);
|
||
color: var(--vip-content-ink, #171717);
|
||
font: inherit;
|
||
font-size: 0.75rem;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.inventory-month-picker__trigger:hover { border-color: var(--vip-content-accent, #b9954f); }
|
||
.inventory-month-picker__trigger:focus-visible {
|
||
border-color: var(--vip-content-accent-dark, #856632);
|
||
outline: 2px solid rgba(133, 102, 50, 0.2);
|
||
outline-offset: 1px;
|
||
}
|
||
.inventory-month-picker__trigger [data-inventory-month-label]:empty::before { content: '选择月份'; color: var(--vip-content-muted, #595959); }
|
||
.inventory-month-picker__trigger [data-inventory-month-label] { flex: 1 1 auto; min-width: 0; }
|
||
.inventory-month-picker__trigger .material-icons-outlined { flex: 0 0 auto; color: var(--vip-content-accent-dark, #856632); font-size: 1.05rem; }
|
||
|
||
.inventory-month-picker__panel {
|
||
position: absolute;
|
||
top: calc(100% + 0.4rem);
|
||
left: 0;
|
||
z-index: 12;
|
||
width: min(18rem, calc(100vw - 2rem));
|
||
padding: 0.8rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
background: var(--vip-surface, #fff);
|
||
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.1);
|
||
}
|
||
.inventory-month-picker__panel[hidden] { display: none; }
|
||
.inventory-month-picker__yearbar,
|
||
.inventory-month-picker__footer { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
|
||
.inventory-month-picker__yearbar { padding-bottom: 0.65rem; border-bottom: 1px solid #eeeae3; }
|
||
.inventory-month-picker__yearbar strong { color: var(--vip-content-ink, #171717); font-size: 0.8125rem; font-weight: 600; }
|
||
.inventory-month-picker__nav,
|
||
.inventory-month-picker__footer button {
|
||
height: 1.9rem;
|
||
border: 1px solid transparent;
|
||
border-radius: 0.3rem;
|
||
background: transparent;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
font: inherit;
|
||
cursor: pointer;
|
||
}
|
||
.inventory-month-picker__nav { display: inline-flex; width: 1.9rem; align-items: center; justify-content: center; }
|
||
.inventory-month-picker__nav:hover,
|
||
.inventory-month-picker__nav:focus-visible,
|
||
.inventory-month-picker__footer button:hover,
|
||
.inventory-month-picker__footer button:focus-visible { border-color: var(--vip-border, #d8d2c7); background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||
.inventory-month-picker__nav .material-icons-outlined { font-size: 1rem; }
|
||
.inventory-month-picker__months { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.35rem; padding: 0.75rem 0; }
|
||
.inventory-month-picker__month {
|
||
height: 2.15rem;
|
||
border: 1px solid transparent;
|
||
border-radius: 0.3rem;
|
||
background: transparent;
|
||
color: var(--vip-content-ink, #171717);
|
||
font: inherit;
|
||
font-size: 0.75rem;
|
||
cursor: pointer;
|
||
}
|
||
.inventory-month-picker__month:hover,
|
||
.inventory-month-picker__month:focus-visible { border-color: var(--vip-content-accent, #b9954f); background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||
.inventory-month-picker__month.is-selected { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; }
|
||
.inventory-month-picker__footer { padding-top: 0.65rem; border-top: 1px solid #eeeae3; }
|
||
.inventory-month-picker__footer button { padding: 0 0.55rem; }
|
||
.inventory-month-picker__footer button:last-child { border-color: var(--vip-content-accent, #b9954f); background: var(--vip-content-accent-soft, #f7f0de); }
|
||
|
||
.directory-field-picker {
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
.directory-field-picker__panel {
|
||
position: absolute;
|
||
top: calc(100% + 0.35rem);
|
||
right: 0;
|
||
z-index: 5;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(10rem, 1fr));
|
||
gap: 0.35rem 0.75rem;
|
||
min-width: 24rem;
|
||
max-height: 22rem;
|
||
overflow: auto;
|
||
padding: 0.75rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
background: var(--vip-surface, #fff);
|
||
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.1);
|
||
}
|
||
|
||
.directory-field-picker__panel[hidden] { display: none; }
|
||
.directory-field-picker__option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
min-width: 0;
|
||
color: var(--vip-content-ink, #171717);
|
||
font-size: 0.75rem;
|
||
line-height: 1.35;
|
||
cursor: pointer;
|
||
}
|
||
.directory-field-picker__option[draggable="true"] { cursor: grab; }
|
||
.directory-field-picker__option.is-dragging { opacity: 0.55; }
|
||
.directory-field-picker__option.is-drop-target { outline: 1px solid var(--vip-content-accent, #b9954f); background: var(--vip-content-accent-soft, #f7f0de); }
|
||
.directory-field-picker__hint { grid-column: 1 / -1; color: var(--vip-content-muted, #595959); font-size: 0.6875rem; }
|
||
.directory-table-tools .directory-export-group { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
||
.directory-field-picker__option input[type="checkbox"] {
|
||
flex: 0 0 auto;
|
||
width: 1rem;
|
||
min-width: 1rem;
|
||
height: 1rem;
|
||
margin: 0;
|
||
accent-color: var(--vip-content-accent, #b9954f);
|
||
}
|
||
.directory-field-picker__option span {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.directory-field-picker__option label { display: block; flex: 1 1 auto; min-width: 0; cursor: pointer; }
|
||
.directory-table [data-column-key="sales_01"],
|
||
.directory-table [data-column-key="sales_02"],
|
||
.directory-table [data-column-key="sales_03"],
|
||
.directory-table [data-column-key="sales_04"],
|
||
.directory-table [data-column-key="sales_05"],
|
||
.directory-table [data-column-key="sales_06"] { display: none; }
|
||
|
||
.directory-table[data-columns-ready] [data-column-key="sales_01"],
|
||
.directory-table[data-columns-ready] [data-column-key="sales_02"],
|
||
.directory-table[data-columns-ready] [data-column-key="sales_03"],
|
||
.directory-table[data-columns-ready] [data-column-key="sales_04"],
|
||
.directory-table[data-columns-ready] [data-column-key="sales_05"],
|
||
.directory-table[data-columns-ready] [data-column-key="sales_06"] { display: table-cell; }
|
||
|
||
.directory-table[data-columns-ready] [data-column-key].is-column-hidden { display: none; }
|
||
|
||
.directory-table-scroll {
|
||
max-height: calc(100vh - 20rem);
|
||
overflow: auto;
|
||
scrollbar-color: var(--vip-content-accent, #b9954f) #efede8;
|
||
scrollbar-width: thin;
|
||
}
|
||
|
||
.directory-table-page.has-field-query .directory-table-scroll {
|
||
margin-top: 1.25rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
background: var(--vip-surface, #fff);
|
||
}
|
||
|
||
.directory-table-page.has-field-query .directory-table-pagination {
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-top: 0;
|
||
background: var(--vip-surface, #fff);
|
||
}
|
||
|
||
.directory-table {
|
||
width: 100%;
|
||
min-width: 68rem;
|
||
border-collapse: collapse;
|
||
font-size: 0.78125rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.directory-table th,
|
||
.directory-table td {
|
||
min-width: 8rem;
|
||
height: 2.5rem;
|
||
padding: 0.5rem 0.625rem;
|
||
border-right: 1px solid var(--vip-border, #d8d2c7);
|
||
border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
text-align: left;
|
||
vertical-align: middle;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.directory-table th:first-child,
|
||
.directory-table td:first-child {
|
||
min-width: 3rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.directory-table th:last-child,
|
||
.directory-table td:last-child {
|
||
border-right: 0;
|
||
}
|
||
|
||
.directory-table thead th {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 2;
|
||
background: var(--vip-content-accent-soft, #f7f0de);
|
||
color: var(--vip-content-ink, #171717);
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
box-shadow: 0 1px 0 var(--vip-border, #d8d2c7);
|
||
}
|
||
.directory-sort-link { display: inline-flex; align-items: center; gap: 0.25rem; color: inherit; text-decoration: none; }
|
||
.directory-sort-link:hover { color: var(--vip-content-accent-dark, #856632); }
|
||
.directory-sort-icon { width: 1rem; height: 1rem; color: var(--vip-content-muted, #595959); font-size: 1rem; line-height: 1; }
|
||
.directory-sort-link[aria-current="true"] .directory-sort-icon { color: var(--vip-content-accent-dark, #856632); }
|
||
.directory-inventory-sort { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
||
.directory-inventory-sort:hover, .directory-inventory-sort:focus-visible { color: var(--vip-content-accent-dark, #856632); }
|
||
.directory-header-menu { position: relative; display: inline-flex; align-items: center; gap: 0.15rem; }
|
||
.directory-header-menu summary { display: inline-flex; align-items: center; cursor: pointer; list-style: none; }
|
||
.directory-header-menu summary::-webkit-details-marker { display: none; }
|
||
.directory-header-menu__panel { position: absolute; top: calc(100% + 0.35rem); left: 0; z-index: 9; display: grid; min-width: 8rem; gap: 0.2rem; padding: 0.35rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.375rem; background: #fff; box-shadow: 0 4px 8px rgba(23, 23, 23, 0.10); }
|
||
.directory-header-menu__panel a, .directory-header-menu__panel button { display: block; width: 100%; padding: 0.35rem 0.5rem; border: 0; border-radius: 0.25rem; background: transparent; color: var(--vip-content-ink, #171717); font: inherit; font-size: 0.6875rem; text-align: left; text-decoration: none; cursor: pointer; white-space: nowrap; }
|
||
.directory-header-menu__panel a:hover, .directory-header-menu__panel button:hover, .directory-header-menu__panel a:focus, .directory-header-menu__panel button:focus { background: var(--vip-content-accent-soft, #f7f0de); outline: 0; }
|
||
|
||
.directory-table tbody tr:hover {
|
||
background: #fbf8f0;
|
||
}
|
||
|
||
.directory-table tbody tr[data-subject-row] { cursor: pointer; }
|
||
.directory-table tbody tr[data-subject-row][aria-selected="true"] { background: var(--vip-content-accent-soft, #f7f0de); }
|
||
|
||
.directory-subject-details {
|
||
margin: 1rem 1.25rem 1.25rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
background: var(--vip-surface, #fff);
|
||
}
|
||
.directory-subject-details__head {
|
||
display: flex; justify-content: space-between; gap: 1rem; align-items: center;
|
||
padding: 0.75rem 1rem; border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
color: var(--vip-content-ink, #171717); font-size: 0.8125rem;
|
||
}
|
||
.directory-subject-details__head span { color: var(--vip-content-muted, #595959); font-size: 0.75rem; }
|
||
.directory-subject-details__body { padding: 0.75rem 1rem; overflow-x: auto; }
|
||
.directory-subject-details__record { display: grid; grid-template-columns: repeat(3, minmax(12rem, 1fr)); gap: 0.75rem 1rem; margin: 0; }
|
||
.directory-subject-details__record + .directory-subject-details__record { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--vip-border, #d8d2c7); }
|
||
.directory-subject-details__record dt { color: var(--vip-content-muted, #595959); font-size: 0.6875rem; }
|
||
.directory-subject-details__record dd { margin: 0.2rem 0 0; color: var(--vip-content-ink, #171717); font-size: 0.75rem; }
|
||
@media (max-width: 800px) { .directory-subject-details__record { grid-template-columns: repeat(2, minmax(12rem, 1fr)); } }
|
||
@media (max-width: 520px) { .directory-subject-details__record { grid-template-columns: minmax(12rem, 1fr); } }
|
||
|
||
.directory-table .is-number {
|
||
text-align: right;
|
||
}
|
||
|
||
.directory-table .is-empty td {
|
||
height: 8rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
text-align: center;
|
||
white-space: normal;
|
||
}
|
||
|
||
.directory-table-empty {
|
||
display: flex;
|
||
min-height: 8rem;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
padding: 1.5rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
text-align: center;
|
||
}
|
||
|
||
.directory-table-empty .material-icons-outlined {
|
||
color: var(--vip-content-accent, #b9954f);
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.directory-table-pagination {
|
||
display: flex;
|
||
min-height: 3rem;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 0.625rem 1.25rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.directory-table-pagination a {
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.directory-table-pagination a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.inventory-report-tabs {
|
||
min-height: 5rem;
|
||
margin: 0;
|
||
padding: 0 2rem;
|
||
gap: 2rem;
|
||
border-color: var(--vip-border, #d8d2c7);
|
||
border-radius: 1rem;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.inventory-report-tabs [data-inventory-report-tab] {
|
||
position: relative;
|
||
display: inline-flex;
|
||
min-height: 5rem;
|
||
align-items: center;
|
||
padding: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--vip-content-muted, #595959);
|
||
font: inherit;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.inventory-report-tabs [data-inventory-report-tab]:hover,
|
||
.inventory-report-tabs [data-inventory-report-tab][aria-selected="true"] {
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
}
|
||
|
||
.inventory-report-tabs [data-inventory-report-tab][aria-selected="true"]::after {
|
||
position: absolute;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
height: 3px;
|
||
border-radius: 999px 999px 0 0;
|
||
background: var(--vip-content-accent, #b9954f);
|
||
content: '';
|
||
}
|
||
|
||
.inventory-report-tabs [data-inventory-report-tab]:focus-visible {
|
||
outline: 2px solid var(--vip-content-accent-dark, #856632);
|
||
outline-offset: 3px;
|
||
}
|
||
|
||
.inventory-report-panel[hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.inventory-warehouse-mapping {
|
||
overflow: visible;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table {
|
||
min-width: 52rem;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
isolation: isolate;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table thead th {
|
||
position: sticky !important;
|
||
top: 0 !important;
|
||
z-index: 20 !important;
|
||
background: var(--vip-content-accent-soft, #f7f0de);
|
||
box-shadow: 0 1px 0 var(--vip-border, #d8d2c7);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table thead,
|
||
.inventory-warehouse-mapping__table thead tr {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 5;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table thead th[data-directory-fixed="start"],
|
||
.inventory-warehouse-mapping__table thead th[data-directory-fixed="end"] {
|
||
z-index: 21 !important;
|
||
background: var(--vip-content-accent-soft, #f7f0de);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table tbody td {
|
||
position: relative !important;
|
||
z-index: 0 !important;
|
||
background: var(--vip-surface, #fff);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__table tbody tr:hover td {
|
||
background: #fbf8f0;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__empty {
|
||
padding: 3rem 1.25rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
text-align: center;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__filters {
|
||
grid-template-columns: repeat(2, minmax(12rem, 1fr)) auto;
|
||
align-items: end;
|
||
border: 0;
|
||
border-bottom: 1px solid var(--vip-border, #d8d2c7);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__filters .directory-query-actions {
|
||
grid-column: auto;
|
||
justify-content: flex-start;
|
||
padding-top: 0;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog input {
|
||
min-height: 2.25rem;
|
||
padding: 0.45rem 0.65rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
background: #fff;
|
||
color: var(--vip-content-ink, #171717);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__filters .biz-btn {
|
||
display: inline-flex;
|
||
height: 2.1rem;
|
||
align-items: center;
|
||
padding: 0 0.8rem;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__import {
|
||
display: inline-flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__export,
|
||
.inventory-warehouse-mapping__file-label,
|
||
.inventory-warehouse-mapping__import button {
|
||
display: inline-flex;
|
||
height: 2.25rem;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0 0.875rem;
|
||
border: 1px solid var(--vip-content-accent, #b9954f);
|
||
border-radius: 0.25rem;
|
||
background: #fff;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
font: inherit;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__file-label input[type="file"] {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
overflow: hidden;
|
||
clip: rect(0 0 0 0);
|
||
clip-path: inset(50%);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__actions {
|
||
display: inline-flex;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__actions button {
|
||
padding: 0.25rem 0.45rem;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.4rem;
|
||
background: #fff;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview {
|
||
margin: 1rem 1.25rem;
|
||
overflow: auto;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview summary {
|
||
padding: 0.75rem 1rem;
|
||
cursor: pointer;
|
||
color: var(--vip-content-accent-dark, #856632);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview th,
|
||
.inventory-warehouse-mapping__preview td {
|
||
padding: 0.5rem 0.75rem;
|
||
border-top: 1px solid var(--vip-border, #d8d2c7);
|
||
text-align: left;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview .is-invalid,
|
||
.inventory-warehouse-mapping__preview .is-conflict {
|
||
color: #a33a32;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__preview .is-same {
|
||
color: var(--vip-content-muted, #595959);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog {
|
||
width: min(32rem, calc(100vw - 2rem));
|
||
padding: 0;
|
||
border: 1px solid var(--vip-border, #d8d2c7);
|
||
border-radius: 0.75rem;
|
||
box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog::backdrop {
|
||
background: rgba(21, 18, 12, 0.38);
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog form {
|
||
display: grid;
|
||
gap: 1rem;
|
||
padding: 1.25rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog h3 {
|
||
margin: 0;
|
||
color: var(--vip-content-ink, #171717);
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog label {
|
||
display: grid;
|
||
gap: 0.35rem;
|
||
color: var(--vip-content-muted, #595959);
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.inventory-warehouse-mapping__dialog__actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
@media (max-width: 700px) {
|
||
.directory-table-head {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.directory-table-status {
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.directory-table-pagination {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<section
|
||
class="directory-table-page {{ $directoryUsesFieldQuery ? 'has-field-query' : '' }}"
|
||
data-directory-page="{{ $page['slug'] ?? '' }}"
|
||
data-directory-last-page="{{ $directoryLastPage }}"
|
||
data-directory-user="{{ auth()->id() ?? 'guest' }}"
|
||
data-directory-section="{{ $section['slug'] ?? '' }}"
|
||
data-directory-tab="{{ $activeTab['slug'] ?? '' }}"
|
||
aria-label="{{ $page['title'] ?? $section['title'] }}"
|
||
>
|
||
@if ($directoryUsesFieldQuery)
|
||
<header class="directory-query-page-head">
|
||
<div>
|
||
<strong>{{ $page['title'] ?? $section['title'] }}</strong>
|
||
<p>{{ $directoryQueryPageDescriptions[$directoryPageKey] ?? '基础资料的查询与导出' }}</p>
|
||
</div>
|
||
<span class="sr-only" data-directory-status>{{ $directory['status'] ?? '尚未同步' }}</span>
|
||
</header>
|
||
@if (!empty($upstreamError))
|
||
<div class="directory-table-notice" role="alert">
|
||
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
||
<span>{{ $upstreamError }}</span>
|
||
</div>
|
||
@elseif ($directoryTotal > 0 && in_array($directoryPageKey, ['store-management/store-accounts', 'store-management/store-report', 'company-management/subject-operating-report', 'company-management/company-cost-arrangement', 'company-management/personnel-inventory'], true))
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">cloud_done</span>
|
||
<span>已从共享业务镜像读取 {{ $directoryTotal }} 条数据,当前页面为只读展示。</span>
|
||
</div>
|
||
@endif
|
||
@else
|
||
@if ($isInventoryReportPage)
|
||
<nav class="business-tab-bar inventory-report-tabs" data-inventory-report-tabs role="tablist" aria-label="进销存报表数据表">
|
||
<button type="button" role="tab" aria-selected="true" aria-controls="inventory-report-total-panel" id="inventory-report-total-tab" data-inventory-report-tab="total">
|
||
进销存总表
|
||
</button>
|
||
<button type="button" role="tab" aria-selected="false" aria-controls="inventory-report-mapping-panel" id="inventory-report-mapping-tab" data-inventory-report-tab="mapping" tabindex="-1">
|
||
仓库名称映射表
|
||
</button>
|
||
</nav>
|
||
<section
|
||
id="inventory-report-total-panel"
|
||
class="inventory-report-panel"
|
||
data-inventory-report-panel="total"
|
||
data-directory-table-scope
|
||
data-directory-table-key="total"
|
||
data-directory-last-page="{{ $directoryLastPage }}"
|
||
data-directory-source-url="{{ request()->fullUrlWithQuery(['page' => 1]) }}"
|
||
role="tabpanel"
|
||
aria-labelledby="inventory-report-total-tab"
|
||
>
|
||
@else
|
||
<header class="directory-table-head">
|
||
<div class="directory-table-title">
|
||
<span class="material-icons-outlined" aria-hidden="true">{{ $directory['icon'] ?? 'table_view' }}</span>
|
||
<div>
|
||
<strong>{{ $page['title'] ?? $section['title'] }} · 字段结构</strong>
|
||
@if (!empty($directory['description']))
|
||
<p class="directory-table-description">{{ $directory['description'] }}</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<span class="directory-table-status" data-directory-status>{{ $directory['status'] ?? '尚未同步' }}</span>
|
||
</header>
|
||
@endif
|
||
|
||
@if (!empty($upstreamError))
|
||
<div class="directory-table-notice" role="alert">
|
||
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
|
||
<span>{{ $upstreamError }}</span>
|
||
</div>
|
||
@elseif (($directory['status'] ?? null) === '演示数据' && $directoryTotal > 0)
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">preview</span>
|
||
<span>已按附件模板导入 {{ $directoryTotal }} 条演示数据,当前页面为只读展示。</span>
|
||
</div>
|
||
@elseif ($directoryTotal > 0)
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">cloud_done</span>
|
||
<span>已从共享业务镜像读取 {{ $directoryTotal }} 条数据,当前页面为只读展示。</span>
|
||
</div>
|
||
@elseif (($directory['status'] ?? null) === '既有只读上游')
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">visibility</span>
|
||
<span>当前页面只读显示既有上游资料;未匹配的 Excel 字段保留为空,不代表业务上没有数据。</span>
|
||
</div>
|
||
@elseif (($directory['status'] ?? null) === '共享镜像' && $directoryTotal > 0)
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">cloud_done</span>
|
||
<span>当前显示团队共享业务镜像数据,分页和汇总均基于已同步镜像。</span>
|
||
</div>
|
||
@else
|
||
<div class="directory-table-notice" role="status">
|
||
<span class="material-icons-outlined" aria-hidden="true">sync_problem</span>
|
||
<span>{{ $directory['empty'] ?? '数据尚未同步,当前只展示已确认的字段结构。' }}</span>
|
||
</div>
|
||
@endif
|
||
@endif
|
||
|
||
@if (!empty($directoryFields))
|
||
@php
|
||
$directoryQueryFields = $directoryPageKey === 'company-management/basic-information'
|
||
? [
|
||
['name' => 'unified_social_credit_code', 'label' => '统一社会信用代码', 'placeholder' => '输入完整或部分代码'],
|
||
['name' => 'company_name', 'label' => '公司名称', 'placeholder' => '输入公司名称关键词'],
|
||
['name' => 'taxpayer_type', 'label' => '纳税人类型'],
|
||
['name' => 'company_status', 'label' => '公司状态'],
|
||
['name' => 'legal_representative', 'label' => '法人', 'placeholder' => '按姓名查询'],
|
||
['name' => 'city', 'label' => '城市', 'placeholder' => '城市较多,使用搜索'],
|
||
['name' => 'related_people', 'label' => '相关人员', 'placeholder' => '输入人员姓名,模糊匹配'],
|
||
['name' => 'company_type', 'label' => '公司类型'],
|
||
]
|
||
: $directoryFields;
|
||
$directoryExcludedQueryFields = match ($directoryPageKey) {
|
||
'store-management/basic-information' => ['opened_at', 'renewed_at'],
|
||
'trademark-management/information' => ['registration_date', 'expiry_date'],
|
||
default => [],
|
||
};
|
||
if (!empty($directoryExcludedQueryFields)) {
|
||
$directoryQueryFields = collect($directoryQueryFields)
|
||
->reject(fn (array $field): bool => in_array(
|
||
$field['name'],
|
||
$directoryExcludedQueryFields,
|
||
true,
|
||
))
|
||
->values()
|
||
->all();
|
||
}
|
||
$directoryMoreQueryFields = collect($directoryQueryFields)
|
||
->filter(function (array $field) use ($directoryPageKey): bool {
|
||
$kind = $field['kind'] ?? 'text';
|
||
|
||
return in_array($directoryPageKey, [
|
||
'store-management/store-report',
|
||
'company-management/subject-operating-report',
|
||
'company-management/company-cost-arrangement',
|
||
'company-management/personnel-inventory',
|
||
], true) && in_array($kind, ['date', 'number', 'integer', 'secret', 'secret_status'], true);
|
||
})
|
||
->values()
|
||
->all();
|
||
$directoryPrimaryQueryFields = collect($directoryQueryFields)
|
||
->reject(fn (array $field): bool => in_array($field['name'], array_column($directoryMoreQueryFields, 'name'), true))
|
||
->values()
|
||
->all();
|
||
$directorySelectFields = match (($section['slug'] ?? '') . '/' . ($page['slug'] ?? '')) {
|
||
'company-management/basic-information' => ['taxpayer_type', 'company_status', 'company_type'],
|
||
'store-management/basic-information' => ['platform', 'store_status', 'withdrawal_account'],
|
||
'trademark-management/information' => ['trademark_status'],
|
||
'store-management/store-accounts' => ['account_type', 'password_configured'],
|
||
'store-management/store-report' => ['status', 'can_change', 'company_type'],
|
||
'company-management/subject-operating-report' => ['company_type', 'can_change_entity'],
|
||
'company-management/company-cost-arrangement' => ['company_type', 'invoice_type'],
|
||
'company-management/personnel-inventory' => ['company_type'],
|
||
default => [],
|
||
};
|
||
$directoryColumnNames = collect($directoryFields)->pluck('name')->all();
|
||
$requestedDirectoryColumns = array_values(array_intersect(
|
||
array_filter(explode(',', (string) request('columns', ''))),
|
||
$directoryColumnNames,
|
||
));
|
||
$visibleDirectoryColumns = request()->has('columns') ? $requestedDirectoryColumns : $directoryColumnNames;
|
||
$directoryQueryFormId = 'directory-query-form-' . ($page['slug'] ?? 'page');
|
||
@endphp
|
||
@if ($directoryUsesFieldQuery)
|
||
<div class="directory-query-bar" data-directory-query aria-label="{{ $page['title'] ?? '' }}查询条件">
|
||
<div class="directory-query-heading">
|
||
<strong>查询条件</strong>
|
||
<span>类型较少的字段直接选择,内容较多的字段支持关键词搜索</span>
|
||
</div>
|
||
<form id="{{ $directoryQueryFormId }}" method="GET" action="{{ request()->url() }}"></form>
|
||
@if (request()->has('columns'))
|
||
<input type="hidden" form="{{ $directoryQueryFormId }}" name="columns" value="{{ implode(',', $visibleDirectoryColumns) }}">
|
||
@endif
|
||
@foreach ($directoryPrimaryQueryFields as $field)
|
||
@php $kind = $field['kind'] ?? 'text'; @endphp
|
||
@continue($kind === 'secret' && $field['name'] !== 'withdrawal_account')
|
||
@continue(($page['slug'] ?? '') === 'store-accounts' && in_array($field['name'], [
|
||
'account',
|
||
'password_configured',
|
||
'bound_phone',
|
||
'verification_contact',
|
||
'account_group_name',
|
||
'created_at',
|
||
'updated_at',
|
||
], true))
|
||
<label class="directory-query-field" data-directory-query-field="{{ $field['name'] }}">
|
||
<span title="{{ $field['label'] }}">{{ $field['label'] }}</span>
|
||
@if ($kind === 'date')
|
||
<span class="directory-query-dates">
|
||
<input type="date" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}_from" value="{{ request($field['name'] . '_from') }}" data-directory-query-from="{{ $field['name'] }}" aria-label="{{ $field['label'] }}开始">
|
||
<em>至</em>
|
||
<input type="date" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}_to" value="{{ request($field['name'] . '_to') }}" data-directory-query-to="{{ $field['name'] }}" aria-label="{{ $field['label'] }}结束">
|
||
</span>
|
||
@elseif (in_array($field['name'], $directorySelectFields, true))
|
||
<select form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}" data-directory-query-select="{{ $field['name'] }}" aria-label="{{ $field['label'] }}">
|
||
<option value="">全部</option>
|
||
@php
|
||
$enumOptions = match ($field['name']) {
|
||
'platform' => ['淘宝', '淘宝分销', '阿里巴巴', '天猫', '唯品会', '京东', '京喜', '抖音', '拼多多', '淘工厂', '得物'],
|
||
'store_status' => ['正常', '关闭中', '已关闭'],
|
||
'withdrawal_account' => ['已配置', '未配置'],
|
||
'trademark_status' => ['未启用', '有效'],
|
||
'account_type' => ['主账号', '子账号'],
|
||
'password_configured' => ['已设置', '未设置', '状态未同步'],
|
||
'taxpayer_type' => ['一般纳税人', '小规模纳税人', '个体工商户', '无税务登记', 'CORP'],
|
||
'company_status' => ['正常', '已注销'],
|
||
'company_type' => ['个体工商户', '个体户', '二级公司', '人力资源公司', '仓储公司', '其他', '合同公司', '品牌公司', '店铺-国标公司', '店铺-国标商标', '店铺-大店公司', '店铺-普通公司', '母公司', '资产型'],
|
||
'status' => ['0' => '开店中', '3' => '关店中', '6' => '关店'],
|
||
'can_change', 'can_change_entity' => ['是', '否'],
|
||
'invoice_type' => ['普票', '专票'],
|
||
default => [],
|
||
};
|
||
@endphp
|
||
@foreach ($enumOptions as $option)
|
||
<option value="{{ $option }}" @selected((string) request($field['name']) === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
@else
|
||
<input type="search" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}" value="{{ request($field['name']) }}" autocomplete="off" placeholder="{{ $field['placeholder'] ?? ('搜索' . $field['label']) }}" data-directory-query-input="{{ $field['name'] }}" aria-label="搜索{{ $field['label'] }}">
|
||
<ul class="directory-query-suggestions" data-directory-query-suggestions="{{ $field['name'] }}" hidden></ul>
|
||
@endif
|
||
</label>
|
||
@endforeach
|
||
@if ($directoryMoreQueryFields !== [])
|
||
<details class="directory-query-more">
|
||
<summary>更多查询条件({{ count($directoryMoreQueryFields) }})</summary>
|
||
<div class="directory-query-more__fields">
|
||
@foreach ($directoryMoreQueryFields as $field)
|
||
@php $kind = $field['kind'] ?? 'text'; @endphp
|
||
@continue($kind === 'secret' && $field['name'] !== 'withdrawal_account')
|
||
@continue(($page['slug'] ?? '') === 'store-accounts' && in_array($field['name'], [
|
||
'account',
|
||
'password_configured',
|
||
'bound_phone',
|
||
'verification_contact',
|
||
'account_group_name',
|
||
'created_at',
|
||
'updated_at',
|
||
], true))
|
||
<label class="directory-query-field" data-directory-query-field="{{ $field['name'] }}">
|
||
<span title="{{ $field['label'] }}">{{ $field['label'] }}</span>
|
||
@if ($kind === 'date')
|
||
<span class="directory-query-dates">
|
||
<input type="date" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}_from" value="{{ request($field['name'] . '_from') }}" data-directory-query-from="{{ $field['name'] }}" aria-label="{{ $field['label'] }}开始">
|
||
<em>至</em>
|
||
<input type="date" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}_to" value="{{ request($field['name'] . '_to') }}" data-directory-query-to="{{ $field['name'] }}" aria-label="{{ $field['label'] }}结束">
|
||
</span>
|
||
@elseif (in_array($field['name'], $directorySelectFields, true))
|
||
<select form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}" data-directory-query-select="{{ $field['name'] }}" aria-label="{{ $field['label'] }}">
|
||
<option value="">全部</option>
|
||
@php
|
||
$enumOptions = match ($field['name']) {
|
||
'platform' => ['淘宝', '淘宝分销', '阿里巴巴', '天猫', '唯品会', '京东', '京喜', '抖音', '拼多多', '淘工厂', '得物'],
|
||
'store_status' => ['正常', '关闭中', '已关闭'],
|
||
'withdrawal_account' => ['已配置', '未配置'],
|
||
'trademark_status' => ['未启用', '有效'],
|
||
'account_type' => ['主账号', '子账号'],
|
||
'password_configured' => ['已设置', '未设置', '状态未同步'],
|
||
'taxpayer_type' => ['一般纳税人', '小规模纳税人', '个体工商户', '无税务登记', 'CORP'],
|
||
'company_status' => ['正常', '已注销'],
|
||
'company_type' => ['个体工商户', '个体户', '二级公司', '人力资源公司', '仓储公司', '其他', '合同公司', '品牌公司', '店铺-国标公司', '店铺-国标商标', '店铺-大店公司', '店铺-普通公司', '母公司', '资产型'],
|
||
'status' => ['0' => '开店中', '3' => '关店中', '6' => '关店'],
|
||
'can_change', 'can_change_entity' => ['是', '否'],
|
||
'invoice_type' => ['普票', '专票'],
|
||
default => [],
|
||
};
|
||
@endphp
|
||
@foreach ($enumOptions as $option)
|
||
<option value="{{ $option }}" @selected((string) request($field['name']) === $option)>{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
@else
|
||
<input type="search" form="{{ $directoryQueryFormId }}" name="{{ $field['name'] }}" value="{{ request($field['name']) }}" autocomplete="off" placeholder="{{ $field['placeholder'] ?? ('搜索' . $field['label']) }}" data-directory-query-input="{{ $field['name'] }}" aria-label="搜索{{ $field['label'] }}">
|
||
<ul class="directory-query-suggestions" data-directory-query-suggestions="{{ $field['name'] }}" hidden></ul>
|
||
@endif
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</details>
|
||
@endif
|
||
<div class="directory-query-actions">
|
||
<details class="directory-query-picker" data-directory-query-picker>
|
||
<summary>筛选字段</summary>
|
||
<div class="directory-query-picker__panel" role="group" aria-label="勾选展示字段">
|
||
<span class="directory-query-picker__hint">勾选需要显示的表头,可重复调整。</span>
|
||
@foreach ($directoryFields as $field)
|
||
<label class="directory-query-picker__option">
|
||
<input type="checkbox" value="{{ $field['name'] }}" data-directory-query-column @checked(in_array($field['name'], $visibleDirectoryColumns, true))>
|
||
<span>{{ $field['label'] }}</span>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</details>
|
||
<button type="button" data-directory-query-reset>重置</button>
|
||
<button type="submit" form="{{ $directoryQueryFormId }}" class="is-primary">查询</button>
|
||
<details class="directory-query-export" data-directory-query-export-picker>
|
||
<summary><span class="material-icons-outlined" aria-hidden="true">file_download</span>导出 Excel</summary>
|
||
<div class="directory-query-export__panel" role="group" aria-label="选择导出字段">
|
||
<span class="directory-query-picker__hint">默认全选,可取消不需要导出的表头。</span>
|
||
@foreach ($directoryFields as $field)
|
||
<label class="directory-query-export__option">
|
||
<input type="checkbox" value="{{ $field['name'] }}" checked data-directory-query-export-field>
|
||
<span>{{ $field['label'] }}</span>
|
||
</label>
|
||
@endforeach
|
||
<button type="button" class="directory-query-export__button" data-directory-query-export>导出选中字段</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
@if (($page['slug'] ?? '') === 'store-report' && ! $directoryUsesFieldQuery)
|
||
<form method="GET" action="{{ request()->url() }}" class="installation-filter-bar" aria-label="店铺维度报表筛选查询栏">
|
||
@php
|
||
$storeFilterTextFields = [
|
||
['name' => 'store_name', 'label' => '店铺名称:'],
|
||
['name' => 'store_id', 'label' => '店铺ID:'],
|
||
['name' => 'profit_center', 'label' => '利润中心:'],
|
||
['name' => 'store_manager', 'label' => '店铺负责人:'],
|
||
['name' => 'department_manager', 'label' => '部门负责人:'],
|
||
['name' => 'entity', 'label' => '主体:'],
|
||
['name' => 'company_type', 'label' => '公司类型:'],
|
||
];
|
||
$storeFilterDateFields = [
|
||
['name' => 'opened_at', 'label' => '开店时间:'],
|
||
['name' => 'effective_at', 'label' => '生效时间:'],
|
||
['name' => 'invalid_at', 'label' => '失效时间:'],
|
||
];
|
||
@endphp
|
||
@foreach ($storeFilterTextFields as $filter)
|
||
<label class="installation-filter-field">
|
||
<span>{{ $filter['label'] }}</span>
|
||
<input type="text" name="{{ $filter['name'] }}" value="{{ request($filter['name']) }}" placeholder="按原值精确查询">
|
||
</label>
|
||
@endforeach
|
||
<label class="installation-filter-field">
|
||
<span>状态:</span>
|
||
<select name="status">
|
||
<option value="">全部</option>
|
||
@foreach (['0' => '开店中', '3' => '关店中', '6' => '关店'] as $value => $label)
|
||
<option value="{{ $value }}" @selected((string) request('status') === $value)>{{ $label }}</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
<label class="installation-filter-field">
|
||
<span>能否变更:</span>
|
||
<select name="can_change">
|
||
<option value="">全部</option>
|
||
@foreach (['是', '否'] as $value)
|
||
<option value="{{ $value }}" @selected((string) request('can_change') === $value)>{{ $value }}</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
@foreach ($storeFilterDateFields as $filter)
|
||
<label class="installation-filter-field">
|
||
<span>{{ $filter['label'] }}</span>
|
||
<input type="date" name="{{ $filter['name'] }}_from" value="{{ request($filter['name'] . '_from') }}" aria-label="{{ $filter['label'] }}开始(含)">
|
||
<span>至</span>
|
||
<input type="date" name="{{ $filter['name'] }}_to" value="{{ request($filter['name'] . '_to') }}" aria-label="{{ $filter['label'] }}结束(不含)">
|
||
</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>
|
||
</div>
|
||
</form>
|
||
@elseif (($page['slug'] ?? '') === 'inventory-report')
|
||
<form method="GET" action="{{ request()->url() }}" class="installation-filter-bar" aria-label="进销存报表筛选查询栏">
|
||
<label class="installation-filter-field">
|
||
<span>月份:</span>
|
||
@php
|
||
$inventorySelectedMonth = (string) request('month', now()->startOfMonth()->subMonth()->format('Y-m'));
|
||
@endphp
|
||
<div class="inventory-month-picker" data-inventory-month-picker data-min-month="2026-07">
|
||
<input type="hidden" name="month" min="2026-07" value="{{ $inventorySelectedMonth }}" data-inventory-month-value>
|
||
<button type="button" class="inventory-month-picker__trigger" data-inventory-month-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="选择进销存报表月份">
|
||
<span data-inventory-month-label>{{ $inventorySelectedMonth !== '' ? str_replace('-', ' 年 ', $inventorySelectedMonth) . ' 月' : '' }}</span>
|
||
<span class="material-icons-outlined" aria-hidden="true">calendar_month</span>
|
||
</button>
|
||
<div class="inventory-month-picker__panel" data-inventory-month-panel role="dialog" aria-label="选择报表月份" hidden>
|
||
<div class="inventory-month-picker__yearbar">
|
||
<button type="button" class="inventory-month-picker__nav" data-inventory-month-prev aria-label="上一年"><span class="material-icons-outlined" aria-hidden="true">chevron_left</span></button>
|
||
<strong data-inventory-month-year></strong>
|
||
<button type="button" class="inventory-month-picker__nav" data-inventory-month-next aria-label="下一年"><span class="material-icons-outlined" aria-hidden="true">chevron_right</span></button>
|
||
</div>
|
||
<div class="inventory-month-picker__months" data-inventory-month-grid></div>
|
||
<div class="inventory-month-picker__footer">
|
||
<button type="button" data-inventory-month-clear>清除</button>
|
||
<button type="button" data-inventory-month-current>本月</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<label class="installation-filter-field">
|
||
<span>商品编码:</span>
|
||
<input type="text" name="product_code" value="{{ request('product_code') }}" placeholder="按商品编码精确查询">
|
||
</label>
|
||
<label class="installation-filter-field">
|
||
<span>仓储方:</span>
|
||
<input type="text" name="warehouse_provider" value="{{ request('warehouse_provider') }}" placeholder="支持包含查询">
|
||
</label>
|
||
<label class="installation-filter-field">
|
||
<span>分类:</span>
|
||
<input type="text" name="category" value="{{ request('category') }}" placeholder="支持包含查询">
|
||
</label>
|
||
<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>
|
||
</div>
|
||
</form>
|
||
@endif
|
||
@if (! $directoryUsesFieldQuery)
|
||
<div class="directory-table-tools" data-directory-tools @if (($page['slug'] ?? '') === 'inventory-report') data-directory-server-export @endif>
|
||
<label class="sr-only" for="directory-search-{{ $page['slug'] ?? 'page' }}">搜索当前表格</label>
|
||
<input id="directory-search-{{ $page['slug'] ?? 'page' }}" type="search" placeholder="搜索当前页数据..." data-directory-search>
|
||
<div class="directory-field-picker" data-directory-field-picker>
|
||
<button type="button" aria-expanded="false" aria-controls="directory-fields-{{ $page['slug'] ?? 'page' }}" data-directory-field-toggle>列设置</button>
|
||
<div class="directory-field-picker__panel" id="directory-fields-{{ $page['slug'] ?? 'page' }}" role="group" aria-label="选择展示字段" hidden>
|
||
<span class="directory-field-picker__hint">拖动字段调整顺序;固定序号列不可隐藏。</span>
|
||
@foreach ($directoryFields as $field)
|
||
<label class="directory-field-picker__option" draggable="true" data-directory-field-option>
|
||
<input type="checkbox" value="{{ $field['name'] }}" data-directory-field-check @checked(!in_array($field['name'], ['sales_01', 'sales_02', 'sales_03', 'sales_04', 'sales_05', 'sales_06'], true))>
|
||
<span>{{ $field['label'] }}</span>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@if (($page['slug'] ?? '') === 'inventory-report')
|
||
<select data-inventory-export-scope aria-label="导出范围">
|
||
<option value="all">全部筛选结果</option>
|
||
<option value="page">当前页(第 {{ $directoryCurrentPage }} 页)</option>
|
||
</select>
|
||
<select data-inventory-export-fields aria-label="导出字段范围">
|
||
<option value="visible">当前显示列及顺序</option>
|
||
<option value="all">全部可导出字段</option>
|
||
</select>
|
||
<button type="button" data-inventory-export data-export-url="{{ route('business.product-inventory.inventory-report.export') }}">导出</button>
|
||
@else
|
||
<div class="directory-export-group" aria-label="导出范围">
|
||
<button type="button" data-directory-export="visible">当前页·显示列</button>
|
||
<button type="button" data-directory-export="all-fields">当前页·全部字段</button>
|
||
<button type="button" data-directory-export-all="visible">全部结果·显示列</button>
|
||
<button type="button" data-directory-export-all="all-fields">全部结果·全部字段</button>
|
||
</div>
|
||
<button type="button" data-directory-reset>恢复默认</button>
|
||
<button type="button" data-directory-template>下载表头模板</button>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
<div class="directory-table-scroll">
|
||
<table
|
||
id="{{ $isInventoryReportPage ? 'inventory-report-total-table' : 'directory-table-' . ($page['slug'] ?? 'page') }}"
|
||
class="directory-table"
|
||
@if($isInventoryReportPage) data-directory-table-key="total" @endif
|
||
@if($directoryUsesFieldQuery) data-columns-ready="true" @endif
|
||
>
|
||
<thead>
|
||
<tr>
|
||
<th scope="col" data-directory-fixed="start">#</th>
|
||
@foreach ($directoryFields as $field)
|
||
<th scope="col" data-column-key="{{ $field['name'] }}" @if($isInventoryReportPage && $field['name'] === 'warehouse_short_name') aria-sort="none" @endif class="{{ in_array($field['kind'] ?? null, ['number', 'integer'], true) ? 'is-number' : '' }} {{ !in_array($field['name'], $visibleDirectoryColumns, true) ? 'is-column-hidden' : '' }}">
|
||
@if ($directoryUsesFieldQuery)
|
||
@php
|
||
$isSorted = request('sort') === $field['name'];
|
||
$direction = $isSorted ? request('direction', 'asc') : null;
|
||
$nextDirection = $isSorted && $direction === 'asc' ? 'desc' : 'asc';
|
||
@endphp
|
||
<div class="directory-header-menu" data-directory-header-menu="{{ $field['name'] }}">
|
||
<span data-directory-header-label>{{ $field['label'] }}</span>
|
||
<details>
|
||
<summary aria-label="{{ $field['label'] }}排序和字段操作"><span class="material-icons-outlined directory-sort-icon" aria-hidden="true">{{ !$isSorted ? 'unfold_more' : ($direction === 'asc' ? 'arrow_upward' : 'arrow_downward') }}</span></summary>
|
||
<div class="directory-header-menu__panel">
|
||
<a href="{{ request()->fullUrlWithQuery(['sort' => $field['name'], 'direction' => 'asc', 'page' => 1]) }}">升序</a>
|
||
<a href="{{ request()->fullUrlWithQuery(['sort' => $field['name'], 'direction' => 'desc', 'page' => 1]) }}">降序</a>
|
||
<button type="button" data-directory-header-hide="{{ $field['name'] }}">隐藏字段</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
@elseif ($isInventoryReportPage && $field['name'] === 'warehouse_short_name')
|
||
<button type="button" class="directory-inventory-sort" data-inventory-short-name-sort aria-label="仓库简称按当前页升序排序" title="当前页排序">{{ $field['label'] }}<span class="material-icons-outlined directory-sort-icon" aria-hidden="true">unfold_more</span></button>
|
||
@else
|
||
{{ $field['label'] }}
|
||
@endif
|
||
</th>
|
||
@endforeach
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($directoryRows as $rowIndex => $row)
|
||
<tr @if (($page['slug'] ?? '') === 'subject-operating-report') data-subject-row data-subject-details='@json($row->company_details ?? [])' tabindex="0" aria-label="查看主体详细资料" @endif>
|
||
<td data-directory-fixed="start">{{ $rowIndex + 1 + (($directoryCurrentPage - 1) * $directoryPerPage) }}</td>
|
||
@foreach ($directoryFields as $field)
|
||
@php
|
||
$source = array_key_exists('source', $field) ? $field['source'] : $field['name'];
|
||
$value = $source ? data_get($row, $source) : null;
|
||
$kind = $field['kind'] ?? 'text';
|
||
$display = $value;
|
||
if ($kind === 'secret') {
|
||
$display = filled($value) ? '已配置' : '—';
|
||
} elseif ($kind === 'secret_status') {
|
||
$display = $value === null || $value === ''
|
||
? '状态未同步'
|
||
: ($value ? '已设置' : '未设置');
|
||
} elseif ($kind === 'date' && filled($value)) {
|
||
$display = substr((string) $value, 0, 10);
|
||
} elseif ($kind === 'number' && is_numeric($value)) {
|
||
$display = number_format((float) $value, 2);
|
||
} elseif ($kind === 'integer' && is_numeric($value)) {
|
||
$display = number_format((int) $value);
|
||
} elseif ($kind === 'boolean' && $value !== null && $value !== '') {
|
||
$display = in_array($value, [true, 1, '1', '是', '✔'], true) ? '是' : '否';
|
||
}
|
||
@endphp
|
||
<td data-column-key="{{ $field['name'] }}" class="{{ in_array($kind, ['number', 'integer'], true) ? 'is-number' : '' }} {{ !in_array($field['name'], $visibleDirectoryColumns, true) ? 'is-column-hidden' : '' }}">
|
||
@if (($page['slug'] ?? '') === 'store-accounts' && $field['name'] === 'password_configured')
|
||
<span class="sr-only" data-password-status>{{ filled($display) ? $display : '—' }}</span>
|
||
@if ($value === false)
|
||
<span class="store-password-state">未设置</span>
|
||
@else
|
||
<span
|
||
data-store-password-cell
|
||
data-account-id="{{ data_get($row, 'id') }}"
|
||
data-copy-url="{{ route('business.store-account-passwords.reveal', ['account' => data_get($row, 'id')]) }}"
|
||
>
|
||
<span class="store-password-value" data-store-password-value aria-live="polite">读取中…</span>
|
||
<button type="button" data-store-password-copy hidden aria-label="复制{{ data_get($row, 'store_name') }}的账号密码">复制</button>
|
||
</span>
|
||
@endif
|
||
@else
|
||
{{ filled($display) ? $display : ($isInventoryReportPage && $field['name'] === 'warehouse_short_name' ? '' : '—') }}
|
||
@endif
|
||
</td>
|
||
@endforeach
|
||
</tr>
|
||
@empty
|
||
<tr class="is-empty">
|
||
<td colspan="{{ count($directoryFields) + 1 }}">
|
||
<div class="directory-table-empty">
|
||
<span class="material-icons-outlined" aria-hidden="true">inbox</span>
|
||
<span>{{ !empty($upstreamError) ? '资料暂时无法读取,请按上方提示检查连接;这不代表业务数据为 0。' : ($directory['empty'] ?? '尚未同步数据。') }}</span>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
@if ($directoryIsPaginated && empty($upstreamError))
|
||
<footer class="directory-table-pagination">
|
||
<span>共 {{ $directoryTotal }} 条 · 第 {{ $directoryCurrentPage }} / {{ $directoryLastPage }} 页</span>
|
||
<span>
|
||
@if ($directoryRows->onFirstPage())
|
||
<span>上一页</span>
|
||
@else
|
||
<a href="{{ $directoryRows->previousPageUrl() }}">上一页</a>
|
||
@endif
|
||
·
|
||
@if ($directoryRows->hasMorePages())
|
||
<a href="{{ $directoryRows->nextPageUrl() }}">下一页</a>
|
||
@else
|
||
<span>下一页</span>
|
||
@endif
|
||
</span>
|
||
</footer>
|
||
@endif
|
||
|
||
@if ($isInventoryReportPage)
|
||
</section>
|
||
<section
|
||
id="inventory-report-mapping-panel"
|
||
class="inventory-report-panel"
|
||
data-inventory-report-panel="mapping"
|
||
data-directory-table-scope
|
||
data-directory-table-key="warehouse-mapping"
|
||
data-directory-last-page="{{ $inventoryWarehouseMappings->lastPage() }}"
|
||
data-directory-source-url="{{ request()->fullUrlWithQuery(['mapping' => 1, 'page' => 1]) }}"
|
||
role="tabpanel"
|
||
aria-labelledby="inventory-report-mapping-tab"
|
||
hidden
|
||
>
|
||
<div class="inventory-warehouse-mapping">
|
||
<form method="GET" action="{{ request()->url() }}" class="directory-query-bar inventory-warehouse-mapping__filters" aria-label="仓库名称映射查询">
|
||
<input type="hidden" name="mapping" value="1">
|
||
<label class="directory-query-field">
|
||
<span>仓储方</span>
|
||
<input type="search" name="mapping_warehouse_provider" value="{{ request('mapping_warehouse_provider') }}" placeholder="按仓储方包含查询">
|
||
</label>
|
||
<label class="directory-query-field">
|
||
<span>仓库名</span>
|
||
<input type="search" name="mapping_warehouse" value="{{ request('mapping_warehouse') }}" placeholder="按仓库名包含查询">
|
||
</label>
|
||
<div class="directory-query-actions">
|
||
<button type="submit" class="is-primary">查询</button>
|
||
<a href="{{ request()->url() }}?mapping=1" class="biz-btn biz-btn-ghost">清除</a>
|
||
</div>
|
||
</form>
|
||
<div class="directory-table-tools" data-directory-tools>
|
||
<label class="sr-only" for="directory-search-inventory-warehouse-mapping">搜索当前表格</label>
|
||
<input id="directory-search-inventory-warehouse-mapping" type="search" placeholder="搜索当前页数据..." data-directory-search>
|
||
<div class="directory-field-picker" data-directory-field-picker>
|
||
<button type="button" aria-expanded="false" aria-controls="directory-fields-inventory-warehouse-mapping" data-directory-field-toggle>列设置</button>
|
||
<div class="directory-field-picker__panel" id="directory-fields-inventory-warehouse-mapping" role="group" aria-label="选择展示字段" hidden>
|
||
<span class="directory-field-picker__hint">可隐藏业务字段并拖动调整顺序;序号和操作列固定。</span>
|
||
@foreach ([
|
||
['name' => 'warehouse_provider', 'label' => '仓储方'],
|
||
['name' => 'warehouse', 'label' => '仓库'],
|
||
['name' => 'warehouse_short_name', 'label' => '仓库简称'],
|
||
] as $mappingField)
|
||
<label class="directory-field-picker__option" draggable="true" data-directory-field-option>
|
||
<input type="checkbox" value="{{ $mappingField['name'] }}" data-directory-field-check checked>
|
||
<span>{{ $mappingField['label'] }}</span>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
<div class="directory-export-group" aria-label="导出范围">
|
||
<button type="button" data-directory-export="visible">当前页·显示列</button>
|
||
<button type="button" data-directory-export="all-fields">当前页·全部字段</button>
|
||
<button type="button" data-directory-export-all="visible">全部筛选结果·显示列</button>
|
||
<button type="button" data-directory-export-all="all-fields">全部筛选结果·全部字段</button>
|
||
</div>
|
||
<button type="button" data-directory-reset>恢复默认</button>
|
||
<button type="button" data-inventory-mapping-template>下载导入模板</button>
|
||
<button type="button" class="is-primary" data-inventory-mapping-create>新增仓库</button>
|
||
<a class="inventory-warehouse-mapping__export" href="{{ route('business.product-inventory.inventory-report.warehouse-mappings.export', request()->only(['mapping_warehouse_provider', 'mapping_warehouse'])) }}">导出完整表格(Excel)</a>
|
||
<form method="POST" enctype="multipart/form-data" action="{{ route('business.product-inventory.inventory-report.warehouse-mappings.import.preview') }}" class="inventory-warehouse-mapping__import">
|
||
@csrf
|
||
<input type="hidden" name="mapping_warehouse_provider" value="{{ request('mapping_warehouse_provider') }}">
|
||
<input type="hidden" name="mapping_warehouse" value="{{ request('mapping_warehouse') }}">
|
||
<label class="inventory-warehouse-mapping__file-label" for="inventory-warehouse-mapping-file">
|
||
<span>选择文件</span>
|
||
<input id="inventory-warehouse-mapping-file" type="file" name="import" accept=".xlsx,.xls,.csv" required data-inventory-mapping-import-file>
|
||
</label>
|
||
<button type="submit" class="inventory-warehouse-mapping__import-button">导入并预览</button>
|
||
</form>
|
||
</div>
|
||
@if (is_array($warehouseMappingPreview))
|
||
@php($previewSummary = $warehouseMappingPreview['summary'] ?? [])
|
||
<details class="inventory-warehouse-mapping__preview" open>
|
||
<summary>导入预览:新增 {{ $previewSummary['created'] ?? 0 }} 条,重复 {{ $previewSummary['same'] ?? 0 }} 条,冲突 {{ $previewSummary['conflicts'] ?? 0 }} 条,失败 {{ $previewSummary['invalid'] ?? 0 }} 条</summary>
|
||
<table>
|
||
<thead><tr><th>行号</th><th>仓储方</th><th>仓库</th><th>仓库简称</th><th>结果</th><th>原因</th></tr></thead>
|
||
<tbody>
|
||
@foreach (($warehouseMappingPreview['rows'] ?? []) as $previewRow)
|
||
<tr class="is-{{ $previewRow['status'] }}">
|
||
<td>{{ $previewRow['row_number'] }}</td>
|
||
<td>{{ $previewRow['warehouse_provider'] ?: '—' }}</td>
|
||
<td>{{ $previewRow['warehouse'] ?: '—' }}</td>
|
||
<td>{{ $previewRow['warehouse_short_name'] ?: '—' }}</td>
|
||
<td>{{ match ($previewRow['status']) { 'new' => '待新增', 'same' => '跳过', 'conflict' => '冲突', default => '失败' } }}</td>
|
||
<td>{{ $previewRow['message'] }}</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
@if (($previewSummary['conflicts'] ?? 0) === 0 && ($previewSummary['invalid'] ?? 0) === 0)
|
||
<form method="POST" action="{{ route('business.product-inventory.inventory-report.warehouse-mappings.import.commit') }}" style="padding:0.75rem 1rem;">
|
||
@csrf
|
||
<input type="hidden" name="preview_token" value="{{ $warehouseMappingPreview['token'] ?? '' }}">
|
||
<button type="submit" class="is-primary">确认导入</button>
|
||
</form>
|
||
@else
|
||
<p style="padding:0 1rem 0.75rem;margin:0;color:#a33a32;">存在失败或冲突行,系统不会写入任何记录。请修正 Excel 后重新预览。</p>
|
||
@endif
|
||
</details>
|
||
@endif
|
||
@if ($inventoryWarehouseMappings->isEmpty())
|
||
<div class="inventory-warehouse-mapping__empty">当前查询暂无仓库名称映射。可点击“新增仓库”或上传 Sheet1 导入。</div>
|
||
@else
|
||
<div class="directory-table-scroll">
|
||
<table id="inventory-warehouse-mapping-table" class="directory-table inventory-warehouse-mapping__table" data-directory-table-key="warehouse-mapping">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col" data-directory-fixed="start">#</th>
|
||
<th scope="col" data-column-key="warehouse_provider">仓储方</th>
|
||
<th scope="col" data-column-key="warehouse">仓库</th>
|
||
<th scope="col" data-column-key="warehouse_short_name">仓库简称</th>
|
||
<th scope="col" data-directory-fixed="end">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($inventoryWarehouseMappings as $mapping)
|
||
<tr>
|
||
<td data-directory-fixed="start">{{ ($inventoryWarehouseMappings->currentPage() - 1) * $inventoryWarehouseMappings->perPage() + $loop->iteration }}</td>
|
||
<td data-column-key="warehouse_provider">{{ $mapping->warehouse_provider }}</td>
|
||
<td data-column-key="warehouse">{{ $mapping->warehouse }}</td>
|
||
<td data-column-key="warehouse_short_name">{{ $mapping->warehouse_short_name }}</td>
|
||
<td data-directory-fixed="end">
|
||
<span class="inventory-warehouse-mapping__actions">
|
||
<button type="button" data-inventory-mapping-edit data-id="{{ $mapping->id }}" data-provider="{{ $mapping->warehouse_provider }}" data-warehouse="{{ $mapping->warehouse }}" data-short-name="{{ $mapping->warehouse_short_name }}">修改</button>
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<footer class="directory-table-pagination">
|
||
<span>共 {{ $inventoryWarehouseMappings->total() }} 条 · 第 {{ $inventoryWarehouseMappings->currentPage() }} / {{ $inventoryWarehouseMappings->lastPage() }} 页</span>
|
||
<span>
|
||
@if ($inventoryWarehouseMappings->onFirstPage()) <span>上一页</span> @else <a href="{{ $inventoryWarehouseMappings->previousPageUrl() }}&mapping=1">上一页</a> @endif
|
||
·
|
||
@if ($inventoryWarehouseMappings->hasMorePages()) <a href="{{ $inventoryWarehouseMappings->nextPageUrl() }}&mapping=1">下一页</a> @else <span>下一页</span> @endif
|
||
</span>
|
||
</footer>
|
||
@endif
|
||
</div>
|
||
<dialog class="inventory-warehouse-mapping__dialog" data-inventory-mapping-dialog>
|
||
<form method="POST" action="{{ route('business.product-inventory.inventory-report.warehouse-mappings.store') }}" data-inventory-mapping-form>
|
||
@csrf
|
||
<input type="hidden" name="_method" value="POST" data-inventory-mapping-method>
|
||
<h3 data-inventory-mapping-dialog-title>新增仓库名称映射</h3>
|
||
<label>仓储方 <input name="warehouse_provider" required maxlength="191" data-inventory-mapping-provider></label>
|
||
<label>仓库 <input name="warehouse" required maxlength="191" data-inventory-mapping-warehouse></label>
|
||
<label>仓库简称 <input name="warehouse_short_name" required maxlength="191" data-inventory-mapping-short-name><small>输入仓储方和仓库后会自动预填,可按业务需要修改。</small></label>
|
||
<div class="inventory-warehouse-mapping__dialog__actions">
|
||
<button type="button" data-inventory-mapping-cancel>取消</button>
|
||
<button type="submit" class="is-primary">保存</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</section>
|
||
@endif
|
||
|
||
@if (($page['slug'] ?? '') === 'subject-operating-report')
|
||
<section class="directory-subject-details" data-subject-details-panel hidden aria-live="polite">
|
||
<header class="directory-subject-details__head">
|
||
<strong>主体详细基础资料</strong>
|
||
<span data-subject-details-name>请选择主体行</span>
|
||
</header>
|
||
<div class="directory-subject-details__body" data-subject-details-body>
|
||
<span>请选择上方主体行查看基础资料。</span>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
@endif
|
||
</section>
|
||
|
||
@if (($page['slug'] ?? '') === 'store-accounts')
|
||
@include('business.pages.partials.store-account-password')
|
||
@endif
|
||
|
||
@once
|
||
<script>
|
||
(() => {
|
||
const csvEscape = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
||
document.querySelectorAll('[data-inventory-month-picker]').forEach((picker) => {
|
||
const valueInput = picker.querySelector('[data-inventory-month-value]');
|
||
const trigger = picker.querySelector('[data-inventory-month-trigger]');
|
||
const label = picker.querySelector('[data-inventory-month-label]');
|
||
const panel = picker.querySelector('[data-inventory-month-panel]');
|
||
const yearLabel = picker.querySelector('[data-inventory-month-year]');
|
||
const grid = picker.querySelector('[data-inventory-month-grid]');
|
||
if (!valueInput || !trigger || !label || !panel || !yearLabel || !grid) return;
|
||
|
||
const monthPattern = /^(\d{4})-(0[1-9]|1[0-2])$/;
|
||
const minMonth = picker.dataset.minMonth || '';
|
||
const parsed = (value) => {
|
||
const match = monthPattern.exec(value || '');
|
||
return match ? { year: Number(match[1]), month: Number(match[2]) } : null;
|
||
};
|
||
const formatValue = (year, month) => `${year}-${String(month).padStart(2, '0')}`;
|
||
const formatLabel = (value) => {
|
||
const date = parsed(value);
|
||
return date ? `${date.year} 年 ${String(date.month).padStart(2, '0')} 月` : '';
|
||
};
|
||
const today = new Date();
|
||
const selected = parsed(valueInput.value);
|
||
let viewYear = selected?.year || today.getFullYear();
|
||
|
||
const isBeforeMin = (value) => Boolean(minMonth && value < minMonth);
|
||
const close = () => {
|
||
panel.hidden = true;
|
||
trigger.setAttribute('aria-expanded', 'false');
|
||
};
|
||
const render = () => {
|
||
yearLabel.textContent = `${viewYear} 年`;
|
||
const current = valueInput.value;
|
||
grid.innerHTML = Array.from({ length: 12 }, (_, index) => {
|
||
const month = index + 1;
|
||
const value = formatValue(viewYear, month);
|
||
const selectedClass = current === value ? ' is-selected' : '';
|
||
const disabled = isBeforeMin(value);
|
||
return `<button type="button" class="inventory-month-picker__month${selectedClass}" data-inventory-month-value-option="${value}"${disabled ? ' disabled aria-disabled="true"' : ''}>${String(month).padStart(2, '0')} 月</button>`;
|
||
}).join('');
|
||
};
|
||
const setValue = (value) => {
|
||
valueInput.value = value;
|
||
label.textContent = formatLabel(value);
|
||
const date = parsed(value);
|
||
if (date) viewYear = date.year;
|
||
render();
|
||
close();
|
||
};
|
||
|
||
label.textContent = formatLabel(valueInput.value);
|
||
render();
|
||
trigger.addEventListener('click', () => {
|
||
if (panel.hidden) {
|
||
render();
|
||
panel.hidden = false;
|
||
trigger.setAttribute('aria-expanded', 'true');
|
||
} else {
|
||
close();
|
||
}
|
||
});
|
||
picker.querySelector('[data-inventory-month-prev]')?.addEventListener('click', () => { viewYear -= 1; render(); });
|
||
picker.querySelector('[data-inventory-month-next]')?.addEventListener('click', () => { viewYear += 1; render(); });
|
||
grid.addEventListener('click', (event) => {
|
||
const option = event.target.closest('[data-inventory-month-value-option]');
|
||
if (option && !option.disabled) setValue(option.dataset.inventoryMonthValueOption);
|
||
});
|
||
picker.querySelector('[data-inventory-month-clear]')?.addEventListener('click', () => setValue(''));
|
||
picker.querySelector('[data-inventory-month-current]')?.addEventListener('click', () => setValue(formatValue(today.getFullYear(), today.getMonth() + 1)));
|
||
document.addEventListener('click', (event) => { if (!picker.contains(event.target)) close(); });
|
||
trigger.addEventListener('keydown', (event) => { if (event.key === 'Escape') close(); });
|
||
panel.addEventListener('keydown', (event) => { if (event.key === 'Escape') { close(); trigger.focus(); } });
|
||
});
|
||
|
||
document.querySelectorAll('[data-directory-query]').forEach((queryBar) => {
|
||
const section = queryBar.closest('[data-directory-page]');
|
||
const table = section?.querySelector('.directory-table');
|
||
if (!table) return;
|
||
const fields = [...queryBar.querySelectorAll('[data-directory-query-field]')];
|
||
const rows = () => [...table.querySelectorAll('tbody tr:not(.is-empty)')];
|
||
const fieldValues = (row, key) => {
|
||
if (key === 'related_people') {
|
||
return ['legal_representative', 'shareholders', 'supervisor', 'billing_officer']
|
||
.map((column) => (row.querySelector(`[data-column-key="${column}"]`)?.textContent || '').trim())
|
||
.filter((value) => value && value !== '—')
|
||
.join(' / ');
|
||
}
|
||
const cell = row.querySelector(`[data-column-key="${CSS.escape(key)}"]`);
|
||
return (cell?.textContent || '').trim();
|
||
};
|
||
const refreshSuggestions = (field, value) => {
|
||
const list = field.querySelector('[data-directory-query-suggestions]');
|
||
if (!list) return;
|
||
const key = field.dataset.directoryQueryField;
|
||
const needle = value.trim().toLocaleLowerCase();
|
||
const candidates = key === 'related_people'
|
||
? rows().flatMap((row) => ['legal_representative', 'shareholders', 'supervisor', 'billing_officer']
|
||
.map((column) => (row.querySelector(`[data-column-key="${column}"]`)?.textContent || '').trim()))
|
||
: rows().map((row) => fieldValues(row, key));
|
||
const values = [...new Set(candidates.filter((item) => item && item !== '—' && (!needle || item.toLocaleLowerCase().includes(needle))))].slice(0, 8);
|
||
list.innerHTML = values.map((item) => `<li><button type="button" data-directory-suggestion-value="${item.replaceAll('&','&').replaceAll('\"','"').replaceAll('<','<').replaceAll('>','>')}">${item.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>')}</button></li>`).join('');
|
||
list.hidden = values.length === 0;
|
||
list.querySelectorAll('button').forEach((button) => button.addEventListener('click', () => {
|
||
const input = field.querySelector('[data-directory-query-input]');
|
||
if (input) { input.value = button.dataset.directorySuggestionValue || ''; input.dispatchEvent(new Event('input', { bubbles: true })); input.focus(); }
|
||
list.hidden = true;
|
||
}));
|
||
};
|
||
fields.forEach((field) => {
|
||
const input = field.querySelector('[data-directory-query-input]');
|
||
const select = field.querySelector('[data-directory-query-select]');
|
||
const key = field.dataset.directoryQueryField;
|
||
const values = [...new Set(rows().map((row) => fieldValues(row, key)).filter((item) => item && item !== '—'))].sort((a,b) => a.localeCompare(b, 'zh-Hans-CN'));
|
||
if (select && select.options.length <= 1) { values.forEach((value) => { const option = document.createElement('option'); option.value = value; option.textContent = value; select.appendChild(option); }); }
|
||
input?.addEventListener('input', () => refreshSuggestions(field, input.value));
|
||
input?.addEventListener('focus', () => refreshSuggestions(field, input.value));
|
||
input?.addEventListener('blur', () => window.setTimeout(() => { const list = field.querySelector('[data-directory-query-suggestions]'); if (list) list.hidden = true; }, 150));
|
||
});
|
||
const columnChecks = [...queryBar.querySelectorAll('[data-directory-query-column]')];
|
||
const pickerSummary = queryBar.querySelector('[data-directory-query-picker] summary');
|
||
const applyVisibleColumns = () => {
|
||
const selected = columnChecks.filter((check) => check.checked).map((check) => check.value);
|
||
table.querySelectorAll('[data-column-key]').forEach((cell) => {
|
||
cell.classList.toggle('is-column-hidden', !selected.includes(cell.dataset.columnKey));
|
||
});
|
||
if (pickerSummary) pickerSummary.textContent = `筛选字段 ${selected.length}/${columnChecks.length}`;
|
||
let hiddenColumns = queryBar.querySelector(`input[form="${CSS.escape(queryBar.querySelector('form[id]')?.id || '')}"][name="columns"]`);
|
||
if (!hiddenColumns) {
|
||
hiddenColumns = document.createElement('input');
|
||
hiddenColumns.type = 'hidden';
|
||
hiddenColumns.name = 'columns';
|
||
hiddenColumns.setAttribute('form', queryBar.querySelector('form[id]')?.id || '');
|
||
queryBar.appendChild(hiddenColumns);
|
||
}
|
||
hiddenColumns.value = selected.join(',');
|
||
const url = new URL(window.location.href);
|
||
url.searchParams.set('columns', selected.join(','));
|
||
url.searchParams.delete('page');
|
||
window.history.replaceState({}, '', url);
|
||
};
|
||
columnChecks.forEach((check) => check.addEventListener('change', applyVisibleColumns));
|
||
section.querySelectorAll('[data-directory-header-hide]').forEach((button) => button.addEventListener('click', () => {
|
||
const key = button.dataset.directoryHeaderHide;
|
||
const check = columnChecks.find((item) => item.value === key);
|
||
if (!check) return;
|
||
check.checked = false;
|
||
check.dispatchEvent(new Event('change', { bubbles: true }));
|
||
button.closest('details')?.removeAttribute('open');
|
||
}));
|
||
queryBar.querySelector('[data-directory-query-reset]')?.addEventListener('click', () => {
|
||
const url = new URL(window.location.href);
|
||
fields.forEach((field) => {
|
||
const key = field.dataset.directoryQueryField;
|
||
url.searchParams.delete(key); url.searchParams.delete(`${key}_from`); url.searchParams.delete(`${key}_to`);
|
||
});
|
||
url.searchParams.delete('page');
|
||
window.location.assign(url);
|
||
});
|
||
queryBar.querySelector('[data-directory-query-export]')?.addEventListener('click', async (event) => {
|
||
const button = event.currentTarget;
|
||
const picker = button.closest('[data-directory-query-export-picker]');
|
||
const selected = new Set([...picker.querySelectorAll('[data-directory-query-export-field]:checked')].map((input) => input.value));
|
||
if (selected.size === 0) {
|
||
window.alert('请至少勾选一个导出字段');
|
||
return;
|
||
}
|
||
const headerCells = [...table.tHead.rows[0].querySelectorAll('[data-column-key]')]
|
||
.filter((cell) => selected.has(cell.dataset.columnKey));
|
||
const header = headerCells.map((cell) => csvEscape((cell.querySelector('[data-directory-header-label]') || cell).textContent.trim())).join(',');
|
||
const lines = [];
|
||
const lastPage = Number(section.dataset.directoryLastPage || 1);
|
||
button.disabled = true;
|
||
const originalLabel = button.textContent;
|
||
try {
|
||
for (let page = 1; page <= lastPage; page += 1) {
|
||
button.textContent = `导出中 ${page}/${lastPage}`;
|
||
const url = new URL(window.location.href);
|
||
url.searchParams.set('page', String(page));
|
||
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||
if (!response.ok) throw new Error(`第 ${page} 页读取失败`);
|
||
const parsed = new DOMParser().parseFromString(await response.text(), 'text/html');
|
||
parsed.querySelectorAll('.directory-table tbody tr:not(.is-empty)').forEach((row) => {
|
||
const values = headerCells.map((headerCell) => {
|
||
const cell = row.querySelector(`[data-column-key="${CSS.escape(headerCell.dataset.columnKey)}"]`);
|
||
return csvEscape((cell?.querySelector('[data-password-status]') || cell)?.textContent?.trim() || '');
|
||
});
|
||
lines.push(values.join(','));
|
||
});
|
||
}
|
||
const blob = new Blob([`\ufeff${[header, ...lines].join('\n')}`], { type: 'text/csv;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `${section.dataset.directoryPage || 'directory'}-export.csv`;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
picker.open = false;
|
||
} catch (error) {
|
||
window.alert(error.message || '导出失败,请稍后重试');
|
||
} finally {
|
||
button.disabled = false;
|
||
button.textContent = originalLabel;
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('[data-directory-tools]').forEach((tools) => {
|
||
const section = tools.closest('[data-directory-page]');
|
||
const tableScope = tools.closest('[data-directory-table-scope]');
|
||
const table = tableScope?.querySelector('.directory-table') || section?.querySelector('.directory-table');
|
||
const search = tools.querySelector('[data-directory-search]');
|
||
if (!table || !search) return;
|
||
table.addEventListener('click', (event) => {
|
||
const shortNameSort = event.target instanceof Element
|
||
? event.target.closest('[data-inventory-short-name-sort]')
|
||
: null;
|
||
if (!shortNameSort) return;
|
||
const header = shortNameSort.closest('th');
|
||
const ascending = header.getAttribute('aria-sort') !== 'ascending';
|
||
const rows = [...table.tBodies[0].querySelectorAll('tr:not(.is-empty)')];
|
||
rows.sort((left, right) => {
|
||
const a = left.querySelector('[data-column-key="warehouse_short_name"]')?.textContent.trim() || '';
|
||
const b = right.querySelector('[data-column-key="warehouse_short_name"]')?.textContent.trim() || '';
|
||
if (!a) return b ? 1 : 0;
|
||
if (!b) return -1;
|
||
return a.localeCompare(b, 'zh-Hans-CN') * (ascending ? 1 : -1);
|
||
}).forEach((row) => table.tBodies[0].appendChild(row));
|
||
header.setAttribute('aria-sort', ascending ? 'ascending' : 'descending');
|
||
shortNameSort.setAttribute('aria-label', `仓库简称按当前页${ascending ? '降序' : '升序'}排序`);
|
||
shortNameSort.querySelector('.directory-sort-icon').textContent = ascending ? 'arrow_upward' : 'arrow_downward';
|
||
});
|
||
const picker = tools.querySelector('[data-directory-field-picker]');
|
||
const toggle = picker?.querySelector('[data-directory-field-toggle]');
|
||
const panel = picker?.querySelector('.directory-field-picker__panel');
|
||
const checks = [...(picker?.querySelectorAll('[data-directory-field-check]') || [])];
|
||
const tableKey = tableScope?.dataset.directoryTableKey || section?.dataset.directoryTab || 'table';
|
||
const sourceUrl = tableScope?.dataset.directorySourceUrl || window.location.href;
|
||
const lastPage = Number(tableScope?.dataset.directoryLastPage || section?.dataset.directoryLastPage || 1);
|
||
const storageKey = [
|
||
'directory-fields',
|
||
section.dataset.directoryUser || 'guest',
|
||
section.dataset.directorySection || 'section',
|
||
section.dataset.directoryPage || 'page',
|
||
tableKey,
|
||
].join(':');
|
||
const defaultOrder = checks.map((check) => check.value);
|
||
const orderedChecks = () => [...(picker?.querySelectorAll('[data-directory-field-option]') || [])]
|
||
.map((option) => option.querySelector('[data-directory-field-check]'))
|
||
.filter(Boolean);
|
||
const orderedExportCells = (row, selected) => {
|
||
const cells = [...row.children];
|
||
const fixedStart = cells.filter((cell) => cell.dataset.directoryFixed === 'start');
|
||
const rank = new Map(orderedChecks().map((check, index) => [check.value, index]));
|
||
const keyed = cells
|
||
.filter((cell) => cell.dataset.columnKey && (!selected || selected.has(cell.dataset.columnKey)))
|
||
.sort((a, b) => (rank.get(a.dataset.columnKey) ?? 0) - (rank.get(b.dataset.columnKey) ?? 0));
|
||
const other = cells.filter((cell) => !cell.dataset.columnKey && !cell.dataset.directoryFixed);
|
||
|
||
return [...fixedStart, ...keyed, ...other];
|
||
};
|
||
const applyColumns = (save = true) => {
|
||
const ordered = orderedChecks();
|
||
const selected = new Set(ordered.filter((check) => check.checked).map((check) => check.value));
|
||
const rank = new Map(ordered.map((check, index) => [check.value, index]));
|
||
table.dataset.columnsReady = 'true';
|
||
table.querySelectorAll('[data-column-key]').forEach((cell) => {
|
||
cell.classList.toggle('is-column-hidden', !selected.has(cell.dataset.columnKey));
|
||
});
|
||
const reorderRow = (row) => {
|
||
const cells = [...row.children];
|
||
const fixedStart = cells.filter((cell) => cell.dataset.directoryFixed === 'start');
|
||
const fixedEnd = cells.filter((cell) => cell.dataset.directoryFixed === 'end');
|
||
const keyed = cells
|
||
.filter((cell) => cell.dataset.columnKey)
|
||
.sort((a, b) => (rank.get(a.dataset.columnKey) ?? 0) - (rank.get(b.dataset.columnKey) ?? 0));
|
||
const other = cells.filter((cell) => !cell.dataset.columnKey && !cell.dataset.directoryFixed);
|
||
[...fixedStart, ...keyed, ...fixedEnd, ...other].forEach((cell) => row.appendChild(cell));
|
||
};
|
||
[...table.tHead.rows].forEach((row) => {
|
||
reorderRow(row);
|
||
});
|
||
table.tBodies[0]?.querySelectorAll('tr').forEach((row) => {
|
||
reorderRow(row);
|
||
});
|
||
if (save) localStorage.setItem(storageKey, JSON.stringify({ order: ordered.map((check) => check.value), selected: [...selected] }));
|
||
};
|
||
try {
|
||
const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
|
||
const savedOrder = Array.isArray(saved) ? saved : saved?.order;
|
||
const savedSelected = Array.isArray(saved) ? saved : saved?.selected;
|
||
if (table.id === 'inventory-report-total-table' && !localStorage.getItem(`${storageKey}:warehouse-short-name-v1`)) {
|
||
if (Array.isArray(savedOrder) && !savedOrder.includes('warehouse_short_name')) {
|
||
savedOrder.splice(savedOrder.indexOf('warehouse') + 1, 0, 'warehouse_short_name');
|
||
}
|
||
if (Array.isArray(savedSelected) && !savedSelected.includes('warehouse_short_name')) {
|
||
savedSelected.push('warehouse_short_name');
|
||
}
|
||
if (saved) localStorage.setItem(storageKey, JSON.stringify({ order: savedOrder, selected: savedSelected }));
|
||
localStorage.setItem(`${storageKey}:warehouse-short-name-v1`, '1');
|
||
}
|
||
if (Array.isArray(savedSelected)) checks.forEach((check) => { check.checked = savedSelected.includes(check.value); });
|
||
if (Array.isArray(savedOrder)) savedOrder.forEach((key) => {
|
||
const option = picker.querySelector(`[data-directory-field-option] input[value="${CSS.escape(key)}"]`)?.closest('[data-directory-field-option]');
|
||
if (option) picker.querySelector('.directory-field-picker__panel').appendChild(option);
|
||
});
|
||
} catch (_) {}
|
||
applyColumns(false);
|
||
toggle?.addEventListener('click', () => {
|
||
const isHidden = panel.hidden;
|
||
panel.hidden = !isHidden;
|
||
toggle.setAttribute('aria-expanded', String(isHidden));
|
||
});
|
||
checks.forEach((check) => check.addEventListener('change', () => applyColumns()));
|
||
let dragging = null;
|
||
picker?.querySelectorAll('[data-directory-field-option]').forEach((option) => {
|
||
option.addEventListener('dragstart', () => { dragging = option; option.classList.add('is-dragging'); });
|
||
option.addEventListener('dragend', () => { dragging = null; option.classList.remove('is-dragging'); picker.querySelectorAll('.is-drop-target').forEach((item) => item.classList.remove('is-drop-target')); applyColumns(); });
|
||
option.addEventListener('dragover', (event) => { event.preventDefault(); if (dragging && dragging !== option) option.classList.add('is-drop-target'); });
|
||
option.addEventListener('dragleave', () => option.classList.remove('is-drop-target'));
|
||
option.addEventListener('drop', (event) => { event.preventDefault(); if (!dragging || dragging === option) return; option.parentNode.insertBefore(dragging, option); option.classList.remove('is-drop-target'); applyColumns(); });
|
||
});
|
||
const resetColumns = () => {
|
||
const options = [...picker.querySelectorAll('[data-directory-field-option]')];
|
||
const byKey = new Map(options.map((option) => [option.querySelector('[data-directory-field-check]').value, option]));
|
||
defaultOrder.forEach((key) => picker.querySelector('.directory-field-picker__panel').appendChild(byKey.get(key)));
|
||
checks.forEach((check) => { check.checked = !['sales_01', 'sales_02', 'sales_03', 'sales_04', 'sales_05', 'sales_06'].includes(check.value); });
|
||
applyColumns();
|
||
};
|
||
const resetButton = tools.querySelector('[data-directory-reset]');
|
||
if (resetButton) resetButton.onclick = resetColumns;
|
||
tools.querySelector('[data-inventory-export]')?.addEventListener('click', (event) => {
|
||
const url = new URL(event.currentTarget.dataset.exportUrl, window.location.origin);
|
||
const current = new URL(window.location.href);
|
||
['month', 'product_code', 'warehouse_provider', 'category'].forEach((key) => {
|
||
if (current.searchParams.has(key)) url.searchParams.set(key, current.searchParams.get(key));
|
||
});
|
||
const scope = tools.querySelector('[data-inventory-export-scope]').value;
|
||
url.searchParams.set('scope', scope);
|
||
if (scope === 'page') url.searchParams.set('page', current.searchParams.get('page') || '1');
|
||
if (tools.querySelector('[data-inventory-export-fields]').value === 'visible') {
|
||
const columns = [...table.tHead.querySelectorAll('th[data-column-key]')]
|
||
.filter((cell) => !cell.classList.contains('is-column-hidden'))
|
||
.map((cell) => cell.dataset.columnKey);
|
||
if (!columns.length) {
|
||
window.alert('请至少选择一个显示列。');
|
||
return;
|
||
}
|
||
url.searchParams.set('columns', columns.join(','));
|
||
}
|
||
const link = document.createElement('a');
|
||
link.href = url.toString();
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
link.remove();
|
||
});
|
||
search.addEventListener('input', () => {
|
||
const query = search.value.trim().toLocaleLowerCase();
|
||
table.querySelectorAll('tbody tr').forEach((row) => {
|
||
row.hidden = Boolean(query) && !row.textContent.toLocaleLowerCase().includes(query);
|
||
});
|
||
});
|
||
tools.querySelectorAll('[data-directory-export]').forEach((button) => button.addEventListener('click', () => {
|
||
const selected = button.dataset.directoryExport === 'all-fields' ? null : new Set(orderedChecks().filter((check) => check.checked).map((check) => check.value));
|
||
const rows = [...table.querySelectorAll('tr')].filter((row) => !row.hidden);
|
||
const csv = rows.map((row) => orderedExportCells(row, selected).map((cell) => csvEscape((cell.querySelector('[data-password-status]') || cell).textContent.trim())).join(',')).join('\n');
|
||
const blob = new Blob([`\ufeff${csv}`], { type: 'text/csv;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `${section.dataset.directoryPage || 'directory'}-current-export.csv`;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
}));
|
||
tools.querySelectorAll('[data-directory-export-all]').forEach((button) => button.addEventListener('click', async (event) => {
|
||
const button = event.currentTarget;
|
||
const selected = button.dataset.directoryExportAll === 'all-fields' ? null : new Set(orderedChecks().filter((check) => check.checked).map((check) => check.value));
|
||
const header = orderedExportCells(table.tHead.rows[0], selected).map((cell) => csvEscape(cell.textContent.trim())).join(',');
|
||
const lines = [];
|
||
button.disabled = true;
|
||
const originalLabel = button.textContent;
|
||
try {
|
||
for (let page = 1; page <= lastPage; page += 1) {
|
||
button.textContent = '导出中 ' + page + '/' + lastPage;
|
||
const url = new URL(sourceUrl, window.location.origin);
|
||
url.searchParams.set('page', String(page));
|
||
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||
if (!response.ok) throw new Error(`第 ${page} 页读取失败`);
|
||
const html = await response.text();
|
||
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
||
const parsedTable = table.id
|
||
? parsed.querySelector(`#${CSS.escape(table.id)}`)
|
||
: parsed.querySelector('.directory-table');
|
||
parsedTable?.querySelectorAll('tbody tr:not(.is-empty)').forEach((row) => {
|
||
lines.push(orderedExportCells(row, selected).map((cell) => csvEscape(cell.textContent.trim())).join(','));
|
||
});
|
||
}
|
||
const csv = [header, ...lines].join('\n');
|
||
const blob = new Blob([`\ufeff${csv}`], { type: 'text/csv;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `${section.dataset.directoryPage || 'directory'}-all-export.csv`;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
} catch (error) {
|
||
window.alert(error.message || '导出全部数据失败,请稍后重试');
|
||
} finally {
|
||
button.disabled = false;
|
||
button.textContent = originalLabel;
|
||
}
|
||
}));
|
||
tools.querySelector('[data-directory-template]')?.addEventListener('click', () => {
|
||
const header = [...table.querySelectorAll('thead th')].map((cell) => csvEscape(cell.textContent.trim())).join(',');
|
||
const blob = new Blob([`\ufeff${header}\n`], { type: 'text/csv;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `${section.dataset.directoryPage || 'directory'}-template.csv`;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
});
|
||
tools.querySelector('[data-inventory-mapping-template]')?.addEventListener('click', () => {
|
||
const header = ['仓储方', '仓库', '仓库简称'].map(csvEscape).join(',');
|
||
const blob = new Blob(['\ufeff' + header + '\n'], { type: 'text/csv;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = '仓库名称映射表-导入模板.csv';
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-inventory-report-tabs]').forEach((tabs) => {
|
||
const report = tabs.closest('[data-directory-page]');
|
||
const buttons = [...tabs.querySelectorAll('[data-inventory-report-tab]')];
|
||
const panels = [...(report?.querySelectorAll('[data-inventory-report-panel]') || [])];
|
||
if (!report || !buttons.length || !panels.length) return;
|
||
const select = (name, focus = false) => {
|
||
buttons.forEach((button) => {
|
||
const selected = button.dataset.inventoryReportTab === name;
|
||
button.setAttribute('aria-selected', String(selected));
|
||
button.tabIndex = selected ? 0 : -1;
|
||
if (focus && selected) button.focus();
|
||
});
|
||
panels.forEach((panel) => {
|
||
panel.hidden = panel.dataset.inventoryReportPanel !== name;
|
||
});
|
||
};
|
||
buttons.forEach((button, index) => {
|
||
button.addEventListener('click', () => select(button.dataset.inventoryReportTab, true));
|
||
button.addEventListener('keydown', (event) => {
|
||
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
||
event.preventDefault();
|
||
const nextIndex = event.key === 'Home' ? 0
|
||
: event.key === 'End' ? buttons.length - 1
|
||
: (index + (event.key === 'ArrowRight' ? 1 : -1) + buttons.length) % buttons.length;
|
||
select(buttons[nextIndex].dataset.inventoryReportTab, true);
|
||
});
|
||
});
|
||
select(new URLSearchParams(window.location.search).get('mapping') === '1' ? 'mapping' : 'total');
|
||
});
|
||
|
||
document.querySelectorAll('[data-inventory-mapping-dialog]').forEach((dialog) => {
|
||
const form = dialog.querySelector('[data-inventory-mapping-form]');
|
||
const method = form?.querySelector('[data-inventory-mapping-method]');
|
||
const title = dialog.querySelector('[data-inventory-mapping-dialog-title]');
|
||
const provider = form?.querySelector('[data-inventory-mapping-provider]');
|
||
const warehouse = form?.querySelector('[data-inventory-mapping-warehouse]');
|
||
const shortName = form?.querySelector('[data-inventory-mapping-short-name]');
|
||
const storeUrl = @json(route('business.product-inventory.inventory-report.warehouse-mappings.store'));
|
||
const updateUrl = @json(route('business.product-inventory.inventory-report.warehouse-mappings.update', ['id' => '__ID__']));
|
||
if (!form || !method || !title || !provider || !warehouse || !shortName) return;
|
||
const suggest = () => {
|
||
if (shortName.dataset.edited === 'true') return;
|
||
const text = `${provider.value} ${warehouse.value}`;
|
||
let value = '';
|
||
const special = [
|
||
['代发仓', '代发仓'], ['京东云仓', '京东云仓-不看'], ['销退仓', '销退仓-不看'],
|
||
['客退', '客退仓-不看'], ['次品仓', '退货报废-不看'], ['数量校验仓', '数量校验仓-不看'],
|
||
['自动发货仓', '虚拟仓-不看'], ['京东京造', '京东京造-不看'], ['POP仓', '京东入仓-不看'],
|
||
['江西仓送货', '南昌樵舍仓'], ['运营C崔桥工厂仓', '崔桥工厂仓'], ['运营C横林二仓', '横林二仓'],
|
||
['运营C睢宁', '睢宁仓'], ['运营C新苏北', '新苏北仓'],
|
||
];
|
||
for (const [needle, result] of special) if (text.includes(needle)) { value = result; break; }
|
||
if (!value) {
|
||
value = warehouse.value.replace(/(主仓|进货仓)$/u, '').replace(/^(简尚-|京仓-|三方-|奇门-)/u, '');
|
||
const providerBase = provider.value.replace(/^(简尚-|京仓-|三方-|奇门-)/u, '');
|
||
if (value === providerBase || value.startsWith(providerBase)) value = providerBase;
|
||
}
|
||
shortName.value = value;
|
||
};
|
||
provider.addEventListener('input', suggest);
|
||
warehouse.addEventListener('input', suggest);
|
||
shortName.addEventListener('input', () => { shortName.dataset.edited = 'true'; });
|
||
dialog.querySelector('[data-inventory-mapping-cancel]')?.addEventListener('click', () => dialog.close());
|
||
document.querySelector('[data-inventory-mapping-create]')?.addEventListener('click', () => {
|
||
title.textContent = '新增仓库名称映射';
|
||
form.action = storeUrl;
|
||
method.value = 'POST';
|
||
provider.value = '';
|
||
warehouse.value = '';
|
||
shortName.value = '';
|
||
shortName.dataset.edited = 'false';
|
||
dialog.showModal();
|
||
provider.focus();
|
||
});
|
||
document.querySelectorAll('[data-inventory-mapping-edit]').forEach((button) => button.addEventListener('click', () => {
|
||
title.textContent = '修改仓库名称映射';
|
||
form.action = updateUrl.replace('__ID__', button.dataset.id);
|
||
method.value = 'PATCH';
|
||
provider.value = button.dataset.provider || '';
|
||
warehouse.value = button.dataset.warehouse || '';
|
||
shortName.value = button.dataset.shortName || '';
|
||
shortName.dataset.edited = 'true';
|
||
dialog.showModal();
|
||
provider.focus();
|
||
}));
|
||
dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); });
|
||
});
|
||
|
||
document.querySelectorAll('[data-subject-row]').forEach((row) => {
|
||
const section = row.closest('[data-directory-page]');
|
||
const panel = section?.querySelector('[data-subject-details-panel]');
|
||
const body = panel?.querySelector('[data-subject-details-body]');
|
||
const name = panel?.querySelector('[data-subject-details-name]');
|
||
if (!panel || !body || !name) return;
|
||
const render = () => {
|
||
document.querySelectorAll('[data-subject-row]').forEach((item) => item.removeAttribute('aria-selected'));
|
||
row.setAttribute('aria-selected', 'true');
|
||
let details = [];
|
||
try { details = JSON.parse(row.dataset.subjectDetails || '[]'); } catch (_) { details = []; }
|
||
const entity = row.children[2]?.textContent.trim() || '当前主体';
|
||
name.textContent = entity;
|
||
panel.hidden = false;
|
||
if (!details.length) {
|
||
body.textContent = '暂无匹配的主体基础资料。';
|
||
return;
|
||
}
|
||
const labels = {
|
||
unified_social_credit_code: '统一社会信用代码', company_name: '公司名称', taxpayer_category: '纳税人类型',
|
||
company_status: '公司状态', subscribed_capital: '认缴注册资本(万元)', establishment_date: '注册时间',
|
||
legal_representative: '法人', shareholders: '股东', supervisor: '监事', financial_officer: '开票员',
|
||
registered_address: '注册地址', business_scope: '经营范围', basic_deposit_account_number: '基本存款账户信息', subscribed_at: '认缴时间',
|
||
};
|
||
body.innerHTML = details.map((detail) => `<dl class="directory-subject-details__record">${Object.entries(labels).map(([key, label]) => {
|
||
const value = detail[key];
|
||
const display = value === null || value === undefined || value === '' ? '—' : (key === 'basic_deposit_account_number' ? '已配置' : String(value));
|
||
return `<div><dt>${label}</dt><dd>${display.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')}</dd></div>`;
|
||
}).join('')}</dl>`).join('');
|
||
};
|
||
row.addEventListener('click', render);
|
||
row.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); render(); } });
|
||
});
|
||
})();
|
||
</script>
|
||
@endonce
|