809 lines
41 KiB
PHP
809 lines
41 KiB
PHP
@once
|
|
<style>
|
|
/* Shared finance table contract: one compact surface and one scroll owner. */
|
|
.finance-table-surface,
|
|
.directory-table-page,
|
|
.business-content-section {
|
|
color: var(--vip-content-ink, #171717);
|
|
font-family: "JetBrains Mono", "Microsoft YaHei", "PingFang SC", monospace;
|
|
}
|
|
|
|
.business-content-section {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
border: 1px solid var(--vip-border, #d8d2c7);
|
|
border-radius: 0.75rem;
|
|
background: var(--vip-surface, #ffffff);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.business-tab-bar {
|
|
min-height: 3rem;
|
|
gap: 1.25rem;
|
|
padding: 0 1rem;
|
|
border-color: var(--vip-border, #d8d2c7);
|
|
border-radius: 0.5rem;
|
|
background: var(--vip-surface, #ffffff);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.business-tab-link {
|
|
min-height: 3rem;
|
|
gap: 0.4rem;
|
|
color: var(--vip-content-muted, #595959);
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.business-tab-link:hover,
|
|
.business-tab-link.is-active {
|
|
color: var(--vip-content-accent-dark, #856632);
|
|
}
|
|
|
|
.business-page--marketing-account-bills .business-tab-bar {
|
|
min-height: 3rem;
|
|
gap: 1.25rem;
|
|
padding: 0 1rem;
|
|
border-radius: 0.5rem;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.business-page--marketing-account-bills .business-tab-link {
|
|
min-height: 3rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.business-page--marketing-account-bills .business-content-section {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
border-radius: 0.75rem;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.finance-table-controls {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin: 0 0 0.75rem;
|
|
padding: 0.625rem 0.75rem;
|
|
border: 1px solid var(--vip-border, #d8d2c7);
|
|
border-radius: 0.5rem;
|
|
background: var(--vip-content-bg, #f8f6f1);
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.finance-table-controls__button,
|
|
.finance-table-controls select {
|
|
display: inline-flex;
|
|
min-height: 2.125rem;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
border: 1px solid var(--vip-border, #d8d2c7);
|
|
border-radius: 0.5rem;
|
|
background: var(--vip-surface, #ffffff);
|
|
color: var(--vip-content-ink, #171717);
|
|
font: inherit;
|
|
line-height: 1;
|
|
}
|
|
|
|
.finance-table-controls__button {
|
|
padding: 0 0.75rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.finance-table-controls__button:hover,
|
|
.finance-table-controls__button:focus-visible,
|
|
.finance-table-controls select:focus-visible {
|
|
border-color: var(--vip-content-accent-dark, #856632);
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px rgba(133, 102, 50, 0.2);
|
|
}
|
|
|
|
.finance-table-controls__button--primary {
|
|
border-color: var(--vip-content-accent-dark, #856632);
|
|
background: var(--vip-content-accent-dark, #856632);
|
|
color: #ffffff;
|
|
}
|
|
|
|
.finance-table-controls select {
|
|
padding: 0 0.5rem;
|
|
}
|
|
|
|
.finance-table-controls__mode {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
color: var(--vip-content-muted, #595959);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.finance-table-controls__mode select { min-width: 9.5rem; }
|
|
|
|
.finance-table-controls [data-directory-export],
|
|
.finance-table-controls [data-directory-export-all],
|
|
.finance-table-controls [data-finance-server-export] {
|
|
display: inline-flex;
|
|
min-height: 2.125rem;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0 0.75rem;
|
|
border: 1px solid var(--vip-border, #d8d2c7);
|
|
border-radius: 0.5rem;
|
|
background: var(--vip-surface, #ffffff);
|
|
color: var(--vip-content-ink, #171717);
|
|
font: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.finance-table-controls [data-directory-export-all],
|
|
.finance-table-controls [data-finance-server-export] { border-color: var(--vip-content-accent-dark, #856632); }
|
|
|
|
.finance-table-controls [data-directory-export]:hover,
|
|
.finance-table-controls [data-directory-export-all]:hover,
|
|
.finance-table-controls [data-finance-server-export]:hover { background: var(--vip-content-accent-soft, #f7f0de); }
|
|
|
|
.finance-table-controls .is-column-hidden { display: none !important; }
|
|
|
|
.finance-table-fallback-export { display: inline-flex; }
|
|
[data-finance-enhanced] .finance-table-fallback-export { display: none; }
|
|
|
|
/* Legacy business-card buttons inherit the shared VIP black-gold contract. */
|
|
.business-content-section .biz-btn-primary {
|
|
border-color: var(--vip-content-accent-dark, #856632);
|
|
background: var(--vip-content-accent-dark, #856632);
|
|
color: var(--vip-surface, #ffffff);
|
|
}
|
|
|
|
.business-content-section .biz-btn-primary:hover {
|
|
border-color: #6a5129;
|
|
background: #6a5129;
|
|
}
|
|
|
|
.business-content-section .biz-btn-ghost {
|
|
border-color: var(--vip-border, #d8d2c7);
|
|
background: var(--vip-surface, #ffffff);
|
|
color: var(--vip-content-ink, #171717);
|
|
}
|
|
|
|
.business-content-section .biz-btn-ghost:hover {
|
|
border-color: var(--vip-content-accent-dark, #856632);
|
|
background: var(--vip-content-accent-soft, #f7f0de);
|
|
}
|
|
|
|
.finance-table-field-picker {
|
|
position: relative;
|
|
}
|
|
|
|
.finance-table-field-picker__panel {
|
|
position: absolute;
|
|
top: calc(100% + 0.35rem);
|
|
right: 0;
|
|
z-index: 20;
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(10rem, 1fr));
|
|
gap: 0.25rem 0.75rem;
|
|
min-width: 25rem;
|
|
max-height: min(24rem, 60vh);
|
|
overflow: auto;
|
|
padding: 0.75rem;
|
|
border: 1px solid var(--vip-border, #d8d2c7);
|
|
border-radius: 0.5rem;
|
|
background: var(--vip-surface, #ffffff);
|
|
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.1);
|
|
}
|
|
|
|
.finance-table-field-picker__panel[hidden] { display: none; }
|
|
|
|
.finance-table-field-picker__option {
|
|
display: flex;
|
|
min-width: 0;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
min-height: 1.75rem;
|
|
color: var(--vip-content-ink, #171717);
|
|
cursor: grab;
|
|
}
|
|
|
|
.finance-table-field-picker__option.is-dragging { opacity: 0.45; }
|
|
.finance-table-field-picker__option input { flex: 0 0 auto; color: var(--vip-content-accent-dark, #856632); accent-color: var(--vip-content-accent, #b9954f); }
|
|
.finance-table-field-picker__option span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.finance-table-field-picker__actions { display: flex; grid-column: 1 / -1; gap: 0.35rem; padding-top: 0.35rem; border-top: 1px solid var(--vip-border, #d8d2c7); }
|
|
.finance-table-field-picker__actions button { min-height: 1.75rem; padding: 0 0.5rem; border: 1px solid var(--vip-border, #d8d2c7); border-radius: 0.35rem; background: #fff; color: var(--vip-content-accent-dark, #856632); cursor: pointer; font: inherit; }
|
|
|
|
.finance-table-scroll-owner,
|
|
.directory-table-scroll,
|
|
.upstream-data-table-scroll,
|
|
.bill-table-wrapper,
|
|
.tb-scroll,
|
|
.ab-scroll,
|
|
.marketing-bills-table-wrapper,
|
|
.jd-wallet-table-wrap,
|
|
.alibaba-bill-table-wrapper,
|
|
.zhongcai-table-wrapper {
|
|
max-height: calc(100vh - 21rem);
|
|
overflow: auto;
|
|
scrollbar-color: var(--vip-content-accent, #b9954f) #efede8;
|
|
scrollbar-width: thin;
|
|
scrollbar-gutter: stable both-edges;
|
|
}
|
|
|
|
[data-finance-table-ready] thead th,
|
|
.directory-table thead th,
|
|
.upstream-data-table thead th,
|
|
.bill-table thead th,
|
|
.tb-table thead th,
|
|
.ab-table thead th,
|
|
.marketing-bills-table thead th,
|
|
.jd-wallet-table thead th,
|
|
.alibaba-bill-table thead th,
|
|
.zhongcai-table thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 4;
|
|
background: var(--vip-content-accent-soft, #f7f0de);
|
|
color: var(--vip-content-ink, #171717);
|
|
}
|
|
|
|
.tb-table thead .tb-group th,
|
|
.ab-table thead .ab-group th {
|
|
top: 0;
|
|
z-index: 5;
|
|
}
|
|
|
|
.tb-table thead .tb-col th,
|
|
.ab-table thead .ab-col th { top: 2.25rem; z-index: 4; background: #fffdf8; }
|
|
|
|
[data-finance-fixed="true"] {
|
|
position: sticky;
|
|
left: 0;
|
|
z-index: 6;
|
|
background: var(--vip-surface, #ffffff);
|
|
}
|
|
|
|
[data-finance-fixed="end"] {
|
|
position: sticky;
|
|
right: 0;
|
|
z-index: 6;
|
|
background: var(--vip-surface, #ffffff);
|
|
}
|
|
|
|
thead [data-finance-fixed="true"],
|
|
thead [data-finance-fixed="end"] { background: var(--vip-content-accent-soft, #f7f0de); }
|
|
|
|
[data-finance-column-key].is-column-hidden { display: none !important; }
|
|
|
|
.finance-table-controls [data-finance-server-export][disabled],
|
|
.finance-table-controls [data-directory-export][disabled],
|
|
.finance-table-controls [data-directory-export-all][disabled] { opacity: 0.55; cursor: wait; }
|
|
|
|
@media (max-width: 720px) {
|
|
.business-content-section { padding: 0.75rem; }
|
|
.finance-table-field-picker__panel { right: auto; left: 0; min-width: min(25rem, calc(100vw - 2rem)); grid-template-columns: minmax(0, 1fr); }
|
|
.finance-table-controls__button, .finance-table-controls select { flex: 1 1 9rem; }
|
|
}
|
|
</style>
|
|
|
|
@php
|
|
$financeTableContext = [
|
|
'user' => auth()->id() ?? 'guest',
|
|
'section' => $section['slug'] ?? '',
|
|
'page' => $page['slug'] ?? '',
|
|
'tab' => $activeTab['slug'] ?? '',
|
|
];
|
|
@endphp
|
|
<script>
|
|
(() => {
|
|
const context = @json($financeTableContext);
|
|
window.__CAIWU_FINANCE_CONTEXT = context;
|
|
|
|
const selector = [
|
|
'[data-finance-table]', '.directory-table', '.upstream-data-table', '.biz-table',
|
|
'.business-table', '.after-sales-refund-table', '.refund-data-table', '.small-payment-table',
|
|
'.tb-table', '.ab-table', '.marketing-bills-table', '.bill-table', '.biz-table',
|
|
'.jd-wallet-table', '.alibaba-bill-table', '.zhongcai-table', '.marketing-balance-table',
|
|
'.installation-detail-table', '.courier-information-table', '.edge-banding-table',
|
|
'.agreement-table', '.quote-table', '.recon-table', '.sm-table', '.ding-payable-table',
|
|
].join(',');
|
|
const tableStates = new WeakMap();
|
|
const tableNames = new WeakSet();
|
|
const routeContext = () => {
|
|
const parts = window.location.pathname.split('/').filter(Boolean);
|
|
const workspaceIndex = parts.indexOf('workspace');
|
|
return workspaceIndex < 0 ? context : {
|
|
user: context.user,
|
|
section: parts[workspaceIndex + 1] || context.section,
|
|
page: parts[workspaceIndex + 2] || context.page,
|
|
tab: parts[workspaceIndex + 3] || '',
|
|
};
|
|
};
|
|
const keyFor = (table) => {
|
|
if (table.dataset.financeTableKey) return table.dataset.financeTableKey;
|
|
const currentContext = routeContext();
|
|
const tableScope = table.closest('[data-directory-table-scope]');
|
|
const parent = tableScope || table.closest('[data-directory-page], [data-refund-page], [data-finance-table-surface], .business-content-section') || table.parentElement;
|
|
const name = table.getAttribute('aria-label') || table.dataset.financeTableName || table.className || 'table';
|
|
const siblings = parent ? [...parent.querySelectorAll(selector)].filter((candidate) => candidate.matches(selector)) : [table];
|
|
const index = Math.max(0, siblings.indexOf(table));
|
|
const scope = tableScope?.dataset.directoryTableKey || parent?.dataset.directoryPage || parent?.dataset.refundPage || name;
|
|
table.dataset.financeTableKey = `${currentContext.user}:${currentContext.section}:${currentContext.page}:${currentContext.tab}:${scope}:${index}`;
|
|
return table.dataset.financeTableKey;
|
|
};
|
|
const storageKey = (table, suffix) => `caiwu.finance.${keyFor(table)}.${suffix}`;
|
|
const slug = (text) => String(text || '').trim().toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, '_').replace(/^_|_$/g, '') || 'column';
|
|
const csvText = (value) => {
|
|
const text = String(value ?? '').trim();
|
|
if (/^\d{12,}$/.test(text)) return `="${text}"`;
|
|
return /^[=+\-@]/u.test(text) ? `'${text}` : text;
|
|
};
|
|
const csvEscape = (value) => `"${csvText(value).replaceAll('"', '""')}"`;
|
|
const cellText = (cell) => {
|
|
const passwordStatus = cell?.querySelector('[data-password-status]');
|
|
if (passwordStatus) return passwordStatus.textContent.trim();
|
|
return cell?.dataset.exportValue ?? cell?.textContent.trim() ?? '';
|
|
};
|
|
const downloadCsv = (filename, lines) => {
|
|
const blob = new Blob([`\ufeff${lines.join('\r\n')}`], { type: 'text/csv;charset=utf-8' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = filename;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
};
|
|
|
|
function leafHeaders(table) {
|
|
const rows = [...table.tHead?.rows || []];
|
|
if (!rows.length) return [];
|
|
return rows.reduce((best, row) => row.cells.length > best.length ? [...row.cells] : best, []);
|
|
}
|
|
|
|
function headerLeafKeys(table) {
|
|
const rows = [...table.tHead?.rows || []];
|
|
if (rows.length < 2) return leafHeaders(table).map((cell) => cell.dataset.financeColumnKey);
|
|
const leafRow = rows[rows.length - 1];
|
|
return [...leafRow.cells].map((cell) => cell.dataset.financeColumnKey).filter(Boolean);
|
|
}
|
|
|
|
function prepareColumns(table) {
|
|
const headers = leafHeaders(table);
|
|
const headerRows = [...table.tHead?.rows || []];
|
|
const scrollOwner = table.closest('.finance-table-scroll-owner, .directory-table-scroll, .upstream-data-table-scroll, .bill-table-wrapper, .tb-scroll, .ab-scroll, .marketing-bills-table-wrapper, .jd-wallet-table-wrap, .alibaba-bill-table-wrapper, .zhongcai-table-wrapper') || table.parentElement;
|
|
scrollOwner?.classList.add('finance-table-scroll-owner');
|
|
headers.forEach((cell, index) => {
|
|
if (!cell.dataset.financeColumnKey) {
|
|
cell.dataset.financeColumnKey = cell.dataset.columnKey || cell.dataset.field || cell.dataset.exportField || `${slug(cell.textContent)}_${index}`;
|
|
}
|
|
cell.dataset.financeColumnIndex = String(index);
|
|
if (!cell.dataset.financeFixed && cell.dataset.directoryFixed === 'start') cell.dataset.financeFixed = 'true';
|
|
if (!cell.dataset.financeFixed && cell.dataset.directoryFixed === 'end') cell.dataset.financeFixed = 'end';
|
|
if (!cell.dataset.financeFixed && index === 0 && (cell.querySelector('input[type="checkbox"]') || ['#', '序号'].includes(cell.textContent.trim()))) cell.dataset.financeFixed = 'true';
|
|
});
|
|
const keys = headers.map((cell) => cell.dataset.financeColumnKey);
|
|
const leafRow = headerRows[headerRows.length - 1];
|
|
if (leafRow) {
|
|
[...leafRow.cells].forEach((cell, index) => {
|
|
cell.dataset.financeColumnKey = keys[index];
|
|
cell.dataset.financeColumnIndex = String(index);
|
|
if (headers[index]?.dataset.financeFixed) cell.dataset.financeFixed = headers[index].dataset.financeFixed;
|
|
});
|
|
}
|
|
const groupRows = headerRows.slice(0, -1).map((row) => {
|
|
let offset = 0;
|
|
return [...row.cells].map((cell, groupIndex) => {
|
|
const span = Math.max(Number(cell.colSpan || 1), 1);
|
|
const group = { index: groupIndex, label: cell.textContent.trim(), span, template: cell.cloneNode(true) };
|
|
for (let i = offset; i < Math.min(offset + span, keys.length); i += 1) {
|
|
headers[i].dataset.financeGroup = String(groupIndex);
|
|
}
|
|
offset += span;
|
|
cell.dataset.financeGroupHeader = 'true';
|
|
return group;
|
|
});
|
|
});
|
|
table.querySelectorAll('tbody tr').forEach((row) => {
|
|
if (row.classList.contains('is-empty') || row.children.length !== keys.length) return;
|
|
[...row.children].forEach((cell, index) => {
|
|
cell.dataset.financeColumnKey = cell.dataset.financeColumnKey || keys[index];
|
|
if (headers[index]?.dataset.financeFixed) cell.dataset.financeFixed = headers[index].dataset.financeFixed;
|
|
});
|
|
});
|
|
tableStates.set(table, { headers, keys, leafRow, groupRows });
|
|
table.dataset.financeTableReady = 'true';
|
|
window.requestAnimationFrame(() => applyStickyOffsets(table));
|
|
return { headers, keys };
|
|
}
|
|
|
|
function applyStickyOffsets(table) {
|
|
let offset = 0;
|
|
[...table.tHead?.rows || []].forEach((row) => {
|
|
[...row.cells].forEach((cell) => { cell.style.top = `${offset}px`; });
|
|
offset += row.getBoundingClientRect().height || 36;
|
|
});
|
|
}
|
|
|
|
function reorderCells(table, order) {
|
|
const byKey = (row) => new Map([...row.children].map((cell) => [cell.dataset.financeColumnKey, cell]));
|
|
const rows = [...table.tHead?.rows || []];
|
|
const state = tableStates.get(table);
|
|
const leafRow = rows[rows.length - 1];
|
|
if (leafRow && leafRow.cells.length === order.length) {
|
|
const map = byKey(leafRow);
|
|
order.forEach((key) => { const cell = map.get(key); if (cell) leafRow.appendChild(cell); });
|
|
}
|
|
rows.slice(0, -1).forEach((row, rowIndex) => {
|
|
const definitions = state?.groupRows?.[rowIndex];
|
|
if (!definitions?.length) return;
|
|
const byGroup = new Map(definitions.map((group) => [String(group.index), group]));
|
|
const runs = [];
|
|
order.forEach((key) => {
|
|
const group = String(state.headers.find((header) => header.dataset.financeColumnKey === key)?.dataset.financeGroup ?? '');
|
|
const previous = runs[runs.length - 1];
|
|
if (previous && previous.group === group) previous.span += 1;
|
|
else runs.push({ group, span: 1 });
|
|
});
|
|
row.replaceChildren(...runs.map((run) => {
|
|
const definition = byGroup.get(run.group);
|
|
const cell = definition?.template?.cloneNode(true) || document.createElement('th');
|
|
cell.colSpan = run.span;
|
|
cell.dataset.financeGroupHeader = 'true';
|
|
return cell;
|
|
}));
|
|
});
|
|
table.querySelectorAll('tbody tr').forEach((row) => {
|
|
if (row.classList.contains('is-empty') || row.children.length !== order.length) return;
|
|
const map = byKey(row);
|
|
order.forEach((key) => { const cell = map.get(key); if (cell) row.appendChild(cell); });
|
|
});
|
|
window.requestAnimationFrame(() => applyStickyOffsets(table));
|
|
}
|
|
|
|
function setVisibility(table, checks, save = true) {
|
|
const selected = new Set(checks.filter((check) => check.checked).map((check) => check.value));
|
|
table.dataset.columnsReady = 'true';
|
|
table.querySelectorAll('[data-finance-column-key]').forEach((cell) => {
|
|
if (cell.dataset.financeFixed === 'true' || cell.dataset.financeFixed === 'end') return;
|
|
cell.classList.toggle('is-column-hidden', !selected.has(cell.dataset.financeColumnKey));
|
|
});
|
|
const state = tableStates.get(table);
|
|
if (state?.groupRows?.length) {
|
|
const order = [...state.leafRow?.cells || []].map((cell) => cell.dataset.financeColumnKey).filter(Boolean);
|
|
state.groupRows.forEach((definitions, rowIndex) => {
|
|
const row = table.tHead?.rows?.[rowIndex];
|
|
if (!row) return;
|
|
const runs = [];
|
|
order.forEach((key) => {
|
|
const header = state.headers.find((cell) => cell.dataset.financeColumnKey === key);
|
|
if (!header || (!selected.has(key) && !['true', 'end'].includes(header.dataset.financeFixed))) return;
|
|
const group = String(header.dataset.financeGroup ?? '');
|
|
const previous = runs[runs.length - 1];
|
|
if (previous && previous.group === group) previous.span += 1;
|
|
else runs.push({ group, span: 1 });
|
|
});
|
|
const byGroup = new Map(definitions.map((definition) => [String(definition.index), definition]));
|
|
row.replaceChildren(...runs.map((run) => {
|
|
const template = byGroup.get(run.group)?.template;
|
|
const cell = template?.cloneNode(true) || document.createElement('th');
|
|
cell.colSpan = run.span;
|
|
cell.dataset.financeGroupHeader = 'true';
|
|
return cell;
|
|
}));
|
|
});
|
|
}
|
|
window.requestAnimationFrame(() => applyStickyOffsets(table));
|
|
if (save) {
|
|
try { localStorage.setItem(storageKey(table, 'visibility'), JSON.stringify([...selected])); } catch (_) {}
|
|
}
|
|
}
|
|
|
|
function createPicker(table, headers, host) {
|
|
let picker = host.querySelector('[data-finance-field-picker], [data-directory-field-picker]');
|
|
const isLegacyDirectoryPicker = picker?.hasAttribute('data-directory-field-picker') === true;
|
|
if (!picker) {
|
|
picker = document.createElement('div');
|
|
picker.className = 'finance-table-field-picker';
|
|
picker.dataset.financeFieldPicker = 'true';
|
|
picker.innerHTML = '<button type="button" class="finance-table-controls__button" aria-expanded="false">列设置</button><div class="finance-table-field-picker__panel" role="group" aria-label="选择展示字段" hidden></div>';
|
|
host.prepend(picker);
|
|
}
|
|
picker.dataset.financeFieldPicker = 'true';
|
|
picker.classList.add('finance-table-field-picker');
|
|
const toggle = picker.querySelector('button');
|
|
const panel = picker.querySelector('.finance-table-field-picker__panel, .directory-field-picker__panel');
|
|
panel.classList.add('finance-table-field-picker__panel');
|
|
let checks = [...panel.querySelectorAll('input[data-finance-field-check], input[data-directory-field-check]')];
|
|
if (!checks.length) {
|
|
headers.forEach((header) => {
|
|
const label = document.createElement('label');
|
|
label.className = 'finance-table-field-picker__option';
|
|
label.draggable = true;
|
|
const key = header.dataset.financeColumnKey;
|
|
const fixed = Boolean(header.dataset.financeFixed);
|
|
label.dataset.financeOptionKey = key;
|
|
label.innerHTML = `<input type="checkbox" value="${key.replaceAll('"', '"')}" data-finance-field-check ${fixed ? 'checked disabled' : 'checked'}><span>${header.textContent.trim() || '未命名字段'}</span>`;
|
|
panel.appendChild(label);
|
|
});
|
|
const actions = document.createElement('div');
|
|
actions.className = 'finance-table-field-picker__actions';
|
|
actions.innerHTML = '<button type="button" data-finance-all>全选</button><button type="button" data-finance-none>全不选</button><button type="button" data-finance-reset>恢复默认</button>';
|
|
panel.appendChild(actions);
|
|
}
|
|
if (!panel.querySelector('[data-finance-all]')) {
|
|
const actions = document.createElement('div');
|
|
actions.className = 'finance-table-field-picker__actions';
|
|
actions.innerHTML = '<button type="button" data-finance-all>全选</button><button type="button" data-finance-none>全不选</button><button type="button" data-finance-reset>恢复默认</button>';
|
|
panel.appendChild(actions);
|
|
}
|
|
checks = [...panel.querySelectorAll('input[data-finance-field-check], input[data-directory-field-check]')];
|
|
const defaults = headers.map((header) => header.dataset.financeColumnKey);
|
|
const fixedKeys = headers.filter((header) => header.dataset.financeFixed === 'true').map((header) => header.dataset.financeColumnKey);
|
|
const fixedEndKeys = headers.filter((header) => header.dataset.financeFixed === 'end').map((header) => header.dataset.financeColumnKey);
|
|
const defaultVisible = new Set(checks.filter((check) => check.defaultChecked || check.disabled).map((check) => check.value));
|
|
const applyOrder = (order, save = true) => {
|
|
const valid = fixedKeys.concat(order.filter((key) => defaults.includes(key) && !fixedKeys.includes(key) && !fixedEndKeys.includes(key)));
|
|
defaults.forEach((key) => { if (!valid.includes(key)) valid.push(key); });
|
|
fixedEndKeys.forEach((key) => {
|
|
const currentIndex = valid.indexOf(key);
|
|
if (currentIndex >= 0) valid.splice(currentIndex, 1);
|
|
valid.push(key);
|
|
});
|
|
reorderCells(table, valid);
|
|
[...panel.querySelectorAll('[data-finance-option-key], .directory-field-picker__option')].sort((a, b) => valid.indexOf(a.dataset.financeOptionKey || a.querySelector('input')?.value) - valid.indexOf(b.dataset.financeOptionKey || b.querySelector('input')?.value)).forEach((option) => panel.insertBefore(option, panel.querySelector('.finance-table-field-picker__actions')));
|
|
if (save) {
|
|
try { localStorage.setItem(storageKey(table, 'order'), JSON.stringify(valid)); } catch (_) {}
|
|
}
|
|
};
|
|
try {
|
|
const savedVisibility = JSON.parse(localStorage.getItem(storageKey(table, 'visibility')) || 'null');
|
|
const savedOrder = JSON.parse(localStorage.getItem(storageKey(table, 'order')) || 'null');
|
|
if (table.id === 'inventory-report-total-table' && !localStorage.getItem(storageKey(table, 'warehouse-short-name-v1'))) {
|
|
if (Array.isArray(savedOrder) && !savedOrder.includes('warehouse_short_name')) {
|
|
savedOrder.splice(savedOrder.indexOf('warehouse') + 1, 0, 'warehouse_short_name');
|
|
localStorage.setItem(storageKey(table, 'order'), JSON.stringify(savedOrder));
|
|
}
|
|
if (Array.isArray(savedVisibility) && !savedVisibility.includes('warehouse_short_name')) {
|
|
savedVisibility.push('warehouse_short_name');
|
|
localStorage.setItem(storageKey(table, 'visibility'), JSON.stringify(savedVisibility));
|
|
}
|
|
localStorage.setItem(storageKey(table, 'warehouse-short-name-v1'), '1');
|
|
}
|
|
if (Array.isArray(savedVisibility)) checks.forEach((check) => { if (!check.disabled) check.checked = savedVisibility.includes(check.value); });
|
|
applyOrder(Array.isArray(savedOrder) ? savedOrder : defaults, false);
|
|
} catch (_) {}
|
|
setVisibility(table, checks, false);
|
|
if (!isLegacyDirectoryPicker) {
|
|
toggle.addEventListener('click', () => { const open = panel.hidden; panel.hidden = !open; toggle.setAttribute('aria-expanded', String(open)); });
|
|
}
|
|
checks.forEach((check) => check.addEventListener('change', () => setVisibility(table, checks)));
|
|
panel.querySelector('[data-finance-all]')?.addEventListener('click', () => { checks.forEach((check) => { if (!check.disabled) check.checked = true; }); setVisibility(table, checks); });
|
|
panel.querySelector('[data-finance-none]')?.addEventListener('click', () => { checks.forEach((check) => { if (!check.disabled) check.checked = false; }); setVisibility(table, checks); });
|
|
panel.querySelector('[data-finance-reset]')?.addEventListener('click', () => { checks.forEach((check) => { if (!check.disabled) check.checked = defaultVisible.has(check.value); }); applyOrder(defaults); setVisibility(table, checks); });
|
|
let dragging = null;
|
|
panel.querySelectorAll('[data-finance-option-key], .directory-field-picker__option').forEach((option) => {
|
|
option.dataset.financeOptionKey = option.dataset.financeOptionKey || option.querySelector('input')?.value;
|
|
if (fixedKeys.includes(option.dataset.financeOptionKey) || fixedEndKeys.includes(option.dataset.financeOptionKey)) {
|
|
option.draggable = false;
|
|
return;
|
|
}
|
|
option.draggable = true;
|
|
option.querySelector('input')?.setAttribute('aria-keyshortcuts', 'Alt+ArrowUp Alt+ArrowDown');
|
|
option.addEventListener('keydown', (event) => {
|
|
if (!event.altKey || !['ArrowUp', 'ArrowDown'].includes(event.key)) return;
|
|
event.preventDefault();
|
|
const order = [...panel.querySelectorAll('[data-finance-option-key]')].map((item) => item.dataset.financeOptionKey);
|
|
const index = order.indexOf(option.dataset.financeOptionKey);
|
|
const next = Math.max(0, Math.min(order.length - 1, index + (event.key === 'ArrowUp' ? -1 : 1)));
|
|
if (next === index) return;
|
|
[order[index], order[next]] = [order[next], order[index]];
|
|
applyOrder(order);
|
|
option.querySelector('input')?.focus();
|
|
});
|
|
option.addEventListener('dragstart', () => { dragging = option; option.classList.add('is-dragging'); });
|
|
option.addEventListener('dragend', () => { option.classList.remove('is-dragging'); dragging = null; });
|
|
option.addEventListener('dragover', (event) => { event.preventDefault(); if (dragging && dragging !== option) panel.insertBefore(dragging, option); });
|
|
option.addEventListener('drop', () => { const order = [...panel.querySelectorAll('[data-finance-option-key]')].map((item) => item.dataset.financeOptionKey); applyOrder(order); });
|
|
});
|
|
return picker;
|
|
}
|
|
|
|
function ensureControls(table, headers) {
|
|
const tableScope = table.closest('[data-directory-table-scope]');
|
|
const existingDirectoryTools = tableScope?.querySelector('[data-directory-tools]') || table.closest('[data-directory-page]')?.querySelector('[data-directory-tools]');
|
|
const root = tableScope || table.closest('[data-finance-table-surface], .upstream-data-table-panel, .directory-table-page, .tb-panel, .ab-panel, .marketing-bills-table-wrapper, .bill-table-wrapper, .jd-wallet-table-wrap, .alibaba-bill-table-wrapper, .zhongcai-table-wrapper') || table.parentElement;
|
|
if (!root) return;
|
|
let controls = existingDirectoryTools || root.querySelector('[data-finance-controls]');
|
|
if (!controls) {
|
|
controls = document.createElement('div');
|
|
controls.className = 'finance-table-controls';
|
|
controls.dataset.financeControls = 'true';
|
|
root.insertBefore(controls, root.firstChild);
|
|
}
|
|
createPicker(table, headers, controls);
|
|
const pageScope = table.closest('[data-finance-table-surface], .upstream-data-page, .business-content-section, [data-directory-page]') || root;
|
|
pageScope.dataset.financeEnhanced = 'true';
|
|
pageScope.querySelectorAll('[data-finance-fallback-export]').forEach((fallback) => {
|
|
fallback.hidden = true;
|
|
});
|
|
if (existingDirectoryTools && !existingDirectoryTools.hasAttribute('data-directory-server-export')) {
|
|
controls.dataset.financeExportEnhanced = 'true';
|
|
const currentPage = controls.querySelector('[data-directory-export]');
|
|
const allRows = controls.querySelector('[data-directory-export-all]');
|
|
if (currentPage) currentPage.textContent = '当前页·显示列';
|
|
if (allRows) allRows.textContent = '全部数据·显示列';
|
|
const ensureDirectoryExport = (selector, label, allRows) => {
|
|
const button = controls.querySelector(selector) || document.createElement('button');
|
|
if (!button.parentElement) controls.appendChild(button);
|
|
button.type = 'button';
|
|
button.dataset.financeExportAllFields = 'true';
|
|
if (allRows) button.dataset.directoryExportAll = 'true';
|
|
button.textContent = label;
|
|
return button;
|
|
};
|
|
ensureDirectoryExport('[data-finance-export-all-fields]:not([data-directory-export-all])', '当前页·全部字段', false);
|
|
ensureDirectoryExport('[data-finance-export-all-fields][data-directory-export-all]', '全部数据·全部字段', true);
|
|
}
|
|
if (table.dataset.financeExportBase && !controls.querySelector('[data-finance-server-export]')) {
|
|
const disabled = table.dataset.financeExportDisabled === 'true';
|
|
const addServerExport = (label, allColumns) => {
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.dataset.financeServerExport = allColumns ? 'all' : 'visible';
|
|
button.textContent = label;
|
|
button.disabled = disabled;
|
|
if (disabled) {
|
|
button.setAttribute('aria-disabled', 'true');
|
|
button.title = '当前没有可导出的数据或查询索引尚未就绪';
|
|
}
|
|
controls.appendChild(button);
|
|
};
|
|
addServerExport('导出当前显示列', false);
|
|
addServerExport('导出全部可导出字段', true);
|
|
}
|
|
}
|
|
|
|
function tableRows(table, includeHiddenRows, query) {
|
|
const normalized = String(query || '').trim().toLocaleLowerCase();
|
|
return [...table.querySelectorAll('tbody tr:not(.is-empty)')].filter((row) => {
|
|
if (!includeHiddenRows && row.hidden) return false;
|
|
return !normalized || row.textContent.toLocaleLowerCase().includes(normalized);
|
|
});
|
|
}
|
|
|
|
function orderedCells(row, order, allColumns) {
|
|
const cells = [...row.children];
|
|
const byKey = new Map(cells.map((cell) => [cell.dataset.financeColumnKey || cell.dataset.columnKey, cell]));
|
|
const keys = allColumns ? order : order.filter((key) => !byKey.get(key)?.classList.contains('is-column-hidden'));
|
|
return keys.map((key) => byKey.get(key)).filter(Boolean);
|
|
}
|
|
|
|
async function exportDirectory(table, button, includeHiddenRows, allColumns) {
|
|
const tableScope = table.closest('[data-directory-table-scope]');
|
|
const page = table.closest('[data-directory-page]');
|
|
const search = tableScope?.querySelector('[data-directory-search]') || page?.querySelector('[data-directory-search]');
|
|
const headers = leafHeaders(table);
|
|
const exportableHeaders = headers.filter((header) => header.dataset.financeFixed !== 'end' && header.dataset.directoryFixed !== 'end');
|
|
const selected = new Set(exportableHeaders
|
|
.filter((cell) => !cell.classList.contains('is-column-hidden'))
|
|
.map((cell) => cell.dataset.financeColumnKey));
|
|
const order = exportableHeaders.map((header) => header.dataset.financeColumnKey);
|
|
const exportOrder = allColumns ? order : order.filter((key) => selected.has(key));
|
|
const header = exportOrder.map((key) => csvEscape(headers.find((cell) => cell.dataset.financeColumnKey === key)?.textContent.trim())).join(',');
|
|
const rows = [];
|
|
const lastPage = Number(tableScope?.dataset.directoryLastPage || page?.dataset.directoryLastPage || 1);
|
|
const sourceUrl = tableScope?.dataset.directorySourceUrl || window.location.href;
|
|
button.disabled = true;
|
|
const originalLabel = button.textContent;
|
|
try {
|
|
if (includeHiddenRows && lastPage > 1) {
|
|
for (let current = 1; current <= lastPage; current += 1) {
|
|
button.textContent = `导出中 ${current}/${lastPage}`;
|
|
const url = new URL(sourceUrl, window.location.origin);
|
|
url.searchParams.set('page', String(current));
|
|
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
|
if (!response.ok) throw new Error(`第 ${current} 页读取失败`);
|
|
const parsed = new DOMParser().parseFromString(await response.text(), '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) => {
|
|
const cells = [...row.children];
|
|
const fixedStartKey = headers.find((header) => header.dataset.financeFixed === 'true')?.dataset.financeColumnKey;
|
|
const fixedEndKey = headers.find((header) => header.dataset.financeFixed === 'end')?.dataset.financeColumnKey;
|
|
const map = new Map(cells.map((cell, index) => {
|
|
const key = cell.dataset.financeColumnKey
|
|
|| cell.dataset.columnKey
|
|
|| (cell.dataset.directoryFixed === 'start' ? fixedStartKey : null)
|
|
|| (cell.dataset.directoryFixed === 'end' ? fixedEndKey : null)
|
|
|| headers[index]?.dataset.financeColumnKey;
|
|
return [key, cell];
|
|
}).filter(([key]) => key));
|
|
if (search?.value && !row.textContent.toLocaleLowerCase().includes(search.value.trim().toLocaleLowerCase())) return;
|
|
rows.push(exportOrder.map((key) => csvEscape(cellText(map.get(key)))).join(','));
|
|
});
|
|
}
|
|
} else {
|
|
tableRows(table, includeHiddenRows, search?.value).forEach((row) => rows.push(orderedCells(row, exportOrder, true).map((cell) => csvEscape(cellText(cell))).join(',')));
|
|
}
|
|
downloadCsv(`${page?.dataset.directoryPage || 'directory'}-${includeHiddenRows ? '全部' : '当前页'}-${allColumns ? '全部字段' : '显示列'}.csv`, [header, ...rows]);
|
|
} catch (error) {
|
|
window.alert(error.message || '导出失败,请稍后重试');
|
|
} finally {
|
|
button.disabled = false;
|
|
button.textContent = originalLabel;
|
|
}
|
|
}
|
|
|
|
function exportServer(table, button, allColumns) {
|
|
const headers = leafHeaders(table);
|
|
const columns = (allColumns ? headers : headers.filter((header) => !header.classList.contains('is-column-hidden')))
|
|
.map((header) => header.dataset.exportField)
|
|
.filter(Boolean);
|
|
if (!columns.length) {
|
|
window.alert('请至少保留一个可导出的字段。');
|
|
return;
|
|
}
|
|
|
|
const url = new URL(table.dataset.financeExportBase, window.location.origin);
|
|
url.searchParams.set('columns', columns.join(','));
|
|
try {
|
|
const query = JSON.parse(table.dataset.financeExportQuery || '{}');
|
|
Object.entries(query).forEach(([key, value]) => {
|
|
if (value !== null && value !== undefined && String(value) !== '') url.searchParams.set(key, String(value));
|
|
});
|
|
} catch (_) {
|
|
// Keep the export usable when an older Vue bundle has no query metadata.
|
|
}
|
|
|
|
button.disabled = true;
|
|
const link = document.createElement('a');
|
|
link.href = url.toString();
|
|
link.download = '';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
window.setTimeout(() => { button.disabled = false; }, 500);
|
|
}
|
|
|
|
function initialise(table) {
|
|
if (!table || table.hasAttribute('data-finance-table-native') || tableNames.has(table)) return;
|
|
const prepared = prepareColumns(table);
|
|
if (!prepared.headers.length) return;
|
|
tableNames.add(table);
|
|
ensureControls(table, prepared.headers);
|
|
}
|
|
|
|
function scan() { document.querySelectorAll(selector).forEach(initialise); }
|
|
scan();
|
|
document.addEventListener('DOMContentLoaded', scan, { once: true });
|
|
const observer = new MutationObserver(() => scan());
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
window.setTimeout(scan, 0);
|
|
window.setTimeout(scan, 300);
|
|
|
|
document.addEventListener('click', (event) => {
|
|
const serverButton = event.target.closest('[data-finance-server-export]');
|
|
if (serverButton) {
|
|
const table = serverButton.closest('[data-finance-table-surface], .upstream-data-table-panel, .directory-table-page, .tb-panel, .ab-panel, .finance-table-scroll-owner, .tb-scroll, .ab-scroll, .marketing-bills-table-wrapper, .bill-table-wrapper, .jd-wallet-table-wrap, .alibaba-bill-table-wrapper, .zhongcai-table-wrapper')?.querySelector('table[data-finance-export-base]');
|
|
if (table) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
exportServer(table, serverButton, serverButton.dataset.financeServerExport === 'all');
|
|
}
|
|
return;
|
|
}
|
|
const button = event.target.closest('[data-directory-export], [data-directory-export-all], [data-finance-export-all-fields]');
|
|
if (!button || !button.closest('[data-directory-page]')) return;
|
|
const tableScope = button.closest('[data-directory-table-scope]');
|
|
const table = tableScope?.querySelector('.directory-table') || button.closest('[data-directory-page]')?.querySelector('.directory-table');
|
|
if (!table) return;
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
exportDirectory(table, button, button.hasAttribute('data-directory-export-all'), button.hasAttribute('data-finance-export-all-fields'));
|
|
}, true);
|
|
})();
|
|
</script>
|
|
@endonce
|