Files
srdb/webui/static/js/components/DataTable.js
bourdon 30c3e74bd2 feat: 完善 WebUI basePath 支持并简化示例代码
主要改动:

1. WebUI basePath 逻辑完善
   - NewWebUI 支持可变参数 basePath
   - 新增 path() 辅助方法统一路径处理
   - handleTableAPI 正确处理 basePath 前缀
   - handleIndex 根据 basePath 替换占位符

2. 简化示例代码
   - 删除反向代理实现(111行)
   - 直接使用带 basePath 的 WebUI
   - 代码量减少 33%,架构更清晰

3. 前端优化
   - 新增 api.js 统一 API 服务层
   - 所有组件使用统一的 API 调用
   - 支持通过 window.API_BASE 配置 basePath

4. 修复 .gitignore
   - 使用通用模式支持 commands 目录
   - 无需为新示例项目修改配置
2025-10-14 22:23:30 +08:00

202 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { html } from 'htm/preact';
import { useState, useEffect } from 'preact/hooks';
import { RowDetailModal } from '~/components/RowDetailModal.js';
import { Pagination } from '~/components/Pagination.js';
import { TableRow } from '~/components/TableRow.js';
import { useCellPopover } from '~/hooks/useCellPopover.js';
import { useTooltip } from '~/hooks/useTooltip.js';
import { getTableData } from '~/utils/api.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 result = await getTableData(tableName, { limit: pageSize, offset });
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>
`;
}