feat: 新增 HTML 导出功能 (#164)

- 后端:在 writeRowsToFile 中新增 html 分支
- 后端:实现 writeRowsToHTML 函数,生成包含内嵌 CSS 的独立 HTML 文件
- 后端:实现 formatExportHTMLCell 函数,进行 HTML 转义和换行处理
- 后端:新增测试用例验证 XSS 转义、样式存在、换行处理、空值显示
- 前端:在 DataGrid 所有导出菜单中新增 HTML 选项(右键菜单、工具栏、单元格菜单)
- 前端:在 Sidebar 表节点右键菜单中新增 HTML 选项
- 样式:响应式表格设计,支持斑马纹、悬停效果、表头吸顶
- 安全:所有用户数据经过 HTML 转义,防止 XSS 攻击
This commit is contained in:
Toskysun
2026-03-04 17:46:18 +08:00
committed by GitHub
parent 4570516678
commit e6da986927
4 changed files with 278 additions and 0 deletions

View File

@@ -537,6 +537,7 @@ const ContextMenuRow = React.memo(({ children, record, ...props }: any) => {
{ key: 'exp-xlsx', label: 'Excel', onClick: () => handleExportSelected('xlsx', record) },
{ key: 'exp-json', label: 'JSON', onClick: () => handleExportSelected('json', record) },
{ key: 'exp-md', label: 'Markdown', onClick: () => handleExportSelected('md', record) },
{ key: 'exp-html', label: 'HTML', onClick: () => handleExportSelected('html', record) },
]
}
];
@@ -2588,6 +2589,7 @@ const DataGrid: React.FC<DataGridProps> = ({
{ key: 'filtered-xlsx', label: 'Excel (XLSX)', onClick: () => handleExportFilteredAll('xlsx') },
{ key: 'filtered-json', label: 'JSON', onClick: () => handleExportFilteredAll('json') },
{ key: 'filtered-md', label: 'Markdown', onClick: () => handleExportFilteredAll('md') },
{ key: 'filtered-html', label: 'HTML', onClick: () => handleExportFilteredAll('html') },
]},
{ type: 'divider' },
{ type: 'group', label: '全表', children: [
@@ -2595,12 +2597,14 @@ const DataGrid: React.FC<DataGridProps> = ({
{ key: 'table-xlsx', label: 'Excel (XLSX)', onClick: () => handleExport('xlsx') },
{ key: 'table-json', label: 'JSON', onClick: () => handleExport('json') },
{ key: 'table-md', label: 'Markdown', onClick: () => handleExport('md') },
{ key: 'table-html', label: 'HTML', onClick: () => handleExport('html') },
]},
] : [
{ key: 'csv', label: 'CSV', onClick: () => handleExport('csv') },
{ key: 'xlsx', label: 'Excel (XLSX)', onClick: () => handleExport('xlsx') },
{ key: 'json', label: 'JSON', onClick: () => handleExport('json') },
{ key: 'md', label: 'Markdown', onClick: () => handleExport('md') },
{ key: 'html', label: 'HTML', onClick: () => handleExport('html') },
];
const columnInfoSettingContent = (
@@ -3572,6 +3576,21 @@ const DataGrid: React.FC<DataGridProps> = ({
>
JSON
</div>
<div
style={{
padding: '8px 12px',
cursor: 'pointer',
transition: 'background 0.2s',
}}
onMouseEnter={(e) => e.currentTarget.style.background = darkMode ? '#303030' : '#f5f5f5'}
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
onClick={() => {
if (cellContextMenu.record) handleExportSelected('html', cellContextMenu.record);
setCellContextMenu(prev => ({ ...prev, visible: false }));
}}
>
HTML
</div>
</div>,
document.body
)}

View File

@@ -2806,6 +2806,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
{ key: 'export-xlsx', label: '导出 Excel (XLSX)', onClick: () => handleExport(node, 'xlsx') },
{ key: 'export-json', label: '导出 JSON', onClick: () => handleExport(node, 'json') },
{ key: 'export-md', label: '导出 Markdown', onClick: () => handleExport(node, 'md') },
{ key: 'export-html', label: '导出 HTML', onClick: () => handleExport(node, 'html') },
]
}
];