Files
srdb/webui/static/js/components/DataTable.js

204 lines
6.5 KiB
JavaScript
Raw Normal View History

2025-10-12 13:06:05 +08:00
import { html } from 'htm/preact';
import { useState, useEffect } from 'preact/hooks';
import { RowDetailModal } from './RowDetailModal.js';
import { Pagination } from './Pagination.js';
import { TableRow } from './TableRow.js';
import { useCellPopover } from '../hooks/useCellPopover.js';
import { useTooltip } from '../hooks/useTooltip.js';
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
gap: '12px',
position: 'relative'
},
loadingBar: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'var(--primary)',
zIndex: 100,
animation: 'loading-slide 1.5s ease-in-out infinite'
},
loadingOverlay: {
position: 'fixed',
top: '16px',
left: '50%',
transform: 'translateX(-50%)',
padding: '10px 20px',
background: 'var(--bg-elevated)',
border: '1px solid var(--border-color)',
borderRadius: 'var(--radius-md)',
boxShadow: 'var(--shadow-lg)',
zIndex: 999,
display: 'flex',
alignItems: 'center',
gap: '10px',
fontSize: '14px',
color: 'var(--text-primary)',
fontWeight: 500
},
tableWrapper: {
overflowX: 'auto',
background: 'var(--bg-surface)',
border: '1px solid var(--border-color)',
borderRadius: 'var(--radius-md)'
},
table: {
width: '100%',
borderCollapse: 'collapse',
fontSize: '13px'
},
th: {
background: 'var(--bg-elevated)',
color: 'var(--text-secondary)',
fontWeight: 600,
textAlign: 'left',
padding: '12px',
borderBottom: '1px solid var(--border-color)',
position: 'sticky',
top: 0,
zIndex: 1
}
};
export function DataTable({ schema, tableName, totalRows, selectedColumns = [] }) {
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(20);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedSeq, setSelectedSeq] = useState(null);
const { showPopover, hidePopover } = useCellPopover();
const { showTooltip, hideTooltip } = useTooltip();
useEffect(() => {
fetchData();
}, [tableName, page, pageSize]);
const fetchData = async () => {
try {
setLoading(true);
const offset = page * pageSize;
const response = await fetch(`/api/tables/${tableName}/data?limit=${pageSize}&offset=${offset}`);
if (response.ok) {
const result = await response.json();
setData(result.data || []);
}
} catch (error) {
console.error('Failed to fetch data:', error);
} finally {
setLoading(false);
}
};
const getColumns = () => {
let columns = [];
if (selectedColumns && selectedColumns.length > 0) {
// 使用选中的列
columns = [...selectedColumns];
} else if (schema && schema.fields) {
// 没有选择时,显示所有字段
columns = schema.fields.map(f => f.name);
} else {
return ['_seq', '_time'];
}
// 过滤掉 _seq 和 _time它们会被固定放到特定位置
const filtered = columns.filter(c => c !== '_seq' && c !== '_time');
// _seq 在开头其他字段在中间_time 在倒数第二Actions 列之前)
return ['_seq', ...filtered, '_time'];
};
const handleViewDetail = (seq) => {
setSelectedSeq(seq);
};
const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
setPage(0);
};
const getFieldComment = (fieldName) => {
if (!schema || !schema.fields) return '';
const field = schema.fields.find(f => f.name === fieldName);
return field?.comment || '';
};
const columns = getColumns();
if (!data || data.length === 0) {
return html`<div class="empty"><p>暂无数据</p></div>`;
}
return html`
<div style=${styles.container}>
${loading && html`
<div style=${styles.loadingOverlay}>
<span style=${{ fontSize: '16px' }}></span>
<span>加载中...</span>
</div>
`}
<div style=${styles.tableWrapper}>
<table style=${styles.table}>
<thead>
<tr>
${columns.map(col => {
const comment = getFieldComment(col);
return html`
<th
key=${col}
style=${styles.th}
onMouseEnter=${(e) => comment && showTooltip(e.currentTarget, comment)}
onMouseLeave=${hideTooltip}
>
${col}
</th>
`;
})}
<th style=${{ ...styles.th, textAlign: 'center' }}>操作</th>
</tr>
</thead>
<tbody>
${data.map((row, idx) => html`
<${TableRow}
key=${row._seq || idx}
row=${row}
columns=${columns}
onViewDetail=${handleViewDetail}
onShowPopover=${showPopover}
onHidePopover=${hidePopover}
/>
`)}
</tbody>
</table>
</div>
<!-- 分页控件 -->
<${Pagination}
page=${page}
pageSize=${pageSize}
totalRows=${totalRows}
onPageChange=${setPage}
onPageSizeChange=${handlePageSizeChange}
onJumpToPage=${setPage}
/>
<!-- 详情模态框 -->
${selectedSeq !== null && html`
<${RowDetailModal}
tableName=${tableName}
seq=${selectedSeq}
onClose=${() => setSelectedSeq(null)}
/>
`}
</div>
`;
}