feat(ui): implement frozen columns and refine menu positioning & scrolling

This commit is contained in:
AI Engineer 2026-05-25 14:38:16 +08:00
parent 8bd7e9cbd9
commit b76ae9fbf5

View File

@ -63,6 +63,7 @@ Component.register('DataTable', container => {
container.onScroll = () => {
perf.onScroll(); scroll.refresh();
container.hideColumnMenu();
const prev = container.querySelector('.dt-spacer-prev'), post = container.querySelector('.dt-spacer-post');
if (prev) { prev.style.height = (state.prevHeight || 0) + 'px'; prev.style.display = state.prevHeight > 0 ? 'block' : 'none'; }
if (post) { post.style.height = (state.postHeight || 0) + 'px'; post.style.display = state.postHeight > 0 ? 'block' : 'none'; }
@ -120,7 +121,8 @@ Component.register('DataTable', container => {
}
state.activeField = field; state.activeFieldId = field.id;
menu.style.display = 'block';
const rect = btn.getBoundingClientRect(), rootRect = container.getBoundingClientRect();
const cellNode = btn.closest('.dt-cell');
const rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect();
const menuWidth = menu.offsetWidth || 260;
let leftPos = rect.right - rootRect.left - menuWidth;
if (leftPos < 0) leftPos = Math.max(0, rect.left - rootRect.left);
@ -185,6 +187,21 @@ Component.register('DataTable', container => {
state._fieldsDirty = true;
container.style.setProperty('--dt-grid-template', fields.map(f => `var(--w-${f.id}, ${f.width || 150}px)`).join(' '));
container.style.setProperty('--dt-row-width', fields.reduce((sum, f) => sum + (f.width || 150), 0) + 'px');
let leftSum = 0;
fields.forEach(f => {
if (f.pinned === 'left') {
container.style.setProperty(`--l-${f.id}`, leftSum + 'px');
leftSum += (f.width || 150);
}
});
let rightSum = 0;
[...fields].reverse().forEach(f => {
if (f.pinned === 'right') {
container.style.setProperty(`--r-${f.id}`, rightSum + 'px');
rightSum += (f.width || 150);
}
});
});
state.__watch('list', list => {
@ -283,7 +300,7 @@ Component.register('DataTable', container => {
<div class="dt-header border-bottom bg-light sticky-top" style="z-index:20">
<div class="dt-header-row fw-bold text-muted small">
<template $each="this.state?.fields || []">
<div $data-id="item.id" class="dt-cell dt-col border-end d-flex align-items-center header-cell" style="position:relative; padding: 0">
<div $data-id="item.id" $class="'dt-cell dt-col border-end d-flex align-items-center header-cell' + (item.pinned ? ' pinned-' + item.pinned : '')" $style="(item.pinned ? 'position: sticky; z-index: 11; background-color: inherit; ' : 'position:relative; ') + 'padding: 0; ' + (item.pinned === 'left' ? 'left: var(--l-' + item.id + '); border-right: 1px solid var(--bs-border-color); box-shadow: 2px 0 5px -2px rgba(0,0,0,0.1);' : (item.pinned === 'right' ? 'right: var(--r-' + item.id + '); border-left: 1px solid var(--bs-border-color); box-shadow: -2px 0 5px -2px rgba(0,0,0,0.1);' : ''))">
<div class="d-flex align-items-center overflow-hidden flex-grow-1 h-100 px-2 cursor-pointer" $onclick="this.showColumnMenu(item, event)">
<i $if="this.state?.filterConfig?.[item.id] && (this.state.filterConfig[item.id].value || this.state.filterConfig[item.id].selectedValues?.length)" class="bi bi-filter me-1 text-primary"></i>
<i $if="this.state?.sortConfig?.fieldId === item.id && this.state.sortConfig.direction" $class="'bi bi-sort-' + (this.state.sortConfig.direction === 'asc' ? 'down' : 'up-alt') + ' me-1 text-primary'"></i>
@ -299,7 +316,7 @@ Component.register('DataTable', container => {
<div class="dt-spacer-prev flex-shrink-0" style="display:none"></div>
<template $each="this.state?._renderedList || []" key="id" index="rIdx">
<div class="dt-row dt-body-row border-bottom bg-white" $.="this._initRow(thisNode)">
<template as="f"><div class="dt-cell border-end px-2 d-flex align-items-center"><span $text="this.format(item[f.id], f)" class="text-truncate"></span></div></template>
<template as="f"><div $class="'dt-cell border-end px-2 d-flex align-items-center' + (f.pinned ? ' pinned-' + f.pinned : '')" $style="(f.pinned ? 'position: sticky; z-index: 1; background-color: inherit; ' : '') + (f.pinned === 'left' ? 'left: var(--l-' + f.id + '); border-right: 1px solid var(--bs-border-color); box-shadow: 2px 0 5px -2px rgba(0,0,0,0.1);' : (f.pinned === 'right' ? 'right: var(--r-' + f.id + '); border-left: 1px solid var(--bs-border-color); box-shadow: -2px 0 5px -2px rgba(0,0,0,0.1);' : ''))"><span $text="this.format(item[f.id], f)" class="text-truncate"></span></div></template>
</div>
</template>
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>